Python-Functional Programming decorator (I.)

Source: Internet
Author: User
Tags closure function definition wrapper

1. Open Closure principle

In simple terms, it is对扩展开放,对修改封闭

In object-oriented programming, various functions are often defined.

The use of a function is divided into the definition phase and the usage stage, and after a function definition is complete, it may be called in many places.

This means that if the definition phase code of a function is modified, there will be a lot of impact, and it is easy to change the entire system because of a small local modification,

Therefore, for the modern process development industry, a system once on-line, the system's source code must not be able to change again.

However, after a set of systems on-line, with the increasing number of users, will certainly be a set of system extensions to add new features.

At this time, but also can not modify the original system source code, but also for the original system development to add new features, which is the program development industry, the open closure principle, it is necessary to use the adorner.

2. What is an adorner??

An adorner, as the name implies, is a tool for decorating and modifying other objects.

So the adorner can be any callable object, and the decorated object can be any callable object.

3. The role of the adorner

Add new functionality to a decorated object without modifying the source code of the decorated object and calling the method

Principle:

1.不修改被装饰对象的源代码2.不修改被装饰对象的调用方式

Goal:

为被装饰对象添加新功能
4. Definition and use of adorners

Look at the following code:

    import time    import random        def index():        time.sleep(random.randrange(1,5))        print("welcome to index page")        index()

The function of the index function is that the program prints a word after 1-5 seconds of random sleep

Now you want to add a new function to the INDEX function: How do you do it by counting the run time of the index function??

Modify the index function as follows:

    import time    import random        def index():        start_time=time.time()        time.sleep(random.randrange(1,5))        print("welcome to index page")        end_time=time.time()        print("cost time: %s" %(end_time - start_time))        index()

Run the program and execute the results as follows:

welcome to index pagecost time: 2.000999927520752    

As you can see, adding new functionality to the index function is true, but it violates the open closure principle.

If you want to add a new feature to the index function, you will need to use the adorner when it conforms to the open closure principle.

Modify Code

    import time    import random        def index():        time.sleep(random.randrange(1,5))        print("welcome to index page")        def timmer():        def inner():            start_time=time.time()            index()            end_time=time.time()            print("run time: %s " %(end_time-start_time))        return inner        f=timmer()    f()

Run the program to see the results of the execution

As can be seen from the program execution results, the run time of the index function has been counted.

But look at the source code can be known that the index function source code is not really modified, but the call method of index has been modified

And another problem is that the Timmer can only be used to decorate the index function, if you want to count the time of other functions, but also redefine the other adorners, it is too inflexible.

Modify the above code

    import time    import random        def timmer(func):        def inner():            start_time=time.time()            func()            end_time=time.time()            print("run time: %s " %(end_time-start_time))        return inner            def index():        time.sleep(random.randrange(1,5))        print("welcome to index page")        index=timmer(index)    index()

Run the program to see the results of the program execution

As you can see, the source code of the index function has not been modified and the index function is not changed, but the function of statistical time is added to the index function, and the decorator is used here.

To analyze the execution flow of the above code:

    1.导入time和random模块,定义index函数和timmer函数    2.把原始的index函数的内存地址作为参数传给timmer函数。    3.timmer函数内部嵌套定义一个函数inner,然后返回inner函数的内存地址    4.timmer函数执行完成,返回timmer函数的内部函数inner的内存地址,然后把inner的内存地址赋值给index变量    5.index是inner函数的内存地址,index变量加括号运行,实际上就是在运行inner函数    6.运行inner函数,定义程序开始时间。    7.执行timmer函数的变量func,在第2步知道,func这个变量就是index的内存地址,所以这里实际上是执行被装饰过后的index函数    8.index函数执行完成,定义程序的终止时间    9.统计并打印整个程序的执行过程中所花费的时间

This is the adorner decoration. The execution flow of the index function

5. Simplified use of adorners

Now I have another function home, now I also want to count the run time of the home function, you can change the code as follows

    import time    import random        def timmer(func):        def inner():            start_time=time.time()            func()            end_time=time.time()            print("run time: %s " %(end_time-start_time))        return inner        def index():        time.sleep(random.randrange(1,5))        print("welcome to index page")        def home():        time.sleep(random.randrange(1,5))        print("welcome to home page")            index=timmer(index)    index()        home=timmer(home)    home()

Run the program and execute the results as follows

As you can see, each time you invoke the Timmer of the Statistics program, the function name of the called function is passed to the Timmer adorner as a parameter.

Then the execution result of the Timmer decorator is assigned to the called function name itself, and finally can call the decorated function, too troublesome is there??

