< Python全景系列-9 > Python 裝飾器:優雅地增強你的函數和類

来源:https://www.cnblogs.com/xfuture/archive/2023/05/31/17445659.html
-Advertisement-
Play Games

裝飾器在 Python 中扮演了重要的角色,這是一種精巧的語言特性,讓我們能夠修改或增強函數和類的行為,無需修改它們的源代碼。這篇文章將深入探討裝飾器的所有相關主題,包括裝飾器的基礎知識、實現與使用、工作原理,以及通過實際例子學習裝飾器的獨特用法。 ...


歡迎來到我們的系列博客《Python全景系列》第九篇!在這個系列中,我們將帶領你從Python的基礎知識開始,一步步深入到高級話題,幫助你掌握這門強大而靈活的編程語法。無論你是編程新手,還是有一定基礎的開發者,這個系列都將提供你需要的知識和技能。

** 裝飾器在 Python 中扮演了重要的角色,這是一種精巧的語言特性,讓我們能夠修改或增強函數和類的行為,無需修改它們的源代碼。這篇文章將深入探討裝飾器的所有相關主題,包括裝飾器的基礎知識、實現與使用、工作原理,以及通過實際例子學習裝飾器的獨特用法。**

Python 裝飾器深入探討

在 Python 中,裝飾器提供了一種簡潔的方式,用來修改或增強函數和類的行為。裝飾器在語法上表現為一個前置於函數或類定義之前的特殊標記:

@simple_decorator
def hello_world():
    print("Hello, world!")

在這個例子中,simple_decorator 是一個裝飾器,它作用於下方的 hello_world 函數。裝飾器在概念上就像一個包裝器,它可以在被裝飾的函數執行前後插入任意的代碼,進而改變被裝飾函數的行為。

參數化裝飾器

我們還可以進一步將裝飾器參數化,這讓裝飾器的行為更具靈活性。比如,我們可以定義一個裝飾器,讓它在函數執行前後列印自定義的消息:

def message_decorator(before_message, after_message):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(before_message)
            result = func(*args, **kwargs)
            print(after_message)
            return result
        return wrapper
    return decorator

@message_decorator("Start", "End")
def hello_world():
    print("Hello, world!")

在這個例子中,message_decorator 是一個參數化裝飾器,它接受兩個參數,分別代表函數執行前後要列印的消息。

理解裝飾器的工作原理

在 Python 中,函數是第一類對象。這意味著函數和其他對象一樣,可以作為變數進行賦值,可以作為參數傳給其他函數,可以作為其他函數的返回值,甚至可以在一個函數裡面定義另一個函數。這個特性是實現裝飾器的基礎。

def decorator(func):
    def wrapper():
        print('Before function execution')
        func()
        print('After function execution')
    return wrapper

def hello_world():
    print('Hello, world!')

decorated_hello = decorator(hello_world)
decorated_hello()

在這個例子中,decorator 函數接收一個函數 hello_world 作為參數,並返回了一個新的函數 wrapped_func。這個新函數在 hello_world 函數執行前後分別列印一條消息。我們可以看到,裝飾器實際上是一個返回函數的函數。

函數簽名保持

預設情況下,裝飾器會“掩蓋”掉原函數的名字和文檔字元串。這是因為在裝飾器內部,我們返回了一個全新的函數。我們可以使用 functools.wraps 來解決這個問題:

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper():
        print('Before function execution')
        func()
        print('After function execution')
    return wrapper

@decorator
def hello_world():
    "Prints 'Hello, world!'"
    print('Hello, world!')

print(hello_world.__name__)
print(hello_world.__doc__)

這樣,使用裝飾器後的函數名和文檔字元串能夠保持不變。

Python 裝飾器的應用實例

裝飾器在實際的 Python 編程中有許多應用場景,比如日誌記錄、性能測試、事務處理、緩存、許可權校驗等。

一個常見的應用就是使用裝飾器進行日誌記錄:

import logging

def log_decorator(func):
    logging.basicConfig(level=logging.INFO)
    
    def wrapper(*args, **kwargs):
        logging.info(f'Running "{func.__name__}" with arguments {args} and kwargs {kwargs}')
        result = func(*args, **kwargs)
        logging.info(f'Finished "{func.__name__}" with result {result}')
        return result
    
    return wrapper

@log_decorator
def add(x, y):
    return x + y

這個裝飾器記錄了函數的名稱,函數調用的參數,以及函數返回的結果。

裝飾器鏈

Python 允許我們將多個裝飾器應用到一個函數上,形成一個裝飾器鏈。例如,我們可以同時應用日誌裝飾器和性能測試裝飾器:

import time
import logging
from functools import wraps

def log_decorator(func):
    logging.basicConfig(level=logging.INFO)
    
    @wraps(func)
    def wrapper(*args, **kwargs):
        logging.info(f'Running "{func.__name__}" with arguments {args} and kwargs {kwargs}')
        result = func(*args, **kwargs)
        logging.info(f'Finished "{func.__name__}" with result {result}')
        return result

    return wrapper

def timer_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f'Function "{func.__name__}" took {end_time - start_time} seconds to run.')
        return result

    return wrapper

@log_decorator
@timer_decorator
def add(x, y):
    time.sleep(2)
    return x + y

在這個例子中,@log_decorator@timer_decorator 兩個裝飾器被同時應用到 add 函數上,它們分別負責記錄日誌和測量函數運行時間。

One More Thing: 自動註冊裝飾器

一個有趣的裝飾器應用是自動註冊。這個裝飾器會在裝飾函數時自動將函數添加到一個列表或字典中,這樣我們就可以在程式的其他地方訪問到這個列表或字典,知道有哪些函數被裝飾過。

# 裝飾器將函數註冊到一個列表中
def register_decorator(func_list):
    def decorator(func):
        func_list.append(func)
        return func
    return decorator

# 自動註冊函數
registered_functions = []
@register_decorator(registered_functions)
def foo():
    pass

@register_decorator(registered_functions)
def bar():
    pass

print(registered_functions)  # 輸出: [<function foo at 0x10d38d160>, <function bar at 0x10d38d1f0>]

這個裝飾器可以用於自動註冊路由、插件系統、命令行參數處理等場景,能夠大大提高代碼的靈活性和可擴展性。

總結

Python 裝飾器是一種強大的工具,它可以讓我們更有效地管理和組織代碼。希望通過這篇文章,你能夠更深入地理解裝飾器的工作原理和用法,從而在你的項目中更好地使用裝飾器。

如有幫助,請多關註
個人微信公眾號:【Python全視角】
TeahLead_KrisChang,10+年的互聯網和人工智慧從業經驗,10年+技術和業務團隊管理經驗,同濟軟體工程本科,復旦工程管理碩士,阿裡雲認證雲服務資深架構師,上億營收AI產品業務負責人。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在Java中,序列化(Serialization)是指將對象的狀態轉換為位元組流的過程,以便將其保存到文件、在網路中傳輸或持久化到資料庫中。而反序列化(Deserialization)則是將位元組流轉換回對象的過程,恢復對象的狀態。 序列化和反序列化主要用於以下場景: 1. 對象持久化:通過序列化,可以 ...
  • > 內容摘自我的學習網站:topjavaer.cn Redis連環40問,絕對夠全! ## Redis是什麼? Redis(`Remote Dictionary Server`)是一個使用 C 語言編寫的,高性能非關係型的鍵值對資料庫。與傳統資料庫不同的是,Redis 的數據是存在記憶體中的,所以讀寫 ...
  • > 本文首發於公眾號:Hunter後端 > 原文鏈接:[Python連接es筆記四之創建和刪除操作](https://mp.weixin.qq.com/s/ZCe0JT9TDEiZI7M5dxC9qA) 這一篇筆記介紹一下索引和數據的創建和刪除。 其實對於索引來說,如果可以接觸到 kibana 的話 ...
  • > 源碼都背下來了,你給我看這 **我是 javapub,一名 `Markdown` 程式員從👨‍💻,八股文種子選手。** **面試官: 你好,我看到你的簡歷上寫著你熟悉 Java 中的 "synchronized" 關鍵字。你能給我講講它的作用嗎?** **候選人:** 當然,"synchro ...
  • # 1. Netty總體結構 ## 1.1 Netty簡介 ​ Netty是一款用於創建高性能網路應用程式的高級框架。它的基於 Java NIO 的非同步的和事件驅動的實現,保證了高負載下應用程式性能的最大化和可伸縮性。 ​ 其次,Netty 也包含了一組**設計模式**,將應用程式邏輯從網路層解耦, ...
  • 1. 多線程程式在一個核的CPU運行 ![image.png](https://cdn.nlark.com/yuque/0/2023/png/35902537/1685453577663-714d9c16-e8a3-4828-bb86-86dfa10c8e52.png#averageHue=%23f ...
  • ![](https://img2023.cnblogs.com/other/1218593/202305/1218593-20230531145646615-374710580.png) ## **1.簡介** Grep Console是一款方便開發者對idea控制台輸出日誌進行個性化管理的插件。 ...
  • # 基於 Web 實現 m3u8 視頻播放的簡單應用示例 ## 實現思路 將視頻(MP4 等)轉換為 M3U8 視頻的服務,可以按照以下步驟進行操作: 1. 將視頻(MP4 等)轉換為 M3U8:在服務中,使用適當的工具(如 FFmpeg)將接收到的視頻(MP4 等)轉換為 M3U8 格式。這將生成 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...