Heim > Backend-Entwicklung > Python-Tutorial > Vertiefendes Verständnis der Verwendung von Dekoratoren in Python

Vertiefendes Verständnis der Verwendung von Dekoratoren in Python

高洛峰
Freigeben: 2017-01-23 14:46:16
Original
1150 Leute haben es durchsucht

Da Funktionen oder Klassen Objekte sind, können sie auch weitergegeben werden. Sie sind veränderliche Objekte und können geändert werden. Das Verhalten beim Ändern einer Funktion oder eines Klassenobjekts nach der Erstellung, aber bevor es an einen Namen gebunden wird, ist ein Dekorator.

Hinter „Dekorator“ verbergen sich zwei Bedeutungen: Die eine ist, dass die Funktion eine dekorative Rolle spielt, also beispielsweise echte Arbeit ausführt, und die andere ist ein Ausdruck, der an die Syntax des Dekorators angehängt ist, zum Beispiel at Symbol und Der Name der dekorierten Funktion.

Funktionen können über die Function-Decorator-Syntax dekoriert werden:

@decorator       # ②
def function():    # ①
  pass
Nach dem Login kopieren

Funktionen werden auf die Standardmethode definiert. ①
Verwenden Sie @ als Ausdruck, der als Präfix der Dekoratorfunktion definiert ist②. Der Teil nach @ muss ein einfacher Ausdruck sein, normalerweise nur der Name einer Funktion oder Klasse. Dieser Teil wird zuerst ausgewertet, und nachdem die unten definierte Funktion fertig ist, wird der Dekorator mit dem neu definierten Funktionsobjekt als einzelnem Parameter aufgerufen. Der vom Dekorateur zurückgegebene Wert wird an den dekorierten Funktionsnamen angehängt.
Dekoratoren können auf Funktionen und Klassen angewendet werden. Die Semantik für Klassen ist klar: Die Klassendefinition wird als Parameter zum Aufrufen des Dekorators verwendet, und alles, was zurückgegeben wird, wird dem dekorierten Namen zugewiesen.

Vor der Implementierung der Decorator-Syntax (PEP 318) konnte das Gleiche erreicht werden, indem Funktions- und Klassenobjekte temporären Variablen zugewiesen wurden und dann explizit der Decorator aufgerufen und dann der Rückgabewert dem Funktionsnamen zugewiesen wurde. Dies scheint mehr Eingabe zu erfordern, und es stimmt, dass der Name der Dekoratorfunktion zweimal und die temporäre Variable mindestens dreimal verwendet wird, was leicht zu Fehlern führt. Das obige Beispiel entspricht:

def function():         # ①
  pass
function = decorator(function)  # ②
Nach dem Login kopieren

Dekorateure können gestapelt werden – die Reihenfolge der Anwendung erfolgt von unten nach oben oder von innen nach außen. Das heißt, die ursprüngliche Funktion wird als Parameter des ersten Parametrisierers verwendet und alles, was zurückgegeben wird, wird als Parameter des zweiten Dekorators verwendet ... Was auch immer der letzte Dekorator zurückgibt, wird an den Namen der ursprünglichen Funktion angehängt.

Die Decorator-Syntax wurde aufgrund ihrer Lesbarkeit gewählt. Da der Dekorator vor dem Funktionsheader angegeben wird und offensichtlich nicht Teil des Funktionskörpers ist, kann er nur auf die gesamte Funktion angewendet werden. Wenn dem Ausdruck ein @ vorangestellt wird, ist er zu offensichtlich, um ihn zu ignorieren (laut PEP schreit er einem ins Gesicht ... :)). Wenn mehrere Dekoratoren angewendet werden, erleichtert die Platzierung jedes Dekorators in einer anderen Zeile die Lesbarkeit.

Ersetzen und Anpassen des ursprünglichen Objekts
Dekoratoren können entweder dieselbe Funktion oder dasselbe Klassenobjekt oder ein völlig anderes Objekt zurückgeben. Im ersten Fall nutzt der Dekorateur die Tatsache aus, dass eine Funktion oder ein Klassenobjekt veränderbar ist, um Eigenschaften hinzuzufügen, z. B. das Hinzufügen einer Dokumentzeichenfolge zur Klasse. Der Dekorateur kann sogar nützliche Dinge tun, ohne das Objekt zu ändern, z. B. globale Register dekorierte Klasse im Register. Im zweiten Fall ist nichts unmöglich: Wenn die dekorierte Klasse oder Funktion durch etwas anderes ersetzt wird, kann das neue Objekt völlig anders sein. Dies ist jedoch nicht der Zweck von Dekorateuren: Sie sollen das dekorierte Objekt verändern, anstatt unerwartete Dinge zu tun. Wenn also eine Funktion während der Dekoration vollständig durch eine andere Funktion ersetzt wird, ruft die neue Funktion nach einiger Vorbereitung normalerweise die ursprüngliche Funktion auf. Wenn eine Klasse in eine neue Klasse umgewandelt wird, geht die neue Klasse in der Regel aus der dekorierten Klasse hervor. Wenn der Zweck eines Dekorators darin besteht, „jedes Mal“ etwas zu tun, beispielsweise jeden Aufruf der dekorierten Funktion zu protokollieren, ist nur die zweite Art von Dekorator verfügbar. Wenn andererseits die erste Kategorie ausreichend ist, ist es besser, sie zu verwenden, da sie einfacher ist.

