A decorator

Python decorators can be roughly divided into no-parameter decorators and parameterized decorators. Let’s find out


Use multiple decorators together

""" Specific Use of decorators ""

print("# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- multiple decorators used together -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #")


# bold
def make_bold(fn) :
    def wrapped() :
        return "<b>" + fn() + "</b>"

    return wrapped


# italics
def make_italic(fn) :
    def wrapped() :
        return "<i>" + fn() + "</i>"

    return wrapped


@make_bold
def test1() :
    return "hello world-1"


@make_italic
def test2() :
    return "hello world-2"


# Use two decorators together
@make_bold
@make_italic
def test3() :
    return "hello world-3"


print(test1())
print(test2())
print(test3())

Copy the code


Running results:

# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- multiple decorators used together -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #<b>hello world-1</b>
<i>hello world-2</i>
<b><i>hello world-3</i></b>
Copy the code


You can see that the result of decorating Test3 is italic first and then bold. First, the program executes from the top down. When it encounters @make_bold, it passes the following function reference to the make_bold function, but below it is another decorator, @make_italic, The decorator also passes the following function test3 to make_italic by assigning the wrapped return value of make_ITALic to test3, and then passing the new test3 function to make_bold. So it’s italic first, then bold.

May understand up will be very round, look at the following results of this procedure you will understand more, it is best to try personally, strengthen understanding.

""" Specific Use of decorators ""

print("# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- multiple decorators used together -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #")


# bold
def make_bold(fn) :
    print("make_bold called")
    print(fn.__name__)

    def wrapped() :
        return "<b>" + fn() + "</b>"

    return wrapped


# italics
def make_italic(fn) :
    print("make_italic called")
    print(fn.__name__)

    def wrapped() :
        return "<i>" + fn() + "</i>"

    return wrapped


# Use two decorators together
@make_bold
@make_italic
def test3() :
    return "hello world-3"


print(test3())

Copy the code


Running results:

# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- multiple decorators used together -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #
make_italic called
function name: test3
    
make_bold called
function name: wrapped
<b><i>hello world-3</i></b>
Copy the code

Test3 = wrapped after the @make_italic decorator, and test3 is wrapped. So the second time fn.__name__, the print is wrapped.


Various decorator examples

Decorator with no arguments

print("# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- no parameter decorator -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #")
from time import ctime, sleep


Print the current running time
def cur_time(func) :
    def wrapped_func() :
        print("%s called at %s" % (func.__name__, ctime()))
        func()

    return wrapped_func


@cur_time
def foo() :
    print("I am foo")


foo()
sleep(2)
foo()
Copy the code


The code above understands that the decorator execution behavior can be understood as


After # foo is assigned to func as an argument, Foo receives wrapped_func that points to the timefun return
foo = timefun(foo)

# call foo(), equivalent to wrapped_func()
foo()

The inner function wrapped_func is referenced, so the outer function's func variable (free variable) is not freed
# func holds the original foo function object
Copy the code


Running results:

# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- no parameter decorator -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #
foo called at Thu Apr 15 20:40:35 2021
I am foo
foo called at Thu Apr 15 20:40:37 2021
I am foo
Copy the code


The decorated function has arguments

print("# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- be illuminative function parameters -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #")
from time import ctime, sleep


def cur_time(func) :

    Note that function arguments are accepted here
    def wrapped_func(a, b) :
        print("%s called at %s" % (func.__name__, ctime()))
        print(a, b)
        func(a, b)

    return wrapped_func


@cur_time
def foo(a, b) :
    print(a + b)


foo(3.5)
sleep(2)
foo(2.4)
Copy the code


Running results:

# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- be illuminative function parameters -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #
foo called at Thu Apr 15 20:47:18 2021
3 5
8
foo called at Thu Apr 15 20:47:20 2021
2 4
6
Copy the code


Decorated functions have variable length arguments

print("# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- by illuminative function has a variable length argument -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #")
from time import ctime, sleep


def cur_time(func) :

    *arg **kwargs is used to accept variable length arguments
    def wrapped_func(*args, **kwargs) :
        print("%s called at %s" % (func.__name__, ctime()))
        print("args:", args)
        print("kwargs:", kwargs)
        func(*args, **kwargs)

    return wrapped_func


@cur_time
def foo(a, b, c) :
    print(a + b + c)


foo(1.3.5)
sleep(2)
foo(2.4, c=6)
Copy the code


Running results:

# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- by illuminative function has a variable length argument -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #
foo called at Thu Apr 15 21:10: 022021
args: (1.3.5)
kwargs: {}
9

