cook book(3:数字日期和时间 4:迭代器与生成器)
1:数字的四舍五入 round() 方法返回浮点数x的四舍五入值。
# 对浮点数执行指定精度的舍入运算 # 简单的舍入运算,使用内置的 round(value, ndigits) 函数 # round函数对小数的取舍默认四舍五入 round(1.23, 1) # 1.2 round(1.27, 1) # 1.3 round(-1.27, 1) # -1.3 round(1.25361,3) # 1.254
# 当一个值刚好在两个边界的中间的时候, round 函数返回离它最近的偶数。 对1.5或者2.5的舍入运算都会得到2 print(round(1.5)) # 2 print(round(2.5)) # 2
# 不传两个参数保留到整数,传了后面参数小数位数 round(10.0/3, 2) # 3.33 round(20/7) # 3
round函数坑一:四舍六入,为五偏偶 # 在python2.7中:保留值将保留到离上一位更近的一端(四舍六入), 如果距离两端一样远,则保留到离0远的一边。所以round(0.5)会近似到1,而round(-0.5)会近似到-1 # 在python3.5c中: 保留值将保留到离上一位更近的一端(四舍六入), 如果距离两边一样远,会保留到偶数的一边。比如round(0.5)和round(-0.5)都会保留到0,而round(1.5)会保留到2 python3中:round(1.5)输出2,round(2.5)也输出2,偏向偶数,一句话,四舍六入,为五偏偶 所以round并不是单纯的四舍五入,特殊数字round出来的结果可能未必是想要的。
round函数坑二:特殊数字round出来的结果可能未必是想要的 round(2.675, 2) # 2.67 # round(2.675, 2) 的结果,不论我们从python2还是3来看,结果都应该是2.68的,
结果它偏偏是2.67,为什么?这跟浮点数的精度有关。我们知道在机器中浮点数不一定能精确表达,
因为换算成一串1和0后可能是无限位数的,机器已经做出了截断处理。那么在机器中保存的2.675这个数字就比实际数字要小那么一点点。
这一点点就导致了它离2.67要更近一点点,所以保留两位小数时就近似到了2.67
# 尽量避开使用round函数,使用其他优秀的内置近似函数 1:使用math模块中的一些函数,比如math.ceiling(天花板除法)。 2:python自带整除,python2中是/,3中是//,还有div函数。 3:字符串格式化可以做截断使用,例如 "%.2f" % value(保留两位小数并变成字符串……如果还想用浮点数请披上float()的外衣)。 4:对浮点数精度要求如果很高的话,请用decimal模块
# 传给 round() 函数的 ndigits 参数可以是负数,这种情况下, 舍入运算会作用在十位、百位、千位等上面 print(round(1627731, -1)) # 1627730(作用在十位上) print(round(1627731, -2)) # 1627700(作用在百位上) print(round(1627731, -3)) # 1628000(作用在千位上)
# 舍入和格式化差别:输出一定宽度的数,你不需要使用 round() 函数。 而仅仅只需要在格式化的时候指定精度即可 x = 1.23456 print(f"{x:0.2f}") # 1.23 print(format(x, '0.3f')) # 1.235 print("{:0.4f}".format(x)) # 1.2346 #三种优秀的format大法
# 不要试着去舍入浮点值来”修正”表面上看起来正确的问题 a = 2.1 b = 4.2 c = a + b print(c) # 6.300000000000001 print(round(c, 2)) # 6.3 # 对于大多数使用到浮点的程序,不推荐这样做。 尽管在计算的时候会有一点点小的误差,
# 但是这些小的误差是能被理解与容忍的。 如果不能允许这样的小误差(比如涉及到金融领域),那么就得考虑使用 decimal 模块了
2:执行精确的浮点数运算 对浮点数执行精确的计算操作,并且不希望有任何小误差的出现使用decimal
模块
# 浮点数的一个普遍问题是它们并不能精确的表示十进制数。 并且,即使是最简单的数学运算也会产生小的误差 a = 4.2 b = 2.1 a + b # 输出 6.300000000000001 (a + b) == 6.3 # 输出 False 这些错误是由底层CPU和IEEE 754标准通过自己的浮点单位去执行算术时的特征。 由于Python的浮点数据类型使用底层表示存储数据
# 更加精确计算(并能容忍一定的性能损耗),使用 decimal 模块: from decimal import Decimal a = Decimal('4.2') b = Decimal('2.1') print(a+b) # 6.3 print((a+b) == Decimal("6.3")) # True print(4.2 + 2.1) # 6.300000000000001 # Decimal 对象会像普通浮点数一样的工作(支持所有的常用数学运算) # 打印它们或者在字符串格式化函数中使用它们,看起来跟普通数字没什么两样
# decimal 模块允许控制计算的每一方面,包括数字位数和四舍五入运算。 为了这样做,先得创建一个本地上下文并更改它的设置 from decimal import Decimal from decimal import localcontext a = Decimal('1.3') b = Decimal('1.7') print(a/b) # 0.7647058823529411764705882353 with localcontext() as ctx: ctx.prec = 3 # 保留三位小数四舍五入 print(a/b) # 0.765 with localcontext() as ctx: ctx.prec = 50 # 保留50位小数四舍五入 print(a/b) # 0.76470588235294117647058823529411764705882352941176
# 减法删除以及大数和小数的加分运算所带来的影响 nums = [1.23e+18, 1, -1.23e+18] print(sum(nums)) # 输出0 # 上面的错误可以利用 math.fsum() 所提供的更精确计算能力来解决: import math nums = [1.23e+18, 1, -1.23e+18] print(math.fsum(nums)) # 1.0
3:数字的格式化输出 控制数字的位数、对齐、千位分隔符输出
# 格式化输出单个数字的时候使用内置的 format() 函数 x = 1234.56789 print(format(x, "0.2f")) # 1234.57 print(format(x, '<10.1f')) # 1234.6 (<左对齐,10输出的位置占十个字符,.1f浮点数保留一位小数) print(format(x, "^10.1f")) # 1234.6 (^中间对齐,10输出的位置占十个字符,.1f浮点数保留一位小数) print(format(x, '0,.1f')) # 1,234.6 print(format(x, '0.1f')) # 1234.6
# 使用指数记法,将f改成e或者E(取决于指数输出的大小写形式) x = 1234.56789 print(format(x, 'e')) # 1.234568e+03 print(format(x, '0.2E')) # 1.23E+03
# 同时指定宽度和精度的一般形式是 '[<>^]?width[,]?(.digits)?' ,
# 其中 width 和 digits 为整数,?代表可选部分。 同样的格式也被用在字符串的 format() 方法中 x = 1234.56789 print('The value is {:0,.2f}'.format(x)) # The value is 1,234.57
# format格式化字符串适合浮点数和 decimal 模块中的 Decimal 数字对象 # 当指定数字的位数后,结果值会根据 round() 函数同样的规则进行四舍五入后返回 x = 1234.56789 print(format(x, "0.1f")) # 1234.6 print(format(-x, '0.1f')) # -1234.6
# 包含千位符的格式化跟本地化没有关系。 如果你需要根据地区来显示千位符,
# 你需要自己去调查下 locale 模块中的函数了。 你同样也可以使用字符串的 translate() 方法来交换千位符 x = 1234.56789 swap_separators = {ord('.'): ',', ord(','): '.'} # 转换规则, .转成, ,转换成. print(format(x, ',').translate(swap_separators)) # 1.234,56789
# % 号格式化数字 x = 1234.56789 print('%0.2f' % x) # 1234.57 print('%10.1f' % x) # 1234.6 十个长度,右靠 print('%-10.1f' % x) # 1234.6 十个长度,左靠
4:二八十六进制整数
# 将整数转换为二进制、八进制或十六进制的文本串, 可以分别使用 bin() , oct() 或 hex() 函数 ps:重点,使用这三个函数返回的数据任然是字符串,并不是用了bin()就是二进制了,本质上返回的还是二进制模样的字符串而已 x = 1234 print(bin(x), type(bin(x))) # 0b10011010010 <class 'str'> print(oct(x), type(oct(x))) # 0o2322 <class 'str'> print(hex(x), type(hex(x))) # 0x4d2 <class 'str'>
# 如果你不想输出 0b , 0o 或者 0x 的前缀的话,可以使用 format() 函数 x = 1234 print(format(x, 'b'), type(format(x, 'b'))) # 10011010010 <class 'str'> print(format(x, 'o'), type(format(x, 'o'))) # 2322 <class 'str'> print(format(x, 'x'), type(format(x, 'x'))) # 4d2 <class 'str'>
# 整数是有符号的,所以如果你在处理负数的话,输出结果会包含一个负号。 x = -1234 print(format(x, "b")) # -10011010010(二进制) print(format(x, "x")) # -4d2(十六进制) # 想产生一个无符号值,你需要增加一个指示最大位长度的值。比如为了显示32位的值 x = -1234 print(format(2**32 + x, 'b')) # 11111111111111111111101100101110 print(format(2**32 + x, 'x')) # fffffb2e 4个二进制标识一个16进制
# 以不同的进制转换整数字符串,简单的使用带有进制的 int() 函数即可: print(int('4d2', 16)) # 1234 16进制的4d2转换成10进制1234 print(int('10011010010', 2)) # 1234 2进制的10011010010转化成十进制的1234
# 这些函数的转换属于整数和其对应的文本表示之间的转换,永远只有一种整数类型 # Python指定八进制数的语法跟其他语言稍有不同 import os # print(os.chmod('script.py', 0755)) # 报错 # 需确保八进制数的前缀是 0o print(os.chmod('script.py', 0o755)) # linux修改文件权限为755,7表示可读可写可执行,
5:字节到大整数的打包与解包
# 字节字符串想将它解压成一个整数,大整数转换为一个字节字符串 # 128位长的16个元素的字节字符串 data = b'\x00\x124V\x00x\x90\xab\x00\xcd\xef\x01\x00#\x004' # bytes数据类型 print(len(data)) # 16长度x00一个长度,4v什么也是一个长度 # 将bytes解析为整数:int.from_bytes() 方法 print(int.from_bytes(data, 'little')) # 69120565665751139577663547927094891008 print(type(int.from_bytes(data, 'little'))) # int类型 print(len('69120565665751139577663547927094891008')) # 38 print(int.from_bytes(data, 'big')) # 94522842520747284487117727783387188
# 将一个大整数转换为一个字节字符串,使用 int.to_bytes() 方法 x = 94522842520747284487117727783387188 print(x.to_bytes(16, 'big')) # b'\x00\x124V\x00x\x90\xab\x00\xcd\xef\x01\x00#\x004' # 这么大的整数必须使用16位128个字节来转换,指定位数小了报错 print(type(x.to_bytes(16, 'big'))) # <class 'bytes'> print(x.to_bytes(16, 'little')) # b'4\x00#\x00\x01\xef\xcd\x00\xab\x90x\x00V4\x12\x00'
# struct 模块来解压对于整数的大小是有限制的。可能想解压多个字节串并将结果合并为最终的结果 import struct data = b'\x00\x124V\x00x\x90\xab\x00\xcd\xef\x01\x00#\x004' hi, lo = struct.unpack('>QQ', data) print((hi << 64) + lo) # 94522842520747284487117727783387188 print(int.from_bytes(data, 'big')) # 94522842520747284487117727783387188
& 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 (a & b) 输出结果 12 ,二进制解释: 0000 1100
| 按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。 (a | b) 输出结果 61 ,二进制解释: 0011 1101
^ 按位异或运算符:当两对应的二进位相异时,结果为1 (a ^ b) 输出结果 49 ,二进制解释: 0011 0001
~ 按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1 。~x 类似于 -x-1 (~a ) 输出结果 -61 ,二进制解释: 1100 0011,在一个有符号二进制数的补码形式。
<< 左移动运算符:运算数的各二进位全部左移若干位,由 << 右边的数字指定了移动的位数,高位丢弃,低位补0。 a << 2 输出结果 240 ,二进制解释: 1111 0000
>> 右移动运算符:把">>"左边的运算数的各二进位全部右移若干位,>> 右边的数字指定了移动的位数
a = 60 # 60 = 0011 1100 b = 13 # 13 = 0000 1101 c = 0 c = a & b; # 12 = 0000 1100 12 c = a | b; # 61 = 0011 1101 61 c = a ^ b; # 49 = 0011 0001 49 c = ~a; # -61 = 1100 0011 -61 c = a << 2; # 240 = 1111 0000 240 c = a >> 2; # 15 = 0000 1111 15
# 字节顺序规则(little或big)仅仅指定了构建整数时的字节的低位高位排列方式
x = 0x01020304
print(x.to_bytes(4, 'big')) # b'\x01\x02\x03\x04'
print(x.to_bytes(4, 'little')) # b'\x04\x03\x02\x01'
# 将一个整数打包为字节字符串使用 int.bit_length() 方法来决定需要多少字节位来存储这个值 x = 523 ** 23 # print(x.to_bytes(16, 'little')) # OverflowError: int too big to convert 报错,16位128字节无法编码这个大数据 print(x.bit_length()) # 208,求出这个整数的二进制位数位208,然后208/8 = 26 print(x.to_bytes(26, 'little')) # b'\x03X\xf1\x82iT\x96\xac\xc7c\x16\xf3\xb9\xcf\x18\xee\xec\x91\xd1\x98\xa2\xc8 # \xd9R\xb5\xd0' nbytes, rem = divmod(x.bit_length(), 8) if rem: nbytes += 1 print(nbytes) # 26 print(x.to_bytes(nbytes, 'little'))
方法1:使用x.bit_length()求出这个大整数需要多少二进制位才能转换,然后除8查看多少字节然后转换
方法2:使用divmod()求需要多少字节才能转换这个大整数
divmod(a, b)除法,返回一个包含商+余数的数组
6:复数的数学运算
# 复数可以用使用函数 complex(real, imag) 或者是带有后缀j的浮点数来指定 a = complex(2, 4) b = 3 - 5j # 实部、虚部和共轭复数的获取 a = complex(2, 3) print(a.real) # 2.0 print(a.imag) # 3.0 print(a.conjugate()) # (2-3j)
# 复数场景的数学运算 a = complex(2, 4) b = 3 - 5j print(a+b) # (5-1j) print(a*b) # (26+2j) print(a/b) # (-0.4117647058823529+0.6470588235294118j) print(abs(a)) # 4.47213595499958
# 复数函数比如正弦、余弦或平方根,使用 cmath 模块 import cmath a = complex(2, 4) b = 3 - 5j print(cmath.sin(a)) # (24.83130584894638-11.356612711218174j) 正弦 print(cmath.cos(a)) # (-11.36423470640106-24.814651485634187j) 余弦 print(cmath.exp(a)) # (-4.829809383269385-5.5920560936409816j) 平方根
# numpy数学模块的使用,可以构造一个复数数组并在这个数组上执行各种操作 import numpy as np a = np.array([2+3j, 4+5j, 6-7j, 8+9j]) print(a) # [2.+3.j 4.+5.j 6.-7.j 8.+9.j] print(a+2) # [ 4.+3.j 6.+5.j 8.-7.j 10.+9.j] print(np.sin(a)) # [9.15449915 -4.16890696j -56.16227422 -48.50245524j -153.20827755-526.47684926j 4008.42651446-589.49948373j]
# Python的标准数学函数确实情况下并不能产生复数值 import math import cmath # print(math.sqrt(-1)) # 报错,求不出结果 print(cmath.sqrt(-1)) # 1j
7:无穷大与NaN 创建或测试正无穷、负无穷或NaN(非数字)的浮点数。
# Python没有特殊的语法来表示特殊的浮点值,但是可以使用 float() 来创建 print(float('inf')) # inf正无穷 print(float("-inf")) # -inf负无穷 print(float('nan')) # nan nan代表Not A Number(不是一个数)
因为nan不是一个数,所以相关计算都无法得到数字。
所有涉及nan的操作,返回的都是nan
测试这些值的存在,使用 math.isinf() 和 math.isnan() 函数 import math print(math.isinf(float('inf'))) # True print(math.isnan(float('nan'))) # True
# 无穷大数在执行数学计算的时候会传播 print(float('inf')+45) # inf print(float('inf')*10) # inf print(10/float('inf')) # 0.0 # 有些操作时未定义的并会返回一个NaN结果 a = float('inf') print(a/a) # nan b = float('-inf') print(a + b) # nan # NaN值的一个特别的地方是它们之间的比较操作总是返回False c = float('nan') d = float('nan') print(c == d) # False print(c is d) # False
8:分数运算 fractions
模块可以被用来执行包含分数的数学运算
from fractions import Fraction a = Fraction(5, 4) print(a) # 5/4 b = Fraction(7, 16) print(b) # 7/16 print(a*b) # 35/64 c = a*b print(c.numerator) # 35 求分子 print(c.denominator) # 64 求分母 print(float(c)) # 0.546875 print(c.limit_denominator(8)) # 4/7 limit_denominator找到并返回一个 Fraction 使得其值最接近 self 并且分母不大于 max_denominator(8) x = 3.75 y = Fraction(*x.as_integer_ratio()) # 求x整数的分数形式 print(y) # 15/4
9:大型数组运算 大数据集(比如数组或网格)上面执行计算
# 数组的重量级运算操作,使用 NumPy 库 # NumPy 的一个主要特征是它会给Python提供一个数组对象,相比标准的Python列表更适合用来做数学运算 x = [1, 2, 3, 4] y = [5, 6, 7, 8] print(x*2) # [1, 2, 3, 4, 1, 2, 3, 4] # print(x +10) # 报错 TypeError: can only concatenate list (not "int") to list print(x+y) # [1, 2, 3, 4, 5, 6, 7, 8] import numpy as np ax = np.array([1, 2, 3, 4]) bx = np.array([5, 6, 7, 8]) print(ax*2) # [2 4 6 8] print(ax+10) # [11 12 13 14] print(ax+bx) # [ 6 8 10 12] print(ax*bx) # [ 5 12 21 32] 两种方案中数组的基本数学运算结果并不相同。NumPy 中的标量运算(比如 ax * 2 或 ax + 10 )会作用在每一个元素上。
另外,当两个操作数都是数组的时候执行元素对等位置计算,并最终生成一个新的数组。
# 对整个数组中所有元素同时执行数学运算可以使得作用在整个数组上的函数运算简单而又快速 # 计算多项式的值 import numpy as np def f(x): return 3*x**2 - 2*x + 7 ax = np.array([1, 2, 3, 4]) print(f(ax)) # [ 8 15 28 47] 每个列表中的数据都执行上面的计算操作
# NumPy 还为数组操作提供了大量的通用函数,这些函数可以作为 math 模块中类似函数的替代 import numpy as np ax = np.array([1, 2, 3, 4]) print(np.sqrt(ax)) # [1. 1.41421356 1.73205081 2. ] print(np.cos(ax)) # [ 0.54030231 -0.41614684 -0.9899925 -0.65364362] 这些通用函数要比循环数组并使用 math 模块中的函数执行计算要快的多。 因此,只要有可能的话尽量选择 NumPy 的数组方案
# 底层实现中, NumPy 数组使用了C或者Fortran语言的机制分配内存。 也就是说,它们是一个非常大的连续的并由同类型数据组成的内存区域。 所以,你可以构造一个比普通Python列表大的多的数组。 比如,构造一个10,000*10,000的浮点数二维网格, import numpy as np grid = np.zeros(shape=(10000,10000), dtype=float) print(grid) #打印 [[0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.] ... [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.]] 所有的普通操作还是会同时作用在所有元素上: grid = grid + 10 print(grid) [[10. 10. 10. ... 10. 10. 10.] [10. 10. 10. ... 10. 10. 10.] [10. 10. 10. ... 10. 10. 10.] ... [10. 10. 10. ... 10. 10. 10.] [10. 10. 10. ... 10. 10. 10.] [10. 10. 10. ... 10. 10. 10.]] print(np.sin(grid)) [[-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111 -0.54402111] [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111 -0.54402111] [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111 -0.54402111] ... [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111 -0.54402111] [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111 -0.54402111] [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111 -0.54402111]]
# NumPy扩展Python列表的索引功能 - 特别是对于多维数组 import numpy as np a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) print(a[1]) # [5 6 7 8] # 取数组的索引1的值 print(a[:, 1]) # [ 2 6 10] # :取值到了所有的内嵌列表,所有的内勤列表取索引1的元素 print(a[1:3, 1:3]) # [[ 6 7] [10 11]] # 大列表的1-3个元素找出两个小列表,再取值小列表里面的1-3元素 a[1:3, 1:3] += 10 # a重新赋值 print(a) # [[ 1 2 3 4] [ 5 16 17 8] [ 9 20 21 12]] print(a + [100, 101, 102, 103]) # [[101 103 105 107] [105 117 119 111] [109 121 123 115]] print(a) # [[ 1 2 3 4] [ 5 16 17 8] [ 9 20 21 12]] print(np.where(a < 10, a, 10)) # [[ 1 2 3 4] [ 5 10 10 8] [ 9 10 10 10]]
10:矩阵与线性代数运算 矩阵乘法、寻找行列式、求解线性方程组等操作
# 矩阵类似于数组对象,但是遵循线性代数的计算规则。 import numpy as np m = np.matrix([[1, -2, 3], [0, 4, 5], [7, 8, -9]]) # 创建一个矩阵 print(m) print(m.T) print(m.I) v = np.matrix([[2], [3], [4]]) print(v) print(m * v)
# 在 numpy.linalg 子包中找到更多的操作函数 import numpy as np m = np.matrix([[1, -2, 3], [0, 4, 5], [7, 8, -9]]) v = np.matrix([[2], [3], [4]]) print(np.linalg.det(m)) # -229.99999999999983 print(np.linalg.eigvals(m)) # [-13.11474312 2.75956154 6.35518158] print(np.linalg.solve(m, v)) # [[0.96521739] [0.17391304] [0.46086957]] print(m*v) # [[ 8] [32] [ 2]] print(v) # [[2] [3] [4]]
11:随机选择 一个序列中随机抽取若干元素,或者想生成几个随机数 random模块
# 从一个序列中随机的抽取一个元素 random.choice() import random values = [1, 2, 3, 4, 5, 6] print(random.choice(values)) # 随机选中列表中的一个元素 # 提取出N个不同元素的样本用来做进一步的操作,可以使random.sample() print(random.sample(values, 2)) # 随机选中列表中的两个元素返回 # 打乱序列中元素的顺序,可以使用 random.shuffle() # 直接打乱原始的valus数据 random.shuffle(values) print(values) # 生成随机整数,请使用 random.randint() print(random.randint(0, 10)) # 0和10都能取到 # 生成0到1范围内均匀分布的浮点数,使用 random.random() print(random.random()) # 获取N位随机位(二进制)的整数,使用 random.getrandbits() print(random.getrandbits(200)) # 904044266802069222366841245088267971004294309158816797761943
random.getrandbits(k)函数输出的是一个0~2^k-1范围内的一个随机整数,k表示的是2进制的位数
# random 模块使用 Mersenne Twister 算法来计算生成随机数。
# 这是一个确定性算法, 但是你可以通过 random.seed() 函数修改初始化种子 random.seed() random.seed(12345) random.seed(b'bytedata')
# random.uniform() 计算均匀分布随机数, random.gauss() 计算正态分布随机数
# seed()方法改变随机数生成器的种子,可以在调用其他随机模块函数之前调用此函数 random.seed( [x] ) x:改变随机数生成器的种子seed。如果你不了解其原理,你不必特别去设定seed,Python会帮你选择seed import random # 随机数不一样 random.seed() print('随机数1:',random.random()) random.seed() print('随机数2:',random.random()) # 随机数一样 random.seed(1) print('随机数3:',random.random()) random.seed(1) print('随机数4:',random.random()) random.seed(2) print('随机数5:',random.random()) ''' 随机数1: 0.7643602170615428 随机数2: 0.31630323818329664 随机数3: 0.13436424411240122 随机数4: 0.13436424411240122 随机数5: 0.9560342718892494 ''' 当seed()没有参数时,每次生成的随机数是不一样的,
而当seed()有参数时,每次生成的随机数是一样的,同时选择不同的参数生成的随机数也不一样
12:基本的日期与时间转换 天到秒,小时到分钟等的转换 datetime模块
# 为了执行不同时间单位的转换和计算,请使用 datetime 模块 # 为了表示一个时间段,可以创建一个 timedelta 实例 from datetime import timedelta a = timedelta(days=2, hours=6) b = timedelta(hours=4.5) c = a + b print(c) # 2 days, 10:30:00 print(c.days) # 2 print(c.seconds) # 37800 10.5*60*60=37800.0 37800秒钟 print(c.total_seconds()) # 210600 print(c.total_seconds()/3600) # 58.5小时
# 表示指定的日期和时间,先创建一个 datetime 实例然后使用标准的数学运算来操作它们 from datetime import datetime, timedelta a = datetime(2012, 9, 23) print(a + timedelta(days=10)) # 2012-10-03 00:00:00 b = datetime(2012, 12, 21) d = b - a print(d) # 89 days, 0:00:00 print(d.days) # 89 print(d.seconds) # 0 print(d.total_seconds()) # 7689600.0 now = datetime.today() print(now) # 2021-07-30 15:38:08.947479 print(now.year) # 2021 print(now.month) # 7 print(now.hour) # 15 print(now.second) # 56 print(now + timedelta(minutes=10)) # 现在的时间基础上+10分钟
# 计算的时候,需要注意的是 datetime 会自动处理闰年 from datetime import datetime, timedelta a = datetime(2012, 3, 1) b = datetime(2012, 2, 28) f = a - b print(f) # 2 days, 0:00:00 print(f.days) # 2 print(f.total_seconds()) # 172800.0 c = datetime(2013, 3, 1) d = datetime(2013, 2, 28) print((c - d).days) # 1
# 大多数基本的日期和时间处理问题, datetime 模块已经足够了。
如果你需要执行更加复杂的日期操作,比如处理时区,模糊时间范围,节假日计算等等, 可以考虑使用 dateutil模块 # 许多类似的时间计算可以使用 dateutil.relativedelta() 函数代替 # 会在处理月份(还有它们的天数差距)的时候填充间隙 from datetime import datetime, timedelta a = datetime(2012, 9, 23) # a + timedelta(months=1) # 报错,TypeError: 'months' is an invalid keyword argument for __new__() from dateutil.relativedelta import relativedelta a + relativedelta(months=+1) # datetime.datetime(2012, 10, 23, 0, 0) a + relativedelta(months=+4) # datetime.datetime(2013, 1, 23, 0, 0) b = datetime(2012, 12, 21) d = b - a d # datetime.timedelta(89) d = relativedelta(b, a) d # relativedelta(months=+2, days=+28) d.months # 2 d.days # 28
13:计算上一个周五的日期 一个通用方法来计算一周中某一天上一次出现的日期,如上一个周五的日期。
# datetime 模块 计算当前时间上一个某天出现的日期(上一个某天表示周一,周二,周三,周四等) from datetime import datetime, timedelta weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] def get_previous_by_day(dayname, start_date=None): """ 计算指定时间前面的一个周一,周二,周三,周四,周五,周六,周日是几月几号 没有没有传start_date默认是当前时间 :param dayname: :param start_date: :return: """ if start_date is None: start_date = datetime.today() day_num = start_date.weekday() # weekday()函数返回的是当前日期所在的星期数,星期一为0,星期天为6 day_num_target = weekdays.index(dayname) # 找出传入dayname(周几周几)的下标,周一返回0,周五返回4,周日返回6 days_ago = (7 + day_num - day_num_target) % 7 # 7+当前周几-查找的周几 if days_ago == 0: days_ago = 7 target_date = start_date - timedelta(days=days_ago) # return target_date print(datetime.today()) # 2021-07-30 17:00:05.310713 print(get_previous_by_day('Monday')) # 2021-07-26 17:00:27.387392 print(get_previous_by_day('Tuesday')) # 2021-07-27 17:01:04.218766 print(get_previous_by_day('Friday')) # 2021-07-23 17:01:04.218766
可选的start_date
参数可以由另外一个datetime
实例来提供。
get_previous_byday('Sunday', datetime(2012, 12, 21))
# 算法原理,先将开始日期和目标日期映射到星期数组的位置上(星期一索引为0),
# 然后通过模运算计算出目标日期要经过多少天才能到达开始日期。然后用开始日期减去那个时间差即得到结果日期
# 执行大量的日期计算的话,你最好安装第三方包 python-dateutil 来代替 from datetime import datetime from dateutil.relativedelta import relativedelta from dateutil.rrule import * d = datetime(2012, 12, 23) print(d, type(d)) # 2012-12-23 00:00:00 <class 'datetime.datetime'> print(d + relativedelta(weekday=FR)) # 2012-12-28 00:00:00 print(d + relativedelta(weekday=FR(-1))) # 2012-12-21 00:00:00
14:计算当前月份的日期范围
# 代码需要在当前月份中循环每一天,想找到一个计算这个日期范围的高效方法。 # 日期上循环并需要事先构造一个包含所有日期的列表 # 计算出开始日期和结束日期, 然后在你步进的时候使用 datetime.timedelta 对象递增这个日期变量即可 # 一个接受任意 datetime 对象并返回一个由当前月份开始日和下个月开始日组成的元组对象 from datetime import datetime, date, timedelta import calendar def get_month_range(start_date=None): if start_date is None: start_date = date.today().replace(day=1) _, days_in_month = calendar.monthrange(start_date.year, start_date.month) end_date = start_date + timedelta(days=days_in_month) return (start_date, end_date) # 在返回的日期范围上面做循环 a_day = timedelta(days=1) first_day, last_day = get_month_range() print(first_day) while first_day < last_day: print(first_day) first_day += a_day
先计算出一个对应月份第一天的日期。 一个快速的方法就是使用date
或datetime
对象的replace()
方法简单的将days
属性设置成1即可。replace()
方法一个好处就是它会创建和你开始传入对象类型相同的对象。
所以,如果输入参数是一个date
实例,那么结果也是一个date
实例。 同样的,如果输入是一个datetime
实例,那么你得到的就是一个datetime
实例
然后,使用calendar.monthrange()
函数来找出该月的总天数。 任何时候只要你想获得日历信息,
那么calendar
模块就非常有用了。monthrange()
函数会返回包含星期和该月天数的元组
该月的天数已知了,那么结束日期就可以通过在开始日期上面加上这个天数获得。
有个需要注意的是结束日期并不包含在这个日期范围内(事实上它是下个月的开始日期)。
这个和Python的slice
与range
操作行为保持一致,同样也不包含结尾
为了在日期范围上循环,要使用到标准的数学和比较操作。
比如,可以利用timedelta
实例来递增日期,小于号<用来检查一个日期是否在结束日期之前
理想情况下,如果能为日期迭代创建一个同内置的range()
函数一样的函数就好了。 幸运的是,可以使用一个生成器来很容易的实现这个目标
calendar.monthrange(year,month)方法 可返回两个整数 第一个整数:代表本月起始星期数(0:星期一 ... 6:星期天) 第二个整数:代表本月最后一天的日期数 from datetime import datetime, date, timedelta import calendar print(date.today()) # 2021-07-30 print(datetime.today()) # 2021-07-30 20:24:19.577038 print(date.today().replace(day=1)) # 2021-07-01 print(date.today().year) # 2021 print(date.today().month) # 7 print(calendar.monthrange(date.today().year, date.today().month)) # (3, 31) 当前输入年月开始日期位周四,本月31天
# 生成器为日期迭代创建一个同内置的 range() 函数 def date_range(start, stop, step): while start < stop: yield start start += step for d in date_range(datetime(2012, 9, 1), datetime(2012, 10, 1), timedelta(hours=6)): print(d) # 6小时的step长度 Python中的日期和时间能够使用标准的数学和比较操作符来进行运算
15:字符串转换为日期 datetime.strptime(text, '%Y-%m-%d')
# 字符串格式的输入转换为 datetime 对象以便操作 from datetime import datetime text = '2012-09-20' dt = datetime.now() print(dt.strftime("%Y-%m-%d %H:%M:%S %f")) # 2021-07-30 21:13:22 164664 y = datetime.strptime(text, '%Y-%m-%d') print(y) # 2012-09-20 00:00:00 z = datetime.now() print(z) diff = z - y print(diff) # 3235 days, 21:15:51.629448
# datetime.strptime() 方法支持很多的格式化代码, 比如 %Y 代表4位数年份, %m 代表两位数月份。
# 还有一点值得注意的是这些格式化占位符也可以反过来使用,将日期输出为指定的格式字符串形式 # 生成了一个 datetime 对象,想将它格式化为漂亮易读形式 import datetime z = datetime.datetime(2012, 9, 23, 21, 37, 4, 177393) # 类的初始化,返回一个class类 print(z, type(z)) # 2012-09-23 21:37:04.177393 <class 'datetime.datetime'> nice_z = datetime.datetime.strftime(z, '%A %B %d, %Y') # 模块.类.方法调用类里的方法strftime返回一个字符串 print(nice_z, type(nice_z)) # Sunday September 23, 2012 <class 'str'>
# strptime() 的性能要比你想象中的差很多, 因为它是使用纯Python实现,并且必须处理所有的系统本地设置。 # 如果你要在代码中需要解析大量的日期并且已经知道了日期字符串的确切格式, # 可以自己实现一套解析方案来获取更好的性能。 比如,如果你已经知道所以日期格式是 YYYY-MM-DD ,实现一个解析函数 from datetime import datetime # strptime(),字符串转换成日期对象 # strftime(),日期对象转换成字符串格式 def parse_ymd(s): year_s, mon_s, day_s = s.split('-') return datetime(int(year_s), int(mon_s), int(day_s)) print(parse_ymd('2021-6-20')) # 2021-06-20 00:00:00 print(datetime(2021, 6, 20)) # 2021-06-20 00:00:00 # 这个函数比 datetime.strptime() 快7倍多。 处理大量的涉及到日期的数据使用类似函数
16:结合时区的日期操作
# 有一个安排在2012年12月21日早上9:30的电话会议,地点在芝加哥。 朋友在印度的班加罗尔,几点参加这个会议 # 所有涉及到时区的问题,你都应该使用 pytz 模块。这个包提供了Olson时区数据库, 它是时区信息的事实上的标准,在很多语言和操作系统里面都可以找到 # pytz 模块一个主要用途是将 datetime 库创建的简单日期对象本地化 # 创建一个芝加哥时间 from datetime import datetime from pytz import timezone d = datetime(2012, 12, 21, 9, 30, 0) print(d) # 2012-12-21 09:30:00 central = timezone('US/Central') loc_d = central.localize(d) print(loc_d) # 2012-12-21 09:30:00-06:00 # 日期被本地化了, 它就可以转换为其他时区的时间了 bang_d = loc_d.astimezone(timezone('Asia/Kolkata')) # 得到班加罗尔对应的时间 print(bang_d) # 2012-12-21 21:00:00+05:30 # 在本地化日期上执行计算,你需要特别注意夏令时转换和其他细节。 # 比如,在2013年,美国标准夏令时时间开始于本地时间3月13日凌晨2:00(在那时,时间向前跳过一小时)。 # 如果你正在执行本地计算,你会得到一个错误 d = datetime(2013, 3, 10, 1, 45) loc_d = central.localize(d) print(loc_d) # 2013-03-10 01:45:00-06:00 later = loc_d + timedelta(minutes=30) print(later) # 2013-03-10 02:15:00-06:00 # WRONG! WRONG!
# 没有考虑在本地时间中有一小时的跳跃。 为了修正这个错误,可以使用时区对象 normalize() 方法 from datetime import timedelta later = central.normalize(loc_d + timedelta(minutes=30)) print(later)
# 为了不让被这些东东弄的晕头转向,处理本地化日期的通常的策略先将所有日期转换为UTC时间, 并用它来执行所有的中间存储和操作。 from datetime import datetime from pytz import timezone d = datetime(2013, 3, 10, 1, 45) loc_d = central.localize(d) print(loc_d) utc_d = loc_d.astimezone(pytz.utc) print(utc_d) # 一旦转换为UTC,你就不用去担心跟夏令时相关的问题了。
# 因此,你可以跟之前一样放心的执行常见的日期计算。 当你想将输出变为本地时间的时候,使用合适的时区去转换下就行了 later_utc = utc_d + timedelta(minutes=30) print(later_utc.astimezone(central)) # 如何得到时区的名称。 比如,在这个例子中,我们如何知道“Asia/Kolkata”就是印度对应的时区名呢?
# 为了查找,可以使用ISO 3166国家代码作为关键字去查阅字典 pytz.country_timezones pytz.country_timezones['IN']
17:手动遍历迭代器 遍历一个可迭代对象中的所有元素,不使用for循环。while+try expect搭配使用
# 为了手动的遍历可迭代对象,使用 next() 函数并在代码中捕获 StopIteration 异常
# 读取一个文件中所有行 def manual_iter(): with open('./passwd') as f: try: while True: line = next(f) print(line, end='') except StopIteration: pass manual_iter() with open('./passwd') as f: while 1: try: print(f.__next__().strip()) except StopIteration: break
上面的两种方式都可以,文件句柄f返回的是一个迭代器(dir(f)能够里面有__next__和__iter__方法就是一个迭代器)
# 通常来讲, StopIteration 用来指示迭代的结尾。 # 如果你手动使用上面演示的 next() 函数的话,你还可以通过返回一个指定值来标记结尾,比如 None with open('./passwd') as f: while True: line = next(f, None) if line is None: break print(line, end='') def next(iterator, default=None) None表示报错以后的返回值,一直往迭代器里取元素,取到为空 StopIteration报错的时候就返回None,next函数内部自动处理这次报错
# 大多数情况下,我们会使用 for 循环语句用来遍历一个可迭代对象 # 但是,偶尔也需要对迭代做更加精确的控制,需要了解底层迭代机制 # 如下演示了迭代期间所发生的基本细节 items = [1, 2, 3] # 一个可迭代的对象(不是迭代器,只是一个可迭代器对象,对象内部有__iter__方法) it = iter(items) # 返回有一个迭代器,对象内部有__iter__和__next__方法就是迭代器 print(next(it)) # 1 print(next(it)) # 2 print(next(it)) # 3 # 遍历取迭代器里面的元素 print(next(it)) # 迭代器里面三个元素都取完了后再往迭代器里取值就会报错 StopIteration
18:代理迭代
# 自定义容器对象(自己写个类),里面包含有列表、元组或其他可迭代对象 # 直接在这个新容器对象上执行迭代操作 # 只需要定义一个 __iter__() 方法,将迭代操作代理到容器内部的对象上去 class Node: def __init__(self, value): self._value = value self._children = [] def __repr__(self): return 'Node({!r})'.format(self._value) def add_child(self, node): self._children.append(node) def __iter__(self): return iter(self._children)
# 这里必须返回一个迭代器,返回一个列表,元组等可迭代对象不行,因为for循环就是调用对象的__iter__方法得到一个对象a
然后对对象a循环使用__next__方法取值,可迭代对象列表,元组都没有实现__next__方法,只有迭代器才实现了__next__方法 if __name__ == '__main__': root = Node(0) child1 = Node(1) child2 = Node(2) root.add_child(child1) root.add_child(child2) for ch in root: # for循环先调用root对象的__iter__方法返回一个迭代器然后使用__next__方法一个个取值 print(ch, type(ch))__iter__()
方法只是简单的将迭代请求传递给内部的_children
属性。
# Python的迭代器协议需要 __iter__() 方法返回一个实现了 __next__() 方法的迭代器对象。
# 如果你只是迭代遍历其他容器的内容,你无须担心底层是怎样实现的。你所要做的只是传递迭代请求既可。
# 这里的 iter() 函数的使用简化了代码, iter(s) 只是简单的通过调用 s.__iter__() 方法来返回对应的迭代器对象,
就跟 len(s) 会调用 s.__len__() 原理是一样的
class Node: def __init__(self, value): self._value = value self._children = [] def __new__(cls, *args, **kwargs): # 类对象初始化返回的 return "11111" def __str__(self): # '%s'%obj,print(obj),str(obj)的时候,实际上是内部调用了obj__str__()方法, return "Node1111" def __repr__(self): # __repr__是__str__的备胎方法,没有__str__方法,会先找本类中的__repr__方法,再没有找父类的 return 'Node({!r})'.format(self._value) def add_child(self, node): self._children.append(node) def __iter__(self): # 定义一可迭代对象(可迭代协议:对象内部含有__iter__方法就是可迭代对象) return iter(self._children) res = Node("李二狗") print(res, type(res))
res = Node("李二狗") # 因为res对象内部有__iter__方法所有res是一个可迭代的对象
res.__iter__() # 返回的是一个<str_iterator object at 0x000002D1CF9CEFA0>,
可迭代对象调用__iter__后返回的是一个迭代器
class Node: def __init__(self, value): self._value = value self._children = [] def __new__(cls, *args, **kwargs): # 类对象初始化返回的 return "11111" def __iter__(self): # 定义一可迭代对象(可迭代协议__iter__对象内部含有__iter__方法就是可迭代对象) return "22222" res = Node("李二狗") print(res) # 11111 print(res.__iter__()) for i in res.__iter__(): print(i) # 循环打印111
重构了对象Node的__new__方法之后,类的实例化res=Node(xxx)返回的是一个字符串"11111"字符串对象,
后面对象res.__iter__等调用类里面的各种双下方法就是调用的字符串实例化对象"11111"的__iter__方法了
想要调用自己类本身实现的__iter__等各种双下方法,__new__方法一定要返回类本身的object(重点)
19:使用生成器创建新的迭代模式
# 实现一个自定义迭代模式,跟普通的内置函数比如 range() , reversed() 不一样 # 想实现一种新的迭代模式,使用一个生成器函数来定义它 # 生成器,函数里使用yield方法 # 生产某个范围内浮点数的生成器 def frange(start, stop, increment): x = start while x < stop: yield x x += increment for i in frange(0, 4, 0.5): print(i) # 0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 print(list(frange(0, 1, 0.125))) # [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875]
使用生成器函数可以用for循环迭代它或者使用其他接受一个可迭代对象的函数(比如sum()
,list()
等)
# 函数中需要有一个 yield 语句即可将其转换为一个生成器。 跟普通函数不同的是,生成器只能用于迭代操作。 # 生成器简单原理如下 def countdown(n): print('Starting to count from', n) while n > 0: yield n n -= 1 print("Done!") c = countdown(3) # 3传进去3 < 0不成立不会进入循环没有yield返回数据 print(next(c)) # Starting to count from 3, 3 print(next(c)) # 2 print(next(c)) # 1 print(next(c)) # 打印Done!后报错 StopIteration
一个生成器函数主要特征是它只会回应在迭代中使用到的 next 操作。
一旦生成器函数返回退出,迭代终止。我们在迭代中通常使用的for语句会自动处理这些细节
def countdown(n): print('Starting to count from', n) while n < 0: yield n n -= 1 print("Done!") c = countdown(3) # 3传进去3 < 0不成立不会进入循环没有yield返回数据 print(sum(c)) #打印如下 # Starting to count from 3 # Done! # 0
20:实现迭代器协议
# 构建一个能支持迭代操作的自定义对象,找到一个能实现迭代协议的简单方法 # 目前为止,在一个对象上实现迭代最简单的方式是使用一个生成器函数 # 实现一个以深度优先方式遍历树形节点的生成器 class Node: def __init__(self, value): self._value = value self._children = [] def __repr__(self): return 'Node({!r})'.format(self._value) def add_child(self, node): self._children.append(node) def __iter__(self): return iter(self._children) def depth_first(self): yield self for c in self: yield from c.depth_first() if __name__ == '__main__': root = Node(0) child1 = Node(1) child2 = Node(2) root.add_child(child1) root.add_child(child2) child1.add_child(Node(3)) child1.add_child(Node(4)) child2.add_child(Node(5)) # root里有Node(1)和Node(2) # child1里面有Node(3)和Node(4) # child2里面有Node(5) for ch in root.depth_first(): print(ch, end=',') 输出:Node(0),Node(1),Node(3),Node(4),Node(2),Node(5), 在这段代码中,depth_first() 方法简单直观。
它首先返回自己本身,并迭代每一个子节点并 通过调用子节点的 depth_first() 方法(使用 yield from 语句)返回对应元素
深度优先算法
# yield和yield from的差别 # yield不仅可以返回值,也可以接收值 def gen1(): for x in ["a", "b", "c"]: yield x for i in gen1(): print(i) def gen2(): while 1: x = yield print(f"x = {x}") g = gen2() next(g) # # 执行到yield, 激活协程 send(None) ”预激(prime)“协程 g.send(10) g.send(20) g.send(30) g.close() # yield from调用生成器(yile) def gen(): yield from ["x", "y", "z"] for i in gen(): print(i) # 输出:x y z def gen(): yield ["x", "y", "z"] for i in gen(): print(i) # 输出:['x', 'y', 'z']
yield和yield from后边加上可迭代对象的时候,yield from是将可迭代对象中的元素一个一个yield出来,而yield是直接yield的是可迭代对象
# Python的迭代协议要求一个 __iter__() 方法返回一个特殊的迭代器对象,
# 这个迭代器对象实现了 __next__() 方法并通过 StopIteration 异常标识迭代的完成。 #实现这些通常会比较繁琐,如何使用一个关联迭代器类重新实现 depth_first() 方法 class DepthFirstIterator: """ 深度优先遍历 """ def __init__(self, start_node): self._node = start_node self._children_iter = None self._child_iter = None def __iter__(self): return self def __next__(self): # 如果我刚刚开始,请返回我自己;为子对象创建迭代器 if self._children_iter is None: self._children_iter = iter(self._node) return self._node # 如果正在处理子项,请返回其下一项 elif self._child_iter: try: nextchild = next(self._child_iter) return nextchild except StopIteration: self._child_iter = None return next(self) # 前进到下一个子级并开始其迭代 else: self._child_iter = next(self._children_iter).depth_first() return next(self) class Node2: def __init__(self, value): self._value = value self._children = [] def __repr__(self): return 'Node({!r})'.format(self._value) def add_child(self, node): self._children.append(node) def __iter__(self): return iter(self._children) def depth_first(self): return DepthFirstIterator(self) # DepthFirstIterator 类和上面使用生成器的版本工作原理类似 # 迭代器必须在迭代处理过程中维护大量的状态信息。没人愿意写这么晦涩的代码。将你的迭代器定义为一个生成器后一切迎刃而解。
21:反向迭代 反方向迭代一个序列,内置的 reversed()
函数,返回一个迭代器
a = [1, 2, 3, 4] for x in reversed(a): print(x)
reversed()函数返回的也是一个迭代器,
# 反向迭代仅仅当对象的大小可预先确定或者对象实现了 __reversed__() 的特殊方法时才能生效。 如果两者都不符合,那你必须先将对象转换为一个列表才行
f = open('somefile')
for line in reversed(list(f)):
print(line, end='')
for line in reversed(f):
print(line, end='')
报错,TypeError: '_io.TextIOWrapper' object is not reversible
文件不能用下标进行索引
如果可迭代对象元素很多的话,将其预先转换为一个列表要消耗大量的内存
# 可以通过在自定义类上实现 __reversed__() 方法来实现反向迭代 class Countdown: def __init__(self, start): self.start = start # 前向迭代器 def __iter__(self): n = self.start while n > 0: yield n n -= 1 # 反向迭代器 def __reversed__(self): n = 1 while n <= self.start: yield n n += 1 for rr in reversed(Countdown(30)): # 输出1-30 print(rr) for rr in Countdown(30): # 输出30-1 print(rr)
定义一个反向迭代器可以使得代码非常的高效, 因为它不再需要将数据填充到一个列表中然后再去反向迭代这个列表。
22:带有外部状态的生成器函数 定义一个生成器函数,它会调用某个你想暴露给用户使用的外部状态值。
# 生成器暴露外部状态给用户 # 可以简单的将它实现为一个类,然后把生成器函数放到 __iter__() 方法中过去 from collections import deque # 双端队列 class linehistory: def __init__(self, lines, histlen=3): self.lines = lines self.history = deque(maxlen=histlen) def __iter__(self): # 内部实现了__iter__方法的类就可以并且__iter__方法调用后的返回值可以循环调用__next__方法的类实例化就是一个迭代器 for lineno, line in enumerate(self.lines, 1): self.history.append((lineno, line)) yield line def clear(self): self.history.clear() # 为了使用这个类,你可以将它当做是一个普通的生成器函数。 然而,由于可以创建一个实例对象,
# 于是你可以访问内部属性值, 比如 history 属性或者是 clear() 方法 with open('somefile.txt') as f: lines = linehistory(f) for line in lines: if 'python' in line: for lineno, hline in lines.history: print('{}:{}'.format(lineno, hline), end='')
# enumerate()枚举 # enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中 enumerate(sequence, [start=0]) sequence -- 一个序列、迭代器或其他支持迭代对象。 start -- 下标起始位置。 seasons = ['Spring', 'Summer', 'Fall', 'Winter'] print(list(enumerate(seasons))) # [(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')] print(list(enumerate(seasons, start=1))) # [(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')] # 普通的for循环 i = 0 seq = ['one', 'two', 'three'] for element in seq: print(i, seq[i]) i += 1 # for循环使用enumerate枚举 seq = ['one', 'two', 'three'] for i, element in enumerate(seq): print(i, element)
# 生成器,很容易掉进函数无所不能的陷阱。 # 如果生成器函数需要跟你的程序其他部分打交道的话(比如暴露属性值,允许通过方法调用来控制等等), 可能会导致你的代码异常的复杂。 # 如果是这种情况的话,虑使用定义类的方式。 在 __iter__() 方法中定义你的生成器不会改变任何的算法逻辑。
# 由于它是类的一部分,所以允许你定义各种属性和方法来供用户使用 # 如果你在迭代操作时不使用for循环语句,那么你得先调用 iter() 函数 from collections import deque class linehistory: def __init__(self, lines, histlen=3): self.lines = lines self.history = deque(maxlen=histlen) def __iter__(self): for lineno, line in enumerate(self.lines, 1): self.history.append((lineno, line)) yield line def clear(self): self.history.clear() with open('./somefile.txt') as f: line = linehistory(f) # print(next(line)) # TypeError: 'linehistory' object is not an iterator print(next(line.__iter__())) # 输出000000000000
因为line类的实例化返回的并不是一个迭代器,需要调用__iter__后才是返回一个迭代器
23: 迭代器切片 itertools.islice()
# 迭代器生成的切片对象,标准切片操作不能做到 # 函数itertools.islice()用于在迭代器和生成器上做切片操作 import itertools def count(n): while 1: yield n n += 1 c = count(0) # c[0] # 报错:TypeError: 'generator' object is not subscriptable # c[10:20] # 报错:TypeError: 'generator' object is not subscriptable # print(list(itertools.islice(c, 0, 10))) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] for i in itertools.islice(c, 10, 20): print(i)
# 迭代器和生成器不能使用标准的切片操作,因为它们的长度事先我们并不知道(并且也没有实现索引) # 函数 islice() 返回一个可以生成指定元素的迭代器,它通过遍历并丢弃直到切片开始索引位置的所有元素。
然后才开始一个个的返回元素,并直到切片结束索引位置 # islice() 会消耗掉传入的迭代器中的数据。 必须考虑到迭代器是不可逆的这个事实。
所以如果你需要之后再次访问这个迭代器的话,那你就得先将它里面的数据放入一个列表中
24:跳过可迭代对象的开始部分 遍历一个可迭代对象,某些元素你并不感兴趣,想跳过它们
# itertools 模块可以跳过可迭代对象 # itertools.dropwhile() 传递一个函数对象和一个可迭代对象。
它会返回一个迭代器对象,丢弃原有序列中直到函数返回Flase之前的所有元素,然后返回后面所有元素 # 下面一段代码跳过开始部分的注释 ## # User Database # # Note that this file is consulted directly only when the system is running # in single-user mode. At other times, this information is provided by # Open Directory.## nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false root:*:0:0:System Administrator:/var/root:/bin/sh from itertools import dropwhile with open('./passwd') as f: for line in dropwhile(lambda line: line.startswith('#'), f): print(line, end='')
# line.startwith("#")一开始注释返回True,后面到了正式代码第一次返回False就把前面所有返回False之前的都舍弃,然后返回后面的元素
输出: nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false root:*:0:0:System Administrator:/var/root:/bin/sh
# 知道了要跳过的元素的序号的话,那么可以使用 itertools.islice() 来代替 from itertools import islice items = ['a', 'b', 'c', 1, 4, 10, 15] for x in islice(items, 3, None): print(x) # 跳过前三个 # islice() 函数最后那个 None 参数指定了你要跳过前面3个元素,获取第4个到最后的所有元素, # 如果 None 和3的位置对调,意思就是仅仅获取前三个元素恰恰相反, (这个跟切片的相反操作 [3:] 和 [:3] 原理是一样的)
# dropwhile() 和 islice() 其实就是两个帮助函数,为的就是避免写出冗余代码 with open('./passwd') as f: # 跳过最初的评论 while True: line = next(f, '') # 从文件句柄f里面循环迭代取值赋值给line if not line.startswith('#'): # 如果读取出来的每行都是 # 开头的跳过,如果不是# 开头的终止循环走下个while break # 处理剩余的生产线 while line: # line现在都不是# 开头的了,有line走这个循环,打印line,然后去下个line,为空返回None # 替换为有用的处理 print(line, end='') line = next(f, None)
# 跳过一个可迭代对象的开始部分跟通常的过滤是不同的。 with open('/etc/passwd') as f: lines = (line for line in f if not line.startswith('#')) for line in lines: print(line, end='') # 这也写也是可以的,这是直接跳过,不是过滤 # 这样写可以跳过开始部分的注释行,但是同样也会跳过文件中其他所有的注释行。 # 我们的解决方案是仅仅跳过开始部分满足测试条件的行,在那以后,所有的元素不再进行测试和过滤了,这个方案还是处理我们的需求有点问题
25:排列组合的迭代 迭代遍历一个集合中元素的所有可能的排列或组合
# itertools模块提供了三个函数来解决队列足足和的问题。 其中一个是 itertools.permutations() , 它接受一个集合并产生一个元组序列,每个元组由集合中所有元素的一个可能排列组成。 也就是说通过打乱集合中元素排列顺序生成一个元组 from itertools import permutations items = ['a', 'b', 'c'] for p in permutations(items): print(p) 输出: ('a', 'b', 'c') ('a', 'c', 'b') ('b', 'a', 'c') ('b', 'c', 'a') ('c', 'a', 'b') ('c', 'b', 'a') # 得到指定长度的所有排列,传递一个可选的长度参数 from itertools import permutations items = ['a', 'b', 'c'] for p in permutations(items, 2): print(p) 输出: ('a', 'b') ('a', 'c') ('b', 'a') ('b', 'c') ('c', 'a') ('c', 'b')
# itertools.combinations() 可得到输入集合中元素的所有的组合(去重了) from itertools import combinations items = ['a', 'b', 'c'] for c in combinations(items, 3): print(c) # ('a', 'b', 'c') for c in combinations(items, 2): print(c) # ('a', 'b'),('a', 'c'),('b', 'c') for c in combinations(items, 1): print(c) # ('a',),('b',),('c',) 对于 combinations() 来讲,元素的顺序已经不重要了,组合 ('a', 'b') 跟 ('b', 'a') 其实是一样的(最终只会输出其中一个)
# 组合函数itertools.combinations_with_replacement() 允许同一个元素被选择多次 # 在计算组合的时候,一旦元素被选取就会从候选中剔除掉(比如如果元素’a’已经被选取了,那么接下来就不会再考虑它了)
但是组合函数itertools.combinations_with_replacement() 允许同一个元素被选择多次 from itertools import combinations_with_replacement items = ['a', 'b', 'c'] for c in combinations_with_replacement(items, 3): print(c) 输出: ('a', 'a', 'a') ('a', 'a', 'b') ('a', 'a', 'c') ('a', 'b', 'b') ('a', 'b', 'c') ('a', 'c', 'c') ('b', 'b', 'b') ('b', 'b', 'c') ('b', 'c', 'c') ('c', 'c', 'c')
排序里面没有重复的,比如abc和bca就是重复的,被去重了
26:序列上索引值迭代
# 迭代一个序列的同时跟踪正在被处理的元素索引 enumerate() 枚举 my_list = ['a', 'b', 'c'] for idx, val in enumerate(my_list): print(idx, val) 输出: 0 a 1 b 2 c
# 传统行号输出(行号从1开始),你可以传递一个开始参数 my_list = ['a', 'b', 'c'] for idx, val in enumerate(my_list, 1): print(idx, val) 输出: 1 a 2 b 3 c
# enumerate在遍历文件时想在错误消息中使用行号定位时候非常有用 def parse_data(filename): with open(filename, 'rt') as f: for lineno, line in enumerate(f, 1): fields = line.split() try: int(fields[1]) except ValueError as e: print('Line {}: Parse error: {}'.format(lineno, e)) # enumerate() 对于跟踪某些值在列表中出现的位置是很有用的。 # 如果你想将一个文件中出现的单词映射到它出现的行号上去,可以很容易的利用 enumerate() 来完成 from collections import defaultdict word_summary = defaultdict(list) with open('myfile.txt', 'r') as f: lines = f.readlines() for idx, line in enumerate(lines): # Create a list of words in current line words = [w.strip().lower() for w in line.split()] for word in words: word_summary[word].append(idx) print(word_summary) # 处理完文件后打印 word_summary ,会发现它是一个字典(准确来讲是一个 defaultdict ), # 对于每个单词有一个 key ,每个 key 对应的值是一个由这个单词出现的行号组成的列表。
# 如果某个单词在一行中出现过两次,那么这个行号也会出现两次, 同时也可以作为文本的一个简单统计
# 额外定义一个计数变量的时候,使用 enumerate() 函数会更加简单 lineno = 1 for line in f: # Process line ... lineno += 1 for lineno, line in enumerate(f): pass # enumerate() 函数返回的是一个 enumerate 对象实例,
它是一个迭代器,返回连续的包含一个计数和一个值的元组, 元组中的值通过在传入序列上调用 next() 返回 # 在一个已经解压后的元组序列上使用 enumerate() 函数时很容易调入陷阱 data = [(1, 2), (3, 4), (5, 6), (7, 8)] for n, (x, y) in enumerate(data): print(n, x, y) # 正常打印 for n, x, y in enumerate(data): print(n, x, y) # 报错 因为enumerate() 函数返回的是一个迭代器
27:同时迭代多个序列 zip函数
# 同时迭代多个序列,每次分别从一个序列中取一个元素 zip函数 xpts = [1, 5, 4, 2, 10, 7] ypts = [101, 78, 37, 15, 62, 99] for x, y in zip(xpts, ypts): print(x, y) 输出: 1 101 5 78 4 37 2 15 10 62 7 99 # zip(a, b) 会生成一个可返回元组 (x, y) 的迭代器,其中x来自a,y来自b。
# 一旦其中某个序列到底结尾,迭代宣告结束。 因此迭代长度跟参数中最短序列长度一致
# itertools.zip_longest()函数 from itertools import zip_longest a = [1, 2, 3] b = ['w', 'x', 'y', 'z'] for i in zip_longest(a, b): print(i) 输出: (1, 'w') (2, 'x') (3, 'y') (None, 'z') from itertools import zip_longest a = [1, 2, 3] b = ['w', 'x', 'y', 'z'] for i in zip_longest(a, b, fillvalue=0): print(i) 输出: (1, 'w') (2, 'x') (3, 'y') (0, 'z')
# 头列表和一个值列表的处理 headers = ['name', 'shares', 'price'] values = ['ACME', 100, 490.1] # 使用zip()可以让你将它们打包并生成一个字典: s = dict(zip(headers,values)) 输出: {'name': 'ACME', 'shares': 100, 'price': 490.1} # zip() 可以接受多于两个的序列的参数。 这时候所生成的结果元组中元素个数跟输入序列个数一样 a = [1, 2, 3] b = [10, 11, 12] c = ['x', 'y', 'z'] for i in zip(a, b, c): print(i) 输出: (1, 10, 'x') (2, 11, 'y') (3, 12, 'z')
# zip() 会创建一个迭代器来作为结果返回。 如果你需要将结对的值存储在列表中,要使用 list() 函数 a = [1, 2, 3] b = [10, 11, 12] print(zip(a, b)) # <zip object at 0x000001CE6AF2D440> print(list(zip(a, b))) # [(1, 10), (2, 11), (3, 12)] print(dict(zip(a, b))) # {1: 10, 2: 11, 3: 12}
28:不同集合上元素的迭代 itertools.chain()
# 在多个对象执行相同的操作,但是这些对象在不同的容器中 # itertools.chain() 方法,接受一个可迭代对象列表作为输入,并返回一个迭代器,有效的屏蔽掉在多个容器中迭代细节 from itertools import chain a = [1, 2, 3, 4] b = ['x', 'y', 'z'] for i in chain(a, b): print(i) 输出: 1 2 3 4 x y z # 对不同的集合中所有元素执行某些操作的时候使用chain()最合适 from itertools import chain active_items = set() inactive_items = set() for item in chain(active_items, inactive_items): print(item) 比使用两个for循环优雅
# itertools.chain() 接受一个或多个可迭代对象作为输入参数。
# 然后创建一个迭代器,依次连续的返回每个可迭代对象中的元素, 这种方式要比先将序列合并再迭代要高效的多 for x in a + b: for x in chain(a, b): # 第一种方案中, a + b 操作会创建一个全新的序列并要求a和b的类型一致。
# chian() 不会有这一步,所以如果输入序列非常大的时候会很省内存。 并且当可迭代对象类型不一样的时候 chain() 同样可以很好的工作
29:创建数据处理管道
# 想以数据管道(类似Unix管道)的方式迭代处理数据。 # 比如,有个大量的数据需要处理,但是不能将它们一次性放入内存中 # 生成器函数是一个实现管道机制的好办法 # 处理一个非常大的日志文件目录 # 每个日志文件内容如下 124.115.6.12 - - [10/Jul/2012:00:18:50 -0500] "GET /robots.txt ..." 200 71 210.212.209.67 - - [10/Jul/2012:00:18:51 -0500] "GET /ply/ ..." 200 11875 210.212.209.67 - - [10/Jul/2012:00:18:51 -0500] "GET /favicon.ico ..." 404 369 61.135.216.105 - - [10/Jul/2012:00:20:04 -0500] "GET /blog/atom.xml ..." 304 - ... # 可以定义一个由多个执行特定任务独立任务的简单生成器函数组成的容器来处理文件 import os import fnmatch import gzip import bz2 import re def gen_find(filepat, top): """ 在目录树中查找与shell通配符模式匹配的所有文件名 :param filepat: :param top: :return: """ for path, dirlist, filelist in os.walk(top): # 找到top路径下的所有子文件夹和子文件 for name in fnmatch.filter(filelist, filepat): # 根据传入的规则filepat匹配出文件然后生成器返回 yield os.path.join(path,name) def gen_opener(filenames): """ 一次打开一系列文件名,生成一个文件对象。 在进行下一次迭代时,文件将立即关闭。 :param filenames: :return: """ for filename in filenames: # 迭代文件生成器,得到文件句柄yiled出去 if filename.endswith('.gz'): f = gzip.open(filename, 'rt') elif filename.endswith('.bz2'): f = bz2.open(filename, 'rt') else: f = open(filename, 'rt') yield f f.close() def gen_concatenate(iterators): """ 将迭代器序列链接到一个序列中 :param iterators: :return: """ for it in iterators: # 迭代文件句柄生成器,然后yield from文每个件句柄,就算yield文件每一行 yield from it # it是一个可迭代的对象,可以是列表可以是迭代器,所以iteraors参数需要时[[]]这种类型
def gen_grep(pattern, lines): """ 在一系列行中查找正则表达式模式 :param pattern: :param lines: :return: """ pat = re.compile(pattern) # 根据正则表达式匹配文件每一行, for line in lines: if pat.search(line): # 如果匹配上了有返回值就yield line出去 yield line
# os.walk() 方法用于通过在目录树中游走输出在目录中的文件名,向上或者向下。 # os.walk() 方法是一个简单易用的文件、目录遍历器,可以帮助我们高效的处理文件、目录方面的事情。 os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]]) top -- 是你所要遍历的目录的地址, 返回的是一个三元组(root,dirs,files)。 root 所指的是当前正在遍历的这个文件夹的本身的地址 dirs 是一个 list ,内容是该文件夹中所有的目录的名字(不包括子目录) files 同样是 list , 内容是该文件夹中所有的文件(不包括子目录) topdown --可选,为 True,则优先遍历 top 目录,否则优先遍历 top 的子目录(默认为开启)。
如果 topdown 参数为 True,walk 会遍历top文件夹,与top 文件夹中每一个子目录 onerror -- 可选,需要一个 callable 对象,当 walk 需要异常时,会调用。 followlinks -- 可选,如果为 True,则会遍历目录下的快捷方式(linux 下是软连接 symbolic link )
实际所指的目录(默认关闭),如果为 False,则优先遍历 top 的子目录
# fnmatch 文件名称的匹配,并且匹配的模式使用的unix shell风格 # fnmatch,fnmatchcase,filter,translate四个方法 * 匹配所有的字符 ?匹配单个字符 [seq] 匹配指定范围内的字符 [!seq] 匹配不在指定范围内的字符 # fnmatch import fnmatch import os def run(): for file in os.listdir('.'): # os.listdir返回指定的文件夹包含的文件或文件夹的名字的列表 if fnmatch.fnmatch(file, '*.py'): # 判断是否有后缀为.py的文件,*代表文件名长度格式不限制。 print(file) if __name__ == '__main__': run() # fnmatchcase :和fnmatch()类似,只是fnmatchcase 强制区分大小写匹配,不管文件系统是否区分。 import fnmatch print(fnmatch.fnmatchcase("text.py", "text.*")) # True print(fnmatch.fnmatchcase("Text.py", "text.*")) # False print(fnmatch.fnmatchcase("Text.Py", "*.py")) # False print(fnmatch.fnmatchcase("Text.Py", "*.Py")) # True # filter :实现列表特殊字符的过滤或筛选,返回符合匹配模式的字符列表,它的作用类似[n for n in names if fnmatch(n, pattern)] import fnmatch filelist = ["a.text", "b.jpg", "c.png", "d.py", 'e.text', "sss.py"] print(fnmatch.filter(filelist, "?.py")) # 输出:['d.py'] 匹配前面是一个字符的.py文件 # translate 翻译模式, fnmatch将这种全局模式转换成一个正则式, 然后使用re模块来比较名字和模式。 translate() 函数是一个公共API用于将全局模式转换成正则式 regex = fnmatch.translate('[f,d,d,d,g,h].txt') # 将[f,d,d,d,g,h].txt转为正则匹配的模式 print("regex",regex) # (?s:[f,d,d,d,g,h]\.txt)\Z #\Z:匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。 reobj = re.compile(regex)#生成re匹配模块 print(reobj.match ('f.txt'))#返回一个匹配对象 #<_sre.SRE_Match object; span=(0, 5), match='f.txt'>
# 可以很容易的将这些函数连起来创建一个处理管道 # 查找包含单词python的所有日志行 lognames = gen_find('access-log*', 'www') # 找到www路径下所以的文件然后根据access-log*匹配规则匹配出来结果生成器返回 files = gen_opener(lognames) # lines = gen_concatenate(files) pylines = gen_grep('(?i)python', lines) for line in pylines: print(line)
# 扩展管道,可以在生成器表达式中包装数据。 # 计算出传输的字节数并计算其总和 lognames = gen_find('access-log*', 'www') files = gen_opener(lognames) lines = gen_concatenate(files) pylines = gen_grep('(?i)python', lines) bytecolumn = (line.rsplit(None,1)[1] for line in pylines) # 切片一次,从右边(也就是结尾)开始计数切,生成一个生成器 bytes = (int(x) for x in bytecolumn if x != '-') print('Total', sum(bytes))
# 以管道方式处理数据可以用来解决各类其他问题,包括解析,读取实时数据,定时轮询等。 # yield 语句作为数据的生产者, for 循环语句作为数据的消费者。 当这些生成器被连在一起后,
每个 yield 会将一个单独的数据元素传递给迭代处理管道的下一阶段。 在例子最后部分,
sum() 函数是最终的程序驱动者,每次从生成器管道中提取出一个元素。 # 特点是每个生成器函数很小并且都是独立的。这样的话就很容易编写和维护它们了。
很多时候,这些函数如果比较通用的话可以在其他场景重复使用。 并且最终将这些组件组合起来的代码看上去非常简单,也很容易理解。 # 上述代码即便是在一个超大型文件目录中也能工作的很好。 事实上,由于使用了迭代方式处理,代码运行过程中只需要很小很小的内存 # 调用 gen_concatenate() 函数的时候。 这个函数的目的是将输入序列拼接成一个很长的行序列。
itertools.chain() 函数同样有类似的功能,但是它需要将所有可迭代对象作为参数传入。
在上面这个例子中,你可能会写类似这样的语句 lines = itertools.chain(*files) ,
这将导致 gen_opener() 生成器被提前全部消费掉。 但由于 gen_opener() 生成器每次生成一个打开过的文件,
等到下一个迭代步骤时文件就关闭了,因此 chain() 在这里不能这样使用。 上面的方案可以避免这种情况 # gen_concatenate() 函数中出现过 yield from 语句,它将 yield 操作代理到父生成器上去。
语句 yield from it 简单的返回生成器 it 所产生的所有值
import itertools
a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
a_iter = iter(a)
print(list(itertools.chain(*a_iter)))
输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]
30:展开嵌套的序列 将一个多层嵌套的序列展开成一个单层列表 yield from
# yield from:递归生成器 from collections.abc import Iterable def flatten(items, ignore_types=(str, bytes)): for x in items: if isinstance(x, Iterable) and not isinstance(x, ignore_types): yield from flatten(x) else: yield x items = [1, 2, [3, 4, [5, 6], 7], 8] # 制造1 2 3 4 5 6 7 8 for x in flatten(items): print(x) 输出:1,2,3,4,5,6,7,8 # isinstance(x, Iterable) 检查某个元素是否是可迭代的。
如果是的话, yield from 就会返回所有子例程的值。最终返回结果就是一个没有嵌套的简单序列了 # ignore_types 和检测语句 isinstance(x, ignore_types) 用来将字符串和字节排除在可迭代对象外,
防止将它们再展开成单个的字符。 这样的话字符串数组就能最终返回我们所期望的结果了 items = ['Dave', 'Paula', ['Thomas', 'Lewis']] for x in flatten(items): print(x) 输出: Dave Paula Thomas Lewis
# yield from 在生成器中调用其他生成器作为子例程的时候非常有用。 如果你不使用它的话,那么就必须写额外的 for 循环了 def flatten(items, ignore_types=(str, bytes)): for x in items: if isinstance(x, Iterable) and not isinstance(x, ignore_types): for i in flatten(x): yield i else: yield x # yield from 语句看上去感觉更好,并且也使得代码更简洁清爽。 # 对于字符串和字节的额外检查是为了防止将它们再展开成单个字符。 如果还有其他你不想展开的类型,修改参数 ignore_types
31:顺序迭代合并后的排序迭代对象 :一系列排序序列,想将它们合并后得到一个排序序列并在上面迭代遍历 heapq.merge()
import heapq a = [1, 4, 7, 10] b = [2, 5, 6, 11] for i in heapq.merge(a, b): print(i, end=' ') 输出:1 2 4 5 6 7 10 11
# heapq.merge 可迭代特性意味着它不会立马读取所有序列。 这就意味着你可以在非常长的序列中使用它,而不会有太大的开销 # 合并两个排序文件: with open('sorted_file_1', 'rt') as file1, \ open('sorted_file_2', 'rt') as file2, \ open('merged_file', 'wt') as outf: for line in heapq.merge(file1, file2): outf.write(line) # heapq.merge() 需要所有输入序列必须是排过序的。 特别的,它并不会预先读取所有数据到堆栈中或者预先排序,
也不会对输入做任何的排序检测。 它仅仅是检查所有序列的开始部分并返回最小的那个,这个过程一直会持续直到所有输入序列中的元素都被遍历完
32:迭代器代替while无限循环
# 常见的IO操作程序 CHUNKSIZE = 8192 def reader(s): while True: data = s.recv(CHUNKSIZE) if data == b'': break process_data(data) # 使用 iter() 来代替 重点(iter的妙用) def reader2(s): for chunk in iter(lambda: s.recv(CHUNKSIZE), b''): pass # process_data(data)
# iter 函数一个鲜为人知的特性是它接受一个可选的 callable 对象和一个标记(结尾)值作为输入参数。
当以这种方式使用的时候,它会创建一个迭代器, 这个迭代器会不断调用 callable 对象直到返回值和标记值相等为止
# 这种特殊的方法对于一些特定的会被重复调用的函数很有效果,比如涉及到I/O调用的函数
# 从套接字或文件中以数据块的方式读取数据,通常你得要不断重复的执行 read() 或 recv() ,
并在后面紧跟一个文件结尾测试来决定是否终止。这节中的方案使用一个简单的 iter() 调用就可以将两者结合起来了。
其中 lambda 函数参数是为了创建一个无参的 callable 对象,并为 recv 或 read() 方法提供了 size 参数