关于Python Decorator你应该知道的一切
admin
2023-07-30 20:44:18
0
关键词:Python、装饰器、装饰器的深入讨论、Python decorator

前言

最近学习Python,在看一个框架源码过程中对装饰器很困惑,Google了一圈,在stack overflow的一个问题讨论下面找到了这个总结,这里几乎有关于Python全部的内容。觉得很好,遂翻译过来。翻译基本都是意译,能看英文的还是尽量看上面链接过去的原版吧!

Pyton装饰器基础

在Python中,函数也是对象

为了理解装饰器,你必须首先理解,在Python中函数也是对象。
理解这个知识点很重要。让我们使用一个简单的例子来说明一下:

def shout(word=\"yes\"):
    return word.capitalize()+\"!\"

print shout()
# 输出为: \'Yes!\'


# 函数作为一个对象,你可以像其他对象一样,把它赋值给其他的变量
scream = shout

# 注意我们没有使用圆括号:我们不是调用这个函数,我们把\"shout\"这个函数赋值给变量\"scream\"   
# 那意味着你之后可以使用\"scream\"来调用\"shout\"这个函数
print scream()
# 输出为: \'Yes!\'

# 不仅如此,那意味着你可以移除\'shout\'这个老的名称,但这个函数仍然可以通过\'scream\'访问
del shout
try:
    print shout()
except NameError, e:
    print e
    # 输出为: \"name \'shout\' is not defined\"

print scream()
# 输出为: \'Yes!\'

好了,在心里记住这个知识点。我们之后很快要用到它。

Python中函数还有另一个有趣的特性,那就是它可以在其他函数里面定义!

def talk():

    # 你可以在\"talk\"函数中定义一个函数...
    def whisper(word=\"yes\"):
        return word.lower()+\"...\"

     # ...并且你可以马上使用这个函数
    print whisper()

# 你每次调用\"talk\"这个函数的时候,它会定义一个\"whisper\"函数,之后这个\"whisper\"将在\"talk\"里面被调用
talk()
# 输出为:\"yes...\"

# 但是在\"talk\"这个函数的作用域之外,\"whisper\"这个函数是不存在的
try:
    print whisper()
except NameError, e:
    print e
    # 输出为: \"name \'whisper\' is not defined\"*

函数的引用

Okay,就这些东西吗?有趣的部分该上场了…
你已经看见,函数是对象。因此,函数:

  • 可以赋值给其他变量
  • 可以在其它函数里面定义

那意味着一个函数可以被另一个函数return。我们来看个例子! ☺

def getTalk(kind=\"shout\"):

    # 我们定义了一些函数
    def shout(word=\"yes\"):
        return word.capitalize()+\"!\"

    def whisper(word=\"yes\") :
        return word.lower()+\"...\";

    # 然后我们返回他们中的一个
    if kind == \"shout\":
        # 我们没有用\"()\", 我们不是要调用这个函数
        # 我们返回了这个函数对象
        return shout  
    else:
        return whisper

# 我们怎么使用它呢?
# 获取函数,并将它赋值给一个变量
talk = getTalk()      

# 你可以看到在这里\"talk\"是一个函数对象:
print talk
# 输出为: 

# 这个就是被函数返回的对象
print talk()
# 输出为: Yes!

# 你甚至可以直接使用它:
print getTalk(\"whisper\")()
# 输出为: yes...

等等…这里有我们没有注意到的地方!

既然你可以return一个函数,你就可以把一个函数当参数传递:

def doSomethingBefore(func): 
    print \"I do something before then I call the function you gave me\"
    print func()

doSomethingBefore(scream)
# 输出为: 
#I do something before then I call the function you gave me
#Yes!

好了,你已经具备了理解装饰器的所有知识点。你知道,装饰器就是 \”封装\”, 这意味着它可以让你在被它装饰的函数前面和后面执行一些代码,而不必改动被装饰的函数本身。

手动创建装饰器

你如何手动构建一个装饰器:

# 装饰是一个函数,该函数需要另一个函数作为它的参数
def my_shiny_new_decorator(a_function_to_decorate):

    # 在装饰器的函数实现里面它定义了另一个函数: 他就是封装函数(wrapper)
    # 这个函数将原来的函数封装到里面
    # 因此你可以在原来函数的前面和后面执行一些附加代码
    def the_wrapper_around_the_original_function():

        # 在这里放置你想在原来函数执行前执行的代码
        print \"Before the function runs\"

        # 调用原来的函数(使用圆括号)
        a_function_to_decorate()

        # 在这里放置你想在原来函数执行后执行的代码
        print \"After the function runs\"

    # 这个时候,\"a_function_to_decorate\"并没有执行
    # 我们返回刚才创建的封装函数
    # 这个封装函数包含了原来的函数,和将在原来函数前面和后面执行的代码。我们就可以使用它了!
    return the_wrapper_around_the_original_function

# 想象你创建了一个你再也不想修改的函数
def a_stand_alone_function():
    print \"I am a stand alone function, don\'t you dare modify me\"

a_stand_alone_function() 
# 输出为: I am a stand alone function, don\'t you dare modify me

# 现在你可以装饰这个函数来扩展它的行为
# 只需要将这个函数传入装饰器,那它将被动态的包在任何你想执行的代码间,并且返回一个可被使用的新函数:
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#输出为:
#Before the function runs
#I am a stand alone function, don\'t you dare modify me
#After the function runs

现在,你可能想在每次调用 a_stand_alone_function的时候,真正被执行的函数是 a_stand_alone_function_decorated。那很容易,只需要使用 my_shiny_new_decorator返回的函数赋值给原来的 a_stand_alone_function这个函数名(其实是个变量):

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#输出为:
#Before the function runs
#I am a stand alone function, don\'t you dare modify me
#After the function runs

# 你猜怎么着?这就是装饰器做的事情。

装饰器揭秘

前面的例子,使用Python的装饰器语法糖来重写就是下面的样子:

@my_shiny_new_decorator
def another_stand_alone_function():
    print \"Leave me alone\"

another_stand_alone_function()  
# 输出为:  
#Before the function runs
#Leave me alone
#After the function runs

是的,这就是全部,就是这么简单。@decorator 只是下面表达式的简写:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

这里的装饰器只是装饰器设计模式的一种Python化变体。Python嵌入了多种经典的设计模式来简化开发(比如迭代器(iterators))。

当然,你可以堆积装饰器(使用多层装饰器):

def bread(func):
    def wrapper():
        print \"\"
        func()
        print \"<\\______/>\"
    return wrapper

def ingredients(func):
    def wrapper():
        print \"#tomatoes#\"
        func()
        print \"~salad~\"
    return wrapper

def sandwich(food=\"--ham--\"):
    print food

sandwich()
# 输出为: --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs:
#
# #tomatoes#
# --ham--
# ~salad~
#<\\______/>

使用Python的装饰器语法糖:

@bread
@ingredients
def sandwich(food=\"--ham--\"):
    print food

sandwich()
#outputs:
#
# #tomatoes#
# --ham--
# ~salad~
#<\\______/>

你放置装饰器的顺序很重要:

@ingredients
@bread
def strange_sandwich(food=\"--ham--\"):
    print food

strange_sandwich()
#outputs:
##tomatoes#
#
# --ham--
#<\\______/>
# ~salad~

现在: 回答问题(请参考stack overflorw上的相关问题)

作为结论,你可以很容易看出如何回答问题:

# 使其变bold的装饰器
def makebold(fn):
    # 装饰器将要返回的函数
    def wrapper():
        # 在原函数前面和后面插入一些代码
        return \"\" + fn() + \"\"
    return wrapper

# 使其变italic的装饰器
def makeitalic(fn):
      # 装饰器将要返回的函数
    def wrapper():
        # 在原函数前面和后面插入一些代码
        return \"\" + fn() + \"\"
    return wrapper

@makebold
@makeitalic
def say():
    return \"hello\"

print say() 
# 输出为: hello

# 这和下面代码效果相同
def say():
    return \"hello\"
say = makebold(makeitalic(say))

print say() 
# 输出为: hello

你可以高兴的离开这里了,或者再费点脑子来看看装饰器的高级用法。


更深入的讨论装饰器

向被装饰的函数传参数

# 这不是黑魔法,你只需要让封装函数传递这些参数:

def a_decorator_passing_arguments(function_to_decorate):
    def a_wrapper_accepting_arguments(arg1, arg2):
        print \"I got args! Look:\", arg1, arg2
        function_to_decorate(arg1, arg2)
    return a_wrapper_accepting_arguments

# 因为当你调用被装饰器返回的函数时,实际你是在调用封装函数  
# 所以向封装函数传递参数可以让封装函数把参数传递给被装饰的函数

@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
    print \"My name is\", first_name, last_name

print_full_name(\"Peter\", \"Venkman\")
# 输出为:
# I got args! Look: Peter Venkman
# My name is Peter Venkman

装饰方法

Python中方法和函数几乎是一样的,这个特性很nice。唯一的不同是方法期望它的第一个参数是对当前对象的引用(self)。

那意味着你可以使用相同的方式来给方法添加装饰器!只是需要将self考虑在内:

def method_friendly_decorator(method_to_decorate):
    def wrapper(self, lie):
        lie = lie - 3 # 很友好吧,再次减少了年龄 :-)
        return method_to_decorate(self, lie)
    return wrapper


class Lucy(object):

    def __init__(self):
        self.age = 32

    @method_friendly_decorator
    def sayYourAge(self, lie):
        print \"I am %s, what did you think?\" % (self.age + lie)

l = Lucy()
l.sayYourAge(-3)
# 输出为: I am 26, what did you think?

如果你在写一个通用的装饰器–可以接收任何参数的函数或者方法–这时候只需要使用 *args, **kwargs:

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    # 封装函数可以接收任何的参数
    def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
        print \"Do I have args?:\"
        print args
        print kwargs
        # 然后你解包出参数,这里是 *args, **kwargs 
        # 如果你不熟悉怎么解包,可以查看:
        # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
        function_to_decorate(*args, **kwargs)
    return a_wrapper_accepting_arbitrary_arguments

@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print \"Python is cool, no argument here.\"

function_with_no_argument()
#输出为:
#Do I have args?:
#()
#{}
#Python is cool, no argument here.

@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print a, b, c

function_with_arguments(1,2,3)
# 输出为:
#Do I have args?:
#(1, 2, 3)
#{}
#1 2 3 

@a_decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, platypus=\"Why not ?\"):
    print \"Do %s, %s and %s like platypus? %s\" %\\
    (a, b, c, platypus)

function_with_named_arguments(\"Bill\", \"Linus\", \"Steve\", platypus=\"Indeed!\")
# 输出为:
#Do I have args ? :
#(\'Bill\', \'Linus\', \'Steve\')
#{\'platypus\': \'Indeed!\'}
#Do Bill, Linus and Steve like platypus? Indeed!

class Mary(object):

    def __init__(self):
        self.age = 31

    @a_decorator_passing_arbitrary_arguments
    def sayYourAge(self, lie=-3): # 这时候你可以添加一个默认参数值
        print \"I am %s, what did you think ?\" % (self.age + lie)

m = Mary()
m.sayYourAge()
# 输出为:
# Do I have args?:
#(<__main__.Mary object at 0xb7d303ac>,)
#{}
#I am 28, what did you think?

给装饰器传递参数

好了,现在你觉得给装饰器本身传递参数该怎么做呢?

这个可能有点绕,因为装饰器必须接收一个函数作为参数。
因此,你不能把被装饰函数的参数直接传递给装饰器。

在我们说出解决办法前,写点代码来找找灵感:

# 装饰器只是普通的函数
def my_decorator(func):
    print \"I am an ordinary function\"
    def wrapper():
        print \"I am function returned by the decorator\"
        func()
    return wrapper

# 因此,你可以不使用任何的 \"@\" 就可以调用它

def lazy_function():
    print \"zzzzzzzz\"

decorated_function = my_decorator(lazy_function)
# 输出为: I am an ordinary function

# 它输出 \"I am an ordinary function\",因为那就是你在代码里面做的事情:  
# 调用一个函数,没有任何的魔法。

@my_decorator
def lazy_function():
    print \"zzzzzzzz\"

# 输出为: I am an ordinary function

上面两种方式几乎一样。\”my_decorator\”被调用。因此当你在代码里面添加 @my_decorato时,你就在告诉Python去调用\’被\”my_decorator\”变量标示的函数\’。

这很重要! 你给出的这个变量名可以直接指向装饰器-也可以不直接指向

我们来干点邪恶的事情。 ☺

def decorator_maker():

    print \"I make decorators! I am executed only once: \"+\\
          \"when you make me create a decorator.\"

    def my_decorator(func):

        print \"I am a decorator! I am executed only when you decorate a function.\"

        def wrapped():
            print (\"I am the wrapper around the decorated function. \"
                  \"I am called when you call the decorated function. \"
                  \"As the wrapper, I return the RESULT of the decorated function.\")
            return func()

        print \"As the decorator, I return the wrapped function.\"

        return wrapped

    print \"As a decorator maker, I return a decorator\"
    return my_decorator

# 我们创建了一个装饰器。它就只是一个新的函数。
new_decorator = decorator_maker()       
# 输出为:  
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator

# 然后我们装饰一个函数       
def decorated_function():
    print \"I am the decorated function.\"

decorated_function = new_decorator(decorated_function)
# 输出为:
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function

# 我们调用这个函数:
decorated_function()
# 输出为:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

这里没有任何惊奇的地方。

让我们再次来做相同的事情,但是省略掉所有讨厌的中间变量:

def decorated_function():
    print \"I am the decorated function.\"
decorated_function = decorator_maker()(decorated_function)
# 输出为:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.

# 最后:
decorated_function()    
# 输出为:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

让我们使它更简洁:

@decorator_maker()
def decorated_function():
    print \"I am the decorated function.\"
# 输出为:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.

# 最终: 
decorated_function()    
# 输出为:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

Hey,你注意到了吗?我们除了 \”@\”格式的语法糖外还使用了函数调用!

相关内容

热门资讯

500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
Prometheus+Graf... 一,Prometheus概述 1,什么是Prometheus?Prometheus是最初在Sound...
python绘图库Matplo... 本文简单介绍了Python绘图库Matplotlib的安装,简介如下: matplotlib是pyt...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...