A deep understanding of the usage of the Python modifier and a deep understanding of python

Source: Internet
Author: User

A deep understanding of the usage of the Python modifier and a deep understanding of python

Because functions or classes are all objects, they can also be passed everywhere. They are mutable objects and can be changed. Decorator is the act of changing a function or class object after it is created but bound to a name ).

There are two meanings hidden after the "decorator": one is that the function plays a decorative role, for example, to perform real work, and the other is an expression attached to the decorator syntax, for example, the at symbol and the name of the decoration function.

Functions can be decorated using the function modifier Syntax:

@decorator       # ②def function():    # ①  pass
Functions are defined in a standard way. ①
Expression ② defined as the prefix of the decorator function. The part after @ must be a simple expression, usually just the name of a function or class. This part is evaluated first. After the defined functions below are ready, the decorator is called as a single parameter by the newly defined function object. The value returned by the decorator is attached to the decorated function name.
The decorator can be applied to functions and classes. The class semantics is very clear-the class definition is used as a parameter to call the decorator. No matter what is returned, the decorated name is assigned. Before the decorator syntax implementation (PEP 318), assign functions and class objects to temporary variables, explicitly call the decorator, and then assign the return value to the function name, you can do the same thing. This seems to require more words. It is also true that the modifier function name is used twice and the temporary variable must be used at least three times, which is prone to errors. The above example is equivalent:
def function():         # ①  passfunction = decorator(function)  # ②
The decorator can stack an application from bottom to top or from inside to outside. That is to say, the initial function is treated as the parameter of the first parameterter, and whatever is returned is used as the parameter of the second annotator ...... No matter what the last modifier returns, it is attached to the name of the original function.

The modifier syntax is selected for readability. Because the decorator is specified before the function header, it is obviously not part of the function body and can only work for the entire function. The expression prefixed with @ makes it obvious that it cannot be ignored (according to PEP, it is called on your face ...... :)). When multiple decorators are applied, it is easy to read each one on a different line.

Replace and adjust the original object
The decorator Can either return the same function or class object or return completely different objects. In the first case, the decorator uses a function or class object to variable attributes, for example, adding a document string (docstring) to the class ). the decorator can even do useful things without changing objects, such as registering a decoration class in the Global Registry. In the second case, it is omnipotent: new objects can be completely different when different things replace decorated classes or functions. However, this is not the purpose of the decorator: they are intended to change the decoration objects rather than doing unexpected things. Therefore, when a function is completely replaced with different functions during decoration, the new function usually calls the original function after some preparation work. Similarly, when a class is decorated into a new class, the new class usually comes from the decoration class. When the purpose of the decorator is "what is done every time", like recording every call to the decorated function, only the second type of decorator is available. On the other hand, if the first class is sufficient, it is better to use it because it is simpler.

Implementation class and function decorators
The only requirement for the decorator is that it can be called with a single parameter. This means that the decorator can be implemented as a regular function or a class with the _ call _ method. Theoretically, it can even be used as a lambda function.

Let's compare functions and class methods. The decorator expression (@) can be just a name. Only the name method is good (less typing and neat looking), but it is only possible if you do not need to use a parameter to customize the decorator. The decorator of the function to be written can be in the following two ways:

>>> 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

The two decorators belong to the class that returns the decorated function. If they want to return new functions, additional Nesting is required. In the worst case, three layers of nesting are required.

>>> 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

The _ wrapper function is defined to accept all location and keyword parameters. Generally, we do not know which parameters will be accepted by the decoration function, so wrapper will pass everything to the decoration function. An unfortunate result is that explicit parameters are confusing.

Compared with the function-defined decorator, it is easier to define a complex decorator as a class. When an object is created, the __init _ method can only return None. The type of the created object cannot be changed. This means that when the decorator is defined as a class, it makes no sense to use the form without parameters: the final object to be decorated is only an instance of the decoration class, And the constructor calls and returns, not very useful. The class-based decorator that gives parameters in the decoration expression is discussed. The __init _ method is used to build the decorator.

>>> 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, () {}

Compared with the regular rules (PEP 8), the class-based decorator performs more like a function, so their names start with lowercase letters.

In fact, it makes no sense to create a new class that only returns the decorated function. The object should be stateful, which is more useful when the decorator returns a new object.

>>> 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) {}

A decorator like this can do anything because it can change the decorated function objects and parameters, call the decorated function or do not call the function, and finally change the return value.

Copy the document string and other attributes of the original function.
When a new function is returned to replace the previously decorated function, the original function name is unfortunately lost in both the document string and parameter list. These attributes can be partially set by _ doc _ (document string) ,__ module _ and _ name _ (full name of the function), _ annotations _ (additional information about parameters and returned values in Python 3) is transplanted to the new function. update_wrapper is automatically completed.

>>> 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

One important thing is the list of parameters missing from the list of migratable attributes. The default values of parameters can be changed through the _ defaults _ and _ kwdefaults _ attributes. Unfortunately, the parameter list itself cannot be set as a property. This means that the help (function) will display a list of useless parameters, confusing users. One effective but ugly way to solve this problem is to use eval to dynamically create wrapper. The external module can be used for automatic implementation. It provides support for the decorator modifier, which accepts wrapper and converts it into a modifier that retains the function signature.

In summary, the decorator should always use functools. update_wrapper or other methods to assign values to function attributes.

Examples in the standard library
The first thing to mention is that there are some practical decorators in the standard library. There are three kinds of decorators:

Classmethod converts a method to a class method, that is, it can be called without creating an instance. When a common method is called, the interpreter inserts an instance object as the first parameter self. When a class method is called, the class itself is given the first parameter, usually cls.
Class methods can also be read through the class namespace, so they do not need to pollute the module namespace. The class method can be used to provide an alternative constructor ):

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

This is much easier than _ init _ with a lot of tags.
Staticmethod is applied to methods to make them "static". For example, if a common function is used, it is accessed through a class namespace. This is useful only when the function is needed in the class (its name should be prefixed with _), or when we want the user to think that the method is useful when connecting to the class-although not necessary for the Implementation itself.
Property is the Python-style answer to the getter and setter questions. The property decoration method is changed to the getter automatically called during attribute access.

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


For example, A. a is A read-only attribute and has a document: help (A) contains the document string of attribute a obtained from the getter method. Define a as property so that it can be directly computed and cause read-only side effects because no setter is defined.
To get setter and getter, two methods are required. The following syntax is preferred from 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

Use the property modifier to replace the getter method with a property object. The above Code takes effect. This object has three methods, getter, setter, and deleter, that can be used for the decorator. They are used to set the getter, setter, and deleter attributes of attribute objects (stored as fget, fset, and fdel attributes (attributes )). When creating an object, getter can be set as in the previous example. When defining a setter, we already have a property object in the area. You can use the setter method to add a setter to it. Everything is done when you create a class.
After the class instance is created, the property object and special. When the interpreter executes attribute access, value assignment, or deletion, it executes the method that is delegated to the property object.
To make everything clear [^ 5], Let's define an example of "debugging:

>>> 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 is a little extension of the modifier syntax. One of the prerequisites for using the decorator-naming is not repeated-is in violation, but there is no better invention at present. It is a good style for getter, setter and deleter methods to use the same name.
Examples of other updates include:

Functools. lru_cache memory arbitrary maintenance limited parameter: Result pair cache function (Python
3.2)
Functools. total_ordering is a class decorator that fills in the lost comparison (ordering) method (_ lt __,__ gt __,__ le _) based on a single comparison method.
Function waste
For example, we want to print an abandon function Warning in a standard error when we call a function that we do not want to be called for the first time. If we don't want to change the function, we can use the decorator.

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)

You can also implement the following functions:

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 remove decorator
For example, we have a function that returns a list, which is created in a loop. If we do not know how many objects are needed, the standard method to implement this is as follows:

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

As long as the loop body is very compact, this is good. Once things become more complex, as they happen in real code, it is hard to understand. We can simplify it using the yield statement, but then the user has to explicitly call the well list (find_answers ()).

We can create a decorator that builds a list for us:

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

Then the function becomes like this:

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

Agent Registration System
This is a class decorator that only puts it into the Global Registry without changing the class. It belongs to the decorator that returns the decorated object.

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}')

Here we use the decorator to complete plug-in registration. We call the decorator through a noun instead of a verb, because we use it to declare that our class is a plug-in of WordProcessor. The plugin method only adds classes to the plug-in list.

About the plug-in itself: it uses the real Unicode break symbol to replace the break in HTML. It uses unicode literal notation to insert a symbol through its name in the unicode database ("em dash. If Unicode characters are inserted directly, it is impossible to distinguish between the inserted and the broken signs in the source program.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.