您的位置:首页 > 编程语言 > Python开发

Python----装饰器/生成器/迭代器

2017-11-11 19:42 651 查看

装饰器

装饰器decorator:本质是函数,功能是装饰其他函数,就是为其他函数添加附加功能。

原则:

1.不能修改被装饰函数的源代码。

2.不能修改被装饰函数的调用方式。

3.装饰器对被装饰函数来说是透明的,被装饰函数不会感知到装饰器的存在。

实现装饰器知识储备:

1.函数即”变量”。

可以理解为函数名就是变量名,函数体就是变量的内容。函数名指向内存中函数体的位置。

def bar():
print("in the bar")

print(bar)
fun=bar
fun()


<function bar at 0x7fe98a3b22f0>
in the bar


如上所示:

bar是函数名,保存了函数bar()在内存中的地址0x7fe98a3b22f0。

fun也保存了bar()的内存地址,所以fun()可以执行,相当于执行bar().

2.高阶函数。

a.把一个函数名当做实参传给另一个函数(在不修改被装饰函数源代码的情况下为其添加功能)。

import time

def bar():
time.sleep(1)
print("in the bar")

def test(func):
start_time = time.time()
func()
end_time = time.time()
print("the run func time is %s " % (end_time-start_time))

test(bar)


in the bar    #没有改变源码
the run func time is 1.0003530979156494  #添加了新的功能


b.返回值中包含函数名(不修改函数的调用方式)。

import time

def bar():
time.sleep(1)
print("in the bar")

def test(func):
start_time = time.time()
func()
end_time = time.time()
print("the run func time is %s " % (end_time-start_time))
return func   #装饰器返回函数名

bar = test(bar)  #重点,返回的源函数名重新赋给源函数名
bar()          #源函数调用方式不变


in the bar
the run func time is 1.0005016326904297
in the bar


3.嵌套函数。

高阶函数+嵌套函数=》》》》装饰器。

函数嵌套注意与函数调用区别,嵌套是指在一个定义函数体内,再用def声明定义另一个函数,才称为函数嵌套,如果只是在单纯调用,则不叫函数嵌套。

函数嵌套

def foo():
print("in the foo")
def bar():
print("in the bar")

bar()


函数调用

def bar():
print("in the bar")

def foo():
print("in the foo")
bar()


装饰器实例:

import time
#装饰器timer()
def timer(func):
def deco(*agrs,**kwargs): #传递多参数符
start_time = time.time()
func(*agrs,**kwargs)  #传递多参数符
end_time = time.time()
print("the run time = %s" % (end_time - start_time))
return deco

@timer #test1=timer(test1)
def test1():
time.sleep(1)
print("int the test1")

@timer #test2=timer(test2)
def test2(name,age):
time.sleep(1)
print("int the test2:%s %d\n" % (name,age))

test1()
test2("Jack Ma",50)


int the test1
the run time = 1.0011448860168457
int the test2:Jack Ma 50
the run time = 1.0011117458343506


例子中特别注意:

@timer 等价于test1=timer(test1),在需要被装饰的函数前加上@装饰器。

从中可以看出,要创建修饰器,步骤包括:

1.要构建一个新功能函数deco(),这个新功能函数deco包含两个部分:一是调用原函数而是添加新功能。

2.构建装饰函数timer(test1),传参是原函数名。装饰函数是一个由新功能函数构成的嵌套函数,并在装饰函数返回新功能函数内存地址(即新功能函数名)。

3.在原函数定义处前加@timer,把新功能函数内存地址重新赋给原函数,原函数地址指向新功能函数地址。

4.执行test1(), —-这里实质已经变成了执行新功能函数。

5.原函数test1不带参数,原函数test2带参数,如果同一个装饰器要同时装饰带参数和不带参数的原函数,则需要在新功能函数deco()传多参数适配符(*args,**kwargs)。

装饰器最终版

