Python装饰器(Decorators )

http://book.pythontips.com/en/latest/decorators.html

在《Built-in Functions(3.6)》和《Python上下文管理器》两篇笔记中,已经有了装饰器初步的示例,本篇结合一个高露洁大学牛人的博客来系统的解释下python中装饰器的作用。

一、首先提出一个统一的概念

Decorators are functions which modify the functionality of other functions. They help to make our code shorter and more Pythonic.

装饰器(也可以叫修饰器)的作用就是改变其他函数的运作方式,这可以使得代码更加简洁和Pythonic。

二、接下来一步一步的接近decorator

首先,函数是可以返回函数的,示例如下:

def hi(name="Leo"):
    def greet():
        return "恭喜!"

    def welcome():
        return "欢迎!"

    if name == "Leo":
        return greet
    else:
        return welcome

a = hi()
print(a)
# <function greet at 0x7f2143c01500>
# 这说明这里的a就是greet(),因为默认name="Leo",而return是不带括号的greet,这会返回一个函数而不是函数执行后的结果

print(a())
# 恭喜!
# 如果像上边那样print(a)那么得到的只是一个greet()函数,但如果是print(a())那么函数就会被执行,因此返回的是greet() return的内容

 然后,函数当然也可以作为其他函数的参数:

def hi():
    return "hi Leo!"

def doSomethingBeforeHi(func):
    print("执行"+func.__name__+"()之前先打个666")
    print(func())

doSomethingBeforeHi(hi)
# 执行hi()之前先打个666
# hi Leo!

到这里再次提出一个概念就可以很好被理解了,即:Python中所有的东西都可以看做一个object。无论是自定义函数还是基础的数据类型亦或是自定义的class或者实例,万物皆对象(很熟悉吧)。这些对象可以被作为输入也可以被返回。

而装饰器的核心也就是上边提出的:为了使代码更加简洁。因为把所有功能写在一个函数里会显得很臃肿,因此把一部分通用功能先写出来,这可以使得代码更加轻便同时也可以对这些函数复用。

三、好,接下来可以写一个decorator了

# coding=utf-8
def decorator(func):
    def doSomethingBeforeFunc():
        print("办正事之前先来点其他的。")
        func()
    return doSomethingBeforeFunc

def func():
    print("真正要做的事在这里。")

func=decorator(func)
func()
# 办正事之前先来点其他的。
# 真正要做的事在这里。

我们可以看到func函数本来只是想打印一句话“真正要做的事在这里”,但是由于经过了decorator()的处理其行为发生了变化多打印了一句,这是因为decorator将func作为输入参数,并return了一个封装了func的函数,这个封装好的函数不仅包含了func()的功能还包含了print("办正事之前先来点其他的。")的功能,因此执行func=decorator(func)后func就不是原来的func了,实际上是doSomethingBeforeFunc()。

而常见的装饰器的调用方式并非上述func=decorator(func),而是使用@符号。

以下写法为,在定义func之前使用@decorator 调用装饰器,相当于在定义func之后执行了func=decorator(func):

# coding=utf-8
def decorator(func):
    def doSomethingBeforeFunc():
        print("办正事之前先来点其他的。")
        func()
    return doSomethingBeforeFunc
@decorator 
def func():
    print("真正要做的事在这里。")

func()
# 办正事之前先来点其他的。
# 真正要做的事在这里。

这段代码与上面的代码的作用是一模一样的,只不过下边的写法更为常见,更Pythonic(对新手不友好)。

四、上述代码的一个BUG

如果我们对上述代码中使用了decorator后的func打印__name__会如何?很容易猜到得到的肯定是doSomethingBeforeFunc这个名字,这一般不是我们想要的

# coding=utf-8
from functools import wraps
def decorator(func):
    @wraps(func)
    def doSomethingBeforeFunc():
        print("办正事之前先来点其他的。")
        func()
    return doSomethingBeforeFunc

@decorator
def func():
    print("真正要做的事在这里。")

func()
print(func.__name__)
# 办正事之前先来点其他的。
# 真正要做的事在这里。
# func

通过引入functools的wraps方法,可以解决__name__的显示问题。至于wraps是通过什么样的机制实现的,源码实在是太绕了就不看了。简单来说就是wraps可以让我们获取func()被修饰器修饰之前的各种内置属性。wraps本身也是一个装饰器。

我们上述编写的装饰器都是最简单的场景,装饰器函数本身还和被装饰的函数都没有参数,假如装饰器函数和被装饰的函数都需要入参该怎么办,这里不再详述,可以参考文章开头的书籍地址查找对应示例。

五、关于装饰器和被装饰函数的参数

之前的示例都是最简单的装饰器场景,装饰器函数和被装饰的函数都不包含入参,实际需求却不会这么理想。
场景一:被装饰函数需要入参:
so easy,我们只需要在装饰器内部函数里加上*args与**kwargs就可以了:
from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapped_func(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)
    return wrapped_func

@decorator
def myfunc(name):
    print name

myfunc('leo')
场景二:装饰器函数本身需要入参:
解释其原理对我来说不太现实,贴个示例可以速成:
from functools import wraps

def logit(logfile='out.log'):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # Open the logfile and append
            with open(logfile, 'a') as opened_file:
                # Now we log to the specified logfile
                opened_file.write(log_string + '\n')
            return func(*args, **kwargs)
        return wrapped_function
    return logging_decorator

@logit()
def myfunc1():
    pass

myfunc1()
# Output: myfunc1 was called
# A file called out.log now exists, with the above string

@logit(logfile='func2.log')
def myfunc2():
    pass

myfunc2()
# Output: myfunc2 was called
# A file called func2.log now exists, with the above string
上述代码展示了当装饰器函数需要入参时该如何处理,既:嵌套一个内部装饰器函数!!外部装饰器只写自己的入参,内部再写一个嵌套的装饰器使用func作为入参,这个内部装饰器封装的函数包含了func所需的入参,其返回func(*args, **kwargs),之后我们返回这个被封装的函数和内部装饰器函数即可。
上述两段代码可以作为编写装饰器的定式记下来,只要需要入参你都可以按上边的格式直接套用。

六、Decorator的经典使用场景

在Python中装饰器一个很经典的使用场景是Django里常见的需要密码验证的时候,举个例子:

# coding=utf-8
from functools import wraps
def require_login(func):
    @wraps(func)
    def new_func(*args, **kwargs):
        auth = request.authorization
        if not auth or check_auth(auth.username, auth.password):
            authenticate()
        return func(*args, **kwargs)
    return new_func

@require_login
def func():
    print("浏览页面中...")

上述只是一段伪代码示例,django中可以直接调用相关的模块:

from django.contrib.auth.decorators import login_required

七、不仅仅是decorator函数,还可以是decorator类

之前的示例全都是修饰器函数,实际上修饰器还可以是类。只要类可以像函数那样被调用就可以了:

# coding=utf-8
class Decorator(object):
    def __init__(self , func):
        self.func = func
    def __call__(self, *args, **kwargs):  # 只要重写类的__call__方法就可以将类当做函数调用
        print("嘿!孙贼!")
        return self.func(*args, **kwargs)

@Decorator
def Charge():
    print("正在向法爷冲锋!!!")

Charge()
posted @ 2019-10-23 15:33  realcp1018  阅读(932)  评论(0)    收藏  举报