迭代器,生成器,装饰器
1.迭代器
1.1 可迭代对象
可迭代对象指的是支持iter调用的对象,本质上,可迭代对象是是序列观念的一种通用化:
如果对象是实际保存的序列或者是可以在迭代工具上下文中一次产生一个结果的对象,那么就可以看做是可迭代的
可迭代工具包括for循环,列表推导,in成员关系测试,以及内置函数map这些。
1.2 迭代器
所有带有__next__
方法的对象会前进到下一个结果,而到达一系列结果的末尾时,__next__
会引发StopIteration,这种对象在Python中称为迭代器。
x = [1, 2, 3]
y = iter(x)
z = iter(x)
print(next(y))
print(next(y))
print(next(z))
print(type(x))
print(type(y))
上述代码输出为
"""
1
1
2
<class 'list'>
<class 'list_iterator'>
"""
在此例中,list是可迭代对象(dict是可迭代对象,set也是可迭代对象),而y和z是两个独立的迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。
x = [1, 2, 3]
for i in x:
pass
上述代码中有二个步骤:
1:X是可迭代对象,把X传入iter函数,从可迭代对象拿到一个迭代器
2.在每次迭代中调用该迭代器对象的next方法遍历值,并捕捉StopIteration异常,从而决定何时停止迭代。
(补充:对于本身就是迭代器的对象,用于初始化的iter调用是可有可无的)
import dis
x = [1, 2, 3]
dis.dis('for _ in X:pass')
dis 模块可以查看字节码,字节码是 CPython 解释器的实现细节。
"""
1 0 LOAD_NAME 0 (X)
2 GET_ITER
>> 4 FOR_ITER 4 (to 10)
6 STORE_NAME 1 (_)
8 JUMP_ABSOLUTE 4
>> 10 LOAD_CONST 0 (None)
12 RETURN_VALUE
"""
反编译该段代码,就可以看到解释器显示地调用GET_ITER指令,相当于调用iter(x),FOR_ITER指令就是调用next()方法,不断地获取迭代器中的下一个元素
1.3 其他内置类型的可迭代对象
1.在Python3中,字典也是可迭代对象自带一个迭代器,在上下文中,会自动一次返回一个键。
D = {'a':1, 'b':1, 'c':3}
for key in D.keys():
print(key,D[key])
I = iter(D)
print(type(I))
print(next(I))
print(next(I))
# 最终的效果是不在需要掉keys()方法来遍历字典的键,——for循环将使用迭代协议在每次迭代的时候获取一个键
for key in D:
print(key,D[key])
上述代码运行结果如下:
"""
a 1
b 1
c 3
<class 'dict_keyiterator'>
a
b
a 1
b 1
c 3
"""
2.range 可迭代对象
由于迭代协议,我们必须把返回结果包装在一个list调用中,才能一次性看到它们的所有值,可迭代对象一次只返回一个结果,而不是一次性返回一个实际的列表。
R = range(4) # range在Python3中也是一个可迭代对象
print(type(R))
I = iter(R) # iter()使R成为迭代器之后,才拥有next方法
print(type(I))
print(next(I))
print(next(I))
print(list(range(4)))
# 代码运行的结果如下
'''
<class 'range'>
<class 'range_iterator'>
0
1
[0, 1, 2, 3]
'''
-
其他可迭代对象
map,zip,filter也是可迭代对象。 -
多遍迭代器 VS 单遍迭代器
zip,map,filter 不支持同一结果上的多个活跃迭代器,iter的结果就是它们自身
R = range(4, 7)
I = iter(R)
Y = iter(R)
print(next(I))
print(next(I))
print(next(I))
print(next(Y))
print(next(Y))
M = map(abs,[-1, 0, 1])
N1 = iter(M)
N2 = iter(M)
print(next(N1))
print(next(N1))
print(next(N2))
print(next(N2))
# 代码运行的结果是
"""
4
5
6
4
5
1
0
1
Traceback (most recent call last):
File "Z:\LearningPython\iterable.py", line 69, in <module>
print(next(N2))
StopIteration
"""
2. 生成器
2.1 生成器函数
生成器其实是一种特殊的迭代器,不过这比迭代器更加优雅。它不需要再写__iter__()和__next__()方法了,只需要一个yiled关键字。 生成器一定是迭代器(反之不成立),因此任何生成器也是以一种懒加载的模式生成值。
用生成器来实现斐波那契数列的例子是:
from itertools import islice
# 迭代器的写法
class Fib:
def __init__(self):
self.prev = 0
self.curr = 1
def __iter__(self):
return self
# 返回self
def __next__(self):
value = self.curr
self.curr += self.prev
self.prev = value
return value
# 生成器的写法
def fib2():
prev, curr = 0, 1
while True:
yield curr
prev, curr = curr, prev + curr
fib = Fib()
print(list(islice(fib, 0, 10)))
print(list(islice(fib2(), 0, 10)))
"""itertools.islice的基本用法为:
itertools.islice(iterable, start, stop[, step])
可以返回从迭代器中的start位置到stop位置的元素。如果stop为None,则一直迭代到最后位置
"""
2.2 生成器表达式
生成器表函数:一个使用了yield表达式的def语句是一个生成器函数。
生成器表达式:
一个包括在圆括号中的列表推导表达式被称为一个生成器表达式。
生成器函数和生成器表达式在本质上没有区别,区别只在表达式的简洁性上和语句的表现上不同而已。
# 生成器函数
def timefour(string):
for i in string:
yield i * 4
G = timefour('spam')
print(list(G))
# 生成器表达式
timefour1 = (i * 4 for i in 'spam')
print(list(timefour1))
# 二种输出方式都是一样的,都为['ssss', 'pppp', 'aaaa', 'mmmm']
3. 装饰器
3.1 闭包
如果在一个函数的内部定义了另一个函数,外部的我们叫他外函数,内部的我们叫他内函数。
闭包就是在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用
def outer(x):
def inner(y):
return x + y
return inner
print(outer(6)(5)) # 运行结果为11
3.2 装饰器
- 装饰器是为函数和类指定管理或扩增代码的一种方式,装饰器本身采取可调用对象的形式(如函数),并处理其他可调用的对象。装饰器有类装饰器和函数装饰器
函数装饰器是一种语法糖:在def语句结束时,通过调用另一个函数来运行这个函数,把最初的函数名重新绑定到返回的结果。
其用法是:
函数装饰器是一种关于函数的运行时声明,函数的定义需要遵守此声明,装饰器在定义函数或者方法的def语句的前一行编写,由@符号以及紧随其后的对于元函数的一个引用组成。
import time
def timeit(func):
def wrapper(x):
start = time.time()
ret = func(x)
print(time.time() - start)
return ret
return wrapper
@timeit
def my_func(x):
time.sleep(x)
print('sleep is {}'.format(x))
# 当my_func 这个函数进行装饰的时候,
# 上述代码等价于 myfunc = timeit(my_func)
# 大多数情况下可以把函数装饰器理解为输入和输出都是函数的函数
my_func(1)
"""
在Python中所有,所有东西都是对象,函数也是object
函数也可以当做参数传到其他的函数中。函数也可以成为返回值.
装饰器本身可以是函数或者类
被装饰的函数也可以是函数或者类
"""
# 类装饰器就是类调用一个函数,返回一个object,但这个object依然是一个callable
class tracer:
def __init__(self, func):
self.calls = 0
self.func = func
def __call__(self, *args):
# __call__ 这个方法使tracer这个类可调用,就是callable的
self.calls += 1
print('call {} to {}'.format(self.calls, self.func.__name__))
return self.func(*args)
@tracer
# 等价于 sapm = tracer(spam)
def spam(a, b, c):
return a + b + c
print(spam(1, 2, 3))
print(spam('a', 'b', 'c'))
# 上述输出是
"""call 1 to spam
6
call 2 to spam
abc
"""
@decorator
def F(arg):
...
F(99)
# 上述代码等同于
def F(arg):
...
F = decorator(F)
# 装饰器是一个可调用对象,它返回实际的装饰器,返回的装饰器反过来返回可调用的对象,这个对象随后以调用最初的函数名
#先用decorator来调用F,再把F(最初的函数名)和decorator(F)返回的结果重新绑定
F(99)
class C:
@property
def name(self):
...
# 等同于 name = property(name)
- 为什么使用装饰器
它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象,装饰器的作用就是为已经存在的对象添加额外的功能。缺点是当装饰器当做调用或者接口代理事,可能会引发额外的调用
参考教程:
https://www.bilibili.com/video/BV1Gu411Q7JV?spm_id_from=333.337.search-card.all.click
https://www.bilibili.com/video/bv19U4y1d79C?spm_id_from=333.788.b_636f6d6d656e74.5
https://nvie.com/posts/iterators-vs-generators/
Python学习手册上下册