美文网首页python笔记
Python 笔记 | 从零开始,理解python装饰器

Python 笔记 | 从零开始,理解python装饰器

作者: WangLane | 来源:发表于2019-12-13 17:26 被阅读0次

还是决定写一篇关于python装饰器的文章. 装饰器实在是太常用,也太好用的东西了. 这篇文章会从函数开始, 一步步解释装饰器. 因为装饰器实际是也只是高阶函数而已.

1. 函数

函数就比较基础了, 就像一个黑箱, 传入参数, 出来返回值. 比如这样

>>> def incr(num):
...     return num + 1
... 
>>> incr(21)
22

2. 函数内部定义函数

python支持在函数内部定义函数, 比如:

def parent():
    print("Printing from the parent() function")
    def first_child():
        print("Printing from the first_child() function")
    def second_child():
        print("Printing from the second_child() function")
    second_child()
    first_child()

这个也很好理解, 输出结果是这样的:

>>> parent()
Printing from the parent() function
Printing from the second_child() function
Printing from the first_child() function

在函数内部定义的函数, 作用域只在函数内部. 当然, 我们可以再来个复杂点的例子.

>>> def hello(name):
...     def say_hello():
...         print("Hello", name)
...     say_hello()
... 
>>> hello('world')
Hello world

注意, 这里在hello函数里面定义了一个函数 say_hello , say_hello这里面用到一个变量name, 是函数hello传入的参数, 这个在装饰器中会用到. 大家自己理解下.

3. 一切皆对象

python中一切皆是对象, 所有的类, 生成器, 变量, 甚至函数也是一样. 比如我们上面定义的incr函数:

>>> a = incr
>>> a
<function incr at 0x7f5485f77050>
>>> a(7)
8
>>> 

那既然是对象, 就可以作为参数传进函数中, 比如说:

>>> def print_incr(fun, num):
...     num = fun(num)
...     print(num+10)
>>> print_incr(incr, 10)
21

同样的道理, 函数既然可以作为参数, 也可以作为返回值, 比如:


>>> def left_or_right(direction):
... 
...     def left():
...         print('<-')
... 
...     def right():
...         print('->')
... 
...     if direction == 'left':
...         return left
...
...     elif direction == 'right':
...         return right
... 
>>> left_or_right('left')
<function left at 0x7f5485f73cb0>

>>> fun = left_or_right('left')

>>> fun()
<-

4. 简单的装饰器

装饰器听得很多了, 那到底装饰器是个什么东西? 听起来这么高端? 其实很简单, 我们玩个补全句子的文字游戏, 括号括起来的是定语

  1. 装饰器是函数.
  2. 装饰器是 (参数是函数, 返回值也是函数的) 函数.

写装饰器的时候大家请一定牢记这两句
那么我们开始写第一个装饰器了.

def decorator(func):
   # 我们在函数内部定义一个函数
   def wrapper():
        print('start')
        # 这里调用一下我们传进来的函数
        func()
        print('end')
   #  返回一个函数
   return wrapper

好了, 这就是一个简单的装饰器了. 我们可以这样去使用这个装饰器.

>>> def hello():
...     print('hello')
... 
>>> say_hello = decorator(hello)
>>> say_hello
<function decorator.<locals>.wrapper at 0x7f5485f4df80>
>>> say_hello()
start
hello
end
>>> 

这里的decorator就是装饰器了, 传入一个hello函数, 返回一个新的函数, 所以归根到底就是个函数. 一切皆对象, 所以返回函数和返回数字, 返回类的实例, 都没什么太大区别.
如果你认真阅读前面三个部分, 这里的装饰器应该没有任何问题. 如果还是感觉到困惑, 再去理解一遍前面三点.

5. 语法糖

如果我们做个这样一个装饰器, 每次调用都要调用一下, 这可不太优雅, 于是python提供一种便捷的语法糖方式. 还是上面的这个装饰器举例子

def decorator(func):
   # 我们在函数内部定义一个函数
   def wrapper():
        print('start')
        # 这里调用一下我们传进来的函数
        func()
        print('end')
   #  返回一个函数
   return wrapper

@decorator
def hello():
    print('hello')

这时候我们再调用hello

>>> hello()
start
hello
end

所以, @decorator 的意思就是说, hello = decorator(hello) , 是不是装饰器也没有那么复杂?

6. 带参数的函数的装饰器

动手试一下, 如何写这样一个函数的装饰器?

def add(a, b):
    return a+b

如果你对前面的部分理解的比较清楚透彻, 你大概已经实现出来了.

我们理清一下思路, 装饰器就是一个函数, 传入函数, 返回函数, 那就是这样子了

def decorator(fun):
    def wrapper(a, b):
        print('start')
        t = fun(a,b)
        print(t)
        print('end')
    return wrapper

>>> a = decorator(add)
>>> a
<function decorator.<locals>.wrapper at 0x7f5485f4d680>
>>> a(7, 8)的
start
15
end

7. 可带参数的装饰器

我们这里实现一个装饰器功能:
给定两个参数, msg(string) 和 times(int), 对于被装饰的函数, 先输出msg信息, 然后将fun运行times次,

同理, 如果你对于前面理解的透彻, 这部分应该写出来了.

给点提示, 装饰器是什么?
装饰器是函数, 参数是函数, 返回值也是函数.

那么我们的装饰器能不能用别的函数返回回来呢? 当然可以.


#  外面这个函数其实是用来接受参数的外壳
def deco(msg, times):
    # 里面的这个函数其实才是我们最终的装饰器
    def wrapper(fun):
        print(msg)
        for i in range(times):   
            fun()
    # 这里返回我们的装饰器
    return wrapper

>>> @deco(time=5) 
... def hello(): 
...     print('hello world') 
...                
                                                                                                                                                                                             
hello world
hello world
hello world
hello world
hello world

8. 举个例子

计数装饰器: 计算函数运行次数

import functools

def count_calls(func):
    @functools.wraps(func)
    def wrapper_count_calls(*args, **kwargs):
        wrapper_count_calls.num_calls += 1
        print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}")
        return func(*args, **kwargs)
    wrapper_count_calls.num_calls = 0
    return wrapper_count_calls

@count_calls
def say_whee():
    print("Whee!")

9. 写在后面

装饰器的用途实在很多很广, 如果有兴趣进一步了解, 可以去python的装饰器库去看一下:
https://wiki.python.org/moin/PythonDecoratorLibrary

我一开始用装饰器的时候也是一脸懵逼, 但是也坚持去用, 只要能用到的地方, 都尽可能的去使用, 不会的地方就去查别人的代码, 然后模仿着去写, 慢慢会用了之后再回头咀嚼原理直到吃透. 所以, 最重要的是多动手, 写就完了.

写了蛮久的, 如果对你有帮助, 顺手点个赞(≧▽≦)/. 大佬们赞赏就更欢迎啦. 还在学习, 如果有哪里不对的请多指教.

相关文章

网友评论

    本文标题:Python 笔记 | 从零开始,理解python装饰器

    本文链接:https://www.haomeiwen.com/subject/ugrfnctx.html