[Python!] - Decorator (1)

15885 ワード



Lmao
Okay.
gotta understand anyway.
I found the following explanation from the below link:
https://python-course.eu/advanced-python/decorators-decoration.php
We have two different kinds of decorators in Python:
  • Function decorators
  • Class decorators
  • A decorator in Python is any callable Python object that is used to modify a function or a class. A reference to a function "func"or a class "C"is passed to a decorator and the decorator returns a modified function or class. The modified functions or classes usually contain calls to the original function "func"or class "C".

    First steps to decorator


    First you have to know or remember that function names are references to functions and that we can assign multiple names to the same function:
    def succ(x):
        return x + 1
    successor = succ
    successor(10)
    OUTPUT: 11
    succ(10)
    OUTPUT: 11This means that we have two names, i.e. "succ"and "successor"for the same function. The next important fact is that we can delete either "succ"or "successor"without deleting the function itself.
    del succ
    successor(10)
    OUTPUT: 11

    機能内部の機能(ネスト関数)

    def f():
        def g():
            print("Hi, it's me 'g'")
            print("Thanks for calling me")
        print("This is the function 'f'")
        print("I am calling 'g' now:")
        g()
    f()
    OUTPUT:
    This is the function 'f'
    I am calling 'g' now:
    Hi, it's me 'g'
    Thanks for calling me
    Another example using "proper"return statements in the functions:
    def temperature(t):
        def celsius2fahrenheit(x):
            return 9 * x / 5 + 32
        result = "It's " + str(celsius2fahrenheit(t)) + " degrees!" 
        return result
    print(temperature(20))
    OUTPUT:
    It's 68.0 degrees!
    The following example is about the factorial function, which we previously defined as follows:
    def factorial(n):
        """ calculates the factorial of n, 
            n should be an integer and n <= 0 """
        if n == 0:
            return 1
        else:
            return n * factorial(n-1)
    What happens if someone passes a negative value or a float number to this function? It will never end. You might get the idea to check that as follows:
    def factorial(n):
        """ calculates the factorial of n, 
            n should be an integer and n <= 0 """
        if type(n) == int and n >=0:
            if n == 0:
                return 1
            else:
                return n * factorial(n-1)
        else:
            raise TypeError("n has to be a positive integer or zero")
    If you call this function with 4 '' for example, i.e. factorial (4) '', the first thing that is checked is whether it is my positive integer. In principle, this makes sense. The "problem"now appears in the recursion step. Now factorial (3) '' is called. This call and all others also check whether it is a positive whole number. But this is unnecessary: If you subtract the value 1 '' from a positive whole number, you get a positive whole number or `` 0 '' again. So both well-defined argument values for our function.
    With a nested function (local function) one can solve this problem elegantly:
    def factorial(n):
        """ calculates the factorial of n, 
            n should be an integer and n <= 0 """
        def inner_factorial(n):
            if n == 0:
                return 1
            else:
                return n * inner_factorial(n-1)
        if type(n) == int and n >=0:
            return inner_factorial(n)
        else:
            raise TypeError("n should be a positve int or 0")

    Functions as Parameters


    If you solely look at the previous examples, this doesn't seem to be very useful. It gets useful in combination with two further powerful possibilities of Python functions. Due to the fact that every parameter of a function is a reference to an object and functions are objects as well, we can pass functions - or better "references to functions"- as parameters to a function. We will demonstrate this in the next simple example:
    def g():
        print("Hi, it's me 'g'")
        print("Thanks for calling me")
    def f(func):
        print("Hi, it's me 'f'")
        print("I will call 'func' now")
        func()
    f(g)
    OUTPUT:
    Hi, it's me 'f'
    I will call 'func' now
    Hi, it's me 'g'
    Thanks for calling me
    You may not be satisfied with the output. 'f' should write that it calls 'g' and not 'func'. Of course, we need to know what the 'real' name of func is. For this purpose, we can use the attribute name, as it contains this name:
    def g():
        print("Hi, it's me 'g'")
        print("Thanks for calling me")
    def f(func):
        print("Hi, it's me 'f'")
        print("I will call 'func' now")
        func()
        print("func's real name is " + func.__name__) 
    f(g)
    OUTPUT:
    Hi, it's me 'f'
    I will call 'func' now
    Hi, it's me 'g'
    Thanks for calling me
    func's real name is g
    The output explains what's going on once more. Another example:
    import math
    def foo(func):
        print("The function " + func.__name__ + " was passed to foo")
        res = 0
        for x in [1, 2, 2.5]:
            res += func(x)
        return res
    print(foo(math.sin))
    print(foo(math.cos))
    OUTPUT:
    The function sin was passed to foo
    2.3492405557375347
    The function cos was passed to foo
    -0.6769881462259364 

    Functions returning Functions


    We write a function with the nearly self-explanatory name greeting_func_gen. So this function returns (or generates) functions which can be used to create people in different languages, i.e. German, French, Italian, Turkish, and Greek:
    def greeting_func_gen(lang):
        def customized_greeting(name):
            if lang == "de":   # German
                phrase = "Guten Morgen "
            elif lang == "fr": # French
                phrase = "Bonjour "
            elif lang == "it": # Italian
                phrase = "Buongiorno "
            elif lang == "tr": # Turkish
                phrase = "Günaydın "
            elif lang == "gr": # Greek
                phrase = "Καλημερα "
            else:
                phrase = "Hi "
            return phrase + name + "!"
        return customized_greeting
    say_hi = greeting_func_gen("tr")
    print(say_hi("Gülay"))    # this Turkish name means "rose moon" by the way
    OUTPUT:
    Günaydın Gülay!
    It is getting more useful and at the same time more mathematically oriented in the following example. We will implement a polynomial "factory"function now. We will start with writing a version which can create polynomials of degree 2.

    The Python implementation as a polynomial factory function can be written like this:
    def polynomial_creator(a, b, c):
        def polynomial(x):
            return a * x**2 + b * x + c
        return polynomial
    p1 = polynomial_creator(2, 3, -1)
    p2 = polynomial_creator(-1, 2, 1)
    for x in range(-2, 2, 1):
        print(x, p1(x), p2(x))
    OUTPUT:
    -2 1 -7
    -1 -2 -2
    0 -1 1
    1 4 2
    We can generalize our factory function so that it can work for polynomials of arbitrary degree:
    def polynomial_creator(*coefficients):
        """ coefficients are in the form a_n, ... a_1, a_0 
        """
        def polynomial(x):
            res = 0
            for index, coeff in enumerate(coefficients[::-1]):
                res += coeff * x** index
            return res
        return polynomial
    p1 = polynomial_creator(4)
    p2 = polynomial_creator(2, 4)
    p3 = polynomial_creator(1, 8, -1, 3, 2)
    p4  = polynomial_creator(-1, 2, 1)
    for x in range(-2, 2, 1):
        print(x, p1(x), p2(x), p3(x), p4(x))
    OUTPUT:
    -2 4 0 -56 -7
    -1 4 2 -9 -2
    0 4 4 2 1
    1 4 6 13 2
    The function p3 implements, for example, the following polynomial:

    The polynomial function inside of our decorator polynomial_creator can be implemented more efficiently. We can factorize it in a way so that it doesn't need any exponentiation.
    Factorized version of a general polynomial without exponentiation:

    Implementation of our polynomial creator decorator avoiding exponentiation:
    def polynomial_creator(*coeffs):
        """ coefficients are in the form a_n, a_n_1, ... a_1, a_0 
        """
        def polynomial(x):
            res = coeffs[0]
            for i in range(1, len(coeffs)):
                res = res * x + coeffs[i]
            return res
        return polynomial
    p1 = polynomial_creator(4)
    p2 = polynomial_creator(2, 4)
    p3 = polynomial_creator(1, 8, -1, 3, 2)
    p4 = polynomial_creator(-1, 2, 1)
    for x in range(-2, 2, 1):
        print(x, p1(x), p2(x), p3(x), p4(x))
    OUTPUT:
    -2 4 0 -56 -7
    -1 4 2 -9 -2
    0 4 4 2 1
    1 4 6 13 2

    A simple decorator


    Now we have everything ready to define our first simple decorator:
    def our_decorator(func):
        def function_wrapper(x):
            print("Before calling " + func.__name__)
            func(x)
            print("After calling " + func.__name__)
        return function_wrapper
    def foo(x):
        print("Hi, foo has been called with " + str(x))
    print("We call foo before decoration:")
    foo("Hi")
    print("We now decorate foo with f:")
    foo = our_decorator(foo)
    print("We call foo after decoration:")
    foo(42)
    OUTPUT:
    We call foo before decoration:
    Hi, foo has been called with Hi
    We now decorate foo with f:
    We call foo after decoration:
    Before calling foo
    Hi, foo has been called with 42
    After calling foo
    If you look at the output of the previous program, you can see what's going on. After the decoration "foo = our_decorator(foo)", foo is a reference to the 'function_wrapper'. 'foo' will be called inside of 'function_wrapper', but before and after the call some additional code will be executed, i.e. in our case two print functions.

    The usual syntax for decorators in python


    The decoration in Python is usually not performed in the way we did it in our previous example, even though the notation foo = our_decorator(foo) is catchy and easy to grasp. This is the reason, why we used it! You can also see a design problem in our previous approach. "foo"existed in the same program in two versions, before decoration and after decoration.
    We will do a proper decoration now. The decoration occurrs in the line before the function header. The "@"is followed by the decorator function name.
    We will rewrite now our initial example. Instead of writing the statement foo = our_decorator(foo)we can write @our_decorator But this line has to be directly positioned in front of the decorated function. The complete example looks like this now:
    def our_decorator(func):
        def function_wrapper(x):
            print("Before calling " + func.__name__)
            func(x)
            print("After calling " + func.__name__)
        return function_wrapper
    @our_decorator
    def foo(x):
        print("Hi, foo has been called with " + str(x))
    foo("Hi")
    OUTPUT:
    Before calling foo
    Hi, foo has been called with Hi
    After calling foo
    We can decorate every other function which takes one parameter with our decorator 'our_decorator'. We demonstrate this in the following. We have slightly changed our function wrapper, so that we can see the result of the function calls:
    def our_decorator(func):
        def function_wrapper(x):
            print("Before calling " + func.__name__)
            res = func(x)
            print(res)
            print("After calling " + func.__name__)
        return function_wrapper
    @our_decorator
    def succ(n):
        return n + 1
    succ(10)
    OUTPUT:
    Before calling succ
    11
    After calling succ
    It is also possible to decorate third party functions, e.g. functions we import from a module. We can't use the Python syntax with the "at"sign in this case:
    from math import sin, cos
    def our_decorator(func):
        def function_wrapper(x):
            print("Before calling " + func.__name__)
            res = func(x)
            print(res)
            print("After calling " + func.__name__)
        return function_wrapper
    sin = our_decorator(sin)
    cos = our_decorator(cos)
    for f in [sin, cos]:
        f(3.1415)
    OUTPUT:
    Before calling sin
    9.265358966049026e-05
    After calling sin
    Before calling cos
    -0.9999999957076562
    After calling cos
    All in all, we can say that a decorator in Python is a callable Python object that is used to modify a function, method or class definition. The original object, the one which is going to be modified, is passed to a decorator as an argument. The decorator returns a modified object, e.g. a modified function, which is bound to the name used in the definition.
    The previous function_wrapper works only for functions with exactly one parameter. We provide a generalized version of the function_wrapper, which accepts functions with arbitrary parameters in the following example:
    from random import random, randint, choice
    def our_decorator(func):
        def function_wrapper(*args, **kwargs):
            print("Before calling " + func.__name__)
            res = func(*args, **kwargs)
            print(res)
            print("After calling " + func.__name__)
        return function_wrapper
    random = our_decorator(random)
    randint = our_decorator(randint)
    choice = our_decorator(choice)
    random()
    randint(3, 8)
    choice([4, 5, 6])
    OUTPUT:
    Before calling random
    0.3206237466802222
    After calling random
    Before calling randint
    6
    After calling randint
    Before calling choice
    5
    After calling choice