What does the “at” (@) symbol do in Python?

前端 未结 12 1314
萌比男神i
萌比男神i 2020-11-22 01:57

I\'m looking at some Python code which used the @ symbol, but I have no idea what it does. I also do not know what to search for as searching Python docs or Goo

12条回答
  •  小鲜肉
    小鲜肉 (楼主)
    2020-11-22 02:37

    Decorators were added in Python to make function and method wrapping (a function that receives a function and returns an enhanced one) easier to read and understand. The original use case was to be able to define the methods as class methods or static methods on the head of their definition. Without the decorator syntax, it would require a rather sparse and repetitive definition:

    class WithoutDecorators:
    def some_static_method():
        print("this is static method")
    some_static_method = staticmethod(some_static_method)
    
    def some_class_method(cls):
        print("this is class method")
    some_class_method = classmethod(some_class_method)
    

    If the decorator syntax is used for the same purpose, the code is shorter and easier to understand:

    class WithDecorators:
        @staticmethod
        def some_static_method():
            print("this is static method")
    
        @classmethod
        def some_class_method(cls):
            print("this is class method")
    

    General syntax and possible implementations

    The decorator is generally a named object ( lambda expressions are not allowed) that accepts a single argument when called (it will be the decorated function) and returns another callable object. "Callable" is used here instead of "function" with premeditation. While decorators are often discussed in the scope of methods and functions, they are not limited to them. In fact, anything that is callable (any object that implements the _call__ method is considered callable), can be used as a decorator and often objects returned by them are not simple functions but more instances of more complex classes implementing their own __call_ method.

    The decorator syntax is simply only a syntactic sugar. Consider the following decorator usage:

    @some_decorator
    def decorated_function():
        pass
    

    This can always be replaced by an explicit decorator call and function reassignment:

    def decorated_function():
        pass
    decorated_function = some_decorator(decorated_function)
    

    However, the latter is less readable and also very hard to understand if multiple decorators are used on a single function. Decorators can be used in multiple different ways as shown below:

    As a function

    There are many ways to write custom decorators, but the simplest way is to write a function that returns a subfunction that wraps the original function call.

    The generic patterns is as follows:

    def mydecorator(function):
        def wrapped(*args, **kwargs):
            # do some stuff before the original
            # function gets called
            result = function(*args, **kwargs)
            # do some stuff after function call and
            # return the result
            return result
        # return wrapper as a decorated function
        return wrapped
    

    As a class

    While decorators almost always can be implemented using functions, there are some situations when using user-defined classes is a better option. This is often true when the decorator needs complex parametrization or it depends on a specific state.

    The generic pattern for a nonparametrized decorator as a class is as follows:

    class DecoratorAsClass:
        def __init__(self, function):
            self.function = function
    
        def __call__(self, *args, **kwargs):
            # do some stuff before the original
            # function gets called
            result = self.function(*args, **kwargs)
            # do some stuff after function call and
            # return the result
            return result
    

    Parametrizing decorators

    In real code, there is often a need to use decorators that can be parametrized. When the function is used as a decorator, then the solution is simple—a second level of wrapping has to be used. Here is a simple example of the decorator that repeats the execution of a decorated function the specified number of times every time it is called:

    def repeat(number=3):
    """Cause decorated function to be repeated a number of times.
    
    Last value of original function call is returned as a result
    :param number: number of repetitions, 3 if not specified
    """
    def actual_decorator(function):
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(number):
                result = function(*args, **kwargs)
            return result
        return wrapper
    return actual_decorator
    

    The decorator defined this way can accept parameters:

    >>> @repeat(2)
    ... def foo():
    ...     print("foo")
    ...
    >>> foo()
    foo
    foo
    

    Note that even if the parametrized decorator has default values for its arguments, the parentheses after its name is required. The correct way to use the preceding decorator with default arguments is as follows:

    >>> @repeat()
    ... def bar():
    ...     print("bar")
    ...
    >>> bar()
    bar
    bar
    bar
    

    Finally lets see decorators with Properties.

    Properties

    The properties provide a built-in descriptor type that knows how to link an attribute to a set of methods. A property takes four optional arguments: fget , fset , fdel , and doc . The last one can be provided to define a docstring that is linked to the attribute as if it were a method. Here is an example of a Rectangle class that can be controlled either by direct access to attributes that store two corner points or by using the width , and height properties:

    class Rectangle:
        def __init__(self, x1, y1, x2, y2):
            self.x1, self.y1 = x1, y1
            self.x2, self.y2 = x2, y2
    
        def _width_get(self):
            return self.x2 - self.x1
    
        def _width_set(self, value):
            self.x2 = self.x1 + value
    
        def _height_get(self):
            return self.y2 - self.y1
    
        def _height_set(self, value):
            self.y2 = self.y1 + value
    
        width = property(
            _width_get, _width_set,
            doc="rectangle width measured from left"
        )
        height = property(
            _height_get, _height_set,
            doc="rectangle height measured from top"
        )
    
        def __repr__(self):
            return "{}({}, {}, {}, {})".format(
                self.__class__.__name__,
                self.x1, self.y1, self.x2, self.y2
        )
    

    The best syntax for creating properties is using property as a decorator. This will reduce the number of method signatures inside of the class and make code more readable and maintainable. With decorators the above class becomes:

    class Rectangle:
        def __init__(self, x1, y1, x2, y2):
            self.x1, self.y1 = x1, y1
            self.x2, self.y2 = x2, y2
    
        @property
        def width(self):
            """rectangle height measured from top"""
            return self.x2 - self.x1
    
        @width.setter
        def width(self, value):
            self.x2 = self.x1 + value
    
        @property
        def height(self):
            """rectangle height measured from top"""
            return self.y2 - self.y1
    
        @height.setter
        def height(self, value):
            self.y2 = self.y1 + value
    

提交回复
热议问题