日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python 装饰器实现_Python装饰器系列01 - 如何正确地实现装饰器

發布時間:2023/12/4 python 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python 装饰器实现_Python装饰器系列01 - 如何正确地实现装饰器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

雖然人們能利用函數閉包(function clouser)寫出簡單的裝飾器,但其可用范圍常受限制。多數實現裝飾器的基本方式會破壞與內省(Introspection)的關聯性。

可大多數人會說:who cares!

但我仍堅持追求正確地寫出漂亮代碼。

我愛內省(introspection),討厭猴子補丁(Monkey Patching)

請記住以下兩點:

要為被裝飾器包裹的函數(wrapped function)保留內省功能。

要理解清楚Python對象模型的執行方式如何工作。

接下來,我會通過14篇blog來向你解釋:

你的典型Python裝飾器及包裹的函數哪里有問題

如何修復這些問題

以下是第一篇內容,我會從幾個方面簡單說明你的典型Python裝飾器如何產生問題。

Python 裝飾器基礎知識

人皆所知Python裝飾器語法如下:

@function_wrapper

def function():

pass

@符號為自Python2.4引入的裝飾器的語法糖(syntactic sugar), 它等同以下寫法

def function():

pass

function = function_wrapper(function)

此@裝飾器語法用于包裹定義或修改的函數

裝飾器與猴子補丁不同,前者作用于定義時,后者作用于運行時

函數wrapper剖析

以下用class來實現一個裝飾器

class function_wrapper(object):

def __init__(self, wrapped):

self.wrapped = wrapped

def __call__(self, *args, **kwargs):

return self.wrapped(*args, **kwargs)

@function_wrapper

def function():

pass

以上例子,class實例初始化后會在其內部記錄一個原函數(self.wrapped = wrapped),在調用這個被class裝飾器包裹起來的函數時,實際上是通過調用class對象的__call()__方法來調用原函數。

你可以通過裝飾器,在調用原函數之前或之后,實現一些額外的功能。如需修改傳遞給原函數的輸入參數,或原函數返回的結果,你只要在__call__()方法內進行修改。

用class來實現裝飾器或許不太流行(2014年)。普遍用函數閉包來實現裝飾器。函數閉包實現方式為:利用嵌套函數逐層返回傳入的原函數(wrapped)。代碼如下:

def function_wrapper(wrapped):

def _wrapper(*args, **kwargs):

return wrapped(*args, **kwargs)

return _wrapper

@function_wrapper

def function():

pass

此例中,無明顯地給內嵌函數_wrapper傳入原函數wrapped,內嵌函數仍可通過外層函數function_wrapper的參數訪問到原函數(閉包原理),與用class實現裝飾器相比,此做法方便多了。

Introspecting a function

函數內省

我們期望函數可指定一些與描述自身相關的特性(properties),如__name__ 及 __doc__ 這樣的屬性。當我們把以函數閉包方式實現的裝飾器應用到普通函數時,函數的這些屬性會發生意料之外的變化。這些屬性細節為內嵌函數提供。

def function_wrapper(wrapped):

def _wrapper(*args, **kwargs):

return wrapped(*args, **kwargs)

return _wrapper

@function_wrapper

def function():

pass

>>> print(function.__name__)

_wrapper

若以class方式實現的wrapper,類實例通常不帶有__name__屬性,以此方式去嘗試訪問原函數的name屬性時會得到一個AttributeError異常

class function_wrapper(object):

def __init__(self, wrapped):

self.wrapped = wrapped

def __call__(self, *args, **kwargs):

return self.wrapped(*args, **kwargs)

@function_wrapper

def function():

pass

>>> print(function.__name__)

Traceback (most recent call last):

File "", line 1, in

AttributeError: 'function_wrapper' object has no attribute '__name__'

當以函數閉包方式實現裝飾器時,為保留原函數相關信息,我們可以把原函數的相關屬性Copy一份給內嵌函數。如下例,可正確獲得原函數的__name__及__doc__內容。

def function_wrapper(wrapped):

def _wrapper(*args, **kwargs):

return wrapped(*args, **kwargs)

_wrapper.__name__ = wrapped.__name__

_wrapper.__doc__ = wrapped.__doc__

return _wrapper

@function_wrapper

def function():

pass

>>> print(function.__name__)

function

這樣Copy屬性實在費力,將來如有要追加的屬性還得更新代碼。例如我們想Copy__module__,還有Python 3新增加的__qualname__及__annotations__屬性。我們可以利用Python標準庫提供的functools.wraps()裝飾器來實現這些需求。

import functools

def function_wrapper(wrapped):

@functools.wraps(wrapped)

def _wrapper(*args, **kwargs):

return wrapped(*args, **kwargs)

return _wrapper

@function_wrapper

def function():

pass

>>> print(function.__name__)

function

如以class方式實現裝飾器,則可用functools.update_wrapper(),如下例所示:

import functools

class function_wrapper(object):

def __init__(self, wrapped):

self.wrapped = wrapped

functools.update_wrapper(self, wrapped)

def __call__(self, *args, **kwargs):

return self.wrapped(*args, **kwargs)

雖然functools.wraps()能解決諸如訪問原函數的__name__及__doc__的問題,但實際上并沒有完美解決函數內省,接下來你會看到。

當我們查詢被裝飾器包裹的原函數的參數定義時,返回的結果卻是wrapper的參數定義。以函數閉包實現的裝飾器為例,返回的為內嵌函數的參數定義。因此,裝飾器不具簽名保護(not signature preserving)

import inspect

def function_wrapper(wrapped):

def _wrapper(*arg, **kwarg):

return wrapped(*arg, **kwarg)

return _wrapper

@function_wrapper

def function(arg1, arg2): pass

>>> print(inspect.signature(function))

(*arg, **kwarg)

以class實現的裝飾器也是同樣的結果。

import inspect

class function_wrapper:

def __init__(self, wrapped):

self.wrapped = wrapped

def __call__(self, *arg, **kwarg):

return self.wrapped(*arg, **kwarg)

@function_wrapper

def function(arg1, arg2): pass

>>> print(inspect.signature(function))

(*arg, **kwarg)

另一個和內省相關的例子是,當用inspect.getsource()嘗試返回函數(此函數被以class方式實現的裝飾器包裹起來)的源碼時,會得到一個TypeError異常。

TypeError: <__main__.function_wrapper object at> is not a module, class, method,

function, traceback, frame, or code object

The terminal process terminated with exit code: 1

包裹class方法

和普通函數一樣,裝飾器也可應用在class的方法上。Python內置的兩個特殊裝飾器——@staticmethod和@classmethod可將普通的實例方法(instance method)轉化為class相關的特殊方法。雖然這些特殊方法也隱含著一些問題。

class Class(object):

@function_wrapper

def method(self):

pass

@classmethod

def cmethod(cls):

pass

@staticmethod

def smethod():

pass

首先,就算在你的裝飾器里用上了 functools.wraps() 或 functools.update_wrapper(),當你把這個裝飾器放在 @classmethod 或 @staticmethod前面時,依然會得到一個異常。這是因為依然有一些屬性并未被functools.wraps()或functools.update_wrapper()Copy進來。以下為Python2的運行情況。

class Class(object):

@function_wrapper

@classmethod

def cmethod(cls):

pass

Traceback (most recent call last):

File "", line 1, in

File "", line 3, in Class

File "", line 2, in wrapper

File ".../functools.py", line 33, in update_wrapper

setattr(wrapper, attr, getattr(wrapped, attr))

AttributeError: 'classmethod' object has no attribute '__module__'

此為Python2的bug所致,此bug已在Python3中得到修正。

就算在Python3中運行,依然有異常拋出。那是因為兩個包裹類型(wrapper types,即@function_wrapper及@classmethod)都期望被包裹函數(wrapped function)是可以被直接調用的(callable)。此被包裹的函數可稱之為描述器(descriptor)。這意味為了返回一個可調用的描述器,它(描述器)須先正確地與實例綁定起來。參考以下代碼

class Class(object):

@function_wrapper

@classmethod

def cmethod(cls):

pass

>>> Class.cmethod()

Traceback (most recent call last):

File "classmethod.py", line 15, in

Class.cmethod()

File "classmethod.py", line 6, in _wrapper

return wrapped(*args, **kwargs)

TypeError: 'classmethod' object is not callable

簡單并非意味著正確

雖然我們可以簡單地實現裝飾器,并不見得這些裝飾器必然正確及長久有效。

至此,比較突出的問題如下:

保留函數的 __name__ and __doc__。

保留函數的參數定義。

保留獲取函數源碼的能力。

能夠在帶有描述器協議的其他裝飾器上應用自己所寫的裝飾器。

functools.wraps() 為我們解決了第一個問題,但不能一勞永逸。例如不能解決內省相關的問題。

就算能解決內省相關的問題,簡單實現的裝飾器依然會破壞python對象的執行模型,譬如被裝飾器包裹著的帶描述器協議的對象。

第三方包(packages)如decorator模塊嘗試解決這些問題,但只能解決前面兩點問題。通用猴子補丁動態地應用函數包裝器(function wrapper)時依然會發生問題。

我們找出了一些問題,后續博文中,我們會看到如何解決這些問題。而且你也會寫出優雅的裝飾器。

請繼續關注我下期博文,希望我能保持繼續寫博的沖勁。

總結

以上是生活随笔為你收集整理的python 装饰器实现_Python装饰器系列01 - 如何正确地实现装饰器的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。