In fact, the adorner in Python can be simplified into the following format

    import time    import random        def timmer(func):        def inner():            start_time=time.time()            func()            end_time=time.time()            print("run time: %s " %(end_time-start_time))        return inner        @timmer    def index():        time.sleep(random.randrange(1,5))        print("welcome to index page")        @timmer    def home():        time.sleep(random.randrange(1,5))        print("welcome to home page")        index()    home()

Program execution Results

As you can see, you @加装饰器名添加到被装饰对象的上方 can also add functions that are defined in adorners for a function

6. Definition and invocation of multiple adorners

In the above example, a timmer is defined and called by the adorner that runs the time of the statistic program,

If you want to add a user-authenticated feature to the index function now, you can define an adorner named Auth

    import time    import random        def auth(func):        def wrapper():            while True:                user=input("Input your username>>>:").strip()                pwd=input("Input your password>>>:").strip()                if user== "abcd" and pwd == "abcd1234":                    print("login successful")                    func()                    break                else:                    print("login error")        return wrapper        @auth    def index():        time.sleep(random.randrange(1,5))        print("welcome to index page")        index()

Run the program

    Input your username>>>:abcd             # 先输入错误的用户名和密码    Input your password>>>:1234    login error                             # 提示用户输入错误,登录失败    Input your username>>>:abcd             # 让用户再次输入用户名和密码    Input your password>>>:abcd1234    login successful                        # 登录成功    welcome to index page                   # 执行index函数

As can be seen from the program execution results, the user login password Authentication Adorner auth has been defined and successfully called

If you want to add user authentication functionality to the index function, and you want to count the function of the index function execution time, how do I call it when I use an adorner?

    import time    import random        def timmer(func):        def inner():            start_time=time.time()            func()            end_time=time.time()            print("run time: %s " %(end_time-start_time))        return inner        def auth(func):        def wrapper():            while True:                user=input("Input your username>>>:").strip()                pwd=input("Input your password>>>:").strip()                if user== "abcd" and pwd == "abcd1234":                    print("login successful")                    func()                    break                else:                    print("login error")        return wrapper        @timmer    @auth    def index():        time.sleep(2)        print("welcome to index page")        index()

In the above code, for the index function added two adorners, now there is a problem, that is, which of the two adorners are called first, which is called after??

To analyze,

如果timmer装饰器先被调用,那么程序就会先执行timmer装饰器,然后再执行auth装饰器,提示输入用户名和密码,    这样一来timmer装饰器统计的时间就会包括输入用户名和密码的时间,这个时间会远远大于index函数睡眠的2秒种;如果auth装饰器先被调用,timmer装饰器后被调用,那么timmer装饰器统计的运行时间就应该只包括index函数的执行时间值应该在2秒多一点点的时间范围内

Run the program, first enter the wrong user name and password to use the program's execution time longer

From the execution of the program can be known, the program is to run the Timmer adorner, and then run the auth adorner, so Timmer statistics time includes the user authentication time, so Timmer statistics to the program run time is much larger than the index sleep 2 seconds

So here's a conclusion:

当一个函数同时被两个装饰器装饰时,加上函数最上面的装饰器先执行,加在下面的装饰器先装饰

Swap the Timmer adorner and the Auth adorner position in the example above.

    import time    import random        def timmer(func):        def inner():            start_time=time.time()            func()            end_time=time.time()            print("run time: %s " %(end_time-start_time))        return inner        def auth(func):        def wrapper():            while True:                user=input("Input your username>>>:").strip()                pwd=input("Input your password>>>:").strip()                if user== "abcd" and pwd == "abcd1234":                    print("login successful")                    func()                    break                else:                    print("login error")        return wrapper        @auth    @timmer    def index():        time.sleep(2)        print("welcome to index page")        index()

Run the index function, still enter the wrong user name and password, increase the user authentication time

As you can see, the time that this timmer is counted only includes the run time of the index function, not the time the user authenticates.

To analyze the example above, the index function is decorated by the Timmer decorator and the auth decorator. Decoration process

    @auth           # index=auth(timmer(index))    @timmer         # index=timmer(index)    def index():        time.sleep(2)        print("welcome to index page")

It concludes that when a function is two adorners, the adorner is decorated

1.timmer装饰器装饰原始的index,可以写成:index=timmer(index)2.在timmer装饰器中,timmer装饰器实际上是返回inner的内存地址,所以在这里,index=inner3.timmer装饰器装饰完成后,由auth装饰器来装饰,此时可以写成index=auth(index),4.这里auth括号里的index已经不再是原始index函数,而是已经被timmer装饰过后的index了,所以index=auth(timmer(index))5.又因为timmer装饰的结果等于inner函数的内存地址,所以:index=auth(inner)

At this point, the decoration process of the two adorners has been known, to see the process of implementation of the procedure

6.程序先执行auth装饰器,进入用户认证,请用户输入用户名和密码7.用户输入正确的用户名和密码后,开始执行func函数,也已经上面分析的inner函数8.timmer装饰器先定义程序的开始运行时间,然后运行func函数,也就是原生的index函数9.index函数先睡眠2秒,然后执行print语句,再定义程序的结束时间10.最后统计并打印程序的运行时间,至此程序运行完毕。

So the time the user entered the user name and password is not counted by the Timmer decorator.

7. Setting and definition of decorated function parameters

Take a look at the code first

    import time        def timmer(func):        def inner():            start_time=time.time()            func()            end_time=time.time()            print("run time: %s " %(end_time-start_time))        return inner        @timmer    def index():        time.sleep(2)        print("welcome to index page")        @timmer    def home(name):        time.sleep(3)        print("welcome to %s home page" % name)

As shown above, the home function adds a parameter, and the index function has no parameters

The way to call the index function and the home function should be in this form, as normal functions are defined and called

index()home("python")

And then we run the program and we see that the program throws an exception

  File "E:\python_learn\py_code\test.py", line 28, in <module>    home("python")TypeError: inner() takes 0 positional arguments but 1 was given

Say an exception stating that the inner function does not require positional arguments, but we have given a positional parameter

Back to the part of the Timmer decorator definition, you can see that the intrinsic function of the Timmer decorator does not define the parameters

In this way, the Timmer adorner can only be used to decorate functions with no parameters,

We can add a parameter to the inner function when the Timmer adorner is defined

    import time        def timmer(func):        def inner(name):            start_time=time.time()            func(name)            end_time=time.time()            print("run time: %s " %(end_time-start_time))        return inner        @timmer    def index():        time.sleep(2)        print("welcome to index page")        @timmer    def home(name):        time.sleep(3)        print("welcome to %s home page" % name)        index()    home("python")

However, when the Timmer adorner decorates the index function, it throws an exception because the index function has no parameters

File "E:\python_learn\py_code\test.py", line 27, in <module>index()TypeError: inner() missing 1 required positional argument: ‘name‘       

Without knowing the number of parameters of the decorated function, that is, when the parameters of the decoration function are variable long and the form is not fixed,

can use *args and **kwargs , modify the above code

    import time        def timmer(func):        def inner(*args,**kwargs):            start_time=time.time()            func(*args,**kwargs)            end_time=time.time()            print("run time: %s " %(end_time-start_time))        return inner        @timmer    def index():        time.sleep(2)        print("welcome to index page")        @timmer    def home(name):        time.sleep(3)        print("welcome to %s home page" % name)        index()    home("python")

Run the program again to see the results of the run

From the above, we can use *args and **kwargs to represent arbitrary lengths of parameters without knowing the number of parameters of the decorated function.

8. The return value of the decorated function

Modify the above code to define a return value for the home function, printing the return value of the index function and the home function, respectively

    import time        def timmer(func):        def inner(*args,**kwargs):            start_time=time.time()            func(*args,**kwargs)            end_time=time.time()            print("run time: %s " %(end_time-start_time))        return inner        @timmer    def index():        time.sleep(2)        print("welcome to index page")        @timmer    def home(name):        time.sleep(3)        print("welcome to %s home page" % name)        return("home func")        index_res=index()    print(index_res)        home_res=home("python")    print(home_res)

Run the program and you can see

welcome to index pagerun time: 2.0 Nonewelcome to python home pagerun time: 3.0 None

As you can see, the return value defined in the Home function is not printed and the value shown is None

Because the home function executed here is not the original defined home function, but rather the result of the execution of the wrapper function

Because the wrapper function does not define a return value, the home function that was decorated does not print out the return value

Modify the code to define and return the return value that is performed by the adornment function in the Timmer adorner

    import time        def timmer(func):        def inner(*args,**kwargs):            start_time=time.time()            res=func(*args,**kwargs)            end_time=time.time()            print("run time: %s " %(end_time-start_time))            return res        return inner        @timmer    def index():        time.sleep(2)        print("welcome to index page")        @timmer    def home(name):        time.sleep(3)        print("welcome to %s home page" % name)        return("home func")        index_res=index()    print(index_res)        home_res=home("python")    print(home_res)

Execute the function again to see the results of the execution

    welcome to index page    run time: 2.0     None        welcome to python home page    run time: 3.0     home func    

As you can see, the return value defined in the original home function is printed.

Conclusion:

如果被装饰函数没有定义返回值,timmer装饰器装饰后的返回值为None而如果被装饰函数定义了返回值,则timmer装饰器装饰后则返回被装饰函数的返回值

Python-Functional Programming decorator (I.)

Related Article

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.