Python

 选择的平台是Ubuntu16.04,发现里面居然已经有Python3.5.2了。不过Ubuntu应该是自带一个python2.*的,所以要输入Python3才是最新版本。

 

命令模式和交互模式

Python也像Lua一样有一个交互模式,终端输入Python即可。

输入输出

print()输出,比如print(“hello”,“world”)括号里可以可以用逗号隔开,print遇到逗号会输出一个空格

input()输入,比如name=input(),等待用户输入一个名字然后存到name中。可以在括号里写上一些提示,比如 name = input(“input your name:”)

注释

Python的注释用# 

 

大小写敏感

整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的(除法难道也是精确的?是的!),而浮点数运算则可能会有四舍五入的误差。

 

用\转义字符,常见的比如 \n 换行,\t 表示制表符等等,同样的用 \来转义\。如果有很多要转义的,那就需要写很多\,Python允许用 r ' ' 表示 ' ' 内部的字符串默认不转义

 

如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,允许用 '''...''' 的格式表示多行内容,>>> 会变成 ... 如

上面是在交互模式下,如果是写在文件中的话,就写成这样

 

布尔值

只有 True和False , 注意大小写 

与或非 和lua同样是 and or not 

 

空值 None

 

算数运算的除法 / 和 //的区别 //取整数

4/3 = 1.3333333333333333 

4//3 = 1

 

关于编码

ASCII只能表示英文,数字,以及一些标点符号,毕竟是美国人弄的。不能表示中文,当然也不能韩文,日文等等,然后各国就有了自己的编码,比如中国的GB2312把中文编码进去了。那么有那么多语言,就会有那么多各自不同的编码,难免会冲突,多语言混合的时候就会出现乱码。然后就有了Unicode把所有语言都统一到一套编码里。ASCII编码是1个字节,而Unicode编码通常是2个字节。如果都用Unicode,当你的文本大量是英文的时候,就造成了一倍的空间浪费。然后,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。

现在计算机系统通用的字符编码工作方式:在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。

比如:用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件

在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言

 

对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:

比如 print(ord(‘a’))   #97

print(chr(97))  # a

 

Python对bytes类型的数据用带b前缀的单引号或双引号表示:b = 'ABC'.encode('ascii') # b = b'ABC'

 

len()函数显示字符串长度。

在操作字符串时,我们经常遇到strbytes的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对strbytes进行转换。

格式化输出字符串,比如

'my name is %s , i am a %s' % ('philo' , 'student') 

特别的,当%后面只有一个参数,可以省略括号,比如

'my name is %s' % 'philo'

 

关于代码规范

我发现在用vim写Python的时候,会提示一些看着奇怪的警告,查了下大概是Python代码规范?

http://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_style_rules/

记录一下实践出来的:

1.括号(包括各种括号)内的逗号前面不要有空格,但是逗号后面要有一个

2.行内的注释至少要在 #号前面有两个空格

3.注释应该以 '# '开始,也就是#后面要有一个空格,且只能有一个空格

4.dist中,键值对的冒号后面要有一个空格

 

list

l = ['kobe' , 'kg' , 'tracy']  这样就表示了一个list,len()函数可以返回list的元素个数,与lua中table不同的是,这里的元素下表是从0开始的而不是1

特别的,用-1可以索引到倒数第一个元素,并且,可以以此类推

list里面的元素类型可以不一样,list里面还可以包含list,这样就是二维数组了,类似的,还可以继续有三维,四维……

l = ['kobe', 'kg', 'tim']  # 创建一个list                                        
a = len(l)   # 用len()函数获取list长度 a = 3                                   
l.append('tracy')  # 用append()函数追加元素到list末尾                            
print(l[-1])   # tracy                                                           
l.insert(2, 'Joe')  # 插入元素到指定位置                                         
print(l[2])  # Joe                                                                  
l.pop()  # 删除最后一个元素                                                         
print(l[-1])  # tim                                                                 
l.pop(2)  # 删除指定位置的元素                                                      
print(l[2])  # tim                                                                  
l[2] = 'curry'  # 对指定位置赋值                                                    
print(l[2])  # curry 

 

tuple

相对于list,tuple中的元素是不可变的,不可变也就意味着数据更安全。因此,也就没有增删改这些方法,但是还是可以同样通过索引来查询元素。

要注意的是这里的不可变的含义,不可变是指的“指向不变”,比如有一个tuple里面有一个list,list里面的元素是可以改变的,这是允许的,因为对于tuple来说,他里面list这个元素的指向并没有变

还有一个要注意的是,当tuple里面只有一个元素的时候,要另外加一个逗号来消除可能产生的歧义

t = ('kobe', 24, True)  # 创建一个tuple                                          
t = (1)  # 表示数字1,而不是一个tuple,这样就产生了歧义                             
t = (1, )  # 避免歧义,加一个逗号,这样t就表示一个tuple了                            
                                                                                    
l = ['kobe', 'tracy']                                                               
t = ('a', 'b', l)                                                                   
print(t[2][0])  # kobe                                                              
l[0] = 'tim'                                                                        
print(t[2][0])  # tim 

 

条件判断

age = input()                                                                    
age = int(age)  # 因为input输入的是str,str不能直接和整数比较,所以先转换成整数  
if age >= 18:                                                                    
    print('adult')                                                               
elif age >= 6:                                                                   
    print('teenager')                                                            
else:                                                                            
    print('kid') 

Python是根据缩进来判断条件语句时候结束了的,不像Lua要加个end

names = ['kobe', 'tracy', 'jordan']                                              
for name in names:                                                                  
    print(name)                                                                     
                                                                                    
sum = 0                                                                             
# range(101)生成一个从0开始小于101的整数list                                        
for x in range(101):                                                                
    sum = sum + x                                                                   
print(sum)  # 5050                                                                  
                                                                                    
sum = 0                                                                             
n = 1                                                                               
while n <= 100:                                                                     
    sum = sum + n                                                                   
    n = n + 1                                                                       
print(sum)  # 5050 

 

dist

就是键值对存储,相当于Java中的map,用花括号{}和冒号来表示,通过方括号[]来索引

和list相比:

dist查找插入的时间很快,不会随着key增多而变慢,需要占用大量内存,浪费空间。(用空间换时间)

list查找和插入的时间随着元素增多而增多,占用空间小。    ####有点像是在比较Java中List和Map的区别

正确的使用dist要记住一条,key一定要是不能改变的,在Python中字符串和整数都是不能改变的,但是list不是,因此list是不能作为key的

d = {'kobe': 24, 'tim': 21, 'arenas': 0}                                         
print(d['kobe'])  # 24                                                           
d['kobe'] = 8                                                                       
print(d['kobe'])  # 8                                                               
if 'tim' in d:  # 通过in来判断dict里面时候有该key,返回布尔值                       
    print('tim in d') 

 

set

set是一组key的集合,但没有value,set中没有重复元素,创建一个set要提供一个list作为输入集合。

和Java中set的概念大概差不多?无序不重复

通过add(key)添加元素,添加了重复元素,也只会有一个存在。remove(key)删除元素

& 来执行两个set的交, | 来执行两个set的并

s = set([1, 2, 3])                                                               
print(s)  # {1, 2, 3}                                                            
s.add(99)                                                                        
print(s)  # {99, 1, 2, 3}                                                        
s.remove(2)                                                                      
print(s)  # {99, 1, 3}                                                              
s1 = set([1, 2, 3])                                                                 
s2 = set([2, 5, 1])                                                                 
print(s1 & s2)  # {1, 2}                                                            
print(s1 | s2)  # {1, 2, 3, 5}                                                      
# 不能有list这种可变的元素,因为可变,所以无法计算哈希值                             
# s = set([1, 2, 4, [1, 2]])  # TypeError: unhashable type: 'list'

 

关于字符串是不可变的,见下面的代码

a = 'abc'                                                                           
a.replace('a', 'A')                                                                 
print(a)  # abc,a指向的内容没有被改变,即字符串是不可变的                          
b = a.replace('a', 'A')                                                             
print(b)  # Abc    

 

函数

#  定义一个函数
def my_abs(x):
    if x>0:
        return x
    else:
        return -x

上述代码写在fun.py里面,那么在交互模式,可以用 from fun import my_abs 来导入这个函数,然后就可以直接使用了

 

空函数

当需要一个什么事都不做的函数,可以用pass语句

有一种这样的情景,当还没想好函数里面写什么的时候,可以用个空函数,让代码先跑起来,pass还可以用在其他语句,如if语句里

 

参数检查

上面的my_abs是没有参数检查的,可以用内置函数isinstance()来检查。这样在调用my_abs('a')的时候就会抛出一个错误

def my_abs(x):
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x>0:
        return x
    else:
        return -x

 

多个返回值

def mul_ret():
    return 22,33

a, b = mul_ret()

print(a, b)  # 22 33
def mul_ret():
    return 22,33

a = mul_ret()

print(a)  # (22, 33)

对比这两个执行结果可以发现,其实Python并不是返回了多个值,用一个值a也同样能接受到,因为Python是返回的一个tuple,在语法上,返回一个tuple可以省略括号,按位置赋给对应值

 

默认参数

def default_para(x, y = 3)
    pass

简化函数调用,当y=3时,就只要传入参数x。

还有就是在之后增加了函数的参数的时候,早期版本的函数调用不就不能用了,因为函数参数个数不匹配了,这个时候就可以用默认参数,这样就不用去修改以前的函数调用

注意有多个默认参数的情况,比如,fun(a, b, c = 1, d = 2)。当d默认,c不默认的时候,可以这样调用,比如fun(a, b, 4 ), 这个好理解

当d默认,c不默认的时候,要怎么调用?指定特定参数进行赋值,比如,fun(a,b,d = 9)

 

特别注意一个问题,当有一个这样的函数时候,正常使用感觉也还好,结果也很合理,但是如果多次用默认参数调用该函数就会出问题。

原因是,Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

所以,定义默认参数要牢记一点:默认参数必须指向不变对象!

改成这样,就调用多少次都没有问题了

 

可变参数

在不使用可变参数的时候,平方和函数要写成这样。通过传入一个list或者一个tuple来计算

用可变参数的时候,写成这样,在调用的时候,不用再套一个list或者tuple。在函数调用的时候会自动组装成一个tuple

如果数据已经是存放到一个list或者tuple里面了,可以这样来调用

 

关键字参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple

而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict

 

**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。 

 

命名关键字参数

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数

 

总结

*args是可变参数,args接收的是一个tuple;

**kw是关键字参数,kw接收的是一个dict。

使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

 

递归函数

见到一个很有趣题目,就是汉诺塔的问题。这个问题用递归来做真是简单优雅,特此一提。传说某菊苣要手下搬运汉诺塔的盘子,一个64个盘子,三根柱子。问他要搬多少年?

答案是2^64 - 1,什么概念?不算不知道,一算吓一跳,如果他走的每一步都是对的的话,一秒走一步,他要走5849亿万年,这个数还是忽略了亿后面的数字的。。。。

这位菊苣太凶残了,卧槽。。。。。有点像那个国王下象棋的故事

 

高级特性

切片

l = ['kobe', 'tracy', 'tim', 'lakers', 'spurs']                                  
print(l[1:3])  # ['tracy', 'tim']表示取从索引1开始取,到索引3为止,但不包括索引3 
print(l[:3])  # ['kobe', 'tracy', 'tim'] 如果索引从0开始,那么0是可以省略不写的  
print(l[-2:])  # ['lakers', 'spurs']倒数切片,从索引为-2开始取,也就是倒数第二个元素开始取
print(l[-2:-1])  # ['lakers'] 从倒数第二个开始取,到倒数第一个为止,但不包括倒数第一个,恩,就这样理解
l = list(range(100))  # 生成一个0到99的list                                      
print(l[:10:2])  # [0, 2, 4, 6, 8] 有点像matlab的for循环?从0开始,到10结束(不包括10),以2为步进长度
print(l[:10])  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 如果不写步进长度,默认为1        
print(l[::10])  # [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] 从头到为,以10为步进长度
                                                                                 
# tuple也是一种list,跟list唯一区别在于元素不可变,因此也是可以用这种切片方式获取元素的
t = ('a', 'b', 'c', 'd', 'e')                                                       
print(t[0:3])  # ('a', 'b', 'c')                                                    
                                                                                    
# 字符串 也可以看做是一个list,每个字符就是一个元素,因此也可以用切片操作           
print('abcdefghijk'[2:7])  # cdefg 

迭代

python用for ... in ...来迭代,对比于其他语言比如Java,这种迭代可以实用很多场景,比如list,tuple,以及dict和字符串

 

d = {'kobe': 24, 'tracy': 1, 'kg': 21}                                           
# 迭代key                                                                        
for key in d:                                                                    
    print(key)                                                                   
'''                                                                              
kg                                                                               
kobe                                                                             
tracy                                                                            
因为dict不是顺序存储,所以每次打印结果顺序可能不一样                             
'''                                                                              
                                                                                 
# 迭代value                                                                      
for value in d.values():                                                         
    print(value)                                                                 
                                                                                 
# 迭代key和value                                                                 
for k, v in d.items():                                                           
    print(k, v)                                                                  
                                                                                 
# 迭代字符串                                                                     
s = 'ABCDEFG'                                                                    
for ch in s:                                                                     
    print(ch)  # 迭代每一个字符打印输出

# 迭代list的索引和对应元素
l = ['a', 'b', 'c', 'd']
for k, v in enumerate(l):
print(k, v)

'''
0 a
1 b
2 c
3 d
'''

 

 

只要一个对象是可迭代的就可以用for,那要如何判断时候可迭代?用collections模块的Iterable类型判断

from collections import Iterable                                                 
a = isinstance('abc', Iterable)                                                     
print(a)  # True 字符串是可以迭代的                                                 
a = isinstance([1, 2, 3], Iterable)                                                 
print(a)  # True                                                                    
a = isinstance(123, Iterable)                                                       
print(a)  # False 整数是不可迭代的

 列表生成式

多写几种熟悉下这种写法,主要还是让代码更简洁点

L = []
for x in range(1, 11):
    L.append(x*x)  # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

# 上述代码同样的可以这样写,结果是一样的
L = [x*x for x in range(1, 11)]

# 里面还可以加条件判断
L = [x*x for x in range(1, 11) if x % 2 == 0]  # [4, 16, 36, 64, 100] 

# 套用两层循环
L = [m + n for m in 'abc' for n in 'ABC']  # ['aA', 'aB', 'aC', 'bA', 'bB', 'bC', 'cA', 'cB', 'cC']

# for循环同时使用多个变量
d = {'kobe':'Lakers', 'tim duncan':'spurs', 'vince carter':'raptors'}
L = [k + ' in ' + v for k, v in d.items()]  # ['kobe in Lakers', 'tim duncan in spurs', 'vince carter in raptors'] 

L = ['Hello', 'World', 18, 'Apple', None]
L = [s.lower() for s in L if isinstance(s, str)]
print(L)

 生成器

g = (x*x for x in range(1, 11))
next(g)

跟之前的列表生成式相比,区别就是[] 换成了 (),g就是一个生成器,生成器只保存算法,而不会像列表生成器一样生成一个完整的列表

优点在于如果要生成一个大量数据的(比如100万)的列表,而经常用到的是前面几个,那么后面的就会一直占用空间

生成器只保存算法,每次通过next()来迭代生成下一个元素。不过使用next还是太傻了,生成器是可以迭代的,所以可以用for

生成器的创建方式有两种:

1.就是上面写的那种,列表生成式的[]换成()

2.含有yield的generator function

g = (x*x for x in range(1, 11))
for x in g:
    print(x)  # 这样就能打印出所有元素

yield

斐波拉契数列用函数写成这样

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

fib(6)
'''
1
1
2
3
5
8
'''

把上面的变成generator,只要把print(b)改为 yield b就可以了。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'

generator和普通函数执行流程的区别:在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行,比如:

>>> f = fib(6)                                                                                                          

>>> next(f)                                                                                                            

1                                                                                                                  

>>> next(f)                                                                                                            

1                                                                                                                  

>>> next(f)                                                                                                            

2                                                                                                                      

>>> next(f)                                                                                                            

3                                                                                                                      

>>> next(f)                                                                                                            

5                                                                                                                      

>>> next(f)                                                                                                            

8                                                                                                                      

>>> next(f)                                                                                                            

Traceback (most recent call last):                                                                                        

File "<stdin>", line 1, in <module>                                                                                  

StopIteration: done 

作业:写一个generator做杨辉三角

n = 0
for t in triangles():
    print(t)
    n = n + 1
    if n == 10:
        break

# 期待输出:
# [1]
# [1, 1]
# [1, 2, 1]
# [1, 3, 3, 1]
# [1, 4, 6, 4, 1]
# [1, 5, 10, 10, 5, 1]
# [1, 6, 15, 20, 15, 6, 1]
# [1, 7, 21, 35, 35, 21, 7, 1]
# [1, 8, 28, 56, 70, 56, 28, 8, 1]
# [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]

记录一下我写的,没有网上那么简洁,但是也还是好理解。思路是第一行和第二行单独考虑,第三行及以后头尾先设置好,中间的再做计算得到即可

