python迭代器
协议 容器
如果说你希望定制的容器是不可变的话,你只需要定义__len__ ()和__ getitem__ ()方 法
如果你希望定制的容器是可变的话,除了__len__()和__getitem__()方法,你还需要定义__setitem__()和__delitem__( )两个方法。
容器类型 | |
__len__(self) | 定义当被 len() 调用时的行为(返回容器中元素的个数) |
__getitem__(self, key) | 定义获取容器中指定元素的行为,相当于 self[key] |
__setitem__(self, key, value) | 定义设置容器中指定元素的行为,相当于 self[key] = value |
__delitem__(self, key) | 定义删除容器中指定元素的行为,相当于 del self[key] |
__iter__(self) | 定义当迭代容器中的元素的行为 |
__reversed__(self) | 定义当被 reversed() 调用时的行为 |
__contains__(self, item) | 定义当使用成员测试运算符(in 或 not in)时的行为 |
1>编写一个不可改变的自定义列表,要求记录列表中每个元素被访问的次数
>>> class Countlist:
def __init__(self,*args):
self.values = [x for x in args]
self.count = {}.fromkeys(range(len(self.values)),0) #创建字典 0为value
def __len__(self):
return len(self,values)
def __getitem__(self,key):
self.count[key] += 1
return self.values[key]
>>> count1 = Countlist(1,3,5,7,9)
>>> count2 = Countlist(2,4,6,8,10)
>>> count1[1]
3
>>> count2[1]
4
>>> count1[1] + count2[1]
7
>>> count1.count
{0: 0, 1: 2, 2: 0, 3: 0, 4: 0}
>>> count1.values
[1, 3, 5, 7, 9]
>>> count2.values
[2, 4, 6, 8, 10]
for 循环迭代
for 循环可以遍历列表、元组和字典等,这些对象都是可迭代的,因此它们都属于迭代器。迭代器其实就是一个实现了迭代器协议的容器类对象
>>> for c in "Hello World":
print(c)
H
e
l
l
o
W
o
r
l
d
2、迭代器 iter() next()
迭代器的实现就是基于 __iter__() 和 __next__() 两个方法
iter()生成迭代器 ,next()使用迭代器迭代
>>> string = "Hello"
>>> it = iter(string)
>>> next(it)
'H'
>>> next(it)
'e'
>>> next(it)
'l'
>>> next(it)
'l'
>>> next(it)
'o'
>>> next(it)
Traceback (most recent call last):
File "<pyshell#953>", line 1, in <module>
next(it)
StopIteration
到元素结尾之后,再次迭代会抛出异常,使用try except 捕获异常,这也使得for循环和while循环中可以正常停止
>>> string = "Hello"
>>> it = iter(string)
>>> while True:
try:
each = next(it)
except StopIteration:
break
print(each)
H
e
l
l
o
在类中自己定义迭代器
>>> class Fibs:
def __init__(self):
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
self.a,self.b = self.b,self.a+self.b
return self.a
>>> fibs = Fibs()
>>> for each in fibs:
if each < 10:
print(each)
else:
break
1
1
2
3
5
8
>> class Fibs:
def __init__(self,n=10):
self.a = 0
self.b = 1
self.n = n
def __iter__(self):
return self
def __next__(self):
self.a,self.b = self.b,self.a+self.b
if self.a > self.n:
raise StopIteration
return self.a
>>> fibs = Fibs()
>>> for each in fibs:
print(each)
1
1
2
3
5
8
2、生成器 yield
1)生成器的使用
yield产生协同程序,协同程序就是可以运行的独立函数调用,函数可以暂停或者挂起,并在需要的时候从程序离开的地方继续或者重新开始。
生成器和迭代器的功能非常相似,它也会提供 __next__() 方法,这意味着程序同样可调用内置的 next() 函数来获取生成器的下一个值,也可使用 for 循环来遍历生成器。
生成器与迭代器的区别在于,迭代器通常是先定义一个迭代器类,然后通过创建实例来创建迭代器;而生成器则是先定义一个包含 yield 语句的函数,然后通过调用该函数来创建生成器。
>>> def MyGenerator():
print("执行生成器")
yield 1
yield 2
yield 3
>>> G = MyGenerator()
>>> next(G)
执行生成器
1
>>> next(G)
2
>>> next(G)
3
>>> next(G)
Traceback (most recent call last):
File "<pyshell#1021>", line 1, in <module>
next(G)
StopIteration
每次返回一个值,到yield处就暂停,然后使用next执行到yield再次暂停。
>>> def square(n):
print("程序开始...")
for i in range(n):
yield(pow(i,2))
>>> s = square(8)
>>> print(next(s))
程序开始...
0
>>> print(next(s))
1
>>> print(next(s))
4
>>> print(next(s))
9
>>> for i in s: #for循环遍历迭代器,就是不停地调用next()获取生成器,得到下一个值
print(i,end=',')
16,25,36,49,
不使用生成器,程序就需要使用列表或元组来收集函数返回的多个值,当函数要返回的数据量较大时,这些列表、元组会带来一定的内存开销。如果使用生成器就不存在这个问题,生成器可以按需、逐个返回数据。
当n值很大的时候,避免了结果返回list,分次调取使用,从而达到节省内存的目的。
2)生成器的方法
send() send()函数实现了生成器与“外部程序” 动态地交换数据。send() 方法可以接收一个参数,它会将该参数传递给接收 yield 语句返回值的变量。这里的send是接受一个参数,也可以理解成像yield的返回值传送一个参数。获取生成器第一次所生成的值,应该使用 next() 函数,或者使用send(None)
def square_gen(val):
out_val = None
for i in range(val):
# 使用yield语句生成值,使用out_val接收send()方法发送的参数值
out_val = (yield out_val ** 2) if out_val is not None else (yield i ** 2)
# 如果程序使用send()方法获取下一个值,out_val会获取send()方法的参数
print("out_val:",out_val)
sg = square_gen(5)
# 第一次调用send()方法获取值,只能传入None作为参数
print(sg.send(None)) # 0
print(next(sg)) # 1
# 调用send()方法获取生成器的下一个值,参数9会被发送给生成器
print(sg.send(9)) # 81
# 再次调用next()函数获取生成器的下一个值
print(next(sg)) # 9
>>> sg = square_gen(5)
>>> print(sg.send(None))
0
>>> print(next(sg)) # 1
out_val: None
1
>>> print(sg.send(9))
out_val: 9
81
>>> print(next(sg))
out_val: None
9
close():该方法用于停止生成器 执行close()之后就不能使用next()获取下一个生成器,程序会报错
throw():让程序抛出异常
>>> def MyGenerator():
print("执行生成器")
yield 1
yield 2
yield 3
>>> G = MyGenerator()
>>> next(G)
执行生成器
1
>>> G.close()
>>> next(G)
Traceback (most recent call last):
File "<pyshell#1073>", line 1, in <module>
next(G)
StopIteration
生成器和迭代器的区别:
生成器与迭代器的区别在于,迭代器通常是先定义一个迭代器类,然后通过创建实例来创建迭代器;而生成器则是先定义一个包含 yield 语句的函数,然后通过调用该函数来创建生成器。
看<<python cookbook>>里面一个例子来看两者的区别
实现一个以深度优先方式遍历树形节点的生成器
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()
# Example
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))
for ch in root.depth_first():
print(ch)
#输出:Outputs Node(0), Node(1), Node(3), Node(4), Node(2), Node(5)
在这段代码中,depth_first() 方法简单直观。它首先返回自己本身并迭代每一个子节点并通过调用子节点的depth_first() 方法(使用yield from 语句) 返回对应元素。
Python 的迭代协议要求一个__iter__() 方法返回一个特殊的迭代器对象,这个迭代器对象实现了__next__() 方法并通过StopIteration 异常标识迭代的完成。但是,实现这些通常会比较繁琐。
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)
class DepthFirstIterator(object):
'''
Depth-first traversal
'''
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):
# Return myself if just started; create an iterator for children
if self._children_iter is None:
self._children_iter = iter(self._node)
return self._node
# If processing a child, return its next item
elif self._child_iter:
try:
nextchild = next(self._child_iter)
return nextchild
except StopIteration:
self._child_iter = None
return next(self)
# Advance to the next child and start its iteration
else:
self._child_iter = next(self._children_iter).depth_first()
return next(self)
DepthFirstIterator 类和上面使用生成器的版本工作原理类似,但是它写起来很繁琐,因为迭代器必须在迭代处理过程中维护大量的状态信息。 使用生成器比起迭代器大大简化
yield from 产生递归生成器
from collections 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]
# Produces 1 2 3 4 5 6 7 8
for x in flatten(items):
print(x)
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
3、装饰器
装饰器就是用 @函数 来修饰另一个函数,@fun1 来修饰fun2,这样运行fun2()相当于运行了fun1(fun2),fun2作为fun1的参数,
最后的返回值也取决于fun1。而函数fun2会变为fun1的返回值,fun1返回一个函数,fun2就是一个函数,fun1返回一个值,fun2就成了一个值。
>>> def fun1(fun):
print("执行fun1...")
fun()
return "hello"
>>> @fun1
def fun2():
print("执行fun2...")
执行fun1...
执行fun2...
>>> fun2
'hello'
执行fun2执行的是,fun1(fun2), fun1返回"hello",最后fun2也就成了字符串"hello"
如果修饰的函数有参数的话,可以使用*args 和 **kwargs 做修饰器内部函数的参数,*args 和 **kwargs 表示接受任意数量和类型的参数。
>>> import time
>>> def timer(fun):
print("调用timer...")
def decorate(*args,**kwds):
t0 = time.time()
fun(*args,**kwds)
t1 = time.time()
print('耗时%0.5f'%(t1-t0,))
return decorate
>>> @timer
def fun2(waittime):
print('fun2 start...')
time.sleep(waittime)
print('fun2 end...')
调用timer...
>>> fun2(5)
fun2 start...
fun2 end...
耗时5.08385
>>> fun2
<function timer.<locals>.decorate at 0x0000011E7FBE7A60>
#fun2这时候就是decorate
第一个指向fun2执行的是timer(fun2),之后fun2变成了decorate函数
定义带参数的装饰器 @fun(arg)
def foo(num):
def my_decorator(fn):
def bar(*args,**kwargs):
for i in range(num):
fn(*args,**kwargs)
return bar
return my_decorator
@foo(3)
def my_test(a):
print("==my_test函数==", a)
@foo(5)
def new_test(a,b):
print("==new_test函数==",a," ",b)
my_test(10)
new_test(6, 5)
==my_test函数== 10
==my_test函数== 10
==my_test函数== 10
==new_test函数== 6 5
==new_test函数== 6 5
==new_test函数== 6 5
==new_test函数== 6 5
==new_test函数== 6 5
函数装饰器的嵌套
@decorator1
@decorator2
@decorator3
def func():
...
执行顺序是 由里到外 decorator1( decorator2( decorator3(func) ) )
1>一个装饰器就是一个函数,它接受一个函数作为参数并返回一个新的函数。当你像下面这样写:
@timethis
def countdown(n):
pass
跟像下面这样写其实效果一样的:
def countdown(n):
pass
countdown = timethis(countdown)
内置的装饰器比如@staticmethod, @classmethod,@property 原理也是一样的。例如,下面这两个代码片段是等价的:
class A:
@classmethod
def method(cls):
pass
class B:
# Equivalent definition of a class method
def method(cls):
pass
method = classmethod(method)
2>任何时候你定义装饰器的时候,都应该使用functools 库中的@wraps 装饰器来注解底层包装函数。
import time
from funtools import wraps
def timethis(func):
#Decorator that reports the execution time
@wraps(func)
def wrapper(*args,**kwargs):
start = time.time()
result = func(*args,**kwargs)
end = time.time()
print(func.__name__,end-start)
return result
return wrapper
下面我们使用这个被包装后的函数并检查它的元信息:
>>> @timethis
... def countdown(n):
... '''
... Counts down
... '''
... while n > 0:
... n -= 1
...
>>> countdown(100000)
countdown 0.008917808532714844
>>> countdown.__name__
'countdown'
>>> countdown.__doc__
'\n\tCounts down\n\t'
>>> countdown.__annotations__
{'n': <class 'int'>}
@wraps 有一个重要特征是它能让你通过属性__wrapped__ 直接访问被包装函数。
>>> countdown.__wrapped__(100000)
__wrapped__ 属性还能让被装饰函数正确暴露底层的参数签名信息
>>> from inspect import signature
>>> print(signature(countdown))
(n:int)
解除一个装饰器
假设装饰器是通过@wraps 来实现的,那么你可以通过访问__wrapped__ 属性来访问原始函数:
>>> @somedecorator
>>> def add(x, y):
... return x + y
...
>>> orig_add = add.__wrapped__
>>> orig_add(3, 4)
7
直接访问未包装的原始函数在调试、内省和其他函数操作时是很有用的。但是我们这里的方案仅仅适用于在包装器中正确使用了@wraps 或者直接设置了__wrapped__属性的情况。
如果有多个包装器,那么访问__wrapped__ 属性的行为是不可预知的,应该避免这样做。
from functools import wraps
def decorator1(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 1')
return func(*args, **kwargs)
return wrapper
def decorator2(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 2')
return func(*args, **kwargs)
return wrapper
@decorator1
@decorator2
def add(x, y):
return x + y
python3.3下测试
>>> add(2, 3)
Decorator 1
Decorator 2
5
>>> add.__wrapped__(2, 3)
5
>>>
python3.4下测试
>>> add(2, 3)
Decorator 1
Decorator 2
5
>>> add.__wrapped__(2, 3)
Decorator 2
5
定义一个可以接受参数的装饰器
假设你想写一个装饰器,给函数添加日志功能,同时允许用户指定日志的级别和其他的选项。下面是这个装饰器的定义
和使用示例:
from functools import wraps
import logging
def logged(level, name=None, message=None):
"""
Add logging to a function. level is the logging
level, name is the logger name, and message is the
log message. If name and message aren't specified,
they default to the function's module and name.
"""
def decorate(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__
@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
return wrapper
return decorate
# Example use
@logged(logging.DEBUG)
def add(x, y):
return x + y
@logged(logging.CRITICAL, 'example')
def spam():
print('Spam!')
核心思想很简单。最外层的函数logged()接受参数并将它们作用在内部的装饰器函数上面。内层的函数decorate() 接受一个函
数作为参数,然后在函数上面放置一个包装器。这里的关键点是包装器是可以使用传递给logged() 的参数的。
定义一个接受参数的包装器看上去比较复杂主要是因为底层的调用序列。特别的,如果你有下面这个代码:
@decorator(x, y, z)
def func(a, b):
pass
装饰器处理过程跟下面的调用是等效的;
def func(a, b):
pass
func = decorator(x, y, z)(func)
decorator(x, y, z) 的返回结果必须是一个可调用对象,它接受一个函数作为参。数并包装它
将装饰器定义为类的一部分
在类里面定义装饰器很简单,但是你首先要确认它的使用方式。比如到底是作为一个实例方法还是类方法。下面我们用例子来阐述它们的不同:
from functools import wraps
class A:
# Decorator as an instance method
def decorator1(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 1')
return func(*args, **kwargs)
return wrapper
# Decorator as a class method
@classmethod
def decorator2(cls, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 2')
return func(*args, **kwargs)
return wrapper
下面是一使用例子
# As an instance method
a = A()
@a.decorator1
def spam():
pass
# As a class method
@A.decorator2
def grok():
pass
在类中定义装饰器初看上去好像很奇怪,但是在标准库中有很多这样的例子。特别的,@property 装饰器实际上是一个类,它里面定义了三个方法getter(), setter(),deleter() , 每一个方法都是一个装饰器。例如:
class Person:
# Create a property instance
first_name = property()
# Apply decorator methods
@first_name.getter
def first_name(self):
return self._first_name
@first_name.setter
def first_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._first_name = value
4、推导式
列表推导式
>>> l = [i for i in range(100) if not(i%2) and i%3]
>>> l
[2, 4, 8, 10, 14, 16, 20, 22, 26, 28, 32, 34, 38, 40, 44,
46, 50, 52, 56, 58, 62, 64, 68, 70, 74, 76, 80, 82,
86, 88, 92, 94, 98]
字典推导式
dict1 = {i:i%2 ==0 for i in range(10)}
>>> dict1
{0: True, 1: False, 2: True, 3: False, 4: True, 5: False, 6: True, 7: False, 8: True, 9: False}
集合推导式
>>> set1 = {i for i in [1,2,2,3,4,4,4,5,6,6,6]}
>>> set1
{1, 2, 3, 4, 5, 6}
生成器推导式
>>> gen = (i for i in range(10))
>>> gen
<generator object <genexpr> at 0x0000011E7FBE82B0>
>>> next(gen)
0
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
3
>>> for each in gen:
print(each)
4
5
6
7
8
9
>>> sum((i for i in range(100) if i % 2))
2500
三元表达式
>>> n =1
>>> print('你好' if n else '再见')
你好