Klassen- und Funktionsdekorateure implementieren
Die einzige Voraussetzung für den Dekorator ist, dass er mit einem einzigen Parameter aufgerufen werden kann. Das bedeutet, dass der Dekorator als reguläre Funktion oder als Klasse mit einer __call__-Methode und theoretisch sogar als Lambda-Funktion implementiert werden kann.

Vergleichen wir Funktionen und Klassenmethoden. Der Dekoratorausdruck (der Teil nach @) kann nur der Name sein. Der Nur-Namen-Ansatz ist nett (weniger Eingabe, sieht ordentlich aus usw.), ist aber nur möglich, wenn keine Notwendigkeit besteht, den Dekorator mit Parametern anzupassen. Der Dekorator der zu schreibenden Funktion kann auf die folgenden zwei Arten verwendet werden:

>>> 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
Nach dem Login kopieren

Diese beiden Dekoratoren gehören zur Kategorie der Rückgabe der dekorierten Funktion. Wenn sie neue Funktionen zurückgeben möchten, ist eine zusätzliche Verschachtelung erforderlich, im schlimmsten Fall drei Verschachtelungsebenen.

>>> 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
Nach dem Login kopieren

_wrapper-Funktion ist so definiert, dass sie alle Positions- und Schlüsselwortargumente akzeptiert. Normalerweise wissen wir nicht, welche Parameter die dekorierte Funktion akzeptiert, daher erstellt der Wrapper alles für die dekorierte Funktion. Eine unglückliche Konsequenz ist, dass explizite Parameter verwirrend sind.

Komplexe Dekoratoren, die als Klassen definiert sind, sind einfacher als Dekoratoren, die als Funktionen definiert sind. Wenn ein Objekt erstellt wird, darf die Methode __init__ nur None zurückgeben und der Typ des erstellten Objekts kann nicht geändert werden. Dies bedeutet, dass es keinen Sinn macht, die formatlose Form zu verwenden, wenn der Dekorator als Klasse definiert ist: Das endgültige dekorierte Objekt ist nur eine Instanz der dekorierten Klasse, die vom Konstruktoraufruf (Konstruktor) zurückgegeben wird, was nicht sehr hilfreich ist nützlich. Bei der Erörterung klassenbasierter Dekoratoren, bei denen Argumente in Dekoratorausdrücken angegeben werden, wird die Methode __init__ zum Erstellen des Dekorators verwendet.

>>> 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, () {}
Nach dem Login kopieren

In Klassen geschriebene Dekoratoren verhalten sich im Vergleich zu den normalen Regeln (PEP 8) eher wie Funktionen, sodass ihre Namen mit einem Kleinbuchstaben beginnen.

Tatsächlich macht es wenig Sinn, eine neue Klasse zu erstellen, die nur die dekorierte Funktion zurückgibt. Objekte sollten einen Status haben und diese Art von Dekorator ist nützlicher, wenn der Dekorator ein neues Objekt zurückgibt.

>>> 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) {}
Nach dem Login kopieren

Ein Dekorator wie dieser kann alles, weil er das dekorierte Funktionsobjekt und die Parameter ändern, die dekorierte Funktion aufrufen oder nicht und schließlich den Rückgabewert ändern kann.

复制原始函数的文档字符串和其它属性
当新函数被返回代替装饰前的函数时,不幸的是原函数的函数名,文档字符串和参数列表都丢失了。这些属性可以部分通过设置__doc__(文档字符串),__module__和__name__(函数的全称)、__annotations__(Python 3中关于参数和返回值的额外信息)移植到新函数上,这些工作可通过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
Nach dem Login kopieren

一件重要的东西是从可迁移属性列表中所缺少的:参数列表。参数的默认值可以通过__defaults__、__kwdefaults__属性更改,但是不幸的是参数列表本身不能被设置为属性。这意味着help(function)将显式无用的参数列表,使使用者迷惑不已。一个解决此问题有效但是丑陋的方式是使用eval动态创建wrapper。可以使用外部external模块自动实现。它提供了对decorator装饰器的支持,该装饰器接受wrapper并将之转换成保留函数签名的装饰器。

综上,装饰器应该总是使用functools.update_wrapper或者其它方式赋值函数属性。

标准库中的示例
首先要提及的是标准库中有一些实用的装饰器,有三种装饰器:

classmethod让一个方法变成“类方法”,即它能够无需创建实例调用。当一个常规方法被调用时,解释器插入实例对象作为第一个参数self。当类方法被调用时,类本身被给做第一个参数,一般叫cls。
类方法也能通过类命名空间读取,所以它们不必污染模块命名空间。类方法可用来提供替代的构建器(constructor):

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

这比用一大堆标记的__init__简单多了。
staticmethod应用到方法上让它们“静态”,例如,本来一个常规函数,但通过类命名空间存取。这在函数仅在类中需要时有用(它的名字应该以_为前缀),或者当我们想要用户以为方法连接到类时也有用——虽然对实现本身不必要。
property是对getter和setter问题Python风格的答案。通过property装饰的方法变成在属性存取时自动调用的getter。

>>> class A(object):
...  @property
...  def a(self):
...   "an important attribute"
...   return "a value"
>>> A.a                 
<property object at 0x...>
>>> A().a
&#39;a value&#39;
Nach dem Login kopieren

例如A.a是只读属性,它已经有文档了:help(A)包含从getter方法获取的属性a的文档字符串。将a定义为property使它能够直接被计算,并且产生只读的副作用,因为没有定义任何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
Nach dem Login kopieren

通过property装饰器取代带一个属性(property)对象的getter方法,以上代码起作用。这个对象反过来有三个可用于装饰器的方法getter、setter和deleter。它们的作用就是设定属性对象的getter、setter和deleter(被存储为fget、fset和fdel属性(attributes))。当创建对象时,getter可以像上例一样设定。当定义setter时,我们已经在area中有property对象,可以通过setter方法向它添加setter,一切都在创建类时完成。
之后,当类实例创建后,property对象和特殊。当解释器执行属性存取、赋值或删除时,其执行被下放给property对象的方法。
为了让一切一清二楚[^5],让我们定义一个“调试”例子:

>>> 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
Nach dem Login kopieren

属性(property)是对装饰器语法的一点扩展。使用装饰器的一大前提——命名不重复——被违反了,但是目前没什么更好的发明。为getter,setter和deleter方法使用相同的名字还是个好的风格。
一些其它更新的例子包括:

functools.lru_cache记忆任意维持有限 参数:结果 对的缓存函数(Python
3.2)
functools.total_ordering是一个基于单个比较方法而填充丢失的比较(ordering)方法(__lt__,__gt__,__le__等等)的类装饰器。
函数的废弃
比如说我们想在第一次调用我们不希望被调用的函数时在标准错误打印一个废弃函数警告。如果我们不想更改函数,我们可用装饰器

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__, &#39;is deprecated&#39;
    return self.func(*args, **kwargs)
Nach dem Login kopieren

也可以实现成函数:

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__, &#39;is deprecated&#39;
    return func(*args, **kwargs)
  return wrapper
Nach dem Login kopieren

while-loop移除装饰器
例如我们有个返回列表的函数,这个列表由循环创建。如果我们不知道需要多少对象,实现这个的标准方法如下:

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

只要循环体很紧凑,这很好。一旦事情变得更复杂,正如真实的代码中发生的那样,这就很难读懂了。我们可以通过yield语句简化它,但之后用户不得不显式调用嗯list(find_answers())。

我们可以创建一个为我们构建列表的装饰器:

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

然后函数变成这样:

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

插件注册系统
这是一个仅仅把它放进全局注册表中而不更改类的类装饰器,它属于返回被装饰对象的装饰器。

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(&#39;—&#39;, u&#39;\N{em dash}&#39;)
Nach dem Login kopieren

   

这里我们使用装饰器完成插件注册。我们通过一个名词调用装饰器而不是一个动词,因为我们用它来声明我们的类是WordProcessor的一个插件。plugin方法仅仅将类添加进插件列表。

关于插件自身说下:它用真正的Unicode中的破折号符号替代HTML中的破折号。它利用unicode literal notation通过它在unicode数据库中的名称(“EM DASH”)插入一个符号。如果直接插入Unicode符号,将不可能区分所插入的和源程序中的破折号。

更多深入理解Python中装饰器的用法相关文章请关注PHP中文网!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage