Python之裝飾器、迭代器和生成器

在學習python的時候,三大「名器」對沒有其餘語言編程經驗的人來講,應該算是一個小難點,本次博客就博主本身對裝飾器、迭代器和生成器理解進行解釋。python

裝飾器

什麼是裝飾器?「裝飾」從字面意思來誰就是對特定的建築物內按照必定的思路和風格進行美化的一種行爲,所謂「器」就是工具,對於python來講裝飾器就是可以在不修改原始的代碼狀況下給其添加新的功能,好比一款軟件上線以後,咱們須要在不修改源代碼和不修改被調用的方式的狀況下還能爲期添加新的功能,在python種就能夠用裝飾器來實現,一樣在寫代碼的時候也要考慮到後面的可擴展性,下面咱們來看一步一步的看一下python的裝飾器。編程

無參裝飾器

先來看簡單的幾行代碼,代碼的運行結果是先睡2秒,再打印"hello boy!":app

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=foocode

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

有參裝飾器

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

# -*- coding: UTF-8 -*-
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
2
當原函數有須要傳入參數的時候,在這個例子my_max有兩個位置造成須要傳入參數,只須要在wrapper上添加兩個形參,本例子中使用了可變參數(args,*kwargs)也是能夠的,這是@timmer就等於my_max(1,2)=timmer(my_max)(1,2)
下面咱們來看一個帶有參數的裝飾器:

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 == '123456' and username == 'Frank':
                     print("Login successful")
                     func()
                else:
                     print("login error!")
               if filetype == 'SQL':
                print("No SQL")
          return wrapper
     return auth2

@auth('file)
def index():
     print("Welcome to China")

index()

若是裝飾器自己有參數,就須要多一層內嵌函數,下面咱們一步一步分析執行流程:
1.定義函數auth

def auth(filetype):

2.調用解釋器,首先要運行函數auth(filetype='file')

@auth(filetype='file')

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 == '123456' and username == 'Frank':
     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 == '123456' and username == 'Frank':
                    print("Login successful")
                    func()
                else:
                    print("login error!")
            if filetype == 'SQL':
                print("No SQL")
        return wrapper
    return auth2


@timmer
# 先先返回一個auth2 ==》@auth2 ==》 index=auth2() ==》 index=wrapper
@auth(filetype='file')
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()

運行結果
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__)

至關於執行

無參數
timmer(now)(1)
# 有參數
timmer("n")(now)(1)

運行結果
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={'a':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={'a':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('first')
    yield 1


g = my_yield()
print(g)

運行結果
<generator object my_yield at 0x0000024366D7E258>
生成器也是一個迭代器

from collections import Iterator


def my_yield():
    print('first')
    yield 1


g = my_yield()
print(isinstance(g, Iterator))

運行結果
True
那就能夠用next()來取值了
print(next(g))
運行結果
first
1
生成器的執行過程
咱們來看如下下面這個例子,瞭解生產的執行流程

def my_yield():
 print('first')
 yield 1
 print('second')
 yield 2
 print('Third')
 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('first')
yield 1

3.再執行2次,打印字符串(每執行一次都會暫停一下)

print('second')
yield 2
print('Third')
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('%s start to eat food'%name)
 while True:
  food=yield
  print('%s get %s ,to start eat'%(name,food))
 print('done')
e=eater('Frank')
next(e)#或者e.send(None)
e.send('egg') #給yield送一個值,並繼續執行代碼
e.send('tomato')

運行結果
Frank start to eat food
Frank get egg ,to start eat
Frank get tomato ,to start eat
send可直接以向yield傳值,含有yield表達式的函數咱們也稱爲協程函數,
這運行程序的時候,不能夠直接send,必須先使用next()初始化生成器(e.send(None)亦能夠初始化)。
若是存在多個這樣的函數,那麼咱們每次執行的時候都要去next()一下,爲了防止忘記這一步操做,能夠使用裝飾器初始化:

def init(func):
 def wrapper(*args):
  res = func(*args)
  next(res)  # 在這裏執行next
  return res
 return wrapper
@init
def eater(name):
 print('%s start to eat food'%name)
 while True:
  food=yield
  print('%s get %s ,to start eat'%(name,food))
 print('done')
e=eater('Frank')
e.send('egg') 
e.send('tomato')

因此在程序中有更多的生成器須要初始化的時候,直接調用這個裝飾器就能夠了。

相關文章
相關標籤/搜索