def triangles():
    pre = cur = []
    n = 1
    while True:
        if n == 1:
            cur = [1]
        elif n == 2:
            cur = [1, 1]
        else:
            cur = [1 for x in range(n)]
            for x in range(1, n-1):
                cur[x] = pre[x] + pre[x-1]
        pre = cur
        n = n + 1
        yield cur

迭代器

可以作用于for循环的数据类型有两类:

1.集合数据类型,比如list,tuple,dict,set,str

2.generator

这些可以直接作用于for循环的对象称为可迭代对象:Iterable

可以使用isinstance()判断一个对象是否是可迭代对象,比如

from collections import Iterable  # 需要导包
a = isinstance([], Iterable)  # True
a = isinstance((), Iterable)  # True
a = isinstance('abc', Iterable)  # True
a = isinstance(123, Iterable)  # Falses
a = isinstance((x*2 for x in range(10)), Iterable)  # True
print(a)

其中,generator不仅可以用于for循环,还可以被next()不断调用返回下一个值,知道最后抛出StopIteration错误表示无法继续返回下一个值

可以被next()调用并不断返回下一个值的对象称为迭代器:Iterator

同样的,可以用isinstance()方法判断是否是迭代器对象

 

from collections import Iterator  # 需要导包
a = isinstance([], Iterator)  # False
a = isinstance((), Iterator)  # False
a = isinstance('abc', Iterator)  # False
a = isinstance(123, Iterator)  # Falses
a = isinstance((x*2 for x in range(10)), Iterator)  # True
print(a)

 

可见,list,tuple,str这些虽然可迭代(iterable)但不是迭代器(iterator)

为什么?

因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的

 

可以使用iter()将list,tuple,str这些iterable变成iterator

from collections import Iterator  # 需要导包
a = isinstance(iter([]), Iterator)  # True
a = isinstance(iter(()), Iterator)  # True
a = isinstance(iter('abc'), Iterator)  # True
print(a)

python的for循环的本质是通过调用next()来实现的

for x in [1, 2, 3, 4]:
    pass

完全等价于

it = iter([1, 2, 3, 4])
while True:
    try:
        x = next(it)
    except StopIteration:
        break

 

高阶函数

Higher-order function,一个函数可以接收另一个函数作为参数,这种函数就称之为高阶函数。这个概念好像跟Lua中的高阶函数是一样的?

Google的MapReduce好像很厉害,然而我并不知道是什么东西。python内建了map()和reduce()函数。

map()

map()函数接受两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

为了理解上面这个话,复习下,

什么是Iterable?可直接用于for循环的对象,称作可迭代对象。有哪些呢?集合数据类型(list,tuple,str等)和generator

什么是Iterator?generator不仅可以用于for循环,还可以被next()不断调用返回下一个值,这样的对象称作Iterator

所以,map的第二个参数是放哪些东西并返回什么东西就知道了。

def f(x):
    return x*x

r = map(f, [1, 2, 3, 4, 5])
print(list(r))  # [1, 4, 9, 16, 25]

上述代码中,map将传入的函数f(x)=x2依次作用到序列[1, 2, 3, 4, 5]的每个元素。返回的r是一个Iterator。等价于下面的代码。只是用map显得更简洁点。

l = []
for x in [1, 2, 3, 4, 5]:
    l.append(f(x))

reduce()

reduce()把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数reduce把结果继续和序列的下一个元素做累积计算,其效果就是

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

比如,求一个list的元素的和

def add(x, y):
    return x+y

r = reduce(add, [1, 2, 3, 4, 5])
print(r)

 也可以写成lambda函数,这样就可以省去add函数了。感觉如果是一样简单的操作,用这个还是比较方便的。因为直接就得到了xy两个参数进行操作。

r = reduce(lambda x, y:x+y , [1, 2, 3, 4, 5])

reduce中的那个函数必须接受两个参数,那如果传入的序列只有一个参数要怎么办?

其实reduce的格式是这样的 reduce( func, seq[, init] ) ,里面的init是可选的,如果写了init,那么init就和序列中的第一个元素作为func的参数传入,但是只参加一次,以后的迭代不会再用到init了。

 

作业

1.利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']

# 测试:
L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2)

 

def normalize(name):
    c = ''  # 因为字符串不可变,构建一个新的字符串返回
    for index, ch in enumerate(name):  # 用enumerate可以得到元素和对应的索引
        if index == 0:
            c = c + ch.upper()  # 第一个元素大写
        else:
            c = c + ch.lower()  # 其他的小写
    return c

查了下,有个叫capitalize()的函数专门干这个的。一句就够。

def normalize(name):
    return name.capitalize()

 

filter

python内建filter()函数用于过滤序列。和map()类似,filter()接收一个函数一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。返回True就留下,False丢弃。最终返回的是一个Iterator。例如

 

def is_odd(x):  # 只要奇数不要偶数
    return x % 2 == 1

l = list(filter(is_odd, range(1, 10)))  # [1, 3, 5, 7, 9]

 

 

作业

用filter()过滤回数,回数就是从左到右跟从右到左年一样的数,

def is_palindrome(n):
    return str(n) == str(n)[::-1]  # 这里[::-1]表示的是字符串从头到尾,步进长度-1,也就是逆序

output = filter(is_palindrome, range(1, 1000))
print(list(output))

 

排序 sorted

l = [99, -2, 0, 2, 18, 10]
print(sorted(l))  # [-2, 0, 2, 10, 18, 99]
# sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序
# 将l的每一个元素作用到函数abs上,然后再进行排序
print(sorted(l, key = abs))  # [0, -2, 2, 10, 18, 99]
l = ['Kobe', 'allen', 'Tracy', 'tim', 'kg']
print(sorted(l))  # ['Kobe', 'Tracy', 'allen', 'kg', 'tim'] 默认的字母排序是'Z' > 'z'的, 也就是大写会排在前面
print(sorted(l, key = str.lower))  # ['allen', 'kg', 'Kobe', 'tim', 'Tracy']  这样就实现的不分大小写来进行排序
print(sorted(l, key = str.lower, reverse = True))  # ['Tracy', 'tim', 'Kobe', 'kg', 'allen'] 逆序输出,注意True是大写的T

作业

假设我们用一组tuple表示学生名字和成绩:

L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)] 

请用sorted()对上述列表分别按名字排序,然后再按成绩从高到低排序::

L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
    return str.lower(t[0])

def by_score(t):
    return t[1]
L2 = sorted(L, key=by_name)
print(L2)
L2 = sorted(L, key=by_score, reverse=True)
print(L2)

 

返回函数

高阶函数除了可以接受函数作为参数外还可以把函数作为返回值返回

def cala_sum(*numbers):  # 求和函数写成这样
    a = 0
    for i in numbers:
        a = a + i
    return a

print(cala_sum(1, 2, 3, 4, 5))  # 15

# 如果不要求立刻执行
def lazy_sum(*numbers):
    def cala_sum():  
        # 注意这里不能*numbers作为形参了,我理解的是这样就覆盖了lazy_sum的形参,因为同名
        # 但是却不会因为同名而把参数传下来,除非实际调用cala_sum(args),函数体里面才能用到传进来的args
        a = 0
        for i in numbers:
            a = a + i
        return a
    return cala_sum

f = lazy_sum(1, 2, 3, 4, 5)
print(f())

 

每次执行lazy_sum都会返回一个函数,但是每次返回的函数是不一样的,也就是不是指向一个地址,大概可以这样理解。两个函数的调用结果互相不影响

f1 = lazy_sum(1, 2, 3, 4, 5)
f2 = lazy_sum(1, 2, 3, 4, 5)
print(f1 == f2) # false

 

再看一个例子

def f():
    l = []
    for i in range(1, 4):
        def r():
            return i*i
        l.append(r)
    return l

f1, f2, f3 = f()

print(f1())  # 9
print(f2())  # 9
print(f3())     # 9

这里不是输出1,4,9 为什么?因为for循环每次循环都创建一个函数,然后将这个函数加到一个list里面,而这个函数并没有被执行,我这样理解,因为这个函数并没有被调用,而只是定义好了,也就是说里面的i*i并不会执行

等到显示的调用该函数的时候,for循环已经结束,并且i=3。

这种搞法叫做Closure,闭包,Lua里面也有

 

匿名函数

比如之前的map提到的lambda,如下代码所示,lambda中表示的就是f(x) = x2, 这就是一个匿名函数。限制是只能有一个表达式,不用写return,返回值就是表达式的结果,匿名函数也可以当做一个函数的返回值来返回

l = list(map(lambda x: x*x , [1, 2, 3, 4, 5]))
print(l)  # [1, 4, 9, 16, 25]

 

装饰器

现在要增强一个函数的功能,比如,在函数调用前执行begin call,在调用后执行end call,可以这样干

 

def hello():
    print('hello world')

def deco(func):
    print('begin call')
    func()
    print('end call')
    return func

hello = deco(hello)

 

这样虽然会输出想要的,hello的定义没变。

继续用语法糖@来实现。在定义hello的时候加上@deco,其实就表示hello = deco(hello),会自动执行deco方法,并且hello没有改变,再单独调用 hello()的时候只会输出 hello world,不会有begin call这些

def deco(func):
    print('begin call')
    func()
    print('end call')
    return func

@deco
def hello():
    print('hello world')

使用内嵌包函数,使得之后每次调用也会出现begin call 和 end call。这个时候hello就指向deco里面的wrapper函数了,可以打印hello.__name__来看函数的名字,结果是wrapper而不是hello,所以之后每次调用hello都会有装饰结果

def deco(func):
    def wrapper():
        print('begin call')
        func()
        print('end call')
        # 这里需要返回原函数func的返回值
    return wrapper

@deco
def hello():
    print('hello world')

 装饰带参数的函数

def deco(func):
    def wrapper(a, b):
        print('begin call')
        res = func(a, b)
        print('end call')
        return res 
    return wrapper

@deco
def add(a, b):
    return a + b

r = add(1, 2)  # 3

 当装饰不确定参数数量时

def deco(func):
    def wrapper(*args, **kw):
        print('begin call')
        print('call', func.__name__)
        res = func(*args, **kw)
        print('end call')
        return res 
    return wrapper

@deco
def add(a, b):
    return a + b

@deco
def addMore(a, b, c):
    return a + b + c

r = add(1, 2)  # 3
print(r)

r = addMore(1, 2, 3)  # 6
print(r)

 

偏函数

通过functools.partial来固定某个函数的某个或者多个参数,产生一个新的函数。创建偏函数时,实际上可以接收函数对象、*args**kw这3个参数
import functools
def hello(s = 'world', st = '!'):
    print('hello', s, st)

h = functools.partial(hello, s = 'kobe', st = ', I see you!')

h()  # hello kobe , I see you!

kw = {'s':'tracy'}
f = functools.partial(hello, **kw)
f()  # hello tracy!

 

模块

有点类似lua的模块,不同的是,Python的每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany

模块头几行的标准注释,当然不写也没有关系

第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码

 

第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;

 

第6行使用__author__变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'Michael Liao'

__**和_**的命名默认是private,其实是可以引用的,只是习惯和一种约定,因为Python没法实现private私有变量,Lua也类似。

 

面向对象

创建类和对象

class Student(object):  # 父类写在括号里面
    def __init__(self, name, score):  # 构造函数,__init__方法的第一个参数永远是self,表示创建的实例本身。
        self.name = name
        self.score = score

    def print_score(self):  # 成员函数的第一个参数也是self
        print(self.score)

bart = Student('kobe', 24)  # 不用传self,这个有点像lua用冒号来定义函数的意思了
bart.print_score()  # 调用不用传self

一个类的两个不同的对象可以有不同的属性,比如创建了对象后,自己定义一个age属性,bart.age = 33 。那么这个属性只会在bart这个对象里。

访问限制

用双下划线开头的成员变量,比如__name是不能直接访问的。双下划线开头就意味着,这个是private。如果用bart.__name = 'aaaa'来赋值,会成功,但是这个__name与类里面定义的__name不是同一个变量了!!!

但是,注意__name__这种变量是特殊变量,并不是私有变量,所以是可以访问的。

 

有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

 

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量

所以!一切靠自觉!养成好的习惯!

 

继承

 

class Animal(object):
    def run(self):
        print('Animal is running')
    
class Dog(Animal):  #括号里写上父类,表示继承自Animal
    pass  # 可以复写run方法

d = Dog()  # 继承了父类的run方法
d.run()  # Animal is running

 

静态语言 vs 动态语言

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了(居然还有这种事!)

获取对象信息

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431866385235335917b66049448ab14a499afd5b24db000

 面相对象高级编程

使用 __slots__

Python是可以动态设置对象的属性的。比如类Student里面没有name这个属性,那么s1.name = "kobe"后,s1对象是有name这个属性的,但是s2就没有了。这个毫无疑问。但是Python是可以直接对类动态设置属性的,比如Student.name = "kobe",那么他的对象就都有这个属性了。

使用 __slots__ 可以限制这种动态设置。比如

class Student(object):
    __slots__ = ("name", "age")
    
s1 = Student()
s1.name = "Kobe"
s1.age = 24
s1.number = 8

Student类中用 __slots__ 限制了只能绑定name和age这两个属性,所以在s1.number = 8 企图绑定number的时候会报错。测试了下,这个只是针对动态绑定这种情况,事先在类里面定义其他属性是没有关系的。

而且这种限定不会延伸到子类。比如

class GraduateStudent(Student):
    pass

g = GraduateStudent()
g.number = 8

子类可以绑定number这个属性

 

使用 @property

简单来说@property可以给属性加上检查。就是当使用 s.score = 59 这种方式给属性赋值时,可以检查赋值的值是否符合规范。

 

class Student(object):
    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be int')
        if value < 0 or value > 100:
            raise ValueError('score must be 0~100')
        self._score = value

s = Student()
s.score = 59
print(s.score)  # 59

 

可以这样理解,@property把一个getter()方法变成了属性,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值。

只读属性。只设置@property不设置对应的@setter,该属性就是可读的。比如

 

class Student(object):
    
    @property
    def age(self):
        return 89

s = Student()

print(s.age)  # 89
s.age = 30  # AttributeError: can't set attribute

 

多重继承

没想到,这货还可以多重继承。方式就是,在定义类的时候,括号里写上所有父类。就实现了多重继承。比如

class Animal(object):
    def eat(self):
        print("i can eat")

class RunnableMixin(object):
    def run(self):
        print("i can run")

class Dog(Animal, RunnableMixin):
    pass

d = Dog()
d.eat()
d.run()  # 同时继承到了eat()和run()方法

 

在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable这种设计通常称之为MixIn。把Runnable写成RunnableMixin是为了更好的看出继承关系。

定制类

__str__

这玩意有点像Java的toString()方法。Java中print一个对象名的时候好像是会去调用toString()方法的,会返回一个内存地址啥的。__str__有点这个意思。

 

class Person(object):
    pass

print(Person())  # <__main__.Person object at 0x02F67810>

 

class Person(object):
    def __str__(self):
        return ("A Person Object")

print(Person())  # A Person Object

 

__iter__

如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环

上面这段描述有点Java的迭代器的感觉,实现一个接口。怎么怎么样的。。。

斐波那契数列

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1

    def __iter__(self):
        return self

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > 50:
            raise StopIteration()

        return self.a
    
for n in Fib():
    print(n)

 

__getitem__

可以让上面的Fib像调用list那样直接取第几个数,比如Fib()[5],其实也就是像前面的__str__一样,python在出现方法后面跟[],Fib()[5]这种类型的时候会去调用__getitem__这个函数。你实现了这个方法就能用这种形式,没实现这样调用就报错。

    def __getitem__(self, n):
        a, b = 1, 1
        for n in range(n):
            a, b = b, a + b
        return a

 

__getattr__

这个有点想Lua的元表里面的__index 元方法,当访问不存在的属性时,就会去调用这个方法。比如

class Person(object):
    pass

print(Person().name)  # AttributeError: 'Person' object has no attribute 'name'
class Person(object):
    def __getattr__(self, attr):
        return "NONE"

print(Person().name)  # NOE

 

__call__

一般调用一个方法是 instance.method() 这种形式的,但是__call__ 可以实现 instance()这种形式调用

class Person(object):
    def __init__(self, name):
        self.name = name
    
    def __call__(self):
        print("My name is %s" % self.name)

p = Person("kobe")
p()  # My name is kobe

去判断一个对象是否能被调用,能被调用的就是一个callable对象,比如callable(Person())就返回True

 

枚举

偷个懒。。。

 

元类

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。

type

type可以查看对象的类型,也可以动态创建类

Car = type("Car", (object,), dict(fn = lambda self:"drive me"))
c = Car()
print(c.fn())  # drive me

要创建一个class对象,type()函数依次传入3个参数:

  1. class的名称;试了下,上例中type里面的Car换成其他也行的,这里不知道有没有其他规定。
  2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
  3. class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

错误、调试、测试

python也是采用像Java那种try...catch方式。格式如下,执行规则与Java一样

try:
    print("try")
    r = 10/0
    print("result:", r)
except ZeroDivisionError as e:
    print("except:", e)
finally:
    print("finally")
print("end")

不过不同的是,可以可以在except后面加个 else表示当没有错误的时候执行

