画图表示如下:
迭代器和可迭代对象:
迭代器都是一个可迭代对象,且所有的Iterable(迭代对象)
都可以通过内置的iter()
转变为Iterator(迭代器)
生成器和迭代器:
联系: 所有的生成器都是迭代器,有yield
的是生成器,因为yield
可以是生成器表达式,也可以是生成器函数。
区别: 迭代器用于从集合中取出元素,生成器用于凭空生成元素。
def fab(max):
n,a,b = 0,0,1
L = []
while n < max:
L.append(b)
a,b = b,a+b
n += 1
return L
为了节省内存,和处于未知输出的考虑,使用迭代器来改善代码。
class fab(object):
'''
Iterator to produce Fibonacci
'''
def __init__(self,max):
self.max = max
self.n = 0
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
if self.n < self.max:
r = self.b
self.a,self.b = self.b,self.a + self.b
self.n += 1
return r
raise StopIteration('Done')
迭代器什么都好,就是写起来不简洁。所以用 yield
来改写第三版。
def fab(max):
n,a,b = 0,0,1
while n < max:
yield b
a,b = b,a+b
n += 1
使用下面来输出
for a in fab(8):
print(a)
看起来很简洁,而且有了迭代器的特性。
可迭代对象(iterable object),简单来理解就是可以使用for
循环来遍历的对象。比如常见的list、set和dict。
可以用以下方法来测试对象是否可迭代:
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False
其实当我们对所有的可迭代对象调用dir()
方法时,会发现其实他们都定义了__iter__
方法。这样就可以通过iter(object)
来返回一个迭代器。
>>> x = [1, 2, 3]
>>> y = iter(x)
>>> type(x)
<class 'list'>
>>> type(y)
<class 'list_iterator'>
可以看到,调用iter()
后,变成了一个list_iterator
的对象。会发现增加了__next__
方法。所有实现了__iter__
和__next__
两个方法的对象,都是迭代器。
迭代器是带状态的对象,它会记录当前迭代所在的位置,以便下次迭代的时候获取正确的元素。
__iter__
方法返回迭代器自身,
__next__
方法返回容器中的下一个值,如果容器中,没有更多元素了,则抛出StopIteration异常
>>> x = [1, 2, 3]
>>> y = iter(x)
>>> next(y)
1
>>> next(y)
2
>>> next(y)
3
>>> next(y)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
和判断是否是可迭代对象差不多,只需要将Iterable
换为Iterator
,即:from collections import Iterator
,然后调用isinstance
方法。
Python中for
循环本质上就是通过不断调用next()
函数实现的,举个例子,下面的代码:
x = [1, 2, 3]
for elem in x:
...
实际上执行的是:
也就是先将可迭代对象
转换为Iterator
,再去迭代。
这样迭代器只有在调用next()
时,才会实际计算到下一个值,起到节省内存的作用。
itertools
库提供了很多常见迭代器的使用:
>>> from itertools import count # 计数器
>>> counter = count(start=13)
>>> next(counter)
13
>>> next(counter)
14
>>> from itertools import cycle
>>> colors = cycle(['red', 'white', 'blue'])
>>> next(colors)
'red'
>>> next(colors)
'white'
>>> next(colors)
'blue'
>>> next(colors)
'red'
Python汇总,一边循环,一边计算的机制,称之为生成器: generator
要创建一个 generator
,最简单的方法是改造列表生成式
>>> [x*x for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> (x * x for x in range(10))
<generator object <genexpr> at 0x03804630>
通过def
定义,然后通过yield
来支持迭代器协议,所以比迭代器写起来看着简单
>>>def spam():
yield"first"
yield"second"
yield"third"
>>> spam
<function spam at 0x011F32B0>
>>> gen
<generator object spam at 0x01220B20>
>>> gen.next()
'first'
>>> gen.next()
'second'
>>> gen.next()
'third'
一般情况下,生成器函数通过for
来使用,这样不用关心StopIteration
的异常。
>>>for x in spam():
print x
first
second
third
进行函数调用的时候,返回一个生成器对象。在使用next()
调用的时候,遇到yield
就返回,记录此时函数调用的位置,下次调用next()
时,从断点处开始。
装饰器(Decorator)是Python中最吸引人的特征,装饰器本质上是一个函数,它可以让已有的函数不作任何改动的情况下,增加功能。
因此装饰器非常适合有切面需求的场景,比如权限校验,日志记录和性能测试等等。比如像要执行某个函数前,记录日志或者记录时间来统计性能,又不想改动这个函数,就可以通过装饰器来实现。
不用装饰器,我们需要在函数执行前插入日志,如下:
def foo():
print('i am foo')
def foo():
print('foo is running')
print('i am foo')
虽然这样写,满足了需求,但是修改了原有代码,如果其他函数也需要插入日志的话,就需要修改所有的代码,不能复用代码,可以这么写:
def use_logg(func):
logging.warn("%s is running" % func.__name__)
func()
def bar():
print('i am bar')
use_log(bar) #将函数作为参数传入
这样写的确可以复用插入的日志,缺点就是显示的封装原来的函数,我们希望透明的做这件事。用装饰器来写,如下:
bar = use_log(bar)def use_log(func):
def wrapper(*args,**kwargs):
logging.warn('%s is running' % func.__name___)
return func(*args,**kwargs)
return wrapper
def bar():
print('I am bar')
bar = use_log(bar)
bar()
use_log()
就是装饰器,它把真正我们想要执行的函数 bar()
封装在里面,返回一个封装了加入代码的新函数,看起来就像是 bar()
被装饰了一样。
这个例子中的切面就是函数进入的时候,在这个时候,我们插入了一句记录日志的代码。这样写还是不够透明,通过@语法糖
来起到 bar = use_log(bar)
的作用。
bar = use_log(bar)def use_log(func):
def wrapper(*args,**kwargs):
logging.warn('%s is running' % func.__name___)
return func(*args,**kwargs)
return wrapper
@use_log
def bar():
print('I am bar')
@use_log
def haha():
print('I am haha')
bar()
haha()
这样看起来就很简洁,而且代码很容易复用。可以看成是一种智能的高级封装。
装饰器也是可以带参数的,这为装饰器提供了更大的灵活性。
def use_log(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator
@use_log(level="warn")
def foo(name='foo'):
print("i am %s" % name)
foo()
实际上是对装饰器的一个函数封装,并返回一个装饰器。这里涉及到作用域的概念,可以把它看成一个带参数的闭包。
当使用 @use_log(level='warn')
时,会将 level
的值传给装饰器的环境中。它的效果相当于 use_log(level='warn')(foo)
,也就是一个三层的调用。
这里有一个美中不足,decorator
不会改变装饰的函数的功能,但会悄悄的改变一个 __name__
的属性(还有其他一些元信息),因为 __name__
是跟着函数命名走的。可以用 @functools.wraps(func)
来让装饰器仍然使用 func
的名字。比如
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
functools.wraps
也是一个装饰器,它将原函数的元信息拷贝到装饰器环境中,从而不会被所替换的新函数覆盖掉。
有了装饰器,我们就可以剥离出大量与函数功能本身无关的代码,增加了代码的重用性。