python语法

https://www.liaoxuefeng.com/wiki/1016959663602400

 

文章目录

    • @[toc]
  • 0、前言
  • 1、转义方式
  • 2、字符串的占位符
    • 百分号占位符
    • format格式化占位符
  • 3、布尔值
  • 4、if判断
  • 5、除法运算
  • 6、list
  • 7、tuple
  • 8、输入输出
  • 9、数字字符串转换
  • 10、循环
  • 11、字典dict
    • 遍历dict:
  • 12、集合set
  • 13、函数
    • 可变参数
    • 关键字参数
    • 命名关键字参数:
    • 参数组合
    • 函数注释/注解
    • 匿名函数lambda
    • 装饰器
    • 偏函数
  • 14、切片
  • 15、迭代
    • 迭代字典dict
    • 下标循环
    • 应用迭代的函数
      • sorted
      • zip
      • enumerate
      • filter
      • map
      • reduce
      • sum
      • any
      • all
      • max
      • min
  • 16、列表生成式
    • 列表生成式
    • 字典生成式
    • 集合生成式
  • 17、生成器
    • 使用生成式来编写生成器
    • 使用yeild来编写生成器
      • 使用send给生成器函数传送值
    • 生成器的return
  • 18、迭代器
    • 单个迭代器和多个迭代器
  • 19、闭包
  • 20、模块
    • main函数
  • 21、类
    • \_\_slots\_\_
    • property
    • 重载运算符
    • 枚举类
    • 元类
      • type
      • metaclass
  • 22、异常
    • 断言assert
    • log
    • pdb
    • 单元测试
  • 23、IO
    • 文件
    • StringIO, BytesIO
    • 文件和目录
    • 序列化
      • pickle
      • json
  • 24、进程和线程
    • 进程
      • fork
      • Process
      • Pool
      • 子进程
      • 进程间通讯
    • 线程
      • 线程局部变量
  • 25、正则表达式
    • 切分字符串
    • 分组
    • 编译正则表达式
  • 26、常用内建模块
    • datatime
    • collections
    • base64
    • struct
    • hashlib
    • hmac
    • itertools
      • groupby
    • contextlib
    • urllib
    • XML
    • HTMLParser
  • 27、第三方模块
    • Pillow
    • requests
    • chardet
    • psutil
    • tqdm
  • 28、virtualenv
  • 29、socket
    • TCP
    • UDP
  • 30、SQL
    • SQLAlchemy
  • 31、异步IO
    • 协程
      • 并发(concurrency)和并行(parallelism)的区别:
      • 协程和线程的区别:
      • 协程的生产者和消费者
      • yeild from
    • asyncio
      • @asyncio.coroutine装饰器
    • async/await
    • aiohttp
    • future
      • ProcessPoolExecutor
    • async with/async for

0、前言

本文是根据廖雪峰python教程学习总结而来。
参考《python学习手册》,《流畅的python》以及python官方手册等网络资料
略过了与C和C++ 语言相同的语法部分

1、转义方式

python的字符串可以用双引号和单引号,作用是相同的。转义的方式和C差不多。不同的是,如果想让一个字符串里面的字符都不用转义的话,可以在字符串前面加字母r来声明。
例如

r'\\\\t\t\t\t\'
  • 1

里面的内容都无需转义。
或者使用3个引号来把字符串包起来,这样也不需要转义了,而且中间还可以换行。
例如

'''123\\\t\t\t\'''
  • 1

2、字符串的占位符

百分号占位符

和C语言一样,都是使用百分号占位,但是不同的是,python里面占位符对应的数据也需要百分号来标志,而且占位符对应的数据与字符串之间不需要加逗号。
例如

print ("我叫%s,今年%d岁,工作%d年了" %('郑德伦', 27, 2))
  • 1

format格式化占位符

还有一种新的占位符使用{0}占位,有点像C#的格式化字符串操作。
python官方手册str.format部分

print('{0:,.3f} {1},{name}, {2},'.format(99999.12345, '嘎嘎',[1,2,3,4], name='123'))
  • 1

输出:

99,999.123 嘎嘎,123, [1, 2, 3, 4],
  • 1

3、布尔值

True False表示真假,布尔运算是and or not,这点与C语言不同

4、if判断

与C语言不同的是,if和else后面都加冒号,else if变成了elif

a = 10
b = 20
if a > b :
    print (a > b)
elif a == b:
    print (a == b)
else:
    print (a < b)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

5、除法运算

C语言中,两个整数相除的结果还是整数,而在python中,结果是一个浮点数。
如果想让两个整数相除的结果变成整数的话,需要用地板除的方式,双斜线
例如:

10//3
  • 1

结果是3

6、list

list有点像是C++ 中的vector,可以动态的添加和删除元素。是一个有序的表。可以对一个list使用len来获取长度。与C++ 不同,C++ 是强类型的语言,vector中只能包含相同类型的元素,而python的list中可以包含不同类型的对象,还可以包含另一个list。
list使用中括号来表示
例如:

['123', '234', '345]
  • 1

通过索引访问list时,还可以使用负数,-1就是取最后一个元素

a=['123', '234', '345']  
a[-1]
  • 1
  • 2

输出345
在后面追加元素,相当于C++ 中vector的push_back,在python中是append
例如

a.append('666')
  • 1

在中间插入元素,相当于C++ 的insert,在python也是insert
例如a.insert(1, ‘777’) 在索引号1的地方插入’777’
在末尾删除元素,相当于C++ 的pop_back,在python中是pop
例如

a.pop()
  • 1

删除指定索引的元素,在C++ 中无法通过索引直接删除vector的元素,需要使用迭代器,在python中也用pop删除指定位置的元素
例如

a.pop(1)
  • 1

删除索引1的元素

7、tuple

tuple类似于C++ 中的tuple,和python的list也很像,只不过tuple一旦创建就不能更改里面的内容了。
tuple可以隐式转换为单个变量例如:

a, b, c = (1, 2, 3)
print(a, b, c)
  • 1
  • 2

输出:

1 2 3
  • 1

也可以使用封包

first, *rest = (1,2,3,4)
print(first)
print(rest)
  • 1
  • 2
  • 3

输出

1
[2, 3, 4]
  • 1
  • 2

8、输入输出

输出函数和C语言类似使用

print ("hello world")
  • 1

输入函数与C和C++ 都比较不同,使用input来获取输入的内容
例如

name = input("input your name")
  • 1

9、数字字符串转换

在C和C++ 中数字转字符串比较麻烦,一般在C语言中可以使用sprintf。C++ 直接使用to_string。
字符串转数字的话,C语言可以使用atoi,C++ 可以使用stringstream。
在python中可以直接使用int() float() str()转换
例如:

a = int('123')
b = float('12.3)
c = str(123.3)
  • 1
  • 2
  • 3

10、循环

python中有两种循环,一种是for循环一种是while循环。和C、C++ 中的不太一样。没有do while循环
for循环:类似于C++中的for (auto i : vec)这种range-based的for循环

for x in range(10):
    print(x)
  • 1
  • 2

while循环:和C、C++中的while循环类似

while x < 10:
    print(x)
    x += 1
  • 1
  • 2
  • 3

python的循环中同样可以使用continue和break来终止循环

11、字典dict

字典类似与C++ 中的unordered_map,是一种哈希表的key-value的查找结构。
使用大括号来表示:

score = {"Mike":100, "冰封飞飞":100, "Fvck":88}
  • 1

判断一个key在不在字典里面可以使用
“冰封飞飞” in score
返回结果是True或者False
删除一个元素使用pop方法。score.pop(“冰封飞飞”)
插入一个元素和C++一样直接可以score[“new”] = 100
查找操作可以使用get操作,因为如果key不存在的时候,直接使用score[“nonexist”]会报错。
get操作还可以指定默认值,如果查找不到key的话,返回默认值

s = {}
print(s.get('a', '3'))
  • 1
  • 2

结果:

3
  • 1

遍历dict:

for (k, v) in score.items():
    print("%s:%d" % (k, v))
  • 1
  • 2

12、集合set

集合类似与C++ 中的unordered_ser,是一种哈希表的集合结构。
如果要创建set,需要提供一个list作为输入集合。
s = set([1, 2, 2, 3]) 初始化时,自动会去重元素。
结果是s = {1, 2, 3}
添加元素使用add方法,删除元素使用remove方法

s.add(4)
s.remove(4)
  • 1
  • 2

集合类似于数学上的集合,有交集和并集的操作,
s1 & s2, s1 | s2

13、函数

函数名是一个指向函数对象的引用,下面可以直接将函数赋值一个别名

a = abs
a(-10)
  • 1
  • 2

而且函数名本身也是一个变量,可以赋值

abs = 10
abs(-10)
  • 1
  • 2

这样执行的话,abs(-10)就会报错了。

python包含了很多内置的函数,可以从python官方手册查看
函数的定义使用def

def myFunc(a, b):
    return a + b
  • 1
  • 2

空语句可以使用pass,类似于C/C++ 中的分号
可变参数:在参数前面加一个星号,就变成了可变参数,可以传入任意个数的参数,实际上是自动封装成了一个tuple。

可变参数

def myFunc(*num):
    result = 0
    for i in num:
    result += i
    return result
  • 1
  • 2
  • 3
  • 4
  • 5

调用时可以myFunc(1, 2, 3)这样调用,
也可以传入一个list或者tuple

param = [1, 2, 3]
myFunc(*param)
  • 1
  • 2

关键字参数

def func(a, b, **kw):
    print(kw)
  • 1
  • 2

这个函数里面第三个参数是**kw,实际上是一个字典。
调用时可以这样调用:func(1, 2, name=“123”, age=18)
参数传进去时,实际上a = 1, b = 2 kw = {“name”:“123”, “age”:18}
也可以直接传一个dict作为第三个参数,但是前面需要加两个星号

kw =  {"name":"123", "age":18}
func(1, 2, **kw)
  • 1
  • 2

关键字参数必须跟在位置参数后面。下面的调用方式是不合法的。

def func(a, b=1, c=2):
    print(a, b, c)
func(a=1, 2)
  • 1
  • 2
  • 3

命名关键字参数:

def func(a, b, *, name, job):
    print(name)
    print(job)
  • 1
  • 2
  • 3

在上面关键字参数,无法控制**kw传入的key是什么。使用关键字参数可以控制可以传入哪些key。需要使用一个星号作为分隔符。星号后面定义可以传入的key的名称。

func(1, 2, name="1", job=2)  -> OK
func(1, 2, job = 3) -> ERROR
  • 1
  • 2

这个星号必不可少,如果正好有一个可变参数,那就不需要额外的星号了
def func(a, b, *args, name, job):也是可以的

参数组合

定义函数的时候可以使用多重参数组合,定义的顺序是,必选参数,默认参数,可变参数,命名关键字参数和关键字参数

def f1(a, b = 0, *args, **kw)
def f2(a, b = 0, *, d, **kw)
  • 1
  • 2

对于任意的函数都可以使用一个tuple和一个dict来组织参数调用。类似于func(*args, **kw)

函数注释/注解

函数注释是3.X新加入的功能,可以给函数每一个参数添加一个注释,以及标记返回值的类型。

def func(a:'spam', b:(1,10),c:float=2.5) -> int:
    return a + b + c
  • 1
  • 2

注解实际上是把上面添加的内容,写入到了函数的__annotations__方法中。
可以使用

print(func.__annotations__)
  • 1

打印函数注解,结果如下

{'a': 'spam', 'b': (1, 10), 'c': , 'return': }
  • 1

匿名函数lambda

lambda比def功能要小,lambda仅能编写简单的函数,连if也不能使用,也不能写return,返回值就是表达式的结果

f = lambda x, y, z : x + y + z
  • 1

装饰器

装饰器是一种委托的调用方式,将被装饰的函数包装一层,增加一些定制处理。
装饰器的用法如下,@log是一个语法糖,相当于test = log(test),将test函数替换为wrapper函数。
@functools.wraps(func)是系统帮忙做的一些处理,可以让装饰后的函数和原函数看起来一致。例如:包装后的函数__name__还是显示和原函数一致,不会显示为wrapper函数的__name__

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print(''.format(time.ctime(), func.__name__, args, kw))
        return func(*args, **kw)
    return wrapper
@log
def test(a):    
    print('test func:{0}'.format(a))    
def main():
    test(5)
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

结果:


test func:5
  • 1
  • 2

偏函数

偏函数类似与C++ 中的bind,可以将函数的某些参数固定下来,生成一个新的函数。

import functools
int2 = functools.partial(int, base=2)
print(int2('10101'))
  • 1
  • 2
  • 3

结果:

21
  • 1

14、切片

通过切片操作可以很方便的将list,tuple,string中一段对象取出来。python没有提供substr的函数,字符串的取子串的操作都使用切片来进行

L = list(range(10))
print("L = %s " % L)
print("L[0:3] = %s " % L[0:3])
print("L[:3]= %s " % L[:3])
print("L[-2:] = %s " % L[-2:])
print("L[-2:-1] = %s " % L[-2:-1])
print("L[:10:2] = %s " % L[:10:2])
print("L[:] = %s " %  L[:])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

结果:

L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
L[0:3] = [0, 1, 2]
L[:3]= [0, 1, 2]
L[-2:] = [8, 9]
L[-2:-1] = [8]
L[:10:2] = [0, 2, 4, 6, 8]
L[:] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

切片是一个左闭右开的区间,例如L[0:3],获取[0, 3)区间的元素。
第二个冒号之后的数字是步长,可以相隔步长来取数
只包含一个冒号就是获取变量本身

15、迭代

python的迭代使用for,10循环中说过一些for的内容。类似于C++ 的for(auto i : vec)这种形式的迭代。不是基于下标迭代。

迭代字典dict

d = {'a' : 1, 'b' : 2}
for key in d:
    print (key)
for value in d.values():
    print (value)
for k, v in d.items():
    print('%s : %d' % (k, v))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

输出结果:

a
b
1
2
a : 1
b : 2
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

对于字典,for迭代只会迭代key,如果迭代value的话需要使用d.values()作为迭代对象。如果迭代key-value的话,需要使用d.items()作为迭代对象
判断一个变量是否能迭代,使用下面代码判断。

from collections.abc import Iterable 
d = {'a' : 1, 'b' : 2}
print(isinstance(d, Iterable))
print(isinstance(1, Iterable))
  • 1
  • 2
  • 3
  • 4

输出

True
False
  • 1
  • 2

下标循环

使用enumerate可以将一个可迭代的对象转换为下标+对象的tuple来迭代

from collections.abc import Iterable 
d = {'a' : 1, 'b' : 2}
for i in enumerate(d.items()):
    print(i)

  • 1
  • 2
  • 3
  • 4
  • 5

输出

(0, ('a', 1))
(1, ('b', 2))
  • 1
  • 2

这个迭代器我用的是i一个元素,这时候i就变成了一个tuple,如果使用两个元素的话,会将这个tuple拆分到两个元素上面去
例如改为:

for i, v in enumerate(d.items()):
    print('%s : %s' % (i, v))
  • 1
  • 2

结果就是

0 : ('a', 1)
1 : ('b', 2)
  • 1
  • 2

应用迭代的函数

sorted

对参数进行排序

L = [3, 2, 4, 1]
L = sorted(L)
print (L)
  • 1
  • 2
  • 3

结果

[1, 2, 3, 4]
  • 1

zip

将参数相同索引的数据组成一个tuple,如果几个参数的数量不同,按最小的算

L = [1, 2, 3, 4]
S = ['a', 'b', 'c', 'd', 'e']
Z = zip(L, S)    
print (list(Z))
  • 1
  • 2
  • 3
  • 4

结果

[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
  • 1

enumerate

将参数返回一个索引+数据的tuple

S = ['a', 'b', 'c', 'd', 'e']    
print (list(enumerate(S)))
  • 1
  • 2

结果

[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e')]
  • 1

filter

将可迭代对象按照参数1,进行过滤。


def isInt(x):
    if isinstance(x, int):
        return True
    else:
        return False
def main():    
    S = [1, 2, 3.0, 4.1, '5', (6,)]
    print (list(filter(isInt, S)))
if __name__ == "__main__":
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

结果

[1, 2]
  • 1

map

使用参数1中的函数,对后面的参数进行迭代运算,如果后面迭代对象数量不一致,按最小的算。

def add(x, y):
    return x + y
def main():    
    S = [1, 2, 3.0, 4.1]
    L = [2, 3, 4, 5, 6]
    print (list(map(add, S, L)))
if __name__ == "__main__":
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

结果

[3, 5, 7.0, 9.1]
  • 1

reduce

使用参数1中的函数,对后面的参数进行累加运算,得到一个结果。

from functools import reduce
def main():    
    d = reduce(lambda x, y : x + y, range(101))
    print(d)
if __name__ == "__main__":
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

结果

5050
  • 1

sum

对参数求和

any

对参数全部进行or运算

all

对参数全部进行and运算

max

求参数最大值

min

求参数最小值

16、列表生成式

使用range可以生成一个范围的数据集合,但是range的返回值是Object对象,需要转换为其他对象才可以使用。例如生成一个list

print(list(range(1, 11)))
  • 1

结果为:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  • 1

列表生成式

L = [ m * n for m in range(1, 10) for n in range(1, 10) if m >= n]
print(L)
  • 1
  • 2

结果为:

[1, 2, 4, 3, 6, 9, 4, 8, 12, 16, 5, 10, 15, 20, 25, 6, 12, 18, 24, 30, 36,  
 7, 14, 21, 28, 35, 42, 49, 8, 16, 24, 32, 40, 48, 56, 64, 9, 18, 27, 36,   
 45, 54, 63, 72, 81]
  • 1
  • 2
  • 3

可以用一句话来使用嵌套循环和条件判断语句以及一个表达式来生成一个list,同样也可以使用相同的方法来生成一个dict,一个set

字典生成式

将列表生成式的中括号换成大括号,然后将表达式换成一个key:value的形式就可以变成字典生成式了。

L = { ('%dx%d' % (m, n)) :(m * n) for m in range(1, 10) for n in range(1, 10) if m <= n}
cntLine = 1
for i, v in enumerate(L.items()):
    print(v[0], '=',  v[1], end=" ")
    if (v[1] % 9 == 0) and (v[1] / 9 == cntLine):
        print()
        cntLine += 1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

结果:

1x1 = 1 1x2 = 2 1x3 = 3 1x4 = 4 1x5 = 5 1x6 = 6 1x7 = 7 1x8 = 8 1x9 = 9
2x2 = 4 2x3 = 6 2x4 = 8 2x5 = 10 2x6 = 12 2x7 = 14 2x8 = 16 2x9 = 18
3x3 = 9 3x4 = 12 3x5 = 15 3x6 = 18 3x7 = 21 3x8 = 24 3x9 = 27
4x4 = 16 4x5 = 20 4x6 = 24 4x7 = 28 4x8 = 32 4x9 = 36
5x5 = 25 5x6 = 30 5x7 = 35 5x8 = 40 5x9 = 45
6x6 = 36 6x7 = 42 6x8 = 48 6x9 = 54
7x7 = 49 7x8 = 56 7x9 = 63
8x8 = 64 8x9 = 72
9x9 = 81
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

集合生成式

和字典生成式没啥区别,只是把表达式换成一个值就可以了
有列表,字典,集合生成式但是没有tuple生成式,如果想生成一个tuple的话,需要先生成一个list然后使用tuple()转换成一个tuple。因为小括号这个符号被生成器生成式占用了。

17、生成器

使用生成式来编写生成器

将上面的生成式中括号换成小括号就变成了生成式,生成式是一种特殊的函数,使用next操作来取下一次的值,不会将所有值全部计算出来,而是用到的时候再做计算,节省内存。

g = (x * x for x in range(1, 10))
print (next(g))
print (next(g))
print (next(g))
print (next(g))
  • 1
  • 2
  • 3
  • 4
  • 5

输出:

1
4
9
16
  • 1
  • 2
  • 3
  • 4

同时,生成式也是一个可迭代的对象,可以使用for来迭代

g = (x * x for x in range(1, 10))
for i in g:
    print (i)
  • 1
  • 2
  • 3

输出:

1
4
9
16
25
36
49
64
81
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

使用yeild来编写生成器

yeild的字典含义是产出和让步,对于python来说,这两个含义都成立 yeild b,这个语句会产出一个值,给next(…)的调用方,此外还会做出让步,暂停执行生成器,让调用方继续工作。

def fib(num):    
    cnt, b, c = 0, 0, 1
    while cnt < num:
        yield b
        b, c = c, b + c
        cnt += 1
    print('done')

f = fib(6)
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

结果:

0
1
1
2
3
5
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

yeild类似于一个不完全的return,可以保存状态。每次到yeild时,程序会返回,然后使用next执行的话,可以从本次yeild的位置继续执行

使用send给生成器函数传送值

yeild是给调用者返回一个值,而send是调用者给函数发送一个值。x = yeild i这种情况下,使用send(N)之后,x就被赋值成了N。send必须在调用完至少一次next操作时,才可调用。要确保函数的语句执行到yeild处。

def test():
    for i in range(10):
        x = yield i ** 2
        print("x={0}".format(x))
def main():
    G = test()       
    print(next(G))
    print(G.send(5)) 
    print(next(G))
    print(next(G))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

结果:

0
x=5
1
x=None
4
x=None
9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

生成器的return

在生成器中的return the_result语句会抛出StopIteration(the_result)异常,这样调用方可以用异常的value属性中获取the_result。

18、迭代器

可以被for循环迭代的对象都叫做可迭代对象,可以使用isinstance Iterfable来判断对象是否可以被for循环迭代

from collections.abc import Iterable
isinstance(obj, Iterable)
  • 1
  • 2

除了被for循环迭代,还有一些对象可以使用next迭代。可以使用isinstance Iteraotr来判断是否可以被next迭代

from collections.abc import Iterator
isinstance(obj, Iterator)
  • 1
  • 2

使用iter()可以将list,dict等Iterable对象变成Iterator的

L = list(range(10))
Iter = iter(L)
print (next(Iter))
print (next(Iter))
  • 1
  • 2
  • 3
  • 4

单个迭代器和多个迭代器

有些对象支持单个迭代器,有些支持多个迭代器。单个迭代器是指,使用iter获取该对象的迭代器时,如同单例模式一样,多次获取都是获取同一个迭代器。迭代器1增加,迭代器2也会同时增加
例如range()支持多个迭代器,内置的list等也支持多个迭代器:

R = range(3)
R = range(3)
I1 = iter(R)
I2 = iter(R)
print(next(I1))
print(next(I2))
print(next(I2))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

结果:

0
0
1
  • 1
  • 2
  • 3

zip,map,filter,生成器不支持多个迭代器

Z = zip((1, 2, 3), (10, 11, 12))
I1 = iter(Z)
I2 = iter(Z)
print(next(I1))
print(next(I1))
print(next(I2))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

结果:

(1, 10)
(2, 11)
(3, 12)
  • 1
  • 2
  • 3

19、闭包

闭包的数学含义是能够读取其他函数变量的函数。在python中在一个函数的内部定义另外一个函数,才可以访问这个函数内部的变量,所以在python中闭包可以理解为定义在函数内部的函数。

def createCounter():
    '''
    利用闭包返回一个计数器函数,每次调用它返回递增整数:
    '''
    num = 0
    def counter():
        nonlocal num
        num += 1
        return num
    return counter
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

20、模块

模块通常是一个文件,在python中,模块也是一个对象。模块内的变量,函数,类都是模块的属性。
python文件开头很常见的两个注释

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

main函数

在python中,一个模块如果是被python直接加载的话,__name__属性为’main’,如果是被其他模块加载的话,__name__就是模块的名称。python一般使用下面的代码作为main函数

if __name__=='__main__':
    test()
  • 1
  • 2

21、类

python3的所有类都是继承自object基类的,也支持多重继承。
python中的类和C++中的类有点区别,python中的类也是一种对象,类的实例也是一个对象。
python中也没有私有属性的概念,需要用一些其他手法来实现。在属性名前面加双下划线是一种伪私有的概念,因为他把属性的名称替换成了_类名__属性名这种表示形式。

class A(object):
    def __init__(self):
        self.name = 'A'
class C(object):
    def __init__(self):
        self.color = 'Red'
        self.__private = 'C'
class B(A):
    def __init__(self):
        self.age = 50
        A.__init__(self)
        C.__init__(self)
    def __str__(self):
        result = ''
        for (k,v) in self.__dict__.items():
            result += '{0}={1}\r\n'.format(k, v)
        return result
def main():
    a = B()
    print(a)
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

结果:

age=50
name=A
color=Red
_C__private=C
  • 1
  • 2
  • 3
  • 4

__slots__

由于python是动态类型的语言,类的属性可以使用代码在运行过程中动态的添加。如果想要限制用户添加的话,需要使用__slots__来实现。

class test:
    __slots__ = ['age', 'name']
    def __init__(self):
        self.test = 10
  • 1
  • 2
  • 3
  • 4

结果:

File "/media/bingfengfeifei/数据/PyCode/helloworld.py", line 37, in __init__
    self.test = 10
AttributeError: 'test' object has no attribute 'test'
  • 1
  • 2
  • 3

实际上这种做法是把类的__dict__属性删除掉来实现的,动态添加和删除属性就是通过对类的__dict__属性来操作实现。

property

property是将类内的属性字段,包装成setter,getter函数处理,可以增加一些检查和限制。有两种定义属性的方式

class newprops:
    def getage(self):
        return self._age
    def setage(self, value):
        self._age = value
    def delage(self):
        del self._age    
    def __init__(self):
        self._age = 0
    age = property(getage, setage, delage, "help age") #get set del doc

class newprops2:    
    @property
    def age(self):
        return self._age
    @age.setter
    def age(self, value):
        self._age = value
    @age.deleter
    def age(self):
        del self._age        
    def __init__(self):
        self._age = 0    
def main():
    a = newprops()   
    a.age = 20 
    del(a.age)
    print(hasattr(a, 'age'))
    print(help(a))
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

结果:

False
Help on newprops in module __main__ object:

class newprops(builtins.object)
 |  Methods defined here:
 |
 |  __init__(self)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |
 |  delage(self)
 |
 |  getage(self)
 |
 |  setage(self, value)
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)
 |
 |  age
 |      help age

None
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

重载运算符

python中类的很多操作,都是重定向到一个双下划线开头和结尾的函数__X__,我们重定义这个函数之后,就可以拦截一些内置的操作。

方法 重载 调用
__init__ 构造函数 对象建立时
__del__ 析构函数 对象删除时
__add__ 运算符+ X+Y,X+=Y(如果没有__iadd__)
__or__ 位或运算符 X|Y X|=Y
__repr__, __str__ 打印,转换 print(X), repr(X), str(X)
__call__ 函数调用 X()
__getattr__ 点号运算 X.undefined
__setattr__ 属性赋值语句 X.any = value
__delattr__ 属性删除 del X.any
__getattribute__ 属性获取 X.any
__getitem__ 索引运算 X[key],X[i:j]
__setitem__ 索引赋值语句 X[key]=value
__delitem__ 索引和分片删除 del X[key]
__len__ 长度 len(X)
__bool__ 布尔测试 bool(X)
__lt__,__gt__,__le__,__ge__,__eq__,__ne__ 特定的比较 X < Y, X > Y, X <= Y等
__radd__ 右侧加法 Other+X
__iadd__ 原地加法 X+=Y
__iter__,__next__ 迭代环境 I=Iter(X), next(I)
__contains__ 成员关系测试 item in X
__index__ 整数值 hex(X), bin(X)
__enter__,__exit__ 环境管理器 with obj as var:
__get__,__set__,__delete__ 属性描述符 x.attr,x.attr=value,del x.attr
__new__ 创建 在__init__之间创建对象

枚举类

from enum import Enum, unique
@unique
class WeekDay(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6
def main():
   print(WeekDay['Mon'])
   print(WeekDay.Sat)
   print(WeekDay(2))
   print('------------')
   for k,v in WeekDay.__members__.items():
       print(k,v,v.value)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

结果:

WeekDay.Mon
WeekDay.Sat
WeekDay.Tue
------------
Sun WeekDay.Sun 0
Mon WeekDay.Mon 1
Tue WeekDay.Tue 2
Wed WeekDay.Wed 3
Thu WeekDay.Thu 4
Fri WeekDay.Fri 5
Sat WeekDay.Sat 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

元类

元类是可以生成类的类。使用元类可以在运行时动态的创建一个类。元类需要继承type类型。

type

type可以查看一个变量的类型,type也可以动态的创建一个类。
参数1:类名
参数2:基类
参数3:方法dict

Hello = type('Hello', (object,), dict(hello=lambda self : print('hello world'))) 
a = Hello()
a.hello()
  • 1
  • 2
  • 3

结果:

hello world
  • 1

metaclass

除了使用type创建类之外,还可以使用元类metaclass来创建。metaclass实际上还是使用type去创建一个新的类。

class myMetaClass(type):
    def __new__(cls, name, bases, attrs):        
        attrs['test'] = lambda self, value: print(value)
        return type.__new__(cls, name, bases, attrs)
class myClass(metaclass=myMetaClass):
    pass

def main():
   a = myClass()
   a.test([1,2])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

结果:

[1, 2]
  • 1

22、异常

由于python是动态语音,python里面任何错误都是通过异常来体现的,包括运行时的错误,“编译错误“等。
python所有的异常都是从BaseException类继承的,但是有一些是系统退出,用户键盘终端等与程序运行无关的异常,也是BaseException继承的。为了让用户可以更准确的捕获python的运行异常,通常只需要捕获Exception异常即可。我们自定义异常时,一般也是继承Exception类
一个异常的例子:
else分支是try没有捕获到异常执行,finally分支是最终都要执行的

class MyException(Exception):
    def __str__(self):
        return 'my Exception'
def main():
   
   try:
       raise MyException
   except Exception as e:
       print (e)       
   else:
       print('else')
   finally:
       print('finally')
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

结果:

my Exception
finally
  • 1
  • 2

异常如果没有被捕获,会一直向上抛出,最终python系统会打印一个错误的调用栈。

断言assert

python中的断言也是一种异常,可以看做是一种条件异常,使用assert去判断用户的输入,而不要使用assert去判断系统的错误,系统的错误由异常来处理。

assert False, 'hello'
  • 1

log

python的logging模块分为debug,info,warning,error等几个级别。log级别的顺序也是按这个顺序从低到高。如果开启了某一个级别的log,这个级别及以上的log都会显示。例如开启了debug级别,所有级别的log都会显示。如果开启了warning级别,warning,error级别的会显示。默认是warning级别。

import logging
logging.basicConfig(level=logging.INFO)

def main():
    logging.info('info level')
    logging.warning('warning level')
    logging.debug('debug level')
    logging.error('error level')
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

结果:

INFO:root:info level
WARNING:root:warning level
ERROR:root:error level
  • 1
  • 2
  • 3

pdb

使用pdb可以单步调试,使用下面的命令可以使用pdb调试

python3 -m pdb helloworld.py 

  • 1
  • 2

也可以在代码中,使用下面代码加入断点

import pdb
pdb.set_trace()
  • 1
  • 2

详细的pdb使用参考python官方手册pdb部分

单元测试

单元测试使用unittest模块,首先编写一个测试类,继承自unittest.TestCase,然后编写各种测试方法。setUp方法可以在每个测试例开始时执行,tearDown可以在每个测试例结束的时候执行。
下面是一个单元测试的例子

import unittest

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def get_grade(self):
        if self.score >= 60 and self.score < 80:
            return 'B'
        elif self.score >= 80 and self.score <= 100:
            return 'A'
        elif self.score >=0 and self.score < 60:
            return 'C'
        else:
            raise ValueError
class TestStudent(unittest.TestCase):
    def setUp(self):
        print('set up')
    def tearDown(self):
        print('tear down')
    def test_80_to_100(self):
        print('start test_80_to_100')
        s1 = Student('Bart', 80)
        s2 = Student('Lisa', 100)
        self.assertEqual(s1.get_grade(), 'A')
        self.assertEqual(s2.get_grade(), 'A')
        print('end test_80_to_100')
    def test_60_to_80(self):
        print('start test_60_to_80')
        s1 = Student('Bart', 60)
        s2 = Student('Lisa', 79)
        self.assertEqual(s1.get_grade(), 'B')
        self.assertEqual(s2.get_grade(), 'B')
        print('end test_60_to_80')
    def test_0_to_60(self):
        print('start test_0_to_60')
        s1 = Student('Bart', 0)
        s2 = Student('Lisa', 59)
        self.assertEqual(s1.get_grade(), 'C')
        self.assertEqual(s2.get_grade(), 'C')
        print('end test_0_to_60')

    def test_invalid(self):
        print('start test_invalid')
        s1 = Student('Bart', -1)
        s2 = Student('Lisa', 101)
        with self.assertRaises(ValueError):
            s1.get_grade()
        with self.assertRaises(ValueError):
            s2.get_grade()
        print('end test_invalid')
    
def main():
    unittest.main()
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

结果:

set up
start test_0_to_60
end test_0_to_60
tear down
.set up
start test_60_to_80
end test_60_to_80
tear down
.set up
start test_80_to_100
end test_80_to_100
tear down
.set up
start test_invalid
end test_invalid
tear down
.
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

执行测试例有两个方法:一个是使用下面的代码执行

python3 -m unittest helloworld
  • 1

还有一种是像上面的测试代码一样,在main函数里面执行unittest.main()。这种方式可以像执行普通文件一样,直接执行:

python3 helloworld.py
  • 1

但是此方法在我的vscode中不能直接运行,需要从命令行输入上面的命令去运行。

23、IO

文件

python的文件操作接口和posix接口类似,打开文本文件时,还可以通过encoding参数选择编码,也可以通过errors参数忽略编码的错误,文件操作的示例:

def main():
    with open('test.txt', 'w', encoding='gbk') as f:
        f.write('测试gbk\r\n')
        f.write('hello world\r\n')
        f.write('哈哈哈\r\n')        
    with open('test.txt', 'r', errors='ignore') as f:
        for line in f.readlines():            
            print(line, end='')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

输出:
由于python3默认是utf8打开的,所以这里使用gbk编码的文件打开会出现错误,忽略编码错误之后,中文编码部分没有被读取出来

gbk
hello world
  • 1
  • 2

使用正确的编码测试:

def main():
    with open('test.txt', 'w', encoding='gbk') as f:
        f.write('测试gbk\r\n')
        f.write('hello world\r\n')
        f.write('哈哈哈\r\n')        
    with open('test.txt', 'r', encoding='gbk') as f:
        for line in f.readlines():            
            print(line, end='')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

输出:

测试gbk
hello world
哈哈哈
  • 1
  • 2
  • 3

StringIO, BytesIO

IO操作的函数不仅可以用在文件上面,也可以用在很多IO对象上面。StringIO和BytesIO可以创建IO对象。

from io import StringIO
from io import BytesIO
def main():
    f = StringIO('test\r\nhello world\r\n哈哈哈\r\n')    
    for i in f:
        print(i, end='')
    print('-------------------')
    f = StringIO()
    f.write('new\r\n')
    f.write('test\r\n')
    print(f.getvalue())
    print('-------------------')
    f = BytesIO('测试'.encode('utf-8'))
    for i in f:
        print(i)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

输出:

test
hello world
哈哈哈
-------------------
new
test

-------------------
b'\xe6\xb5\x8b\xe8\xaf\x95'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

文件和目录

python的os模块包含了一些操作系统相关的操作。
获取环境变量

>>> os.environ.get('PATH')
'/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/sbin:/usr/sbin'

  • 1
  • 2
  • 3

获取当前目录

>>> os.path.abspath('.')
'/media/bingfengfeifei/数据/PyCode'

  • 1
  • 2
  • 3

连接目录,因为不同操作系统的目录分隔符不一样,所以需要使用python的目录连接来保证各个操作系统都可以生效

>>> a = os.path.join(os.path.abspath('.'),'new')
>>> os.mkdir(a)
>>> os.listdir('.')
['.vscode', 'helloworld.py', 'new', 'test.py', 'test.txt', '__pycache__']
>>> os.rmdir(a)
>>> os.listdir('.')
['.vscode', 'helloworld.py', 'test.py', 'test.txt', '__pycache__']
>>> 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

分割目录

>>> os.path.split('/media/bingfengfeifei/数据/PyCode/helloworld.py')
('/media/bingfengfeifei/数据/PyCode', 'helloworld.py')

  • 1
  • 2
  • 3

分割扩展名

>>> os.path.splitext('/media/bingfengfeifei/数据/PyCode/helloworld.py')
('/media/bingfengfeifei/数据/PyCode/helloworld', '.py')
  • 1
  • 2

重命名

>>> os.mkdir('new')
>>> os.listdir('.')
['.vscode', 'helloworld.py', 'new', 'test.py', 'test.txt', '__pycache__']
>>> os.rename('new', 'newnew')
>>> os.listdir('.')
['.vscode', 'helloworld.py', 'newnew', 'test.py', 'test.txt', '__pycache__']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

删除文件

>>> os.listdir('.')
['.vscode', 'helloworld.py', 'newnew', 'test.py', 'test.txt', '__pycache__']
>>> os.remove('test.txt')
>>> os.listdir('.')
['.vscode', 'helloworld.py', 'newnew', 'test.py', '__pycache__']
>>> 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

shutil模块还补充了一些os模块里面没有的系统操作。例如复制文件

>>> shutil.copyfile('helloworld.py', 'new.py')
  • 1

序列化

pickle

pickle可以将内存中的数据结构,序列化成一个byte array,也可以将一个序列化好的byte array转换成一个对象。

import pickle
def main():
    d = {'name':'蛤蛤',
         'age':-1,
         'test':[1,2,3,4]}
    with open('dump.dat', 'wb') as f:
        pickle.dump(d, f)
    with open('dump.dat', 'rb') as f:
        newDict = pickle.load(f)
    print(newDict)
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

输出:

{'name': '蛤蛤', 'age': -1, 'test': [1, 2, 3, 4]}
  • 1

dump和load方法是将序列化好的东西向IO对象操作。dumps和loads操作是向byte array操作:

import pickle
def main():
    d = {'name':'蛤蛤',
         'age':-1,
         'test':[1,2,3,4]}
    s = pickle.dumps(d)
    print(s)
    newDict = pickle.loads(s)
    print(newDict)
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

输出:

b'\x80\x03}q\x00(X\x04\x00\x00\x00nameq\x01X\x06\x00\x00\x00\xe8\x9b\xa4\xe8\x9b\xa4q\x02X\x03\x00\x00\x00ageq\x03J\xff\xff\xff\xffX\x04\x00\x00\x00testq\x04]q\x05(K\x01K\x02K\x03K\x04eu.'
{'name': '蛤蛤', 'age': -1, 'test': [1, 2, 3, 4]}
  • 1
  • 2

json

json模块可以将python中的数据结构和json结构来进行转换。同样可以使用dumps和dump两种方法,和pickle类似

import json
def main():
    d = {'name':'蛤蛤',
         'age':-1,
         'test':[1,2,3,4],
         'job':None,
         'alive':True}
    a = json.dumps(d)
    print(a)
    newDict = json.loads(a)
    print(newDict)

    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

输出:

{"name": "\u86e4\u86e4", "age": -1, "test": [1, 2, 3, 4], "job": null, "alive": true}
{'name': '蛤蛤', 'age': -1, 'test': [1, 2, 3, 4], 'job': None, 'alive': True}
  • 1
  • 2

json还可以用来序列化class,需要自定义一个dumps中的default函数,用来从class转换为字典,还需要自定义一个loads里面的object_hook函数,用来从字典转换成对象。

class JsonTest:    
    def __init__(self, name = '蛤蛤', age=-1, test=[1,2,3,4], job=None, alive=True):
        self.name = name
        self.age = age
        self.test = test
        self.job = job
        self.alive = alive
import json
def main():
    d = JsonTest()
    a = json.dumps(d,default=lambda x : x.__dict__)
    print(a)
    newDict = json.loads(a, object_hook=lambda d : JsonTest(d['name'], d['age'] + 1, d['test'], d['job'], d['alive']))
    print(newDict.__dict__)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

输出:

{"name": "\u86e4\u86e4", "age": -1, "test": [1, 2, 3, 4], "job": null, "alive": true}
{'name': '蛤蛤', 'age': 0, 'test': [1, 2, 3, 4], 'job': None, 'alive': True}
  • 1
  • 2

24、进程和线程

进程

详细内容参考
廖雪峰python教程-多进程

fork

在×Nix系统下面可以使用fork来创建子进程,Windows下面没有这个系统调用。

Process

也可以使用multiprocessing.Process来在各个平台创建子进程,这个更为通用

Pool

如果创建的进程数量过多可以使用multiprocessing.Pool进程池来创建多个进程。

子进程

可以使用subprocess模块来启动一个子进程

import subprocess

def main():
    print('$ ping www.python.org')
    r = subprocess.call(['ping', 'www.python.org', '-c', '5'])
    print('Exit code:', r)
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

结果:

$ ping www.python.org
PING dualstack.python.map.fastly.net (151.101.192.223) 56(84) bytes of data.
64 bytes from 151.101.192.223 (151.101.192.223): icmp_seq=1 ttl=51 time=234 ms
64 bytes from 151.101.192.223 (151.101.192.223): icmp_seq=2 ttl=51 time=238 ms
64 bytes from 151.101.192.223 (151.101.192.223): icmp_seq=3 ttl=51 time=231 ms
64 bytes from 151.101.192.223 (151.101.192.223): icmp_seq=4 ttl=51 time=229 ms
64 bytes from 151.101.192.223 (151.101.192.223): icmp_seq=5 ttl=51 time=225 ms

--- dualstack.python.map.fastly.net ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4003ms
rtt min/avg/max/mdev = 225.711/231.863/238.158/4.302 ms
Exit code: 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果子进程是需要输入内容的,还可以使用communicate()方法来输入内容

import subprocess

def main():
    print('$ import this')
    p = subprocess.Popen(['python3'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    output, err = p.communicate('import this'.encode('utf-8'))
    print(output.decode('utf-8'))
    print('Exit code:', p.returncode)
    
   
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

输出:

$ import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Exit code: 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

进程间通讯

使用Queue来通信,实现一个生产者消费者模型:

from multiprocessing import Process, Queue
def producer(q):
    print('process producer pid({0})'.format(os.getpid()))
    for i in ['A', 'B', 'C', 'D', 'E']:
        print('put value {0}'.format(i))
        q.put(i)
        time.sleep(random.random() * 3)

def consumer(q):
    print('process consumer pid({0})'.format(os.getpid()))
    while True:
        print('start value')
        value = q.get(True)
        print('get value {0}'.format(value))
def main():
    q = Queue()
    pro = Process(target=producer, args=(q,))
    con = Process(target=consumer, args=(q,))
    pro.start()
    con.start()
    pro.join()
    

    con.terminate()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

输出:

process producer pid(16750)
put value A
process consumer pid(16751)
start value
get value A
start value
put value B
get value B
start value
put value C
get value C
start value
put value D
get value D
start value
put value E
get value E
start value
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

线程

python中的线程使用threading模块

import threading
import time
def loop():
    print('thread {0} is starting.'.format(threading.current_thread().name))
    for i in range(5):
        print('thread {0} loop {1}.'.format(threading.current_thread().name, i))
        time.sleep(1)
    print('thread {0} end.'.format(threading.current_thread().name))
def main():
    t = threading.Thread(target=loop,name='test')
    t2 = threading.Thread(target=loop,name='test2')
    t.start()
    t2.start()
    t.join()
    t2.join()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

输出:

thread test is starting.
thread test2 is starting.
thread test loop 0.
thread test2 loop 0.
thread test loop 1.
thread test2 loop 1.
thread test2 loop 2.
thread test loop 2.
thread test loop 3.
thread test2 loop 3.
thread test loop 4.
thread test2 loop 4.
thread test end.
thread test2 end.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

python中的锁,使用threading.Lock()创建,acquire方法来获取锁,release方法来释放锁

lock = threading.Lock()

def loop():
    lock.acquire()
    try:
        print('thread {0} is starting.'.format(threading.current_thread().name))
        for i in range(5):
            print('thread {0} loop {1}.'.format(threading.current_thread().name, i))
            time.sleep(1)
        print('thread {0} end.'.format(threading.current_thread().name))
    finally:
        lock.release()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

CPython解释器有一个GIL锁(Global Interpreter Lock),每个python线程执行前都要先获取GIL,然后每执行100条指令释放锁(python3版本改变成了每隔一段时间释放锁),以便于其他线程执行。所以CPython解释器的线程并不能实现真正的并发。

线程局部变量

每个线程里面可以共享全局变量,使用线程ThreadLocal来实现,使用threading.local()来创建ThreadLocal对象

import threading
import time

thread_local = threading.local()
def loop():
    thread_local.num = [x for x in range(10)]
    start()
def start():
    a = thread_local.num
    for i in a:
        print('thread {0} : {1}'.format(threading.current_thread().name, i))   

def main():
    t = []
    for i in range(multiprocessing.cpu_count()):
        thread = threading.Thread(target=loop)
        t.append(thread)
        thread.start()
    for i in t:
        i.join()    
    
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

25、正则表达式

贪婪模式:如果一个正则表达式可以匹配出多个结果,他会尽量多的匹配字符
非贪婪模式:尽量少的匹配字符

表达式 作用
. 匹配任意字符除了换行符
^ 匹配字符串的开头
$ 匹配字符串的结尾
+ 表示至少一个字符
* 表示任意个字符
? 表示一个或0个字符
*?, +?, ?? 正则表达式默认是贪婪匹配,在匹配符后面加一个问号开启非贪婪的模式
{m} 匹配确定m个字符
{m,n} 匹配m到n个字符
{m,n}? 匹配m到n个字符的非贪婪模式
\ 转义字符
[] 匹配中括号中执行的字符,例如[a-z],[0-9A-Z]
[^5] 匹配除了5的所有字符
A|B 匹配A或者B
(…) 匹配括号里面的表达式,并且创建一个group
\number 匹配第number个group的内容 (\d{3})A\1可以匹配’123A123’
\A 只在字符串的开头匹配,需要配合其他表达式使用
\b 匹配单词的开头或者结尾,需要配合其他匹配使用
\B 匹配非单词的开头或者结尾与\b相对
\d 匹配数字
\D 匹配非数字
\s 匹配空白符
\S 匹配非空白符
\w 匹配字母数字下划线
\W 匹配字母数字下划线之外的字符
\Z 只在字符串的尾部匹配,需要配合其他表达式使用

切分字符串

s = r'123 A1    2 3fd   s a f    daA123A321Af'
    a = re.split('\s+', s)
    print(a)
  • 1
  • 2
  • 3

输出:

['123', 'A1', '2', '3fd', 's', 'a', 'f', 'daA123A321Af']
  • 1

分组

s = r'123 A1    2 3fd   s a f    daA123A321Af'
    a = re.match(r'(\A.)(\d{2}).+([a-zA-Z]{2}).+(.\Z)', s)
    print(a.groups())
  • 1
  • 2
  • 3

输出:

('1', '23', 'aA', 'f')
  • 1

编译正则表达式

s1 = r'[email protected]'
    s2 = r'[email protected]'
    pattern = re.compile(r'(.+)@(.+)')
    print(pattern.match(s1).groups())
    print(pattern.match(s2).groups())
  • 1
  • 2
  • 3
  • 4
  • 5

输出:

('xszhengdelun', '126.com')
('sa614374', 'mail.ustc.edu.cn')
  • 1
  • 2

26、常用内建模块

datatime

from datetime import datetime
def main():
    print(datetime.now())
    dt = datetime(2017, 3, 16, 0, 0)
    print(dt)
    print(datetime.now().timestamp())
    print(datetime.fromtimestamp(1429417200.0))
    print(datetime.strptime('2018-10-05 18:55:22', '%Y-%m-%d %H:%M:%S'))
    print(datetime.now().strftime('%a, %b %d %H:%M'))
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

输出:

2018-10-07 21:41:19.897650
2017-03-16 00:00:00
1538919679.897687
2015-04-19 12:20:00
2018-10-05 18:55:22
Sun, Oct 07 21:41
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

collections

from collections import namedtuple
from collections import deque
from collections import defaultdict
from collections import OrderedDict
from collections import Counter
def main():
    Point = namedtuple('Point', ['x', 'y'])
    p = Point(1, 2)
    print('p =',p)

    deq = deque(['a', 'b', 'c'])
    deq.append('d')
    deq.appendleft('z')    
    print('deq =',deq)
    deq.pop()
    deq.popleft()
    print('deq =',deq)


    dd = defaultdict(lambda : 'N/A')
    dd['A'] = 1
    dd['B'] = 2
    print(dd['C'])

    od = OrderedDict({'a':1, 'b':2, 'c':3})    #有序dict
    print('od =',od)

    c = Counter()
    for i in 'hello world!':
        c[i] += 1
    print('c = ', c)
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

输出:

p = Point(x=1, y=2)
deq = deque(['z', 'a', 'b', 'c', 'd'])
deq = deque(['a', 'b', 'c'])
N/A
od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
c =  Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1, '!': 1})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

base64

base64是一种二进制编码方式,采用64个字符来表示任意的二进制数据

import base64

def main():
    a = base64.b64encode('冰封飞飞'.encode('utf-8'))
    print(a)
    print(base64.b64decode(a).decode('utf-8'))

    a = base64.urlsafe_b64encode(b'test-url-safe')
    print(a)
    print(base64.urlsafe_b64decode(a))
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

输出:

b'5Yaw5bCB6aOe6aOe'
冰封飞飞
b'dGVzdC11cmwtc2FmZQ=='
b'test-url-safe'
  • 1
  • 2
  • 3
  • 4

struct

struct模块可以处理字节相关的数据。详细内容参考python官方手册struct

字符 字节序 尺寸 对齐
< 小端序 标准
> 大端序 标准
! 网络序 标准
@ 主机序 native native
= 主机序 标准
格式 C语言类型 Python 类型 标准尺寸
x 填充类型 no value  
c char 长度为1的bytes 1
b signed char integer 1
B unsigned char integer 1
? _Bool bool 1
h short integer 2
H unsigned short integer 2
i int integer 4
I unsigned int integer 4
l long integer 4
L unsigned long integer 4
q long long integer 8
Q unsigned long long integer 8
n ssize_t integer  
N size_t integer  
e   float 2
f float float 4
d double float 8
s char[] bytes  
p char[] bytes  
P void* integer  
import struct

def main():
   a =  struct.pack('=hHQ',-100, 100,100000000000)   
   print(a.hex())
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

输出:

9cff640000e8764817000000
  • 1

hashlib

import hashlib

def main():
   s = '测试MD5哈希摘要算法'
   md5 = hashlib.md5()
   md5B = hashlib.md5()
   md5.update(s.encode('utf-8'))
   for i in s:
       md5B.update(i.encode('utf-8'))
   print(md5.hexdigest())
   print(md5B.hexdigest())

   sha256 = hashlib.sha256()
   sha256B = hashlib.sha256()
   sha256.update(s.encode('utf-8'))
   for i in s:
       sha256B.update(i.encode('utf-8'))
   print(sha256.hexdigest())
   print(sha256B.hexdigest())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

输出:

725b5fca27c243ab4e61b02b0b74b6e2
725b5fca27c243ab4e61b02b0b74b6e2
bc8497b01d5d7d37bda3e8f39c291b73127242709e761fa38f0a7978f72e3fdc
bc8497b01d5d7d37bda3e8f39c291b73127242709e761fa38f0a7978f72e3fdc
  • 1
  • 2
  • 3
  • 4

hmac

import hmac

def main():
   a = hmac.new(b'test', '测试HMAC'.encode('utf-8'), digestmod='sha256')
   print(a.hexdigest())
  • 1
  • 2
  • 3
  • 4
  • 5

输出:

4599b96ad2c95e565f4f8d29c925a3eb0d3549b0b913d996b18393a02a03ea42
  • 1

itertools

import itertools

def main():
    a = itertools.count(1)
    for i in a:
        print(i) # 1,2,3,4,5,6,7...无限打印

    a = itertools.cycle('ABCD')
    for i in a:
        print(i) # A B C D A B C D 循环无限打印

    a = itertools.repeat('A', 3)
    for i in a:
        print(i) # A A A 打印3次

    a = itertools.count(1)
    b = itertools.takewhile(lambda x : x <= 10, a)
    for i in b:
        print(i) #打印 1 2 3 4 5 6 7 8 9 10
    
    for i in itertools.chain('ABC', 'XYZ'):
        print(i) #打印 A B C X Y Z 连接多个迭代对象
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

groupby

groupby将相邻的重复元素挑选出来放在一组

import itertools

def main():
    a = itertools.groupby('AAabBbBcCAaa', lambda c : c.upper())           
    for k, group in a:
        print(k, list(group))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

输出:

A ['A', 'A', 'a']
B ['b', 'B', 'b', 'B']
C ['c', 'C']
A ['A', 'a', 'a']
  • 1
  • 2
  • 3
  • 4

contextlib

使用with可以让open语句在离开with时,自动调用close

with open('test.txt', 'r') as f:
    f.read()
  • 1
  • 2

我们可以通过重载__enter__和__exit__来实现上下文管理。

class myContext:
    def __enter__(self):
        print('enter')
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type:
            print('Error')
        else:
            print('End')
def main():
    with myContext() as f:
        pass
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

输出:

enter
End
  • 1
  • 2

也可以使用@contextmanager装饰器来定义

from contextlib import contextmanager    
class myContext:
    pass
    
@contextmanager            
def context_func():
    print('begin')
    f = myContext()
    yield f
    print('end')
def main():
    with context_func() as f:
        pass    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

输出:

begin
end
  • 1
  • 2

还可以使用@contextmanager实现在调用前后自动执行语句

@contextmanager
def tag(name):
    print("<%s>" % name)
    yield
    print("" % name)

with tag("h1"):
    print("hello")
    print("world")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

输出:

hello world

  • 1
  • 2
  • 3
  • 4

使用closing函数可以将任意对象转换为可以在with里面使用的对象。closing定义如下,实际上就是在执行完之后调用close方法。如果转换的对象没有close方法,就GG了。

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

urllib

用于操作URL

XML

from xml.parsers.expat import ParserCreate
  • 1

HTMLParser

from html.parser import HTMLParser
  • 1

27、第三方模块

Pillow

PIL是python2的图形处理库,Pillow是将PIL适配到python3的版本

requests

requests库是处理URL的第三方库,使用起来比urllib要简单

chardet

chardet可以预测bytes的编码,应用在TCP章节

psutil

获取系统信息的工具,可以获取CPU,内存,磁盘,网络,进程

tqdm

展示命令行进度条

28、virtualenv

virtualenv可以隔离python运行环境,解决不同版本python之间的冲突问题

29、socket

TCP

python的socket使用思路和posix接口类似,只不过参数的传入要简单,因为没有类型的概念,所以记忆量要小很多,基本可以直接裸写。linux下C语言的socket裸写压力还有有点大。
Server代码:

# -*- coding: utf-8 -*-

import socket
import chardet
import threading

def handlePacket(sock, addr):
    print(sock)
    print(addr)
    while True:      
        data = sock.recv(1024)
        encoding = chardet.detect(data)['encoding']
        data = data.decode(encoding)
        print(data)
        if not data or data[:4] == 'exit':
            break
        data = 'Hello :' + data
        sock.send(data.encode(encoding))
    sock.close()
def main():    
    with socket.socket() as s:
        s.bind(('localhost', 6666))
        s.listen()
        while True:
            sock, addr = s.accept()
            handleThread = threading.Thread(target=handlePacket,args=(sock, addr))
            handleThread.start()           
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

Client代码:

# -*- coding: utf-8 -*-
import time
import socket
def main():
    buffer = []
    with socket.socket() as s:
        data = ('MyClient', 'Test', "Python", 'exit', 'after')
        s.connect(('localhost', 6666))
        for i in data:
            s.send(i.encode('utf-8'))
            recvData = s.recv(1024)
            print(recvData.decode('utf-8'))       
    
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Client也可以直接使用nc来连接

bingfengfeifei@bingfengfeifei-PC:nc localhost 6666
  • 1

UDP

udp的代码写了一个客户端和服务器ping pang报文的测试
Server:

# -*- coding: utf-8 -*-

import socket
import chardet
import threading
def main():    
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
        s.bind(('localhost', 6666))
        while True:
            data, addr = s.recvfrom(1024)
            print(data)
            s.sendto(data, addr)
    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

Client:

# -*- coding: utf-8 -*-
import time
import socket
def main():
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:        
        
        s.sendto(b'ping pang test', ('localhost', 6666))
        while True:
            data, addr = s.recvfrom(1024)
            print(data,i)
            s.sendto(data, addr)

    
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

30、SQL

debian系mysql安装,安装过程中会输入密码,用5.7有问题,换成5.6版本了。

sudo apt install mysql-server-5.6
  • 1
mysql -u root -p #root账户进入数据库
create database test #创建数据库
  • 1
  • 2

修改mysql配置文件vi /etc/mysql/my.cnf,添加如下内容

[client]
default-character-set = utf8

[mysqld]
default-storage-engine = INNODB
character-set-server = utf8
collation-server = utf8_general_ci
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

进入数据库输入下面内容,查看是否生效

show variables like '%char%';
  • 1

pip安装mysql驱动,两者选其一

pip3 install mysql-connector-python --allow-external mysql-connector-python
pip3 install mysql-connector
  • 1
  • 2

下面创建表的时候要写上charset=utf8,否则不支持中文

# -*- coding: utf-8 -*-
import time
import socket
import sqlite3
import os
import mysql.connector
def main():
    conn = mysql.connector.connect(user='root', password='123456', database='test', charset='utf8')
    cursor = conn.cursor()
    try:
        cursor.execute('drop table user')
    except Exception:
        pass
    try:        
        cursor.execute('create table user (id varchar(20) primary key , name varchar(20)) charset=utf8')
        cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'afbcd冰封飞飞'])
        print(cursor.rowcount)
        conn.commit()
        cursor.close()
        
        cursor = conn.cursor()
        cursor.execute('select * from user where id = %s' ,('1',))
        values = cursor.fetchall()
        print(values)
    except Exception as e:
        print(e)
    finally:
        pass
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

输出:

1
[('1', 'afbcd冰封飞飞')]
  • 1
  • 2

SQLAlchemy

使用ORM框架的话可以尝试SQLAlchemy,安装方式:

pip3 install sqlalchemy
  • 1

31、异步IO

协程

协程是一种轻量级的线程,在一些编程语言中语法支持,python中可以通过yeild来实现协程。C和C++目前在语法层面不支持协程,需要通过一些底层的手段hack实现, 在说协程之前需要先了解几个概念。

并发(concurrency)和并行(parallelism)的区别:

并发强调的是在一个时间段,有多个任务可以执行,但是这里不要求多个任务同时执行,他们可以交替执行,类似于单核处理器,操作系统的进程调度。多个任务交替执行,在用户的眼里是同时运行的。
并行强调的是多个任务同时执行,而不是并发这种可以交替执行的行为。例如,在多核处理器上面,不同任务可以在不同的核上在同一时刻同时执行。

协程和线程的区别:

多个线程可能跑在同一个CPU核上,也可以跑在多个CPU核上,如果跑在一个CPU核上的话,多个线程就是由调度器来调度交替执行。如果绑在了不同的CPU核上的话,可以认为两个线程可以并行的执行。但是无论线程以那种形式运行,从宏观上来看,线程是“同时运行”,而且如果没有线程同步的话,多个线程之间的执行顺序是随机的,而且线程之间可能存在资源争用,访问全局资源时,通常需要加锁。
协程也是多个任务交替执行,但是协程的控制流程,函数的切换都是自己代码实现的,协程是多个任务协作完成的,但是逻辑上是连贯的,协程可以让多个任务按照你的逻辑顺序来执行,保证思维的连贯性,这种逻辑的顺序性也让协程不需要考虑全局资源争用的问题,也就不需要加锁。和线程相比,这种主动让出型调度比线程的被动调度要高效,也比操作系统的调度代价要小得多。

协程的生产者和消费者

使用多线程的生产者消费者模式,如果没有同步的话,生成者和消费者之间执行的次序会随机执行,由调度决定,而协程版本,可以由调用者来决定执行顺序,在这里我们使生产者和消费者交替执行。

def consumer():
    while True:
        print('waiting data from producer...')
        data = yield        
        print('data = {0}'.format(data))
def producer(con):
    data = range(1, 20)
    con.send(None)
    for i in data:
        print('generator data {0}'.format(i))
        con.send(i)
    con.close()


def main():    
    con = consumer()
    producer(con)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

输出:

waiting data from producer...
generator data 1
data = 1
waiting data from producer...
generator data 2
data = 2
waiting data from producer...
generator data 3
data = 3
waiting data from producer...
generator data 4
data = 4
waiting data from producer...
generator data 5
data = 5
waiting data from producer...
generator data 6
data = 6
waiting data from producer...
generator data 7
data = 7
waiting data from producer...
generator data 8
data = 8
waiting data from producer...
generator data 9
data = 9
waiting data from producer...
generator data 10
data = 10
waiting data from producer...
generator data 11
data = 11
waiting data from producer...
generator data 12
data = 12
waiting data from producer...
generator data 13
data = 13
waiting data from producer...
generator data 14
data = 14
waiting data from producer...
generator data 15
data = 15
waiting data from producer...
generator data 16
data = 16
waiting data from producer...
generator data 17
data = 17
waiting data from producer...
generator data 18
data = 18
waiting data from producer...
generator data 19
data = 19
waiting data from producer...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

yeild from

yeild from是新增的语法,PEP380讨论加入,用法:RESULT = yield from EXPRyeild from后面跟一个生成器对象,遍历生成器,将生成器yeild的每一个值返回给调用方。这样可以将嵌套的协程简化编写。
在一个生成器函数gen中执行yeild from subgen()时,subgen会获得控制权,把产出的值传给gen的调用方,即调用方可以直接控制嵌套的subgen()。与此同时,gen会阻塞,直到subgen终止执行。
下面看一个yeild from的例子

def subgen():
    for i in range(5):       
        r = yield i
        print('sub gen recive send{0}'.format(r))
    return 'return mywait'

def gen():
    for i in range(2):
        print("hello world!")
        r = yield from subgen()
        print(r)
        print('hello again!')
def main():    
    try:
        g = gen()
        next(g)
        while True:            
            print(g.send('A'))
    except Exception as e:
        pass
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

输出:

hello world!
sub gen recive sendA
1
sub gen recive sendA
2
sub gen recive sendA
3
sub gen recive sendA
4
sub gen recive sendA
return mywait
hello again!
hello world!
0
sub gen recive sendA
1
sub gen recive sendA
2
sub gen recive sendA
3
sub gen recive sendA
4
sub gen recive sendA
return mywait
hello again!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

在这个例子中,r = yield from subgen(),执行到这句话时,函数的控制权从gen()转移到subgen()中,并且遍历执行。这里yeild出来5个值0,1,2,3,4,通过yeild from直接传递给调用方main函数。所以在print(g.send('A'))时,就打印出来了subgen() yeild的值,而在main函数send的数值,也可以直接传递给subgen()里面中去,yeild from像是一个通道打通了subgen()和main()之间的数据,gen()和subgen()之间的数据交换。r = yield from subgen()这句话的r的作用是可以接受subgen()的返回值, gen()也可以通过这样的写法来获取到subgen()给自己传递的信息。

asyncio

以上的协程只是展示了一个在单线程内,任务切换的功能。如何实现真正的异步,还需要配合消息循环和事件来处理。asyncio是python3.4新引入的标准库,内置了异步的IO。asyncio的编程模型是获取模块的主消息循环,并且把协程扔到消息循环中,实现异步IO。这样可以在协程执行耗时操作的IO操作时,交出控制权,调度器来执行其他的协程,当IO操作执行完毕时,又可以切回执行完IO操作的协程。

@asyncio.coroutine装饰器

asyncio包使用的协程是较为严格的定义,适合asyncio API的协程在定义中必须使用yield from,而不能使用yeild。@asyncio.coroutine装饰器应该使用到协程上面,并不是强求的,但是强烈建议这样做。可以将协程从普通函数中凸出出来。
下面一个例子,使用协程来实现异步操作,程序的效果是模拟执行一个耗时的操作,然后控制台绘制一个光标旋转的操作,来表示有任务正在执行。

# -*- coding: utf-8 -*-
import asyncio
import itertools
import sys

@asyncio.coroutine
def spin(msg):
    write, flush = sys.stdout.write, sys.stdout.flush
    for char in itertools.cycle('|/-\\'):
        status = char + ' ' + msg
        write(status)
        flush()
        write('\x08' * len(status))
        try:
            yield from asyncio.sleep(.1)
        except asyncio.CancelledError:
            break
    write(' ' * len(status) + '\x08' * len(status))

@asyncio.coroutine
def slow_function():
    yield from asyncio.sleep(3)
    return 42

@asyncio.coroutine
def supervisor():
    spinner = asyncio.ensure_future(spin('thinking'))
    print('spinner object:', spinner)
    result = yield from slow_function()
    spinner.cancel()
    return result

def main():
    loop = asyncio.get_event_loop()
    result = loop.run_until_complete(supervisor())
    loop.close()
    print('Answer:', result)
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

该程序的main()函数,将协程supervisor()加入主事件循环。
在supervisor()中,使用asyncio.ensure_future创建了一个协程任务(在python3.7+的版本,应该使用asyncio.create_task来创建。)然后使用yield from slow_function()来模拟执行一个耗时的操作。之后耗时操作执行完毕,关闭spinner协程。
slow_function()里面使用asyncio.sleep(3)来模拟一个耗时3秒的操作,对于slow_function()来说,这里会阻塞3秒的时间,而对于整个事件循环来说,这里会将控制权交给主循环,会继续执行其他的协程。这就是异步的sleep,如果使用time.sleep的话,整个主循环都会阻塞在这里,其他的协程不会被调度。
spin()函数里面循环打印字符来模拟等待。’\x08’是退格操作。
在这个例子中,slow_function()的耗时操作和spin()函数的打印是并发执行的,但是仅用到了一个线程。

async/await

python3.5使用了新的语法来表示协程,用于替代@asyncio.coroutine装饰器和yield from语法。如果使用python3.5的协程新语法的话,只需要将
@asyncio.coroutine替换为async
将yield from替换为await
使用新的语法可以更加清晰的表明协程,也和其他支持协程的语言的语法更加一致。
将上面的协程代码替换为新的语法:

# -*- coding: utf-8 -*-
import asyncio
import itertools
import sys

async def spin(msg):
    write, flush = sys.stdout.write, sys.stdout.flush
    for char in itertools.cycle('|/-\\'):
        status = char + ' ' + msg
        write(status)
        flush()
        write('\x08' * len(status))
        try:
            await asyncio.sleep(.1)
        except asyncio.CancelledError:
            break
    write(' ' * len(status) + '\x08' * len(status))


async def slow_function():
    await asyncio.sleep(3)
    return 42


async def supervisor():
    spinner = asyncio.ensure_future(spin('thinking'))
    print('spinner object:', spinner)
    result = await slow_function()
    spinner.cancel()
    return result

def main():
    loop = asyncio.get_event_loop()
    result = loop.run_until_complete(supervisor())
    loop.close()
    print('Answer:', result)
if __name__ == '__main__':
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

aiohttp

aiohttp是一个异步的http框架,下面是使用aiohttp创建一个http server

# -*- coding: utf-8 -*-
import asyncio
from aiohttp import web

routes = web.RouteTableDef()
@routes.get('/')
async def index(request):
    #await asyncio.sleep(0.5)
    return web.Response(body=b'

Index

', content_type='text/html') @routes.get('/hello/{name}') async def hello(request): #await asyncio.sleep(0.5) text = '

hello, %s!

' % request.match_info['name'] return web.Response(body=text.encode('utf-8'), content_type='text/html') def init(): app = web.Application() app.router.add_routes(routes) web.run_app(app, host='localhost', port='8080') init()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

下面是一个aiohttp作为client的例子:
使用aiohttp,使用协程异步下载几个文件

# -*- coding: utf-8 -*-
import time
import sys
import os

import asyncio
import aiohttp

POP20_CC = ('CN IN US ID BR PK NG BD RU JP MX PH VN ET EG DE IR TR CD FR').split()
BASE_URL = 'http://flupy.org/data/flags'

DEST_DIR = 'downloads/'

def save_flag(img, filename):
    path = os.path.join(DEST_DIR, filename)
    with open(path, 'wb') as fp:
        fp.write(img)

def get_flag(cc):
    url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())
    resp = requests.get(url)
    return resp.content

def show(text):
    print(text, end=' ')
    sys.stdout.flush()

async def get_flag(cc):
    url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            image = await resp.read()
    return image

async def download_one(cc):
    image = await get_flag(cc)
    show(cc)
    save_flag(image, cc.lower() + '.gif')
    return image
def download_many(cc_list):
    loop = asyncio.get_event_loop()
    to_do = [download_one(cc) for cc in sorted(cc_list)]
    wait_coro = asyncio.wait(to_do)
    res, _ = loop.run_until_complete(wait_coro)
    loop.close()
    return len(res)
def main(download_many):
    t0 = time.time()
    count = download_many(POP20_CC)
    elapsed = time.time() - t0
    msg = '\n{} flags downloaded in {:.2f}s'
    print(msg.format(count, elapsed))
if __name__ == '__main__':
    main(download_many)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

future

future指一种对象,表示异步执行的结果,是concurrent.futures模块和asyncio包重要的组件,可是这两个库的用户,有时却看不到future。我们编写的代码可能没有直接使用future。
从python3.4起,标准库中有两个名为Future的类,concurrent.futures.Future和asyncio.Future。这两个类的作用相同,都表示可能已经完成或者尚未完成的延迟计算。
future封装待完成的操作,可以放入队列,完成的状态可以查询,得到结果后可以获取结果。
通常情况下,我们不应该自己创建future,而只能由并发框架(concurrent.futures或asyncio)实例化。
下面一个例子使用concurrent.futures模块模拟下载一些内容

# -*- coding: utf-8 -*-
from concurrent import futures
import time
import random 
def download_one(cc):
    time.sleep(random.random() * 2)
    print('download {}'.format(cc))
    return cc

def download_many(cc_list):
    workers = min(20, len(cc_list))
    with futures.ThreadPoolExecutor(workers) as executor:
        res = executor.map(download_one, cc_list)
    return len(list(res))
        
def main(download_many):
    t0 = time.time()
    count = download_many(['S1', 'S2', 'S3', 'S4', 'S5', 'S6'])
    elapsed = time.time() - t0
    msg = '\n{} flags downloaded in {:.2f}s'
    print(msg.format(count, elapsed))
if __name__ == '__main__':
    main(download_many)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

输出:

download S1
download S4
download S3
download S5
download S2
download S6

6 flags downloaded in 1.91s
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

executor.map和内置map类似,不过download_one会在多个线程并发调用。和上面开头介绍的一样,这里我们并没有看到future,我们使用并发框架的时候一般不会自己去创建future。
下面的代码是我们手动创建future对象来实现

# -*- coding: utf-8 -*-
from concurrent import futures
import time
import random 
def download_one(cc):
    time.sleep(random.random() * 2)
    print('download {}'.format(cc))
    return cc

def download_many(cc_list):
    cc_list = cc_list[:5]
    with futures.ThreadPoolExecutor(max_workers=3) as executor:
        to_do = []
        for cc in sorted(cc_list):
            future = executor.submit(download_one, cc)
            to_do.append(future)
            msg = 'Scheduled for {}: {}'
            print(msg.format(cc, future))
        results = []
        
        for future in futures.as_completed(to_do):
            res = future.result()
            msg = '{} result: {!r}'
            print(msg.format(future, res))
            results.append(res)
    return len(list(results))
        
def main(download_many):
    t0 = time.time()
    count = download_many(['S1', 'S2', 'S3', 'S4', 'S5', 'S6'])
    elapsed = time.time() - t0
    msg = '\n{} flags downloaded in {:.2f}s'
    print(msg.format(count, elapsed))
if __name__ == '__main__':
    main(download_many)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

输出:

Scheduled for S1: 
Scheduled for S2: 
Scheduled for S3: 
Scheduled for S4: 
Scheduled for S5: 
download S1
 result: 'S1'
download S2
 result: 'S2'
download S4
 result: 'S4'
download S5
 result: 'S5'
download S3
 result: 'S3'

5 flags downloaded in 1.91s
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

executor.submit将任务提交对执行队列,然后返回一个future对象,这个时候对象就已经开始准备执行了。
for future in futures.as_completed(to_do):这个循环是等待执行完毕,获取结果。
由于受GIL限制,上面的代码都不能并行下载,但是GIL几乎对IO密集型的处理无害,标准库中的所有执行IO操作的函数,在等待操作系统返回结果时,都会释放GIL。这意味着在Python语言这个层次上可以使用多线程,而IO密集型的程序可以从中受益。上面的代码每个任务下载需要时间0-2秒,但是并发下载完5个对象,用时并没有5倍于单次下载的时间。

ProcessPoolExecutor

如果做CPU密集的操作,可以使用ProcessPollExecutor,这个模块将工作分配给多个Python进程处理,可以绕开GIL利用所有的CPU核心。使用方式只需要把上面的ThreadPoolExecutor替换成ProcessPoolExecutor即可

async with/async for

异步中的上下文管理器with和异步迭代器使用了新语法async with和async for。具体的简介参考https://blog.csdn.net/tinyzhao/article/details/52684473

你可能感兴趣的:(python,python)