首頁  >  文章  >  後端開發  >  Python中關於裝飾器與迭代器以及生成器的實例詳解

Python中關於裝飾器與迭代器以及生成器的實例詳解

黄舟
黄舟原創
2017-07-26 15:45:02880瀏覽

下面小編就為大家帶來一篇老生常談Python之裝飾器、迭代器和生成器。小編覺得蠻不錯的,現在就分享給大家,也給大家做個參考。一起跟著小編過來看看吧

在學習python的時候,三大「名器」對沒有其他語言程式設計經驗的人來說,應該算是一個小難點,本次部落格就部落客自己對裝飾器、迭代器和生成器理解進行解釋。

為什麼要使用裝飾器

什麼是裝飾器? 「裝飾」從字面意思來誰就是對特定的建築物內按照一定的思路和風格進行美化的一種行為,所謂「器」就是工具,對於python來說裝飾器就是能夠在不修改原始的代碼情況下為其添加新的功能,例如一款軟體上線之後,我們需要在不修改原始程式碼和不修改被調用的方式的情況下還能為期添加新的功能,在python種就可以用裝飾器來實現,同樣在寫程式碼的時候也要考慮到後面的可擴充性,下面我們來看一步一步的看一下python的裝飾器。

一個簡單範例引入無參裝飾者

#先來看簡單的幾行程式碼,程式碼的運作結果是先睡2秒,再列印"hello boy!":


import time
def foo():
 """打印"""
 time.sleep(2)
 print("Hello boy!")
foo()

我們現在我們需要為其添加一個程式計時功能,但是不能修改原始的程式碼:


#
import time
def timmer(func):
 def wrapper():
  """计时功能"""
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
def foo():
 """打印"""
 time.sleep(2)
 print("Hello boy!")
foo=timmer(foo)
foo()
#运行结果
Hello boy!
Run time is 2.000446

看!我們沒有修改原來的程式碼就實現了這個功能,因為函數也是對象,所以能夠將函數foo當作參數傳遞給了函數timmer。

在python中,有個更簡潔的方式來取代foo=timmer(foo),使用@timmer這種方式,這個在python中被稱為語法糖。


import time
def timmer(func):
 def wrapper():
  """计时功能"""
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
@timmer  #等于 foo=timmer(foo)
def foo():
 """打印"""
 time.sleep(2)
 print("Hello boy!")
foo()

下面我們來一步一步的分析函數的執行過程:

1.導入time模組


# #

import time

2.定義函數timmer,定義函數並不會執行函數內的程式碼


#

def timmer(func):

3.呼叫裝飾器,相當於foo=timer(foo ),就是把函數foo當參數穿給了函數timmer


@timmer

4.運行函數timmer,接受了參數func=foo


#

def timmer(func):

5.在函數timmer內,定義了函數wrapper,wrapper函數內部程式碼也不執行,然後將函數wrapper當作回傳值回傳

##

return wrapper

6.將回傳值賦值給了foo,在第3步中,foo=timmer(foo),還記吧

@timmer #等于 foo=timmer(foo)

7.運行函數foo() ,但是這裡的函數已經不是原來的那個函數了,可以打印foo,對的,因為之前我們將wrapper作為返回值傳給了foo,所以在這裡執行foo就是在執行wrapper了,為了再確定這一點你也可列印wrapper,它們的記憶體位址相同,所以都是指向同一個位址空間:

<function timmer.<locals>.wrapper at 0x00000180E0A8A950> #打印foo的结果
<function timmer.<locals>.wrapper at 0x000001F10AD8A950> #打印wrapper的结果
foo()

8.執行函數wrapper,記錄開始時間,執行函數func,在步驟4的時候,func被foo賦值,運行func就是在運行原函數foo,睡2秒,印出字串;

time_start=time.time()
 time.sleep(2)
 print("Hello boy!")

9.記錄結束時間,列印運行時間,程序結束。

Hello boy!
Run time is 2.000161

有參考裝飾器在前面的例子中,原始函數沒有參數,下面的來看一個當原函數有參數,該怎麼修改裝飾函數呢?

import time
def timmer(func):
 def wrapper(*args,**kwargs):
  """计时功能"""
  start_time=time.time()
  res=func(*args,**kwargs)
  end_time=time.time()
  print("Run time is %f"%(end_time-start_time))
  return res
 return wrapper
@timmer 
def my_max(x,y):
 """返回两个值的最大值"""
 res=x if x > y else y
 time.sleep(2)
 return res
res=my_max(1,2)
print(res)
#运行结果
Run time is 2.000175

當原函數有需要傳入參數的時候,在這個例子my_max有兩個位置形成需要傳入參數,只需要在wrapper上增加兩個形參,本例子使用了可變參數(*args,**kwargs)也是可以的,這是@timmer就等於my_max(1,2)=timmer(my_max)

下面我們來看一個

有參數的裝飾器:

