0. 说明
python中有几个常用的、重要的有关器的概念,包括迭代器、生成器、装饰器及上下文管理器,这里分别对其进行介绍学习。可参考廖雪峰老师博客:https://www.liaoxuefeng.com/wiki/1016959663602400/1017323698112640
1. 迭代器
- 概念:
- 迭代:对对象使用
for...in...
循环遍历称为迭代;- 可迭代对象:可以用
for...in...
循环遍历的对象成为可迭代对象;- 迭代器:可以被
next()
调用并不断返回下一个值的对象成为迭代器;- 理解:
- 可迭代对象:类中有
__iter__()
方法的类创建的对象即是可迭代对象;- 迭代器:类中有
__iter__()
方法和__next__ ()
方法的类创建的对象即是迭代器;- 注意:
- 可以使用
isinstance()
方法判断是否是可迭代对象或迭代器,对应collections
下的Iterable
和Iterator
;- 可迭代对象可以使用for循环,不代表能正常使用for循环遍历(可迭代对象不一定是迭代器;迭代器一定是可迭代对象);
- list、dic、str本身是可迭代对象,不是迭代器;使用
iter()
方法可以获得对应迭代器;
1.1 自定义可迭代对象和迭代器
- 普通类
from collections import Iterable, Iterator
class MyIterable(object):
pass
myIterable = MyIterable()
print(isinstance(myIterable, Iterable)) # False
print(isinstance(myIterable, Iterator)) # False
- 普通类for循环:
TypeError: 'MyIterable' object is not iterable
- 实现
__iter__()
方法后:
from collections import Iterable, Iterator
class MyIterable(object):
def __iter__(self):
pass
myIterable = MyIterable()
print(isinstance(myIterable, Iterable)) # True
print(isinstance(myIterable, Iterator)) # False
- for循环问题:
TypeError: iter() returned non-iterator of type 'NoneType'
-
__iter__()
返回迭代器:
from collections import Iterable, Iterator
class MyIterable(object):
def __iter__(self):
return myIterator
class MyIterator(object):
def __iter__(self):
pass
def __next__(self):
pass
myIterable = MyIterable()
myIterator = MyIterator()
print(isinstance(myIterable, Iterable)) # True
print(isinstance(myIterable, Iterator)) # False
print(isinstance(myIterator, Iterator)) # True
- for循环:可以正常遍历,不过打印的是
None
。
1.2 for循环实质:
-
- for循环首先判断对象是否是可迭代对象,如果是:
-
- 自动调用
iter(对象)
方法,得到类中__iter__()
方法的返回值(迭代器);
- 自动调用
-
- 调用迭代器中的
__next__()
方法得到返回值;
- 调用迭代器中的
-
- 直至捕捉到
StopIteration
停止迭代。
- 直至捕捉到
1.3 自己实现迭代器类
class MyIterator(object):
def __init__(self):
self.items = list()
self.current_num = 0
def add(self, item):
self.items.append(item)
def __iter__(self):
return self
def __next__(self):
if self.current_num < len(self.items):
index = self.current_num
self.current_num += 1
return self.items[index]
else:
raise StopIteration
__iter__()
方法和__next__()
方法定义在同一个类中,既是可迭代对象也是迭代器。
1.4 迭代器实现斐波那契数列
class MyFibonacci(object):
def __init__(self, nums):
self.a = 0
self.b = 1
self.current_num = 0
self.nums = nums
def __iter__(self):
return self
def __next__(self):
if self.current_num < self.nums:
ret = self.a
self.a, self.b = self.b, self.a+self.b
self.current_num += 1
return ret
else:
raise StopIteration
2. 生成器
- 引出:通过列表生成式可以直接生成一个列表,但是当列表元素较多时,受内存限制,列表容量是有限的。所以如果内存保存的不是全部元素而是生成每个元素的算法,则在需要获取元素时调用算法即可获取每个元素。这种一边循环一遍计算的机制成为生成器。
- 注意:生成器是一种特殊的迭代器。
2.1 创建生成器的方式
-
- 将列表生成式的
[]
改为()
:
- 列表:
li = [x*x for x in range(10) ] # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
- 生成器:
g = (x*x for x in range(10)) #
。at 0x106a219a8>
- 将列表生成式的
-
- 通过带
yield
的函数生成:
- 通过带
例如上述斐波那契数列,使用列表生成式很难实现,但定义函数可以轻松打印出:
def fib(n):
current = 0
a, b = 0, 1
while current < n:
ret = a
a, b = b, a+b
current += 1
print(ret)
return 'done'
实际上,只需将上述代码中
print()
替换为yield
,即为生成器第二种创建方式。
def fib(n):
current = 0
a, b = 0, 1
while current < n:
ret = a
a, b = b, a+b
current += 1
yield ret
return 'done'
此时
函数名()
不再是调用函数,而是创建一个生成器。即函数中含有yield
时,该函数不再是普通函数,而是一个生成器。通过next()
方法可以不断获取生成器的下一个值直至StopIteration
异常。一般使用for...in...
遍历。
注意for
循环中时拿不到return
的返回值的,如果要拿到return
的值,必须捕获StopIteration
错误,返回值包含在StopIteration
的value
中。
g = fib(10)
while True:
try:
print(next(g))
except StopIteration as e:
print('return: %s' % e.value)
break
for循环中,执行到函数
yield
处时,将yield
后的值返回并暂停执行,下次循环时从暂停处继续往后执行。
2.2 唤醒生成器的两种方式
- 使用
next()
方法唤醒生成器,获取下一次的值; - 使用
对象.send()
方法唤醒,使用send()
唤醒的好处是可以在唤醒的同时向断点处传入一个附加数据。
def gen():
i = 0
while i < 5:
temp = yield i
print(temp)
i += 1
f = gen()
print(next(f))
print(f.send('hh'))
print(next(f))
for i in f:
print(i)
结果:
0
hh
1
None
2
None
3
None
4
None
执行到yield
时,gen
函数作用暂时保存,返回i的值,temp
接收下次f.send('hh')
时send
过来的值。 next(f)
相当于f.send(None)
。
3. 装饰器
3.1 闭包
了解装饰器之前需要先了解闭包的概念,参考博客:https://www.cnblogs.com/Lin-Yi/p/7305364.html。定义一个函数,该函数内部定义了另一个函数,并且内部函数用到了外部函数的变量,而外部函数的返回是内部函数的引用,这样就构成一个闭包。例如:
def outer():
x = 10
def inner(y):
return x+y
return inner
a = outer()
print(a(5)) # 15
注意内部函数不能改变外部函数内的变量,需要添加
nonlocal
修饰。外部函数返回的是内部函数的引用(不加()
)。另外,内部函数引用了外部函数的变量,因此外部函数的变量无法及时释放,占用内存,使用闭包时需要注意这一点。
3.2 装饰器
引入:我们有时候需要在不改变方法代码的前提下,为一个功能增加一些公共功能,如记录接口调用时间,计算方法执行时长等。python通过装饰器实现这样的功能。
import time
def wrapper(func):
def inner():
print('执行%s方法的时间是:%s' % (func.__name__, time.time()))
func()
return inner
@wrapper
def print_hello():
print('hello')
print_hello()
python解释器从上到下解释代码,
- 解析到
def wrapper()
时,将函数wrapper
加入到内存;- 解析到
@wrapper
时:
- 执行
wrapper
函数,并将@wrapper()
下面的函数作为wrapper()
的参数。等价于wrapper(print_hello)
;- 将返回值再重新赋给
@wrapper()
下面的函数,即print_hello = def inner(): pass
。
这样既执行了增加的功能代码,也保证了原方法被调用。
3.3 常见几种装饰器
- 无参数函数
import time
def timefuc(func):
def wrapped_func():
print('%s called at %s' % (func.__name__, time.ctime()))
func()
return wrapped_func
@timefuc
def foo():
print('i am foo')
foo()
time.sleep(2)
foo()
- 被装饰得函数有参数
import time
def timefuc(func):
def wrapped_func(a, b):
print('%s called at %s' % (func.__name__, time.ctime()))
print(a, b)
func(a, b)
return wrapped_func
@timefuc
def foo(a, b):
print(a+b)
foo(3, 5)
time.sleep(2)
foo(2, 4)
- 被装饰得函数有不定长参数
import time
def timefuc(func):
def wrapped_func(*args, **kwargs):
print('%s called at %s' % (func.__name__, time.ctime()))
func(*args, **kwargs)
return wrapped_func
@timefuc
def foo(a, b, c):
print(a+b+c)
foo(3, 5, 7)
time.sleep(2)
foo(2, 4, 9)
- 被装饰函数有return
import time
def timefuc(func):
def wrapped_func(*args, **kwargs):
print('%s called at %s' % (func.__name__, time.ctime()))
func(*args, **kwargs)
return wrapped_func
@timefuc
def foo(a, b, c):
return a+b+c
foo(3, 5, 7)
time.sleep(2)
print(foo(2, 4, 9))
注意:此时结果为:
foo called at Tue Jul 16 09:25:19 2019
foo called at Tue Jul 16 09:25:21 2019
None
为打印出返回值,应当在装饰器中返回函数:
import time
def timefuc(func):
def wrapped_func(*args, **kwargs):
print('%s called at %s' % (func.__name__, time.ctime()))
return func(*args, **kwargs) # 为了返回值,这里应该return
return wrapped_func
@timefuc
def foo(a, b, c):
return a+b+c
foo(3, 5, 7)
time.sleep(2)
print(foo(2, 4, 9))
一般情况下为装饰器更通用,可以有return。
- 多个装饰器装饰同一个函数:
def add_pass1(func):
def wrapped_func():
print('---pass1---')
return func()
return wrapped_func
def add_pass2(func):
def wrapped_func():
print('---pass2---')
return func()
return wrapped_func
@add_pass1
@add_pass2
def print_hello():
print('---hello---')
print_hello()
---pass1---
---pass2---
---hello---
3.4 类装饰器
将
@函数名
中的函数名替换成类名,即为使用类装饰器。但该类需要有__call__()
方法。实现:
class Test(object):
def __init__(self, func):
print('---初始化---')
print('func name is %s' % func.__name__)
self.__func = func
def __call__(self, *args, **kwargs):
print('---装饰逻辑---')
self.__func()
@Test
def test():
print('---test---')
test()
---初始化---
func name is test
---装饰逻辑---
---test---
4. 上下文管理器
经常看到在文件读写的代码中使用
with open ('***.txt', 'w') as f: f.write()
的使用方式,那么with到底是什么意思和原理呢,这就涉及到上下文管理器的概念。
定义:任何实现了
__enter__()
方法和__exit__()
方法的对象都可称为上下文管理器。上下文管理器对象可以使用with关键字,文件file对象也实现了上下文管理器。
自定义文件对象类:
class MyFile(object):
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
print('enter...')
self.f = open(self.filename, self.mode)
return self.f
def __exit__(self, *args):
print('exit...')
self.f.close()
with MyFile('123.txt', 'w') as f:
print('writing...')
f.write('hello world')
说明:在执行至with
时,调用对象的__enter__()
方法,将返回值保存成as
后面的变量;执行过程中异常或者无异常执行完毕后,调用对象的__exit__()
方法。
另一种实现上下文管理器的方式:
from contextlib import contextmanager
@contextmanager
def my_open(path, mode):
f = open(path, mode)
yield f
f.close()
with my_open('123.txt', 'w') as f:
f.write('123')
说明:通过装饰器实现。使用yield
将函数分割成两部分,yield
之前的语句在__enter__()
方法中执行,之后的语句在__exit__()
方法中执行。紧跟在yield
后面的值是函数的返回值。