同样的,所有错误继承自BaseException,当有多个except的时候,捕获错误的时候,会把所有的子类错误也捕获到。

也可以自定义错误。

 

记录错误

Python内置的logging模块可以非常容易地记录错误信息,import logging后,代码出错了,会打印出错误信息,并且也会继续运行下去.

 

抛出错误

使用raise关键字。这里自定义一个错误

 

class MyError(ValueError):
    pass

def fun():
    raise MyError()

fun()

 

Traceback (most recent call last):
File "e:\LearnPython\try.py", line 7, in <module>
fun()
File "e:\LearnPython\try.py", line 5, in fun
raise MyError()
MyError
尽量使用内置的错误类型。
另外一种处理错误的方式
def foo(s):
    n = int(s)
    if n==0:
        raise ValueError('invalid value: %s' % s)
    return 10 / n

def bar():
    try:
        foo('0')
    except ValueError as e:
        print('ValueError!')
        raise

bar()

捕获到错误后继续 raise 抛出,因为当前函数不知道如何处理,所以继续向上级调用者抛出。

 

assert

断言,格式如下

 

def fun(n):
    assert n != 0, "n is zero"
    print('fun')
fun(0)

 

AssertionError: n is zero

可以用 -O 参数关掉assert,比如, python -O test.py ,那么assert语句就相当于pass了

 

logging

assert比,logging不会抛出错误,而且可以输出到文件

pdb

单步调试,好的IDE PyCharm

单元测试

一个测试类,需要继承自unittest.TestCase。类里面的以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。

unittest.TestCase提供了一些内置的判断,比如assertEqual()

setUp()、tearDown()。没测,估计是在测试类运行前后结束的时候分别调用的。

 

IO

通常的格式

 

try:
    f = open('E:/t.txt', 'r')
    print(f.read())
finally:
    if f:
        f.close()
    

 

和Java一样,打开了记得关闭,为了防止打开文件出错,后面的close执行不到,所以用try catch这种方式,r 表示读文件。

Python还提供一种更简洁的方式,效果和上面的一样。

 

with open("E:/t.txt", "r") as f:
    print(f.read())

 

read()方法会一次性读取全部内容,如果内容有好几个G,内存就爆了,所以,用read(size)多次反复调用是更合理的方法

readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list

 

读取二进制文件(图片,视屏等),  rb

 

with open("E:/t.PNG", "rb") as f:
    print(f.read())

读取GBK编码的文件

with open("E:/t.txt", "r", encoding='gbk') as f:
    print(f.read())

忽略错误(比如编码错误)

with open("E:/t.txt", "r", encoding='gbk', errors="ignore") as f:
    print(f.read())

写文件。参数有 w , 二进制文件写 wb。操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with语句来得保险

StringIO

从内存中读写str,区别于从文件读写

from io import StringIO
#
f = StringIO("Hello World\nLearn Python")  # 初始化一个StringIO
while True:
    s = f.readline()
    if s == "":
        break
    print(s)

#
f = StringIO()
f.write("Hello Python")  # write方法会返回写入的字符数,比如这里是12
print(f.getvalue())

StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO

 

操作文件和目录

import os
os.name  # nt
os.environ  # 打印出环境变量
os.environ.get("PATH")  # 通过get加上key来获取

操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中

os.path.abspath('.')  # 获取绝对路径,有点像pwd
os.mkdir("E:/testdir")  # 创建目录
os.rmdir("E:/testdir")  # 删除目录

把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()函数,这样可以正确处理不同操作系统的路径分隔符

同样的道理,要拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名

os.path.splitext()可以直接让你得到文件扩展名

os.path.splitext("E:/t.txt")  # ('E:/t', '.txt') 得到一个tuple

重命名 os.rename 

删除文件 os.remove

复制文件没有提供,可以用IO来实现,shutil模块也提供了copyfile()的函数

序列化

Python中的序列化叫做pickling,反序列化叫做unpickling,Python提供pickle模块来实现序列化

序列化:

import pickle
d = dict(name = "kobe", num = 24)
with open("D:/t.txt", "wb") as f:
    pickle.dump(d, f)  # 序列化后保存到文件中

文件中的内容是这样的一堆乱七八糟的东西:

(dp0
S'num'
p1
I24
sS'name'
p2
S'kobe'
p3
s.

反序列化

with open("D:/t.txt", "rb") as f:
    p = pickle.load(f)  # load反序列化出对象
print(p)  # {'num': 24, 'name': 'kobe

反序列化出来的对象跟原来的那个不是同一个对象,只是内容相同而已

pickle可能会在不同版本的Python中不兼容,所以,最好只用这个保存不重要的数据。反序列化不了也没关系。。。(所以,就是自己玩玩就好

 

JSON

为了在不同的编程语言之间传递对象,必须把对象序列化成标准格式。比如XML,JSON,最好是JSON,因为JSON表示出来就是一个字符串,可以被所有语言读取。也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式,并且比XML更快,而且可以直接在Web页面中读取,非常方便。

总之,JSON大法好,嗯。

import json
# dumps返回一个标准的json字符串
print(json.dumps(d))  # {"num": 24, "name": "kobe"}
# dump把json字符串写到文件中
with open("D:/t.txt", "w") as f:
    json.dump(d, f)

# 同样有个load方法把json数据反序列化
with open("D:/t.txt", "r") as f:
    j = json.load(f)
    print(j)

类的序列化

就像Java中序列化一个对象需要他的类实现了某些接口。Python中也不能直接序列化,除了那些本来就支持的数据类型,自定义类的话,要自己写个转换方法。比如

class Player(object):
    def __init__(self, name, team, age):
        self.name = name
        self.team = team
        self.age = age

def player2dict(p):
    return {
        "name":p.name,
        "age":p.age,
        "team":p.team
    }
p = Player("kobe", "Lakers", 35)
print(json.dumps(p, default=player2dict))  # {"age": 35, "name": "kobe", "team": "Lakers"}

或者偷懒,写成这样:

json.dumps(p, default=lambda o:o.__dict__)
通常class的实例都有一个__dict__属性,它就是一个dict,用来存储实例变量。也有少数例外,比如定义了__slots__的class。
反序列化也要写一个函数转换,object_hook负责把dict转换成player
class Player(object):
    def __init__(self, name, team, age):
        self.name = name
        self.team = team
        self.age = age

def player2dict(p):
    return {
        "name":p.name,
        "age":p.age,
        "team":p.team
    }
p = Player("kobe", "Lakers", 35)
str = json.dumps(p, default=lambda o:o.__dict__)  # {"age": 35, "name": "kobe", "team": "Lakers"}
def dict2player(d):
    return Player(d["name"], d["age"], d["team"])
s = json.loads(str, object_hook=dict2player)
print(s)  # <__main__.Player object at 0x03B901F0>

 

进程和线程

线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间

Windows下启动子进程

 

from multiprocessing import Process
import os

def fun(name):
    print("Run child proc %s (%s)..." % (name, os.getpid()))

if __name__=="__main__":
    print("parent proc %s." % os.getpid())
    p = Process(target=fun, args=("test", ))
    print("child proc will start")
    p.start()
    p.join()
    print("child proc end.")

 

parent proc 14780.
child proc will start
Run child proc test (15192)...
child proc end.

 

注意:这玩意本来用vs code直接F5,是不会执行fun函数的,但是在终端 py test.py 是可以的!

 

Pool

如果要启动大量的子进程,可以用进程池的方式批量创建子进程

 

from multiprocessing import Pool
import os, time, random

def task(name):
    print("Run task %s (%s)" % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print("task %s run %0.2f seconds" % (name, end - start))

if __name__ == "__main__":
    print("parent proc %s " % os.getpid())
    p = Pool(4)
    for i in range(5):
        p.apply_async(task, args = (i,))
    print("waiting for all subprocess done...")
    p.close()
    p.join()
    print("all subprocess done")
parent proc 10660
waiting for all subprocess done...
Run task 0 (13592)
Run task 1 (5020)
Run task 2 (5180)
Run task 3 (7012)
task 2 run 1.03 seconds
Run task 4 (5180)
task 1 run 2.06 seconds
task 3 run 2.30 seconds
task 0 run 2.78 seconds
task 4 run 1.98 seconds
all subprocess done

Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。

task 0,1,2,3是立刻执行的,而task 4要等待前面某个task完成后才执行,这是因为Pool的默认大小在我的电脑上是4,因此,最多同时执行4个进程。这是Pool有意设计的限制,并不是操作系统的限制。

 

进程的通信

Python的multiprocessing模块包装了底层的机制,提供了QueuePipes等多种方式来交换数据

from multiprocessing import Process, Queue
import os, time, random

def write(q):
    print("write process (%s)" % os.getpid())
    for i in ["kobe", "tracy", "nash"]:
        print("put %s to queue" % i)
        q.put(i)
        time.sleep(random.random())

def read(q):
    print("read process (%s)" % os.getpid())
    while True:
        v = q.get(True)
        print("get %s from queue" % v)

if __name__ == "__main__":
    q = Queue()
    wp = Process(target=write, args=(q,))
    rp = Process(target=read, args=(q,))
    wp.start()
    rp.start()
    wp.join()
    rp.terminate()

rp是死循环,所以不能用join()来等待结束,只能直接杀死他

write process (6576)
put kobe to queue
read process (6032)
get kobe from queue
put tracy to queue
get tracy from queue
put nash to queue
get nash from queue

多线程

Python的标准库提供了两个模块:_threadthreading_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块

启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行

 

import time, threading

def loop():
    print('thread %s is running' % threading.current_thread().name)
    for i in range(5):
        print('thread %s >>> %s ' % (threading.current_thread().name, i))
        time.sleep(1)
    print('thread %s ended' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='loopThread')  # 创建Thread对象,赋值要运行的函数,以及线程名字
t.start()  # 开始
t.join()  # 等待结束
print('thread %s ended' % threading.current_thread().name)

 

thread MainThread is running...
thread loopThread is running
thread loopThread >>> 0 
thread loopThread >>> 1 
thread loopThread >>> 2 
thread loopThread >>> 3 
thread loopThread >>> 4 
thread loopThread ended
thread MainThread ended

 

Lock

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改

嗯。只要有线程,就会要讨论同步的问题

基本使用方式就这样

 

lock = threading.Lock()  # 获取Lock对象
n = 0
def count():
    for i in range(100):
        lock.acquire()  # 获取锁,放到需要数据同步的地方
        try:
            global n  # 关键代码写在try catch中
            n = n + 1
            print("thread %s runing count >>> n = %s \n" % (threading.current_thread().name, n))
        finally:
            lock.release()  # 记得在finally里面释放锁

 

Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核

Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦

 

分布式进程

在Thread和Process中,应当优选Process,因为Process更稳定,而且,Process可以分布到多台机器上,而Thread最多只能分布到同一台机器的多个CPU上。

Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。一个服务进程可以作为调度者,将任务分布到其他多个进程中,依靠网络通信。由于managers模块封装很好,不必了解网络通信的细节,就可以很容易地编写分布式多进程程序。

 

这玩意在Linux下运行没有问题。windows下面不行。

网络编程

TCP/IP

IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此,路由器就负责决定如何把一个IP包转发出去。IP包的特点是按块发送,途径多个路由,但不保证能到达,也不保证顺序到达。

TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。

许多常用的更高级的协议都是建立在TCP协议基础上的,比如用于浏览器的HTTP协议、发送邮件的SMTP协议等。

 

 

 

 
 

转载于:https://www.cnblogs.com/i-love-kobe/p/6744883.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值