def auth(filetype):
 def auth2(func):
  def wrapper(*args,**kwargs):
   if filetype == "file":
    username=input("Please input your username:")
    passwd=input("Please input your password:")
    if passwd == &#39;123456&#39; and username == &#39;Frank&#39;:
     print("Login successful")
     func()
    else:
     print("login error!")
   if filetype == &#39;SQL&#39;:
    print("No SQL")
  return wrapper
 return auth2
@auth(filetype=&#39;file&#39;) #先先返回一个auth2 ==》@auth2 ==》 index=auth2(index) ==》 index=wrapper
def index():
 print("Welcome to China")
index()

如果裝飾本身有參數,就需要多一層內嵌函數,下面我們一步一步分析執行流程:

1.定義函數auth

#

def auth(filetype):

2.呼叫解釋器,首先要執行函數auth(filetype= 'file')

@auth(filetype=&#39;file&#39;)

3.運行函數auth,定義了一個函數auth2,並作為返回值返回,那麼這個@auth(filetype='file')就等同於@auth2,等同於index=auth2(index)


def auth(filetype):
 def auth2(func):
  def wrapper(*args,**kwargs):
  return wrapper
 return auth2

4.auth2(index)執行,func=index,定義函數wrapper,並傳回之,這時候index其實就是等於wrapper了

def wrapper(*args,**kwargs):
return wrapper

5.當運行index,即運行wrapper,運行函數內部代碼,filetype=="file",提示用戶輸出使用者名稱和密碼,判斷輸入是否正確,如果正確,則執行函數func(),等於執行原來的index,列印

if filetype == "file":
    username=input("Please input your username:")
    passwd=input("Please input your password:")
    if passwd == &#39;123456&#39; and username == &#39;Frank&#39;:
     print("Login successful")
     func()

6.執行結果測試

Please input your username:Frank
Please input your password:123456
Login successful
Welcome to China

装饰器也是可以被叠加的:


import time
#
def timmer(func):
 def wrapper():
  """计时功能"""
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
  # print("---",wrapper)
 return wrapper
def auth(filetype):
 def auth2(func):
  def wrapper(*args,**kwargs):
   if filetype == "file":
    username=input("Please input your username:")
    passwd=input("Please input your password:")
    if passwd == &#39;123456&#39; and username == &#39;Frank&#39;:
     print("Login successful")
     func()
    else:
     print("login error!")
   if filetype == 'SQL':
    print("No SQL")
  return wrapper
 return auth2
@timmer
@auth(filetype=&#39;file&#39;) #先先返回一个auth2 ==》@auth2 ==》 index=auth2() ==》 index=wrapper
def index():
 print("Welcome to China")
index()

#测试结果
Please input your username:Frank
Please input your password:123456
Login successful
Welcome to China
Run time is 7.966267

注释优化


import time
def timmer(func):
 def wrapper():
  """计算程序运行时间"""
  start_time=time.time()
  func()
  end_time=time.time()
  print("Run time is %s:"%(end_time-start_time))
 return wrapper
@timmer
def my_index():
 """打印欢迎"""
 time.sleep(1)
 print("Welcome to China!")
my_index()
print(my_index.__doc__)

#运行结果
Welcome to China!
Run time is 1.0005640983581543:
计算程序运行时间

当我们使用了装饰器的时候,虽然没有修改代码本身,但是在运行的时候,比如上面这个例子,运行my_index其实在运行wrapper了,如果我们打印my_index的注释信息,会打印wrapper()的注释信息,那么该怎么优化?

可以在模块functools中导入wraps,具体见以下:


import time
from functools import wraps
def timmer(func):
 @wraps(func)
 def wrapper():
  """计算程序运行时间"""
  start_time=time.time()
  func()
  end_time=time.time()
  print("Run time is %s:"%(end_time-start_time))
 return wrapper
@timmer
def my_index():
 """打印欢迎"""
 time.sleep(1)
 print("Welcome to China!")
my_index()
print(my_index.__doc__)
#运行结果
Welcome to China!
Run time is 1.0003223419189453:
打印欢迎

这样,在表面看来,原函数没有发生任何变化。

为什么要用迭代器

从字面意思,迭代就是重复反馈过程的活动,其目的通常是为了比较所需目标或结果,在python中可以用迭代器来实现,先来描述一下迭代器的优缺点,如果看不懂可以先略过,等看完本博客再回头看,相信你会理解其中的意思:

优点:

迭代器在取值的时候是不依赖于索引的,这样就可以遍历那些没有索引的对象,比如字典和文件

迭代器与列表相比,迭代器是惰性计算,更节省内存

缺点:

无法获取迭代器的长度,没有列表灵活

只能往后取值,不能倒着取值

什么是迭代器

那么在python什么才算是迭代器呢?

只要对象有__iter__(),那么它就是可迭代的,迭代器可以使用函数next()来取值

下面我们来看一个简单的迭代器:


my_list=[1,2,3]
li=iter(my_list)  #li=my_list.__iter__()
print(li)
print(next(li))
print(next(li))
print(next(li))
#运行结果
<list_iterator object at 0x000002591652C470>
2

可以看到,使用内置函数iter可以将列表转换成一个列表迭代器,使用next()获取值,一次值取一个值,当值取完了,再使用一次next()的时候,会报异常StopIteration,可以通过异常处理的方式来避免,try-except-else就是一个最常用的异常处理结构:


my_list=[1,2,3]
li=iter(my_list)
while True:
 try:
  print(next(li))
 except StopIteration:
  print("Over")
  break
 else:
  print("get!")
#运行结果
get!
get!
get!
Over

查看可迭代对象和迭代器对象

使用Iterable模块可以判断对象是否是可迭代的:


from collections import Iterable
s="hello" #定义字符串
l=[1,2,3,4] #定义列表
t=(1,2,3) #定义元组
d={&#39;a&#39;:1} #定义字典
set1={1,2,3,4} #定义集合
f=open("a.txt") #定义文本
# 查看是否都是可迭代的
print(isinstance(s,Iterable))
print(isinstance(l,Iterable))
print(isinstance(t,Iterable))
print(isinstance(d,Iterable))
print(isinstance(set1,Iterable))
print(isinstance(f,Iterable))
#运行结果
True
True
True
True
True
True

通过判断,可以确定我们所知道的常用的数据类型都是可以被迭代的。

使用Iterator模块可以判断对象是否是迭代器:


from collections import Iterable,Iterator
s="hello"
l=[1,2,3,4]
t=(1,2,3)
d={&#39;a&#39;:1}
set1={1,2,3,4}
f=open("a.txt")
# 查看是否都是可迭代的
print(isinstance(s,Iterator))
print(isinstance(l,Iterator))
print(isinstance(t,Iterator))
print(isinstance(d,Iterator))
print(isinstance(set1,Iterator))
print(isinstance(f,Iterator))
#运行结果
False
False
False
False
False
True

可知只有文件是迭代器,所以可以直接使用next(),而不需要转换成迭代器。

什么是生成器

生产器就是一个是带有yield的函数

下面来看一个简单的生成器


def my_yield():
 print(&#39;first&#39;)
 yield 1
g=my_yield()
print(g)
#运行结果
<generator object my_yield at 0x0000024366D7E258>

生成器也是一个迭代器


from collections import Iterator
def my_yield():
 print(&#39;first&#39;)
 yield 1
g=my_yield()
print(isinstance(g,Iterator))
#运行结果
True

那就可以用next()来取值了


print(next(g))
#运行结果
first
1

生成器的执行过程

我们来看以下下面这个例子,了解生产的执行流程


def my_yield():
 print(&#39;first&#39;)
 yield 1
 print(&#39;second&#39;)
 yield 2
 print(&#39;Third&#39;)
 yield 3
g=my_yield()
next(g)
next(g)
next(g)
#运行结果
first
second
Third

1.定义生成器my_yield,并将其赋值给了g


def my_yield():
g=my_yield()

2.开始第一次执行next(),开始执行生产器函数 ,打印第一语句,遇到yileld的时候暂停,并返回一个1,如果你想打印返回值的话,这里会显示1


 print(&#39;first&#39;)
 yield 1

3.再执行2次,打印字符串(每执行一次都会暂停一下)


 print(&#39;second&#39;)
 yield 2
 print(&#39;Third&#39;)
 yield 3

4.如果再加一次next()就会报出StopIteration异常了

生成器在每次暂停的时候,函数的状态将被保存下来,来看下面的例子:


def foo():
 i=0
 while True:
  yield i
  i+=1
g=foo()
for num in g:
 if num < 10:
  print(num)
 else:
  break
#运行结果

for循环中隐含next(),每next一次,暂停一次,if语句判断一次,然后执行下一次next,可以看到我们的while循环并没有无限循环下去,而是状态被保存下来了。

协程函数

我们来看下面这个生成器和执行结果


def eater(name):
 print(&#39;%s start to eat food&#39;%name)
 while True:
  food=yield
  print(&#39;%s get %s ,to start eat&#39;%(name,food))
 print(&#39;done&#39;)
e=eater(&#39;Frank&#39;)
next(e)
e.send(&#39;egg&#39;) #给yield送一个值,并继续执行代码
e.send(&#39;tomato&#39;)
#运行结果
Frank start to eat food
Frank get egg ,to start eat
Frank get tomato ,to start eat

send可直接以向yield传值,含有yield表达式的函数我们也称为协程函数,

这运行程序的时候,不可以直接send,必须先使用next()初始化生成器。

如果存在多个这样的函数,那么我们每次执行的时候都要去next()一下,为了防止忘记这一步操作,可以使用装饰器初始化:


def init(func):
 def wrapper(*args):
  res = func(*args)
  next(res)  # 在这里执行next
  return res
 return wrapper
@init
def eater(name):
 print(&#39;%s start to eat food&#39;%name)
 while True:
  food=yield
  print(&#39;%s get %s ,to start eat&#39;%(name,food))
 print(&#39;done&#39;)
e=eater(&#39;Frank&#39;)
e.send(&#39;egg&#39;) 
e.send(&#39;tomato&#39;)

所以在程序中有更多的生成器需要初始化的时候,直接调用这个装饰器就可以了。

以上是Python中關於裝飾器與迭代器以及生成器的實例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn