亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

 更新時間:2016年06月28日 18:55:20   作者:weakish  
裝飾器的使用是Python的高級技巧之一,通過Decorator裝飾器能夠帶來函數(shù)式編程中所需的很多特性,這里我們就來跟隨文章一同深入理解Python中裝飾器的用法,需要的朋友可以參考下

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

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

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

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

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

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

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

讓我們比較函數(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 decorator
doing decoration, abc
>>> function()
inside function

這兩個裝飾器屬于返回被裝飾函數(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 14
defining the decorator
doing 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ù)。一個不幸的結(jié)果就是顯式參數(shù)很迷惑人。

相比定義為函數(shù)的裝飾器,定義為類的復雜裝飾器更簡單。當對象被創(chuàng)建,__init__方法僅僅允許返回None,創(chuàng)建的對象類型不能更改。這意味著當裝飾器被定義為類時,使用無參數(shù)的形式?jīng)]什么意義:最終被裝飾的對象只是裝飾類的一個實例而已,被構(gòu)建器(constructor)調(diào)用返回,并不非常有用。討論在裝飾表達式中給出參數(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, kwargs
in decorator call, foo
>>> function()
in function, () {}

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

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

>>> 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, kwargs
in decorator call, foo
>>> function(11, 12)
in the wrapper, (11, 12) {}
in function, (11, 12) {}

像這樣的裝飾器可以做任何事,因為它能改變被裝飾函數(shù)對象和參數(shù),調(diào)用被裝飾函數(shù)或不調(diào)用,最后改變返回值。

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

>>> 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 14
defining the decorator
doing decoration, abc
>>> function              
<function function at 0x...>
>>> print function.__doc__
extensive documentation

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

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

標準庫中的示例
首先要提及的是標準庫中有一些實用的裝飾器,有三種裝飾器:

classmethod讓一個方法變成“類方法”,即它能夠無需創(chuàng)建實例調(diào)用。當一個常規(guī)方法被調(diào)用時,解釋器插入實例對象作為第一個參數(shù)self。當類方法被調(diào)用時,類本身被給做第一個參數(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)

這比用一大堆標記的__init__簡單多了。
staticmethod應用到方法上讓它們“靜態(tài)”,例如,本來一個常規(guī)函數(shù),但通過類命名空間存取。這在函數(shù)僅在類中需要時有用(它的名字應該以_為前綴),或者當我們想要用戶以為方法連接到類時也有用——雖然對實現(xiàn)本身不必要。
property是對getter和setter問題Python風格的答案。通過property裝飾的方法變成在屬性存取時自動調(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使它能夠直接被計算,并且產(chǎn)生只讀的副作用,因為沒有定義任何setter。
為了得到setter和getter,顯然需要兩個方法。從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裝飾器取代帶一個屬性(property)對象的getter方法,以上代碼起作用。這個對象反過來有三個可用于裝飾器的方法getter、setter和deleter。它們的作用就是設定屬性對象的getter、setter和deleter(被存儲為fget、fset和fdel屬性(attributes))。當創(chuàng)建對象時,getter可以像上例一樣設定。當定義setter時,我們已經(jīng)在area中有property對象,可以通過setter方法向它添加setter,一切都在創(chuàng)建類時完成。
之后,當類實例創(chuàng)建后,property對象和特殊。當解釋器執(zhí)行屬性存取、賦值或刪除時,其執(zhí)行被下放給property對象的方法。
為了讓一切一清二楚[^5],讓我們定義一個“調(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.a
getting 1
1
>>> d.a = 2
setting 2
>>> del d.a
deleting
>>> d.a
getting 1
1

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

functools.lru_cache記憶任意維持有限 參數(shù):結(jié)果 對的緩存函數(shù)(Python
3.2)
functools.total_ordering是一個基于單個比較方法而填充丟失的比較(ordering)方法(__lt__,__gt__,__le__等等)的類裝飾器。
函數(shù)的廢棄
比如說我們想在第一次調(diào)用我們不希望被調(diào)用的函數(shù)時在標準錯誤打印一個廢棄函數(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)

也可以實現(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移除裝飾器
例如我們有個返回列表的函數(shù),這個列表由循環(huán)創(chuàng)建。如果我們不知道需要多少對象,實現(xià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ā)生的那樣,這就很難讀懂了。我們可以通過yield語句簡化它,但之后用戶不得不顯式調(diào)用嗯list(find_answers())。

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

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

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

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

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

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.plugin
class CleanMdashesExtension(object):
  def cleanup(self, text):
    return text.replace('&mdash;', u'\N{em dash}')

這里我們使用裝飾器完成插件注冊。我們通過一個名詞調(diào)用裝飾器而不是一個動詞,因為我們用它來聲明我們的類是WordProcessor的一個插件。plugin方法僅僅將類添加進插件列表。

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

相關(guān)文章

  • python自動發(fā)微信監(jiān)控報警

    python自動發(fā)微信監(jiān)控報警

    這篇文章主要為大家詳細介紹了python自動發(fā)微信監(jiān)控報警,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • 使用實現(xiàn)pandas讀取csv文件指定的前幾行

    使用實現(xiàn)pandas讀取csv文件指定的前幾行

    下面小編就為大家分享一篇使用實現(xiàn)pandas讀取csv文件指定的前幾行,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-04-04
  • 聊聊Python對CSV文件的讀取與寫入問題

    聊聊Python對CSV文件的讀取與寫入問題

    今天抽空給大家介紹下Python對CSV文件的讀取與寫入問題,首先需要在python環(huán)境里導入csv板塊,下面就通過實例代碼給大家詳細介紹下,感興趣的朋友跟隨小編一起看看吧
    2021-11-11
  • NumPy迭代數(shù)組的實現(xiàn)

    NumPy迭代數(shù)組的實現(xiàn)

    本文主要介紹了NumPy迭代數(shù)組的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-02-02
  • Python調(diào)試神器之PySnooper的使用教程分享

    Python調(diào)試神器之PySnooper的使用教程分享

    對于每個程序開發(fā)者來說,調(diào)試幾乎是必備技能。本文小編就來給大家介紹一款非常好用的調(diào)試工具,它能在一些場景下,大幅度提高調(diào)試的效率, 那就是 PySnooper,希望大家喜歡
    2023-02-02
  • Python自動創(chuàng)建Markdown表格使用實例探究

    Python自動創(chuàng)建Markdown表格使用實例探究

    Markdown表格是文檔中整理和展示數(shù)據(jù)的重要方式之一,然而,手動編寫大型表格可能會費時且容易出錯,本文將介紹如何使用Python自動創(chuàng)建Markdown表格,通過示例代碼詳細展示各種場景下的創(chuàng)建方法,提高表格生成的效率
    2024-01-01
  • 自動在Windows中運行Python腳本并定時觸發(fā)功能實現(xiàn)

    自動在Windows中運行Python腳本并定時觸發(fā)功能實現(xiàn)

    講一下在Python中寫好了一個腳本之后,怎么自動雙擊一個程序自動就跑起來。以及,怎么在Windows 10中設計定期定時觸發(fā)并跑腳本,有需要的朋友可以參考下
    2021-09-09
  • Python實現(xiàn)KNN鄰近算法

    Python實現(xiàn)KNN鄰近算法

    這篇文章主要為大家詳細介紹了Python實現(xiàn)KNN鄰近算法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • numpy自動生成數(shù)組詳解

    numpy自動生成數(shù)組詳解

    這篇文章主要介紹了numpy自動生成數(shù)組詳解,具有一定借鑒價值,需要的朋友可以參考下。
    2017-12-12
  • Python爬蟲工具requests-html使用解析

    Python爬蟲工具requests-html使用解析

    這篇文章主要介紹了Python爬蟲工具requests-html使用解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-04-04

最新評論