Python生成器及常用内置函数补充
一、生成器与yield
若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象:
自定义range功能:
>>> def my_range(start, stop=None, step=1):
... print('start...')
... if not stop:
... stop = start
... start = 0
... while start < stop:
... yield start
... start += step
... print('end...')
...
>>> g = my_range(0,3)
>>> g
<generator object my_range at 0x104105678>
生成器内置有_ _ iter _ _ 和 _ _ next _ _方法,所有生成器本身就是一个迭代器:
>>> g.__iter__
<method-wrapper '__iter__' of generator object at 0x1037d2af0>
>>> g.__next__
<method-wrapper '__next__' of generator object at 0x1037d2af0>
因而我们可以用next(生成器)触发生成器所对应函数的执行:
>>> next(g) # 触发函数执行直到遇到yield则停止,将yield后的值返回,并在当前位置挂起函数
start...
0
>>> next(g) # 再次调用next(g),函数从上次暂停的位置继续执行,直到重新遇到yield...
1
>>> next(g) # 周而复始...
2
>>> next(g) # 触发函数执行没有遇到yield则无值返回,即取值完毕抛出异常结束迭代
end...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
既然生成器对象属于迭代器,那么必然可以使用for循环迭代,如下:
>>> for i in my_range(4):
... print(i)
...
0
1
2
3
有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值;
yield与return对比如下:
yield:
- 可以返回值(支持多个并且组织成元组形式);
- 函数体代码遇到yield不会结束而是"停住"(挂起);
- yield可以将函数变成生成器 并且还支持外界传值;
return:
- 可以返回值(支持多个并且组织成元组形式);
- 函数体代码遇到return直接结束;
二、yield表达式应用
在函数内可以采用表达式形式的yield:
>>> def eater():
... print('Ready to eat')
... while True:
... food = yield
... print('get the food: %s, and start to eat' % food)
...
可以弄到函数的生成器对象持续为函数体send值,如下:
>>> g = eater() # 得到生成器对象
>>> g
<generator object eater at 0x101b6e2b0>
>>> next(g) # 需要事先”初始化”一次,让函数挂起在food = yield,等待调用g.send()方法为其传值
Ready to eat
>>> g.send('包子')
get the food: 包子, and start to eat
>>> g.send('鸡腿')
get the food: 鸡腿, and start to eat
针对表达式形式的yield,生成器对象必须事先先被初始化一次,让函数挂起在food=field的位置,等待调用g.send()方法为函数体传值,g.send(None)等同于next(g)。
我们可以编写装饰器来完成为所有表达式形式yield对应生成器的初始化操作,如下:
def init(func):
def wrapper(*args, **kwargs):
g = func(*args, **kwargs)
next(g)
return g
return wrapper
@init
def eater():
print('Ready to eat')
while True:
food = yield
print('get the food: %s, and start to eat' % food)
表达式形式的yield也可以用于返回多次值,即变量名=yield值的形式,如下:
>>> def eater():
... print('Ready to eat')
... food_list = []
... while True:
... food = yield food_list
... food_list.append(food)
...
>>> e = eater()
>>> next(e)
Ready to eat
[]
>>> e.send('蒸羊羔')
['蒸羊羔']
>>> e.send('蒸熊掌')
['蒸羊羔', '蒸熊掌']
>>> e.send('蒸鹿尾儿')
['蒸羊羔', '蒸熊掌', '蒸鹿尾儿']
三、生成器表达式
创建一个生成器对象有两种方式,一种是调用带yield关键字的函数,另一种就是生成器表达式,与列表生成式的语法格式相同,只需要将[]换成(),即:
(expression for item in iterable if condition)
对比列表生成式返回的是一个列表,生成器表达式返回的是一个生成器对象:
>>> [x * x for x in range(3)]
[0, 1, 4]
>>> g = (x * x for x in range(3))
>>> g
<generator object <genexpr> at 0x101be0ba0>
对比列表生成式,生成器表达式的优点自然是节省内存(一次只产生一个值在内存中)
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g) # 抛出异常StopIteration
如果我们要读取一个大文件的字节数,应该基于生成器表达式的方式完成:
with open('db.txt','rb') as f:
nums = (len(line) for line in f)
total_size = sum(nums) # 依次执行next(nums),然后累加到一起得到结果
四、常用内置函数补充
最后,下面会补充介绍一些Python里的常用内置函数,这些都是Python提供给你直接可以拿来使用的,之前有些我们已经用过了,有些我们还没用到过,还有一些是被封印了,需要等我们学了新知识才能解开封印的,这里先介绍一些我们现阶段可以掌握的内置函数。
-
abs():绝对值
>>> abs(123) 123 >>> abs(-123) 123
-
all()和any():
lis = [11, 22, 33, 0] print(all(lis)) # 所有的元素都为True结果才是True print(any(lis)) # 所有的元素只要有一个为True结果就为True
结果为:
False True
-
bin():二进制转换 oct():八进制转换 hex(): 十六进制转换
print(bin(123)) print(oct(123)) print(hex(123))
结果为:
0b1111011 0o173 0x7b
-
bytes()和str():
这两个内置函数为我们提供了另外一种字符的编码与解码的方式:
res = 'jason 最牛逼' res1 = bytes(res, 'utf8') print(res1) res2 = str(res1, 'utf8') print(res2)
结果为:
b'jason \xe6\x9c\x80\xe7\x89\x9b\xe9\x80\xbc' jason 最牛逼
-
callable():判断是否可调用(能不能加括号运行)
s1 = 'jason' def index(): pass print(callable(s1)) print(callable(index))
结果为:
False True
-
chr()和ord():
print(chr(65)) # 根据ASCII码将数字转成字符 print(ord('Z')) # 根据ASCII码将字符转成数字
结果为:
A 90
-
complex():用于创建一个值为 real + imag * j 的复数或者转化一个字符串或数为复数。
>>> complex(1, 2) (1 + 2j) >>> complex(1) (1 + 0j)
-
dir():不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。
>>> dir() # 获得当前模块的属性列表 ['__builtins__', '__doc__', '__name__', '__package__', 'arr', 'myslice'] >>> dir([ ]) # 查看列表的方法 ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] >>>
-
divmod():接收两个数字类型(非复数)参数,返回一个包含商和余数的元组(a // b, a % b)。
>>> divmod(7, 2) (3, 1) >>> divmod(8, 2) (4, 0)
-
eval():用来执行一个字符串表达式,并返回表达式的值。只能识别简单的语法 exec():执行储存在字符串或文件中的 Python 语句,相比于 eval,exec可以执行更复杂的 Python 代码。
>>> x = 7 >>> eval( '3 * x' ) 21 >>> eval('pow(2, 2)') 4
>>> exec ("""for i in range(5): ... print ("iter time: %d" % i) ... """) iter time: 0 iter time: 1 iter time: 2 iter time: 3 iter time: 4
-
isinstance():判断是否属于某个数据类型:
print(isinstance(123, float)) print(isinstance(123, int))
结果为:
False True
-
pow():返回 (x的y次方) 的值。
>>> print(pow(4, 3)) 64
-
round():该方法返回浮点数 x 的四舍五入值,准确的说保留值将保留到离上一位更近的一端(四舍六入)。
第二个参数表示小数点位数,默认值为 0。
print ("round(70.23456) : ", round(70.23456)) print ("round(56.659,1) : ", round(56.659,1)) print ("round(80.264, 2) : ", round(80.264, 2)) print ("round(100.000056, 3) : ", round(100.000056, 3)) print ("round(-100.000056, 3) : ", round(-100.000056, 3))
以上实例运行后输出结果为:
round(70.23456) : 70 round(56.659,1) : 56.7 round(80.264, 2) : 80.26 round(100.000056, 3) : 100.0 round(-100.000056, 3) : -100.0
看下面的一个例子:
>>> round(2.675, 2) 2.67
按我们的想法返回结果应该是2.68,可结果却是2.67,为什么?
这跟浮点数的精度有关。我们知道在机器中浮点数不一定能精确表达,因为换算成一串 1 和 0 后可能是无限位数的,机器已经做出了截断处理。那么在机器中保存的2.675这个数字就比实际数字要小那么一点点。这一点点就导致了它离 2.67 要更近一点点,所以保留两位小数时就近似到了 2.67。
-
sum():对序列进行求和计算。第二个参数为指定相加的参数,如果没有设置这个值,默认为0。
>>> sum([0, 1, 2]) 3 >>> sum((2, 3, 4), 1) # 元组计算总和后再加 1 10 >>> sum([0, 1, 2, 3, 4], 2) # 列表计算总和后再加 2 12