根据不同用户选择不同登录方式进入网站,要求装饰器根据输入参数不同,装饰不同网页。

import time

user,passwd = 'mayun','123456'

def auth(auth_type):#执行step=4
#在最外层接收区分用户的参数
print("auth_type:",auth_type)#执行step=5
def outer_wrapper(func):#执行step=6
#包装新功能函数wrapper()的外层函数,作用是传入原函数home()的内存地址home。
def wrapper(*args, **kwargs):#执行step=8
#真正的新功能函数wrapper()=原函数home()+新功能
if auth_type == "local" :#执行step=10
print("wrapper func",*args, **kwargs)
Username = input("Username:").strip()
PassWord = input("PassWord:").strip()
if user == Username and passwd == PassWord:#执行step=11
print("\033[32:1m Username has passed authentication\033[0m")
res = func(*args, **kwargs)  # from home#执行step=12
print("-----afer authentication------")
return res#执行step=15
#返回home()执行结果,保证装饰器输出结果与原函数一致,不改变原函数返回值。
else:
print("\032[32:1m Invalid username of passwd!\033[0m")
elif auth_type == "ldap" :#执行step=10
print("go to ldap")#执行step=11
return wrapper#执行step=9
return outer_wrapper#执行step=7

def index():
print("welcome to index page")

@auth(auth_type="local") #home=wrapper() #执行step=3
def home():#执行step=13
print("welcome to home page")
return "from home"#执行step=14

@auth(auth_type="ldap")
def bbs():
print("welcome to bbs page")

index()  #执行step=1
print(home())#执行step=2
print(bbs())


welcome to index page
wrapper func
Username:mayun
PassWord:123456
[32:1m Username has passed authentication
welcome to home page
-----afer authentication------
from home
go to ldap
None


列表生成式

list_original = [0,2,4,6,8]
#需要在原始列表list_original中的每个值加1,方法有以下几种:
#普通青年列表生成式
list_normer = []
for i in list_original:list_normer.append(i+1)
print(list_original)
print(list_normer)
#文艺青年列表生成式
list_artist = []
list_artist=map(lambda i:i+1,list_original)
print(list_original)
print(list_artist)
for i in list_artist:
print(i)
#装逼青年列表生成式
list_bier = [2*i+1 for i in range(5) ]
print(list_original)
print(list_bier)


[0, 2, 4, 6, 8]
[1, 3, 5, 7, 9]
[0, 2, 4, 6, 8]
<map object at 0x7f2d27ce3a58>
1
3
5
7
9
[0, 2, 4, 6, 8]
[1, 3, 5, 7, 9]


生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator

普通列表生成方法是把一次性生成列表所有值,并存储在内存当中,可以随时调用任意值。

生成器只有在调用时才会生成相应的数据,内存只记录生成方法,和当前生成器指针位置。

生成方法只有一个next()—–Python3.X __next()__ —-Python2.X

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>
>>> next(g)
0
>>> next(g)
1


generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, …

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

#Fibonacci
#1, 1, 2, 3, 5, 8, 13, 21, 34, ...
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b   #理解赋值内涵,中间生成临时元组进行赋值。
n = n + 1
return 'done'

fib(5)


注意,赋值语句:

a, b = b, a + b


相当于:

t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]


函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

生成器实例

#Fibonacci generator
#1, 1, 2, 3, 5, 8, 13, 21, 34, ...
def fib(max):
n, a, b = 0, 0, 1
while n < max:
#print(b)
yield b  #生成器标识符yield(产生,出产)
a, b = b, a + b
n = n + 1
return 'done'

f=fib(5)
print(f)
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print("===start loop===")
for i in f:
print(i)


<generator object fib at 0x7f34e84b95c8>
1
1
2
3
d725
5
===start loop===
8
13
21
34
55


generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误,如上max=5时,调用 了6次数f.next(),调用越界报StopIteration异常

print(f.__next__())
StopIteration: done


当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。正确使用生成器的姿势是使用for循环调用

简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。

我们可以得出以下结论:

一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。

yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。

如何判断一个函数是否是一个特殊的 generator 函数?可以利用 isgeneratorfunction 判断:

清单 7. 使用 isgeneratorfunction 判断

from inspect import isgeneratorfunction
print(isgeneratorfunction(fib))


True


要注意区分 fib 和 fib(5),fib 是一个 generator function,而 fib(5) 是调用 fib 返回的一个 generator,好比类的定义和类的实例的区别:

import types
print(isinstance(fib, types.GeneratorType))
print(isinstance(fib(5), types.GeneratorType))


False
True


return 的作用

在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。

串行并发

生成器简单的串行并发实例:

一个生产包子,一个吃包子,同时进行。

import time
def consumer(name):
print("%s 准备吃包子" % name)
while True:
baozi = yield
print("包子[%s]来了,被[%s]吃"%(baozi,name))

def producer(name):
c = consumer('A')
c2 = consumer('B')
c.__next__()  #注意执行一遍next()的意义是为了是consumer函数执行并到达yield出中断。
c2.__next__() #如果没有执行一遍next(),则执行send()时consumer不是在yield中断处获取到参数。
print("厨师准备开始做包子")
for i in range(2):
time.sleep(1)
print("做了1个包子!分两个人吃!")
c.send(i)  #send()方法传递一个值给生成器yield,并触发中断的yield继续执行,知道再次遇到yield。
c2.send(i)

producer("Mayun")


A 准备吃包子
B 准备吃包子
厨师准备开始做包子
做了1个包子!
包子[0]来了,被[A]吃
包子[0]来了,被吃
做了1个包子!
包子[1]来了,被[A]吃
包子[1]来了,被[B]吃


[b]注意生成器传参函数send()的使用。


迭代器

可以直接作用于for循环的数据类型有以下几种:

一类是集合数据类型,如list、tuple、dict、set、str等;

一类是generator,包括生成器和带yield的generator function。

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。

可以使用isinstance()判断一个对象是否是Iterable对象:

from collections import Iterable
print(isinstance([],Iterable))
print(isinstance('abc', Iterable))
print(isinstance(100,Iterable))


True
True
False


而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

可以使用isinstance()判断一个对象是否是Iterator对象:

from collections import Iterator
print(isinstance((x for x in range(10)), Iterator))
print(isinstance([], Iterator))
print(isinstance({}, Iterator))
print(isinstance('abc', Iterator))


True
False
False
False


注意:

可迭代(Iterable)和迭代器(Iterator)是有差别的,

列表等集合数据类型都是可迭代的,所以是可迭代对象,但不是迭代器,因为没有next()方法。

生成器肯定是迭代器,因为生成器可迭代,且有next()方法。

>>> 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']


通过dir([])查看列表内置方法可以看到并没有next()方法,所以只能说列表是可迭代对象,但别不是迭代器。

重点:可迭代对象可以通过iter()方法转成迭代器。

from collections import Iterator
list1 = [1,2,3]
list2=iter(list1)
print(list2.__next__())
print(list2.__next__())
print(isinstance(list1,Iterator))
print(isinstance(list2,Iterator))


1
2
False True


你可能会问,为什么list、dict、str等数据类型不是Iterator?

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

小结:

1.凡是可作用于for循环的对象都是Iterable类型;

2.凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

3.集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

Python的for循环本质上就是通过不断调用next()函数实现的,例如:

for x in [1, 2, 3, 4, 5]:
pass


# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break


事实上,在Python3.X上,ranger()就是一个迭代器。在Python2.X上range()不是迭代器,xrange()才是迭代器,所以有如下区别:

Python2.X

>>> range(5)
[0, 1, 2, 3, 4]
>>> xrange(5)
xrange(5)


读取文件for line in f:利用的就是迭代器的原理取数据,使用next()取数据。readlines()则是一次性读取所有内容到内存。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: