目錄
#一、閉包
二、裝飾器
三、使用多個裝飾器
四、被裝飾的函數帶有參數
五、带参数的装饰器
六、使用类作为装饰器
七、内置装饰器
7.1 @classmethod
7.2 @staticmethod
7.3 @property
首頁 後端開發 Python教學 歸納總結Python中的裝飾器知識點

歸納總結Python中的裝飾器知識點

Jun 17, 2022 pm 01:50 PM
python

本篇文章為大家帶來了關於python的相關知識,其中主要介紹了關於裝飾器的相關問題,包括了閉包、裝飾器、使用多個裝飾器、帶參數的裝飾器等等內容,下面一起來看一下,希望對大家有幫助。

歸納總結Python中的裝飾器知識點

推薦學習:python影片教學

#一、閉包

要了解什麼是裝飾器(decorator),我們首先需要知道閉包(closure)的概念。

閉包,又稱閉包函數或閉合函數,通俗一點來講,當某個函數被當成物件返回時還夾帶了外部變量,就形成了一個閉包。

以列印Hello World為例,我們先來看看巢狀函數的結構應該是什麼樣的:

def print_msg(msg):

    def printer():
        print(msg)

    printer()print_msg('Hello World')# Hello World
登入後複製

執行print_msg('Hello World')相當於執行了printer(),也就是執行print(msg),所以會輸出Hello World

我們再來看一下如果是閉包,該是什麼樣的結構:

def print_msg(msg):

    def printer():
        print(msg)

    return printer


my_msg = print_msg('Hello World')my_msg()# Hello World
登入後複製

本例中的 printer 函數就是閉包。

執行print_msg('Hello World') 實際上是傳回如下這樣一個函數,它夾帶了外部變數'Hello World'

def printer():
    print('Hello World')
登入後複製

於是呼叫my_msg 就相當於執行printer()


那麼要如何判斷函數是否是閉包函數呢?閉包函數的 __closure__ 屬性裡面定義了一個元組用於存放所有的cell對象,每個cell對象保存了這個閉包中所有的外部變數。而普通函數的 __closure__ 屬性為 None

def outer(content):

    def inner():
        print(content)

    return innerprint(outer.__closure__)
    # Noneinner = outer('Hello World')print(inner.__closure__)
    # (<cell at 0x0000023FB1FD0B80: str object at 0x0000023FB1DC84F0>,)
登入後複製

由此可見 outer 函數不是閉包,而 inner 函數是閉包。

我們還可以查看閉包所攜帶的外部變數:

print(inner.__closure__[0].cell_contents)# Hello World
登入後複製

說了那麼多,那麼閉包究竟有什麼用呢?閉包存在的意義就是它夾帶了外部變數(私貨),如果它不夾帶私貨,那麼就和普通的函數沒有任何區別。

閉包的優點如下:

  • 局部變數無法共享且長久的保存,而全域變數可能造成變數污染,閉包既可以長久的保存變數又不會造成全域污染。
  • 閉包使得函數內局部變數的值始終保持在記憶體中,不會在外部函數呼叫後自動清除。

二、裝飾器

我們先考慮這樣一個場景,假設先前寫的一個函數已經實現了4個功能,為簡單起見,我們用print 語句來代表每一個具體的功能:

def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')
登入後複製

現在,由於某種原因,你需要為module 這個函數新增一個功能5,你完全可以這樣修改:

def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')
    print('功能5')
登入後複製

但在現實業務中,直接做出這樣的修改往往是比較危險的(會變得不容易維護)。那麼如何在不修改原函數的基礎上去為它新添一個功能呢?

你可能已經想到了使用之前的閉包知識:

def func_5(original_module):

    def wrapper():
        original_module()
        print('功能5')

    return wrapper
登入後複製

#func_5 代表該函數主要用於實作功能5 ,我們接下來將module 傳入進去觀察效果:

new_module = func_5(module)new_module()# 功能1# 功能2# 功能3# 功能4# 功能5
登入後複製

可以看出,我們的新模組:new_module 已經實作了功能5

在上面的範例中,函數 func_5 就是一個裝飾器,它裝飾了原來的模組(為它新添了一個功能)。

#當然,Python有更簡潔的寫法(稱之為語法糖),我們可以將@符號與裝飾器函數的名稱一起使用,並將其放置在要裝飾的函數的定義上方:

def func_5(original_module):

    def wrapper():
        original_module()
        print('功能5')

    return wrapper@func_5def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5
登入後複製

基於此,我們可以在不修改原始函數的基礎上完成計時任務(計算原始函數的運行時間),如下:

def timer(func):

    def wrapper():
        import time
        tic = time.time()
        func()
        toc = time.time()
        print('程序用时: {}s'.format(toc - tic))

    return wrapper@timerdef make_list():
    return [i * i for i in range(10**7)]my_list = make_list()# 程序用时: 0.8369960784912109s
登入後複製

事實上,my_list 並不是列表,直接列印會顯示None,這是因為我們的wrapper 函數沒有設定回傳值。如果需要取得make_list 的回傳值,可以這樣修改wrapper 函數:

def wrapper():
    import time
    tic = time.time()
    a = func()
    toc = time.time()
    print('程序用时: {}s'.format(toc - tic))
    return a
登入後複製

三、使用多個裝飾器

假如我們要為module 新增功能5功能6(以數字順序),那該如何做呢?

好在Python允許同時使用多個裝飾器:

def func_5(original_module):
    def wrapper():
        original_module()
        print('功能5')
    return wrapperdef func_6(original_module):
    def wrapper():
        original_module()
        print('功能6')
    return wrapper@func_6@func_5def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5# 功能6
登入後複製

上述過程實際上等價於:

def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')new_module = func_6(func_5(module))new_module()
登入後複製

此外,需要注意的是,在使用多個裝飾器時,最靠近函數定義的裝飾器會先裝飾該函數,如果我們改變裝飾順序,則輸出結果也會改變:

@func_5@func_6def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能6# 功能5
登入後複製

四、被裝飾的函數帶有參數

如果被裝飾的函數帶有參數,那該如何去建構裝飾器呢?

考慮這樣一個函數:

def pide(a, b):
    return a / b
登入後複製

b=0 时会出现 ZeropisionError。如何在避免修改该函数的基础上给出一个更加人性化的提醒呢?

因为我们的 pide 函数接收两个参数,所以我们的 wrapper 函数也应当接收两个参数:

def smart_pide(func):
    def wrapper(a, b):
        if b == 0:
            return '被除数不能为0!'
        else:
            return func(a, b)
    return wrapper
登入後複製

使用该装饰器进行装饰:

@smart_pidedef pide(a, b):
    return a / bprint(pide(3, 0))# 被除数不能为0!print(pide(3, 1))# 3.0
登入後複製

如果不知道要被装饰的函数有多少个参数,我们可以使用下面更为通用的模板:

def decorator(func):
    def wrapper(*args, **kwargs):
        # ...
        res = func(*args, **kwargs)
        # ...
        return res  # 也可以不return
    return wrapper
登入後複製

五、带参数的装饰器

我们之前提到的装饰器都没有带参数,即语法糖 @decorator 中没有参数,那么该如何写一个带参数的装饰器呢?

前面实现的装饰器都是两层嵌套函数,而带参数的装饰器是一个三层嵌套函数。

考虑这样一个场景。假如我们在为 module 添加新功能时,希望能够加上实现该功能的开发人员的花名,则可以这样构造装饰器(以 功能5 为例):

def func_5_with_name(name=None):
    def func_5(original_module):
        def wrapper():
            original_module()
            print('功能5由{}实现'.format(name))
        return wrapper    return func_5
登入後複製

效果如下:

@func_5_with_name(name='若水')def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水实现
登入後複製

对于这种三层嵌套函数,我们可以这样理解:当为 func_5_with_name 指定了参数后,func_5_with_name(name='若水') 实际上返回了一个 decorator,于是 @func_5_with_name(name='若水') 就相当于 @decorator

六、使用类作为装饰器

将类作为装饰器,我们需要实现 __init__ 方法和 __call__ 方法。

以计时器为例,具体实现如下:

class Timer:

    def __init__(self, func):
        self.func = func    def __call__(self):
        import time
        tic = time.time()
        self.func()
        toc = time.time()
        print('用时: {}s'.format(toc - tic))@Timerdef make_list():
    return [i**2 for i in range(10**7)]make_list()# 用时: 2.928966999053955s
登入後複製

如果想要自定义生成列表的长度并获得列表(即被装饰的函数带有参数情形),我们就需要在 __call__ 方法中传入相应的参数,具体如下:

class Timer:

    def __init__(self, func):
        self.func = func    def __call__(self, num):

        import time
        tic = time.time()
        res = self.func(num)
        toc = time.time()
        print('用时: {}s'.format(toc - tic))

        return res@Timerdef make_list(num):
    return [i**2 for i in range(num)]my_list = make_list(10**7)# 用时: 2.8219943046569824sprint(len(my_list))# 10000000
登入後複製

如果要构建带参数的类装饰器,则不能把 func 传入 __init__ 中,而是传入到 __call__ 中,同时 __init__ 用来初始化类装饰器的参数。

接下来我们使用类装饰器来复现第五章节中的效果:

class Func_5:

    def __init__(self, name=None):
        self.name = name    def __call__(self, func):

        def wrapper():
            func()
            print('功能5由{}实现'.format(self.name))

        return wrapper@Func_5('若水')def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水实现
登入後複製

七、内置装饰器

Python中有许多内置装饰器,这里仅介绍最常见的三种:@classmethod@staticmethod@property

7.1 @classmethod

@classmethod 用于装饰类中的函数,使用它装饰的函数不需要进行实例化也可调用。需要注意的是,被装饰的函数不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,它可以来调用类的属性,类的方法,实例化对象等。

cls 代表类本身,self 代表实例本身。

具体请看下例:

class A:

    num = 100

    def func1(self):
        print('功能1')

    @classmethod
    def func2(cls):
        print('功能2')
        print(cls.num)
        cls().func1()A.func2()# 功能2# 100# 功能1
登入後複製

7.2 @staticmethod

@staticmethod 同样用来修饰类中的方法,使用它装饰的函数的参数没有任何限制(即无需传入 self 参数),并且可以不用实例化调用该方法。当然,实例化后调用该方法也是允许的。

具体如下:

class A:

    @staticmethod
    def add(a, b):
        return a + bprint(A.add(2, 3))# 5print(A().add(2, 3))# 5
登入後複製

7.3 @property

使用 @property 装饰器,我们可以直接通过方法名来访问类方法,不需要在方法名后添加一对 () 小括号。

class A:

    @property
    def printer(self):
        print('Hello World')a = A()a.printer# Hello World
登入後複製

除此之外,@property 还可以用来防止类的属性被修改。考虑如下场景

class A:

    def __init__(self):
        self.name = 'ABC'a = A()print(a.name)# ABCa.name = 1print(a.name)# 1
登入後複製

可以看出类中的属性 name 可以被随意修改。如果要防止修改,则可以这样做

class A:

    def __init__(self):
        self.name_ = 'ABC'

    @property
    def name(self):
        return self.name_
    
    
a = A()print(a.name)# ABCa.name = 1print(a.name)# AttributeError: can't set attribute
登入後複製

推荐学习:python视频教程

以上是歸納總結Python中的裝飾器知識點的詳細內容。更多資訊請關注PHP中文網其他相關文章!

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

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1662
14
CakePHP 教程
1418
52
Laravel 教程
1311
25
PHP教程
1261
29
C# 教程
1234
24
PHP和Python:解釋了不同的範例 PHP和Python:解釋了不同的範例 Apr 18, 2025 am 12:26 AM

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。

在PHP和Python之間進行選擇:指南 在PHP和Python之間進行選擇:指南 Apr 18, 2025 am 12:24 AM

PHP適合網頁開發和快速原型開發,Python適用於數據科學和機器學習。 1.PHP用於動態網頁開發,語法簡單,適合快速開發。 2.Python語法簡潔,適用於多領域,庫生態系統強大。

PHP和Python:深入了解他們的歷史 PHP和Python:深入了解他們的歷史 Apr 18, 2025 am 12:25 AM

PHP起源於1994年,由RasmusLerdorf開發,最初用於跟踪網站訪問者,逐漸演變為服務器端腳本語言,廣泛應用於網頁開發。 Python由GuidovanRossum於1980年代末開發,1991年首次發布,強調代碼可讀性和簡潔性,適用於科學計算、數據分析等領域。

Python vs. JavaScript:學習曲線和易用性 Python vs. JavaScript:學習曲線和易用性 Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

sublime怎麼運行代碼python sublime怎麼運行代碼python Apr 16, 2025 am 08:48 AM

在 Sublime Text 中運行 Python 代碼,需先安裝 Python 插件,再創建 .py 文件並編寫代碼,最後按 Ctrl B 運行代碼,輸出會在控制台中顯示。

vscode在哪寫代碼 vscode在哪寫代碼 Apr 15, 2025 pm 09:54 PM

在 Visual Studio Code(VSCode)中編寫代碼簡單易行,只需安裝 VSCode、創建項目、選擇語言、創建文件、編寫代碼、保存並運行即可。 VSCode 的優點包括跨平台、免費開源、強大功能、擴展豐富,以及輕量快速。

visual studio code 可以用於 python 嗎 visual studio code 可以用於 python 嗎 Apr 15, 2025 pm 08:18 PM

VS Code 可用於編寫 Python,並提供許多功能,使其成為開發 Python 應用程序的理想工具。它允許用戶:安裝 Python 擴展,以獲得代碼補全、語法高亮和調試等功能。使用調試器逐步跟踪代碼,查找和修復錯誤。集成 Git,進行版本控制。使用代碼格式化工具,保持代碼一致性。使用 Linting 工具,提前發現潛在問題。

notepad 怎麼運行python notepad 怎麼運行python Apr 16, 2025 pm 07:33 PM

在 Notepad 中運行 Python 代碼需要安裝 Python 可執行文件和 NppExec 插件。安裝 Python 並為其添加 PATH 後,在 NppExec 插件中配置命令為“python”、參數為“{CURRENT_DIRECTORY}{FILE_NAME}”,即可在 Notepad 中通過快捷鍵“F6”運行 Python 代碼。

See all articles