Python進階


裝飾器

裝飾器是Python中的一個重要概念,多用於在不修改原函數的基礎上,為函數增加額外的功能。

基礎裝飾器

例如小李給女朋友買了一款iPhone12作為生日禮物,手機原封未拆封。

def gift():
     print('iPhone12')

gift()   # 運行顯示禮物信息

但還是覺得禮物太單薄,於是又買了一盒德芙巧克力,一支dior的口紅,並找了個精美的禮品盒包裝了一下,盒子里放滿了泡沫球。

def gift():
     print('iPhone12')

def box(gift):
    print('='*5 + '禮物盒' + '='*5)
    print('一盒泡沫球')
    print('好多巧克力')
    print('一支dior口紅')
    return gift

gift = box(gift)   # 將禮物包裝后作為禮物
gift()  # 顯示禮物信息

運行后顯示如下:

=====禮物盒=====
一盒泡沫球
好多巧克力
一支dior口紅
iPhone12

這個box便是一個裝飾器,它的參數是一個函數對象,同數字、字符串、列表、字典等數據類型一樣,函數和類也可以作為函數的參數使用,畢竟在Python里人人平等,一切皆對象。
box在使用時依然返回了原來的gift,只是在拿到這個gift之前增加了兩個額外的驚喜,然后我們把box作為gift使用即可。

裝飾器本質上就是以函數作為參數,對函數做一些處理,並替換原函數的一種高階函數。
上例中,使用裝飾器表示為如下。

def box(gift):   #  以函數為參數的裝飾器
    print('='*5 + '禮物盒' + '='*5)
    print('一盒泡沫球')
    print('好多巧克力')
    print('一支dior口紅')
    return gift

@box        # 掛載裝飾器,會自動替換原函數
def gift():
     print('iPhone12')

gift()   # 這里面得到的gift實際上是裝飾后的gift即`box(gift)`

運行后顯示和上例相同。

處理函數參數

小李突然想到,買哪個顏色應該征詢下女友的意見,也就是原來的gift應支持一個可供選擇的顏色參數。

def gift(color):
    print(f'iPhone12{color}版')

作為一個細心的boyfriend,小李需要根據對應的手機顏色選擇同樣顏色的泡沫球,也就是需要能獲取到,被裝飾的gift函數的參數。
這時候我們需要在盒子內部(box裝飾器),重新准備一個新的禮物,根據顏色參數做不同的處理,然后根據顏色拿到指定的iPhone12禮物。

def box(gift):
    print('='*5 + '禮物盒' + '='*5)

    def new_gift(color):  # 准備一個新的禮物,參數和原gift參數一致
        print(f'一盒{color}泡沫球')  # 根據顏色准備泡沫球
        print('好多巧克力')
        print('一支dior口紅')
        return gift(color)  # 根據顏色拿到指定的iPhone12

    return new_gift   # 返回新禮物,新禮物調用時,增加一些驚喜,並返回原有禮物gift(color)的結果。

@box
def gift(color):
    print(f'iPhone12{color}版')

gift('紅色')   # 實際上這里的gift是被box裝飾后狸貓換太子的new_gift函數,而new_gift('紅色'),返回原gift('紅色')的結果。

在box內部為了根據參數做對應處理,我們新建了一個函數,函數內部也可以定義內部函數,內部函數new_gift可以獲取並使用外部box函數的參數,如gift。
為了能獲取到原有函數gift的參數,我們需要建立一個傀儡函數new_gift,這個函數和原函數gift的參數一致、返回結果一致,即new_gift('紅色')返回的就是gift('紅色')。
然后狸貓換太子,不再返回原來的gift函數對象,而是返回替換的new_gift函數對象。

運行后顯示

=====禮物盒=====
一盒紅色泡沫球
好多巧克力
一支dior口紅
iPhone12紅色版

注意:在裝飾器box里,要返回一個函數對象,如上例中的return gift或本例中的return new_gift。而在傀儡函數new_gift中,為了和原函數gift結果一致,要返回原函數的調用結果即gift(color)。

從普遍意義上講,作為商家,為了裝飾器box可以包裝任何形式的禮物,無論禮物有什么參數都可以滿足,這就要求我們的傀儡函數new_gift支持任意類型的參數即def new_gift(*args, **kwargs)
然后把無論什么參數*args, **kwargs交由原函數gift(*args, **kwargs)處理即可。
修改后,我們便得到一個通用的裝飾器,可以包裝任何禮物。

def box(gift):
    print('='*5 + '禮物盒' + '='*5)

    def new_gift(*args, **kwargs):    # 接受任意數量的參數
        if args and len(args) > 0:   # 由於參數不確定了,我們假設萬一有參數,第一個參數是color參數
            color = args[0]
            print(f'一盒{color}泡沫球')
        else:
            print(f'一盒泡沫球')

        print('好多巧克力')
        print('一支dior口紅')
        result = gift(*args, **kwargs)  # 如果我們需要對原函數的結果做出處理,可以先獲取到結果
        # print(f'原函數結果{result}')    由於原函數gift沒有return,這是其實是None
        return result  # 返回原函數結果

    return new_gift

@box
def gift(color, pro=False):   # 新的禮物函數,兩個參數,默認買12,萬一女友要Pro,也可以
    if pro is True:
        print(f'iPhone12 Pro{color}版')
    else:
        print(f'iPhone12{color}版')

gift('海藍色', pro=True)

這樣,無論被裝飾的函數有幾個參數,box裝飾器都可以正常處理。
運行后顯示如下。

=====禮物盒=====
一盒海藍色泡沫球
好多巧克力
一支dior口紅
iPhone12 Pro海藍色版

帶參裝飾器

信心滿滿的小李覺得,在盒子上還可以做些文章,要根據女友的喜好選擇不同形狀的箱子,因此我們需要根據參數來定制我們的裝飾器box,在盒子外面再加一層定制函數。

def custom_box(shape):   # 根據參數定制裝飾器
    def box(gift):   # 裝飾器函數
        print('='*5 + f'{shape}禮物盒' + '='*5)   # 根據形狀定制
        # ...
    return box   # 返回裝飾器函數

此時我們得到一個可以根據參數進行定制的裝飾器函數custom_box,這個裝飾器接收到參數后,傳遞給真實裝飾器box,並返回定制后box裝飾器函數。
完整代碼如下。

def custom_box(shape):   # 根據參數定制裝飾器 =====================

    def box(gift):   # 實際的裝飾器函數 ---------------------------
        print('='*5 + f'{shape}禮物盒' + '='*5)

        def new_gift(*args, **kwargs):   # 傀儡函數 ..............
            if args and len(args) > 0:
                color = args[0]
                print(f'一盒{color}泡沫球')
            else:
                print(f'一盒泡沫球')
            print('好多巧克力')
            print('一支dior口紅')
            result = gift(*args, **kwargs)
            return result  # 返回原函數結果 ......................

        return new_gift # 返回傀儡函數 ---------------------------

    return box   # 返回定制的裝飾器 ===============================

@custom_box('心形')   # 使用可定制的裝飾器
def gift(color, pro=False):
    if pro is True:
        print(f'iPhone12 Pro{color}版')
    else:
        print(f'iPhone12{color}版')

gift('海藍色', pro=True)

注意:裝飾器在導入模塊時立即計算的,即沒調用gift('海藍色', pro=True)之前就已經執行生成定制后的box。

運行后,結果如下。

=====心形禮物盒=====
一盒海藍色泡沫球
好多巧克力
一支dior口紅
iPhone12 Pro海藍色版

生成器和迭代器

可迭代對象

實現了__iter__方法, __iter__方法返回一個迭代器

迭代器

按標准的迭代協議實現__iter__和__next__方法,StopIteration結束

class A:
    start = 0
    def __iter__(self):
        return self

    def __next__(self):
        if self.start > 10:
            raise StopIteration
        self.start += 1
        return self.start

生成器

內部實現了迭代器的一種函數,通過yield記錄當前位置並返回一次迭代結果

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

列表推導式

推倒式:當我們對一批可迭代的數據(如列表或字典)進行提取或處理,最后要得到一個新的列表或字典時,推導式是一種非常簡潔的表達方式。

比如,有一批數據

data = [
    {'name': '張三', 'gender': 'male',  'age': 12},
    {'name': '李四', 'gender': 'female',  'age': 10},
    {'name': '王五', 'gender': 'male',  'age': 20},
    {'name': '趙六', 'gender': 'male',  'age': 11},
    {'name': '周七', 'gender': 'female',  'age': 16},
    {'name': '孫八', 'gender': 'male',  'age': 13},
]

我們想要把數據中的name都提取出來形成一個新的列表,一般的操作是這樣的。

names = []  # 定義一個空列表

for item in data:  # 遍歷數據
    name = item['name']  # 提取每行中的name
    names.append(name)  # 追加到列表中

如果用推導式的話,形式如下。

names = [item['name'] for item in data]     # 遍歷data,提取每項中的name生成一個新列表

數據處理

在提取數據時,我們還可以對每一項數據進行,處理,假設我們需要每個名稱前加上'姓名: '這個字符串,可以這樣。

names = ['姓名: '+item['name'] for item in data]

'姓名: '+item['name'] 就是每一項的數據

數據篩選

同樣我們還可以對數據進行篩選,比如我們只要年齡大於12歲,后面可以使用if進行過濾

names = [item['name'] for item in data if item['age']>12]

多重循環

推導式還支持多重循環,比如

for x in range(1,5)
    if x > 2
        for y in range(1,4)
            if y < 3
                x*y

使用推導式表示如下

[x*y for x in range(1,5) if x > 2 for y in range(1,4) if y < 3]

批量執行操作

由於推導式就是一種循環操作,我們也可以使用推導式來批量執行一些相似操作,比如:

def step1(driver):
    print('步驟1)

def step2(driver):
    print('步驟2)

def step3(driver):
    print('步驟3)

我們可以將函數名放到一個列表里,然后使用推導式循環執行

steps = [step1, step2, step3]   # 函數名列表

[step(driver) for step in steps]  # 不需要變量接收,我們只需要它循環執行

字典推導式

當我們需要遍歷一批數據最后得到一個字典時,同樣可以使用字典推導式,如:

data = [
    {'name': '張三', 'gender': 'male',  'age': 12},
    {'name': '李四', 'gender': 'female',  'age': 10},
    {'name': '王五', 'gender': 'male',  'age': 20},
    {'name': '趙六', 'gender': 'male',  'age': 11},
    {'name': '周七', 'gender': 'female',  'age': 16},
    {'name': '孫八', 'gender': 'male',  'age': 13},
]

假設我們想得到一個{'張三': 12, '李四': 10, ....}這樣的一個字典,使用字典推導式方式如下:

persons = {item['name']: item['age'] for item in data}

字典推導式同樣支持if篩選等操作。

生成器

生成器實際上是一種包含初始數據和推導法則的對象,比如我們可以輕松的寫出1w以內所有的奇數,原因是因為我只需要記住從1開始每次加2即可。
生成器便是這樣。對應大量的數據或者CSV/Excel文件中的數據,生成器可以大量的節省內存,比如csv.Reader(f)就是一個生成器,只存了當前位置和讀取下一行數據的方法。
當你需要遍歷時,它再每次給你讀取一行數據給你。
如列表推導式的例子,

data = [
    {'name': '張三', 'gender': 'male',  'age': 12},
    {'name': '李四', 'gender': 'female',  'age': 10},
    {'name': '王五', 'gender': 'male',  'age': 20},
    {'name': '趙六', 'gender': 'male',  'age': 11},
    {'name': '周七', 'gender': 'female',  'age': 16},
    {'name': '孫八', 'gender': 'male',  'age': 13},
]
names = [item['name'] for item in data]

我們把列表的中括號改為小括號就得到一個生成器

names2 = (item['name'] for item in data)

注意:生成器和推導式不同,其中的循環不是立即執行的,只用你遍歷這個生成器時才會執行

for name in names:  # 遍歷列表推導式生成的新列表
    print(name)

for name in names2:  # 遍歷一個生成器
    print(name)

兩個打印結果是一樣的,生成器更節省內存,只有遍歷時才運行。

魔術方法

魔術方法 描述
__new__ 創建類並返回這個類的實例
__init__ 可理解為“構造函數”,在對象初始化的時候調用,使用傳入的參數初始化該實例
__del__ 可理解為“析構函數”,當一個對象進行垃圾回收時調用
__metaclass__ 定義當前類的元類
__class__ 查看對象所屬的類
__base__ 獲取當前類的父類
__bases__ 獲取當前類的所有父類
__str__ 定義當前類的實例的文本顯示內容
__getattribute__ 定義屬性被訪問時的行為
__getattr__ 定義試圖訪問一個不存在的屬性時的行為
__setattr__ 定義對屬性進行賦值和修改操作時的行為
__delattr__ 定義刪除屬性時的行為
__copy__ 定義對類的實例調用 copy.copy() 獲得對象的一個淺拷貝時所產生的行為
__deepcopy__ 定義對類的實例調用 copy.deepcopy() 獲得對象的一個深拷貝時所產生的行為
__eq__ 定義相等符號“==”的行為
__ne__ 定義不等符號“!=”的行為
__lt__ 定義小於符號“<”的行為
__gt__ 定義大於符號“>”的行為
__le__ 定義小於等於符號“<=”的行為
__ge__ 定義大於等於符號“>=”的行為
__add__ 實現操作符“+”表示的加法
__sub__ 實現操作符“-”表示的減法
__mul__ 實現操作符“*”表示的乘法
__div__ 實現操作符“/”表示的除法
__mod__ 實現操作符“%”表示的取模(求余數)
__pow__ 實現操作符“**”表示的指數操作
__and__ 實現按位與操作
__or__ 實現按位或操作
__xor__ 實現按位異或操作
__len__ 用於自定義容器類型,表示容器的長度
__getitem__ 用於自定義容器類型,定義當某一項被訪問時,使用 self[key] 所產生的行為
__setitem__ 用於自定義容器類型,定義執行 self[key]=value 時產生的行為
__delitem__ 用於自定義容器類型,定義一個項目被刪除時的行為
__iter__ 用於自定義容器類型,一個容器迭代器
__reversed__ 用於自定義容器類型,定義當 reversed( ) 被調用時的行為
__contains__ 用於自定義容器類型,定義調用 in 和 not in 來測試成員是否存在的時候所產生的行為
__missing__ 用於自定義容器類型,定義在容器中找不到 key 時觸發的行為


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM