1. 程式人生 > >Python基礎之(裝飾器,迭代器、生成器)

Python基礎之(裝飾器,迭代器、生成器)

一、裝飾器

1.1、什麼是裝飾器?

裝飾器本質上就是一個python函式,他可以讓其他函式在不需要做任何程式碼變動的前提下,增加額外的功能,裝飾器的返回值也是一個函式物件。

1.2、裝飾器的原則

  •  不修改被裝飾物件的原始碼
  • 不修改被裝飾物件的呼叫方式

1.3、裝飾器的目標

在遵守裝飾器原則的前提下,為被裝飾物件新增上新功能

1.4、裝飾器應用場景:插入日誌,效能測試,事務處理,快取等等

1.5、裝飾器的形成過程

 現在有一個需求,需要在不改變原函式程式碼的情況下去計算這個函式的執行時間

import time
def fun():
    time.sleep(1)
    print("hello world")
def timer(f):
    def inner():
        start = time.time()
        f()
        end = time.time()
        print("run time is %s"%(end-start))
    return inner
fun = timer(fun)
fun()

執行結果:

如果有多個函式想讓你測試他們的執行時間,每次是不是都得是:函式名=timer(函式名),這樣還是有點麻煩,所以更簡單的方法就是Python提供的語法糖

import time
def timer(f):
    def inner():
        start = time.time()
        f()
        end = time.time()
        print("run time is %s"%(end-start))
    return inner
@timer  #語法糖
def fun():
    time.sleep(1)
    print("hello world")

fun()

 剛剛我們討論的裝飾器都是裝飾不帶引數的函式,現在要裝飾一個帶引數的函式怎麼辦呢?

import time
def timer(f):
    def inner(*args,**kwargs):
        start = time.time()
        f(*args,**kwargs)
        end = time.time()
        print("run time is %s"%(end-start))
    return inner
@timer  #語法糖
def fun(a):
    print(a)
    time.sleep(1)
    print("hello world")
@timer
def fun1(a,b):
    print(a,b)

fun(1)
fun1(23,21)

 執行結果:

 上面的裝飾器已經非常完美了,但是有我們正常情況下檢視函式資訊的方法在此處都會失效:

def fun():
    '''in the fun'''
    print('from fun')

print(fun.__doc__)    #檢視函式註釋的方法
print(fun.__name__)   #檢視函式名的方法

解決方法:

from functools import wraps

def demo(func):
    @wraps(func) #加在最內層函式正上方
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper

@demo
def fun():
    '''in the fun'''
    print('from fun')

print(fun.__doc__)
print(fun.__name__)

1.6、帶引數的裝飾器

假如你有500個函式使用了一個裝飾器,現在你需要把這些裝飾器都取消掉,你要怎麼做?過兩天你又需要加上...

import time
flag=False  #通過flag的值來判斷是否進行裝飾
def fun(flag):
    def demo(func):
        def inner(*args,**kwargs):
            if flag:
                start=time.time()
                func(*args,**kwargs)
                end=time.time()
                print("run time is %s"%(end-start))
            else:
                func(*args,**kwargs)
        return inner
    return demo
@fun(flag)
def fun1():
    time.sleep(0.1)
    print("welocme you")

fun1()

登入認證例子:

user = "crazyjump"
passwd = "123"
def auth(auth_type):
    def func(f):
        def wrapper(*args, **kwargs):
            if auth_type == "local":
                username = input("Username:").strip()
                password = input("Password:").strip()
                if user == username and passwd == password:
                    print("\033[32;1mlogin successful\033[0m")
                    f()
                else:
                    exit("\033[31;1mInvalid username or password\033[0m")
            elif auth_type == "ldap":
                f()
                print("不會")
        return wrapper
    return func
@auth(auth_type="local")
def crazyjump():
    print("welcome to crazyjump")
crazyjump()

 1.7、多個裝飾器裝飾一個函式

fun1和fun2的載入順序:自下而上

inner函式的執行順序為:自上而下

def fun1(func):
    def inner():
        print('fun1 ,before')
        func()
        print('fun1 ,after')
    return inner

def fun2(func):
    def inner():
        print('fun2 ,before')
        func()
        print('fun2 ,after')
    return inner

@fun2
@fun1
def f():
    print('in the f')
f()

執行結果:

1.8、開放封閉原則

  • 對擴充套件是開放的

    為什麼要對擴充套件開放呢?

    我們說,任何一個程式,不可能在設計之初就已經想好了所有的功能並且未來不做任何更新和修改。所以我們必須允許程式碼擴充套件、新增新功能。

  • 對修改是封閉的

    為什麼要對修改封閉呢?

    就像我們剛剛提到的,因為我們寫的一個函式,很有可能已經交付給其他人使用了,如果這個時候我們對其進行了修改,很有可能影響其他已經在使用該函式的使用者。

裝飾器完美的遵循了這個開放封閉原則。

二、迭代器

2.1、可迭代物件

可迭代物件指的是內建有__iter__方法的物件,即obj.__iter__,如:

from collections import Iterable
li = [1, 2, 3, 4]
tu = (1, 2, 3, 4)
di = {1: 2, 3: 4}
se = {1, 2, 3, 4}
print(isinstance(se, Iterable))  #判斷是否是可迭代物件
print(isinstance(di, Iterable))
print(isinstance(tu, Iterable))
print(isinstance(li, Iterable))

執行結果:

字串、列表、元組、字典、集合都可以被for迴圈,說明他們都是可迭代的,我們現在所知道:可以被for迴圈的都是可迭代的,要想可迭代,內部必須有一個__iter__方法

2.2、迭代器

迭代器是訪問集合元素的一種方式。迭代器物件從集合的第一個元素開始訪問,直到所有的元素被訪問完結束。迭代器只能往前不會後退。迭代器必須有__iter__方法和__next__方法

from collections import Iterator
list = [1,2,3,4]
list_iter = list.__iter__()  # 將可迭代的轉化成迭代器
print(isinstance(list_iter,Iterator))  #判斷是否是迭代器,結果為True

看個例子:

from collections import Iterable
from collections import Iterator
class a(): 
    def __next__(self):pass
    def __iter__(self):pass   #為a定義兩個方法
dd=a() #例項化
print(isinstance(dd, Iterator)) 
print(isinstance(dd, Iterable))
#結果都為True,如果去掉__next__方法即第一個結果為False
#迭代器物件一定是可迭代物件,而可迭代物件不一定是迭代器物件

2.3、for迴圈

for迴圈,能遍歷一個可迭代物件,他的內部到底進行了什麼?

  • 將可迭代物件轉化成迭代器。(可迭代物件.__iter__())
  • 內部使用__next__方法,一個一個取值。
  • 加了異常處理功能,取值到底後自動停止。

用while迴圈模擬for迴圈

l=[1,2,3,4]
ss=l.__iter__()
while 1:
    try:
        print(ss.__next__())
    except Exception as e:
        break

執行結果:

對於序列型別:字串、列表、元組,我們可以使用索引的方式迭代取出其包含的元素。但對於字典、集合、檔案等型別是沒有索引的,怎樣取出其內部包含的元素?而for迴圈就是基於迭代器協議提供了一個統一的可以遍歷所有物件的方法,即在遍歷之前,先呼叫物件的__iter__方法將其轉換成一個迭代器,然後使用迭代器協議去實現迴圈訪問,這樣所有的物件就都可以通過for迴圈來遍歷了,而且你看到的效果也確實如此,這就是無所不能的for迴圈,最重要的一點,轉化成迭代器,在迴圈時,同一時刻在記憶體中只出現一條資料,極大限度的節省了記憶體。

#基於for迴圈,我們可以完全不再依賴索引去取值了
dic={'a':1,'b':2,'c':3}
for k in dic:
    print(dic[k])

2.4、迭代器優缺點:

優點:

  • 提供一種統一的、不依賴於索引的迭代方式
  • 惰性計算,節省記憶體

缺點:

  • 無法獲取長度(只有在next完畢才知道到底有幾個值)
  • 一次性的,只能往後走,不能往前退

三、生成器

本質:迭代器(所以自帶了__iter__方法和__next__方法,不需要我們去實現)

特點:惰性運算節省記憶體,開發者自定

Python中提供的生成器:

  • 生成器函式:常規函式定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函式的狀態,以便下次重它離開的地方繼續執行
  • 生成器表示式:類似於列表推導,但是,生成器返回按需產生結果的一個物件,而不是一次構建一個結果列表

3.1、生成器函式

包含yield關鍵字的函式就是一個生成器函式。yield可以為我們從函式中返回值,但是yield又不同於return,return的執行意味著程式的結束,呼叫生成器函式不會得到返回的具體的值,而是得到一個可迭代的物件。每一次獲取這個可迭代物件的值,就能推動函式的執行,獲取新的返回值。直到函式執行結束。

def fun():
    a=123
    yield a
    b=456
    yield b

ret=fun()
print("ret:",ret)  #ret為生成器
print("1:",next(ret)) #返回第一個yield結果
print("2:",next(ret))  #返回第二個yield結果  next()等同__next__()

執行結果:

生成器有什麼好處呢?就是不會一下子在記憶體中生成太多資料。

例如:假設你需要去工廠訂購10000000臺蘋果手機,工廠應該是先答應下來然後再去生產,你可以一臺一臺的取,也可以根據需要一批一批的找工廠拿。而不能是一說要生產10000000臺蘋果手機,工廠就立刻生產出10000000臺蘋果手機,這樣工廠的工人和生產線肯定都是要爆掉的.....

def produce():
    """生產手機"""
    for i in range(1,10000000):
        yield "生產了第%s臺手機"%i

ret = produce()
print(ret.__next__()) #要第一臺手機
print(ret.__next__()) #第二臺手機
print(ret.__next__()) #再要一臺手機
count = 0
for i in ret:         #要一批手機,比如6臺
    print(i)
    count +=1
    if count == 6:
        break

執行結果:

send傳值

import time
def consumer(name):
    print("%s 準備生產手機了!" %name)
    while True:
       iPhone = yield

       print("[%s]被[%s]生產出來了!" %(iPhone,name))

def producer():
    c = consumer('A')
    c2 = consumer('B')
    c.__next__()
    c2.__next__()
    print("開始生產手機了!")
    for i in range(10):
        time.sleep(1)
        c.send(i)
        c2.send(i)   #傳值給yield

producer()



#send 獲取下一個值的效果和next基本一致
#只是在獲取下一個值的時候,給上一yield的位置傳遞一個數據
#使用send的注意事項
# 第一次使用生成器的時候 是用next獲取下一個值
# 最後一個yield不能接受外部的值

 執行結果:

 

 3.2、生成器表示式

3.2.1、三元表示式

name="jump"
res='SB' if name == 'crazyjump' else 'xxx'
print(res)

#結果xxx

3.2.2、列表推導式

j=[i for i in range(10) if i >5]   #沒有else
print(j)

執行結果:

3.2.3、生成器表示式

j=(i for i in range(10) if i >5)
print(next(j))
print(j)

執行結果:

  • 把列表推導式的[]換成()得到的就是生成器表示式
  • 列表推導式與生成器表示式都是一種便利的程式設計方式,只不過生成器表示式更節省記憶體