国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Python > 正文

深入理解Python中裝飾器的用法

2019-11-25 16:41:09
字體:
供稿:網(wǎng)友

因?yàn)楹瘮?shù)或類都是對象,它們也能被四處傳遞。它們又是可變對象,可以被更改。在函數(shù)或類對象創(chuàng)建后但綁定到名字前更改之的行為為裝飾(decorator)。

“裝飾器”后隱藏了兩種意思――一是函數(shù)起了裝飾作用,例如,執(zhí)行真正的工作,另一個(gè)是依附于裝飾器語法的表達(dá)式,例如,at符號和裝飾函數(shù)的名稱。

函數(shù)可以通過函數(shù)裝飾器語法裝飾:

@decorator       # ②def function():    # ①  pass
函數(shù)以標(biāo)準(zhǔn)方式定義。①
以@做為定義為裝飾器函數(shù)前綴的表達(dá)式②。在 @ 后的部分必須是簡單的表達(dá)式,通常只是函數(shù)或類的名字。這一部分先求值,在下面的定義的函數(shù)準(zhǔn)備好后,裝飾器被新定義的函數(shù)對象作為單個(gè)參數(shù)調(diào)用。裝飾器返回的值附著到被裝飾的函數(shù)名。
裝飾器可以應(yīng)用到函數(shù)和類上。對類語義很明晰――類定義被當(dāng)作參數(shù)來調(diào)用裝飾器,無論返回什么都賦給被裝飾的名字。
在裝飾器語法實(shí)現(xiàn)前(PEP 318),通過將函數(shù)和類對象賦給臨時(shí)變量然后顯式調(diào)用裝飾器然后將返回值賦給函數(shù)名,可以完成同樣的事。這似乎要打更多的字,也確實(shí)裝飾器函數(shù)名用了兩次同時(shí)臨時(shí)變量要用至少三次,很容易出錯(cuò)。以上實(shí)例相當(dāng)于:
def function():         # ①  passfunction = decorator(function)  # ②
裝飾器可以堆棧(stacked)――應(yīng)用的順序是從底到上或從里到外。就是說最初的函數(shù)被當(dāng)作第一次參數(shù)器的參數(shù),無論返回什么都被作為第二個(gè)裝飾器的參數(shù)……無論最后一個(gè)裝飾器返回什么都被依附到最初函數(shù)的名下。

裝飾器語法因其可讀性被選擇。因?yàn)檠b飾器在函數(shù)頭部前被指定,顯然不是函數(shù)體的一部分,它只能對整個(gè)函數(shù)起作用。以@為前綴的表達(dá)式又讓它明顯到不容忽視(根據(jù)PEP叫在您臉上……:))。當(dāng)多個(gè)裝飾器被應(yīng)用時(shí),每個(gè)放在不同的行非常易于閱讀。

代替和調(diào)整原始對象
裝飾器可以或者返回相同的函數(shù)或類對象或者返回完全不同的對象。第一種情況中,裝飾器利用函數(shù)或類對象是可變的添加屬性,例如向類添加文檔字符串(docstring).裝飾器甚至可以在不改變對象的情況下做有用的事,例如在全局注冊表中注冊裝飾的類。在第二種情況中,簡直無所不能:當(dāng)什么不同的東西取代了被裝飾的類或函數(shù),新對象可以完全不同。然而這不是裝飾器的目的:它們意在改變裝飾對象而非做不可預(yù)料的事。因此當(dāng)一個(gè)函數(shù)在裝飾時(shí)被完全替代成不同的函數(shù)時(shí),新函數(shù)通常在一些準(zhǔn)備工作后調(diào)用原始函數(shù)。同樣,當(dāng)一個(gè)類被裝飾成一個(gè)新類時(shí),新類通常源于被裝飾類。當(dāng)裝飾器的目的是“每次都”做什么,像記錄每次對被裝飾函數(shù)的調(diào)用,只有第二類裝飾器可用。另一方面,如果第一類足夠了,最好使用它因?yàn)楦唵巍?/p>

實(shí)現(xiàn)類和函數(shù)裝飾器
對裝飾器惟一的要求是它能夠單參數(shù)調(diào)用。這意味著裝飾器可以作為常規(guī)函數(shù)或帶有__call__方法的類的實(shí)現(xiàn),理論上,甚至lambda函數(shù)也行。

讓我們比較函數(shù)和類方法。裝飾器表達(dá)式(@后部分)可以只是名字。只有名字的方法很好(打字少,看起來整潔等),但是只有當(dāng)無需用參數(shù)定制裝飾器時(shí)才可能。被寫作函數(shù)的裝飾器可以用以下兩種方式:

>>> def simple_decorator(function):...  print "doing decoration"...  return function>>> @simple_decorator... def function():...  print "inside function"doing decoration>>> function()inside function>>> def decorator_with_arguments(arg):...  print "defining the decorator"...  def _decorator(function):...    # in this inner function, arg is available too...    print "doing decoration,", arg...    return function...  return _decorator>>> @decorator_with_arguments("abc")... def function():...  print "inside function"defining the decoratordoing decoration, abc>>> function()inside function

這兩個(gè)裝飾器屬于返回被裝飾函數(shù)的類別。如果它們想返回新的函數(shù),需要額外的嵌套,最糟的情況下,需要三層嵌套。

>>> def replacing_decorator_with_args(arg):...  print "defining the decorator"...  def _decorator(function):...    # in this inner function, arg is available too...    print "doing decoration,", arg...    def _wrapper(*args, **kwargs):...      print "inside wrapper,", args, kwargs...      return function(*args, **kwargs)...    return _wrapper...  return _decorator>>> @replacing_decorator_with_args("abc")... def function(*args, **kwargs):...   print "inside function,", args, kwargs...   return 14defining the decoratordoing decoration, abc>>> function(11, 12)inside wrapper, (11, 12) {}inside function, (11, 12) {}14

_wrapper函數(shù)被定義為接受所有位置和關(guān)鍵字參數(shù)。通常我們不知道哪些參數(shù)被裝飾函數(shù)會接受,所以wrapper將所有東西都創(chuàng)遞給被裝飾函數(shù)。一個(gè)不幸的結(jié)果就是顯式參數(shù)很迷惑人。

相比定義為函數(shù)的裝飾器,定義為類的復(fù)雜裝飾器更簡單。當(dāng)對象被創(chuàng)建,__init__方法僅僅允許返回None,創(chuàng)建的對象類型不能更改。這意味著當(dāng)裝飾器被定義為類時(shí),使用無參數(shù)的形式?jīng)]什么意義:最終被裝飾的對象只是裝飾類的一個(gè)實(shí)例而已,被構(gòu)建器(constructor)調(diào)用返回,并不非常有用。討論在裝飾表達(dá)式中給出參數(shù)的基于類的裝飾器,__init__方法被用來構(gòu)建裝飾器。

>>> class decorator_class(object):...  def __init__(self, arg):...    # this method is called in the decorator expression...    print "in decorator init,", arg...    self.arg = arg...  def __call__(self, function):...    # this method is called to do the job...    print "in decorator call,", self.arg...    return function>>> deco_instance = decorator_class('foo')in decorator init, foo>>> @deco_instance... def function(*args, **kwargs):...  print "in function,", args, kwargsin decorator call, foo>>> function()in function, () {}

相對于正常規(guī)則(PEP 8)由類寫成的裝飾器表現(xiàn)得更像函數(shù),因此它們的名字以小寫字母開始。

事實(shí)上,創(chuàng)建一個(gè)僅返回被裝飾函數(shù)的新類沒什么意義。對象應(yīng)該有狀態(tài),這種裝飾器在裝飾器返回新對象時(shí)更有用。

>>> class replacing_decorator_class(object):...  def __init__(self, arg):...    # this method is called in the decorator expression...    print "in decorator init,", arg...    self.arg = arg...  def __call__(self, function):...    # this method is called to do the job...    print "in decorator call,", self.arg...    self.function = function...    return self._wrapper...  def _wrapper(self, *args, **kwargs):...    print "in the wrapper,", args, kwargs...    return self.function(*args, **kwargs)>>> deco_instance = replacing_decorator_class('foo')in decorator init, foo>>> @deco_instance... def function(*args, **kwargs):...  print "in function,", args, kwargsin decorator call, foo>>> function(11, 12)in the wrapper, (11, 12) {}in function, (11, 12) {}

像這樣的裝飾器可以做任何事,因?yàn)樗芨淖儽谎b飾函數(shù)對象和參數(shù),調(diào)用被裝飾函數(shù)或不調(diào)用,最后改變返回值。

復(fù)制原始函數(shù)的文檔字符串和其它屬性
當(dāng)新函數(shù)被返回代替裝飾前的函數(shù)時(shí),不幸的是原函數(shù)的函數(shù)名,文檔字符串和參數(shù)列表都丟失了。這些屬性可以部分通過設(shè)置__doc__(文檔字符串),__module__和__name__(函數(shù)的全稱)、__annotations__(Python 3中關(guān)于參數(shù)和返回值的額外信息)移植到新函數(shù)上,這些工作可通過functools.update_wrapper自動(dòng)完成。

>>> import functools>>> def better_replacing_decorator_with_args(arg):...  print "defining the decorator"...  def _decorator(function):...    print "doing decoration,", arg...    def _wrapper(*args, **kwargs):...      print "inside wrapper,", args, kwargs...      return function(*args, **kwargs)...    return functools.update_wrapper(_wrapper, function)...  return _decorator>>> @better_replacing_decorator_with_args("abc")... def function():...   "extensive documentation"...   print "inside function"...   return 14defining the decoratordoing decoration, abc>>> function              <function function at 0x...>>>> print function.__doc__extensive documentation

一件重要的東西是從可遷移屬性列表中所缺少的:參數(shù)列表。參數(shù)的默認(rèn)值可以通過__defaults__、__kwdefaults__屬性更改,但是不幸的是參數(shù)列表本身不能被設(shè)置為屬性。這意味著help(function)將顯式無用的參數(shù)列表,使使用者迷惑不已。一個(gè)解決此問題有效但是丑陋的方式是使用eval動(dòng)態(tài)創(chuàng)建wrapper。可以使用外部external模塊自動(dòng)實(shí)現(xiàn)。它提供了對decorator裝飾器的支持,該裝飾器接受wrapper并將之轉(zhuǎn)換成保留函數(shù)簽名的裝飾器。

綜上,裝飾器應(yīng)該總是使用functools.update_wrapper或者其它方式賦值函數(shù)屬性。

標(biāo)準(zhǔn)庫中的示例
首先要提及的是標(biāo)準(zhǔn)庫中有一些實(shí)用的裝飾器,有三種裝飾器:

classmethod讓一個(gè)方法變成“類方法”,即它能夠無需創(chuàng)建實(shí)例調(diào)用。當(dāng)一個(gè)常規(guī)方法被調(diào)用時(shí),解釋器插入實(shí)例對象作為第一個(gè)參數(shù)self。當(dāng)類方法被調(diào)用時(shí),類本身被給做第一個(gè)參數(shù),一般叫cls。
類方法也能通過類命名空間讀取,所以它們不必污染模塊命名空間。類方法可用來提供替代的構(gòu)建器(constructor):

class Array(object):  def __init__(self, data):    self.data = data  @classmethod  def fromfile(cls, file):    data = numpy.load(file)    return cls(data)

這比用一大堆標(biāo)記的__init__簡單多了。
staticmethod應(yīng)用到方法上讓它們“靜態(tài)”,例如,本來一個(gè)常規(guī)函數(shù),但通過類命名空間存取。這在函數(shù)僅在類中需要時(shí)有用(它的名字應(yīng)該以_為前綴),或者當(dāng)我們想要用戶以為方法連接到類時(shí)也有用――雖然對實(shí)現(xiàn)本身不必要。
property是對getter和setter問題Python風(fēng)格的答案。通過property裝飾的方法變成在屬性存取時(shí)自動(dòng)調(diào)用的getter。

>>> class A(object):...  @property...  def a(self):...   "an important attribute"...   return "a value">>> A.a                  <property object at 0x...>>>> A().a'a value'


例如A.a是只讀屬性,它已經(jīng)有文檔了:help(A)包含從getter方法獲取的屬性a的文檔字符串。將a定義為property使它能夠直接被計(jì)算,并且產(chǎn)生只讀的副作用,因?yàn)闆]有定義任何setter。
為了得到setter和getter,顯然需要兩個(gè)方法。從Python 2.6開始首選以下語法:

class Rectangle(object):  def __init__(self, edge):    self.edge = edge  @property  def area(self):    """Computed area.    Setting this updates the edge length to the proper value.    """    return self.edge**2  @area.setter  def area(self, area):    self.edge = area ** 0.5

通過property裝飾器取代帶一個(gè)屬性(property)對象的getter方法,以上代碼起作用。這個(gè)對象反過來有三個(gè)可用于裝飾器的方法getter、setter和deleter。它們的作用就是設(shè)定屬性對象的getter、setter和deleter(被存儲為fget、fset和fdel屬性(attributes))。當(dāng)創(chuàng)建對象時(shí),getter可以像上例一樣設(shè)定。當(dāng)定義setter時(shí),我們已經(jīng)在area中有property對象,可以通過setter方法向它添加setter,一切都在創(chuàng)建類時(shí)完成。
之后,當(dāng)類實(shí)例創(chuàng)建后,property對象和特殊。當(dāng)解釋器執(zhí)行屬性存取、賦值或刪除時(shí),其執(zhí)行被下放給property對象的方法。
為了讓一切一清二楚[^5],讓我們定義一個(gè)“調(diào)試”例子:

>>> class D(object):...  @property...  def a(self):...   print "getting", 1...   return 1...  @a.setter...  def a(self, value):...   print "setting", value...  @a.deleter...  def a(self):...   print "deleting">>> D.a                  <property object at 0x...>>>> D.a.fget                <function a at 0x...>>>> D.a.fset                <function a at 0x...>>>> D.a.fdel                <function a at 0x...>>>> d = D()        # ... varies, this is not the same `a` function>>> d.agetting 11>>> d.a = 2setting 2>>> del d.adeleting>>> d.agetting 11

屬性(property)是對裝飾器語法的一點(diǎn)擴(kuò)展。使用裝飾器的一大前提――命名不重復(fù)――被違反了,但是目前沒什么更好的發(fā)明。為getter,setter和deleter方法使用相同的名字還是個(gè)好的風(fēng)格。
一些其它更新的例子包括:

functools.lru_cache記憶任意維持有限 參數(shù):結(jié)果 對的緩存函數(shù)(Python
3.2)
functools.total_ordering是一個(gè)基于單個(gè)比較方法而填充丟失的比較(ordering)方法(__lt__,__gt__,__le__等等)的類裝飾器。
函數(shù)的廢棄
比如說我們想在第一次調(diào)用我們不希望被調(diào)用的函數(shù)時(shí)在標(biāo)準(zhǔn)錯(cuò)誤打印一個(gè)廢棄函數(shù)警告。如果我們不想更改函數(shù),我們可用裝飾器

class deprecated(object):  """Print a deprecation warning once on first use of the function.  >>> @deprecated()          # doctest: +SKIP  ... def f():  ...   pass  >>> f()               # doctest: +SKIP  f is deprecated  """  def __call__(self, func):    self.func = func    self.count = 0    return self._wrapper  def _wrapper(self, *args, **kwargs):    self.count += 1    if self.count == 1:      print self.func.__name__, 'is deprecated'    return self.func(*args, **kwargs)

也可以實(shí)現(xiàn)成函數(shù):

def deprecated(func):  """Print a deprecation warning once on first use of the function.  >>> @deprecated           # doctest: +SKIP  ... def f():  ...   pass  >>> f()               # doctest: +SKIP  f is deprecated  """  count = [0]  def wrapper(*args, **kwargs):    count[0] += 1    if count[0] == 1:      print func.__name__, 'is deprecated'    return func(*args, **kwargs)  return wrapper

while-loop移除裝飾器
例如我們有個(gè)返回列表的函數(shù),這個(gè)列表由循環(huán)創(chuàng)建。如果我們不知道需要多少對象,實(shí)現(xiàn)這個(gè)的標(biāo)準(zhǔn)方法如下:

def find_answers():  answers = []  while True:    ans = look_for_next_answer()    if ans is None:      break    answers.append(ans)  return answers

只要循環(huán)體很緊湊,這很好。一旦事情變得更復(fù)雜,正如真實(shí)的代碼中發(fā)生的那樣,這就很難讀懂了。我們可以通過yield語句簡化它,但之后用戶不得不顯式調(diào)用嗯list(find_answers())。

我們可以創(chuàng)建一個(gè)為我們構(gòu)建列表的裝飾器:

def vectorized(generator_func):  def wrapper(*args, **kwargs):    return list(generator_func(*args, **kwargs))  return functools.update_wrapper(wrapper, generator_func)

然后函數(shù)變成這樣:

@vectorizeddef find_answers():  while True:    ans = look_for_next_answer()    if ans is None:      break    yield ans

插件注冊系統(tǒng)
這是一個(gè)僅僅把它放進(jìn)全局注冊表中而不更改類的類裝飾器,它屬于返回被裝飾對象的裝飾器。

class WordProcessor(object):  PLUGINS = []  def process(self, text):    for plugin in self.PLUGINS:      text = plugin().cleanup(text)    return text  @classmethod  def plugin(cls, plugin):    cls.PLUGINS.append(plugin)@WordProcessor.pluginclass CleanMdashesExtension(object):  def cleanup(self, text):    return text.replace('—', u'/N{em dash}')

這里我們使用裝飾器完成插件注冊。我們通過一個(gè)名詞調(diào)用裝飾器而不是一個(gè)動(dòng)詞,因?yàn)槲覀冇盟鼇砺暶魑覀兊念愂荳ordProcessor的一個(gè)插件。plugin方法僅僅將類添加進(jìn)插件列表。

關(guān)于插件自身說下:它用真正的Unicode中的破折號符號替代HTML中的破折號。它利用unicode literal notation通過它在unicode數(shù)據(jù)庫中的名稱(“EM DASH”)插入一個(gè)符號。如果直接插入U(xiǎn)nicode符號,將不可能區(qū)分所插入的和源程序中的破折號。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 沭阳县| 台前县| 林芝县| 利川市| 兰州市| 彩票| 南城县| 禹城市| 洛隆县| 光山县| 龙里县| 屏山县| 文登市| 垫江县| 辰溪县| 武宣县| 寿阳县| 万山特区| 滁州市| 涿鹿县| 海口市| 呼图壁县| 大宁县| 锦屏县| 敦煌市| 财经| 多伦县| 黑山县| 镇坪县| 如皋市| 浏阳市| 临沧市| 昌黎县| 鸡泽县| 台中县| 桂东县| 永川市| 城步| 桐城市| 施秉县| 五家渠市|