foo called at Thu Apr 15 21:10: 052021
args: (2.4)
kwargs: {'c': 6}
12
Copy the code

For example, foo(2, 4, c=6), 2, 4 are passed to args as a tuple, while c=6 is passed to Kwargs as a dictionary. Note that the parameter names must be preceded by * when defining the function parameters. Parameter names can be customized but not less than *, otherwise it is not an indeterminate parameter function. For example, *args can be replaced by *params, and *kwargs can be replaced by * KWS. But we’d better not change it.


Return in decorator

print("# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- a decorator's return -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #")
from time import ctime, sleep


def cur_time(func) :
    def wrapped_func() :
        print("%s called at %s" % (func.__name__, ctime()))
        func()

    return wrapped_func


@cur_time
def foo() :
    print("I am foo")


@cur_time
def get_info() :
    return '----hei hei---'


foo()
sleep(2)
foo()

print(get_info())
Copy the code


Execution Result:

# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- a decorator's return -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #
foo called at Thu Apr 15 21:27:52 2021
I am foo

foo called at Thu Apr 15 21:27:54 2021
I am foo

get_info called at Thu Apr 15 21:27:54 2021
None
Copy the code


If you modify the decorator as follows

def cur_time(func) :
    def wrapped_func() :
        print("%s called at %s" % (func.__name__, ctime()))
        ret = func() Receive the return value of the function first
        return ret   # Then go back out

    return wrapped_func
Copy the code


Run result:

# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- a decorator's return -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #
foo called at Thu Apr 15 21:32:43 2021
I am foo

foo called at Thu Apr 15 21:32:45 2021
I am foo

get_info called at Thu Apr 15 21:32:45 2021
----hei hei---
Copy the code


Description:

In general, to make decorators more versatile, you can have a return


Decorator with parameters

print("# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- a decorator parameters -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #")
from time import ctime, sleep


def time_arg(pre="hello") :

    def cur_time(func) :
        def wrapped_func() :
            print("%s called at %s %s" % (func.__name__, ctime(), pre))
            return func()

        return wrapped_func

    return cur_time


@time_arg("hui")
def foo() :
    print("I am foo")


@time_arg("python")
def goo() :
    print("I am goo")


foo()
sleep(2)
foo()

goo()
sleep(2)
goo()

Copy the code


The decoration process is as follows

1.First call time_arg ("hui")

2.The steps1The resulting return value, cur_time, is returned, and the decorator @time_arg('hui') becomes @cur_time3.Pass foo to cur_time3.Return the result of cur_time(foo), wrapped_func4.Make foo = wrapped_fun, that is, foo now points to wrapped_funcCopy the code

Can be understood as

foo() == timef_arg("hui")(foo)()
goo() == timef_arg("python")(goo)()
Copy the code


Remember that XXX () is the XXX call, and @decorate executes the function that you pass along as an argument to.


Class decorator (extension)

The decorator function is actually an interface constraint that must take a Callable object as an argument and then return a Callable object. Callable objects are generally functions in Python, but there are exceptions. An object is callable whenever it overrides the __call__() method.

  • callableIt’s an object that can be called and executed
class Test() :
    def __call__(self) :
        print('call me! ')

t = Test()
t()  # output call me
Copy the code


Class decorator Demo

class Test(object) :
    
    def __init__(self, func) :
        print("-- initialize --")
        print("func name is %s" % func.__name__)
        self.__func = func
        
    def __call__(self) :
        print("-- Function in decorator --")
        self.__func()

@Test
def test() :
    print("----test---")
test()

Copy the code


Description:

1.When you decorate the Test function with Test as a decorator, an instance of Test is created and the name of the function is passed as an argument to the __init__ method, where the __func attribute points to the function to which Test points2.Test refers to the instance object created with test3.When you call test(), you call the object (), so the object's __call__ method is called4.In order to be able to call the function body referred to by test in the __call__ method, an instance property is required to hold the reference to the function body in the __init__ method, hence the self.__func = func line, The function body before test can be called in the __call__ methodCopy the code


The running results are as follows:

-- Initializing -- func nameisTest -- Function in decorator --- ----test--Copy the code


The source code

The source code has been uploaded to Gitee PythonKnowledge: PythonKnowledge Repository.

✍ code word is not easy, the long journey always feeling, praise again go line, also hope you heroes support ❤️


The public,

Create a new folder X

Nature took tens of billions of years to create our real world, while programmers took hundreds of years to create a completely different virtual world. We knock out brick by brick with a keyboard and build everything with our brains. People see 1000 as authority. We defend 1024. We are not keyboard warriors, we are just extraordinary builders of ordinary world.