博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python装饰器
阅读量:4189 次
发布时间:2019-05-26

本文共 10747 字,大约阅读时间需要 35 分钟。

目录


 

函数即对象

为了理解装饰器,必须首先理解在Python中函数都是对象。这一点有重要的意义,让我们通过一个简单的例子来看一看:

def shout(word="yes"):    return word.capitalize() + "!"print(shout())# outputs: "Yes!"# 作为一个对象,你可以像其它对象一样,将函数赋给一个变量scream = shout# 注意这里并没有使用括号:我们没有调用函数,只是把函数名shout放进了变量scream中。这意味# 着,你可以通过scream来调用shoutprint(scream())# outputs: "Yes!"# 更近一步说,这意味着你可以删除原来的函数名shout,仍然可以通过scream来调用函数。del shouttry:    print(shout())except NameError as e:    print(str(e))    # outputs: "name 'shout' is not defined!"print(scream())# outputs: "Yes!"

好的!把这个记录下来,稍后我们再回头看这里。Python函数另外一个有趣的特性是:它可以被定义在另外一个函数中。定义一个talk函数,在talk函数内部被定义一个whisper函数:

def talk():    # 你可以在函数talk运行时定义其它函数    def whisper(word="yes") -> str:        return word.lower() + "..."    # 并立即使用它!    print(whisper())# 你可以调用talk,每次调用的时候都会定义whisper,然后whisper在talk中被调用talk()# outputs: "yes..."# 但是whisper在talk之外并不存在try:    print(whisper())except NameError as e:    print(str(e))    # outputs: "name 'whisper' is not defined"

 

函数参考

接下来是有趣的部分,你已经看到了,函数就是对象,因此:

  • 它可以被赋给另一个变量

  • 它可以在另一个函数中被定义

这意味着,函数可以返回另外一个函数。来看看吧:

def get_talk(type="shout"):    def shout(word="yes"):        return word.capitalize() + "!"    def whisper(word="yes"):        return word.lower() + "..."    # 然后我们返回其中一个    if type == "shout":        # 这里没有使用括号,并不是在调用函数。这里返回的是函数对象        return shout    else:        return whisper# 那么怎么使用呢?# 获取返回的函数并把它赋给一个变量talk = get_talk()# 你可以看到talk在这里是一个函数对象print(talk)# outputs: 
# 这个对象是函数的返回值之一print(talk())# outputs: "Yes!"# 更疯狂一点,甚至你可以直接使用它print(get_talk("whisper")())# outputs: "yes..."

不过,等一等,这里还有更多。如果你能返回一个函数,那么你也能用它做参数来传递一个。

def do_something_before(func):    print("I do something before then I call the function you gave me")    print(func())def scream() -> str:    return "Yes!"do_something_before(scream)# output: "# I do something before then I call the function you gave me# Yes! "

现在你已经掌握了你要理解装饰器所需要的一切知识了。装饰器就是一个包装,它的意思是,“他们让你在函数运行前后执行被装饰过的代码”,而不需要去修改函数本身。

 

手工制作装饰器

你怎么会想要手动做装饰器:

# 装饰器是一个函数,它期望由另外一个函数作为其参数def my_shiny_new_decorator(a_function_to_decorate):    # 在内部,装饰器在运行时定义了一个函数:一个包装。    # 这个函数会将原始函数包装起来,所以在其前后执行代码。    def the_wrapper_around_the_originak_function():        # 在这里添加原始函数调用之前你所希望执行的代码        print("Before the function runs")        # 调用函数(记得使用括号)        a_function_to_decorate()        # 在这里添加在原始函数调用之后你所希望执行的代码        print("After the function runs")    # 在这个位置,a_function_to_decorate函数已经被执行了。我们返回刚才创建的包装函数。这个包装    # 一个函数及其前后要执行的代码。它已经能够使用了!    return the_wrapper_around_the_originak_function# 现在想象你创建了一个函数,但是你再也不想碰它了。def a_stand_alone_function():    print("I am a stand alone function, do not you dare modify me")a_stand_alone_function()# outputs: I am a stand alone function, do not you dare modify me# 你也可以通过装饰器来扩展它们的行为# 仅仅把它传递给装饰器,就可以用任何你所想要的代码进行动态的包装,并返回一个你可以使用的新函数a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)a_stand_alone_function_decorated()# outputs:# Before the function runs# I am a stand alone function, do not 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()# outputs:# Before the function runs# I am a stand alone function, do not you dare modify me# After the function runs

猜猜怎么样?这恰恰就是装饰器所做的事情!

 

装饰器解密

先前的例子,就使用了装饰器语法:

@my_shiny_new_decoratordef another_stand_alone_function():    print("Leave me alone")another_stand_alone_function()# outputs:# Before the function runs# Leave me alone# After the function runs

这就是全部了,如此简单。@my_shiny_new_decorator就是下面这种形式的简写:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

装饰器就仅仅是装饰器设计模式在Python中的变异。有很多不同的经典设计模式都嵌入到Python中来方便开发,比如迭代器。

当然,你也可以对装饰器进行累加:

def bread(func):    def wrapper():        print("
") func() print("<\______/>") return wrapperdef ingredients(func): def wrapper(): print("#tomatoes#") func() print("~salad~") return wrapperdef sandwich(food="--ham--"): print(food)sandwich()# outputs: --ham--sandwich = bread(ingredients(sandwich))sandwich()# outputs:#
# #tomatoes## --ham--# ~salad~# <\______/>

你设置的装饰器顺序有很大关系:

@ingredients@breaddef strange_sandwich(food="--ham--"):    print(food)strange_sandwich()# outputs:# #tomatoes## 
# --ham--# <\______/># ~salad~

 

实际解答开头的问题

总结一下,你就很容易的看到如何解答这个问题了。

# 构造黑体的装饰器def make_bold(func):    def wrapper():        return "" + func() + ""    return wrapper# 构造斜体的装饰器def make_italic(func):    def wrapper():        return "" + func() + ""    return wrapper@make_bold@make_italicdef say():    return "hello"print(say())# outputs: hello# 它完全等同于下面这段代码:def say2():    return "hello"say2 = make_bold(make_italic(say2))print(say2())# outputs: 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_argumentsdef print_full_name(first_name, last_name):    print("My name is", first_name, last_name)print_full_name("Peter", "Venkman")# outputs:# I got args! Look: Peter Venkman# My name is Peter Venkman

包装层的参数必须与被装饰函数的参数完全一致。

 

装饰方法

Python最伟大的地方就在于方法和函数完全相同,除了方法期望它的第1个参数是当前对象的第一个引用(self)(这里说明一下,类方法第一个期望的参数是cls,静态方法则没有第一个固定的期望参数)。这就意味着,你能够用同样的方式为方法建立装饰器,记得要把self/cls考虑上:

def method_friendly_decorator(method_to_decorate):    def wrapper(self, lie):        # 非常友好,减少更多的年龄        lie -= 3        return method_to_decorate(self, lie)    return wrapperclass Lucy(object):    def __init__(self):        self.age = 32    @method_friendly_decorator    def say_your_age(self, lie):        print("I am %s, what did you think?" % str(self.age + lie))i = Lucy()i.say_your_age(-3)# outputs:# I am 26, what did you think?

 

当然,如果你制作了一个非常通用的装饰器,并且希望应用到任何函数和方法,不用考虑它的参数,使用位置参数*args和关键字参数**kwargs就可以了。

def a_decorator_passing_arbitrary_arguments(function):    def wrapper(*args, **kwargs):        print("Do I have args?")        print(args)        print(kwargs)        # 然后解包参数,这里是*args, **kwargs        function(*args, **kwargs)    return wrapper@a_decorator_passing_arbitrary_argumentsdef function_with_no_argument():    print("Python is cool, no argument here.")function_with_no_argument()# outputs:# Do I have args?# ()# {}# Python is cool, no argument here.@a_decorator_passing_arbitrary_argumentsdef function_with_arguments(a, b, c):    print(a, b, c)function_with_arguments(1, 2, 3)# outputs:# Do I have args?# (1, 2, 3)# {}# 1 2 3@a_decorator_passing_arbitrary_argumentsdef 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!")# outputs:# 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 say_your_age(self, lie=-3):        print("I am %s, what did you think?" % (self.age + lie))m = Mary()m.say_your_age()# outputs:# Do I have args?# (<__main__.Mary object at 0x104e3bbb0>,)# {}# I am 28, what did you think?

 

给装饰器传递参数

现在,给装饰器本身传递参数会怎么样呢?这个有点绕,因为装饰器本身就要接收一个函数来作为参数,因此,你不能直接传递被装饰函数的参数来给装饰器。

在解决这个问题之前,先来看一些提醒:

# 装饰器就是普通函数def my_decorator(func):    print("I am a ordinary function.")    def wrapper():        print("I am function returned by the decorator")        func()    return wrapper# 因此,你可以不用'@'符号就可以调用它def lazy_function():    print("zzz")decorated_function = my_decorator(lazy_function)# outputs: I am a ordinary function.# 它输出 "I am a ordinary function.",因为你所做的仅仅就是"调用一个函数",# 没什么神奇的。@my_decoratordef lazy_function2():    print("zzz")# outputs: I am a ordinary function.

它完全相同。"my_decorator"被调用。所以当你@my_decorator的时候,其实是你告诉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 wrapper():            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 wrapper function.")        return wrapper    print("As a decorator maker, I return a decorator.")    return my_decorator# 让我们创建一个装饰器,实际上它就是一个新函数new_decorator = decorator_maker()# outputs:# 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)# outputs:# I am a decorator! I am executed only when you decorate a function.# As the decorator, I return the wrapper function.# 让我们调用函数decorated_function()# outputs:# 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.

就像你看到的,你可以使用这个技巧,像使用任何函数一样,给装饰器传递参数。如果你喜欢,你甚至可以使用*args, **kwargs。但是记住,装饰器仅仅只在被Python导入脚本的时候调用一次。之后,你就不能动态地设置参数了。当你"import x"的时候,函数就已经被装饰了,所以你什么都不能修改了。

下面的代码展示了执行3次demo()函数:

def new_decorator(times):    def test(func):        def wrapper(*args, **kwargs):            for i in range(times):                func(*args, **kwargs)            print("------")        return wrapper    return test@new_decorator(3)def demo():    print("666")demo()# outputs:# 666# 666# 666# ------

 

装饰类

至此,我们已经熟练的掌握了如何使用装饰器去装饰一个函数或一个方法。在Python的世界里,一切皆为对象。因此装饰器同样可以去装饰一个class。与装饰函数和方法的原理相同,对于装饰器来讲,class仅仅也是一个对象。

def single(cls):    instance = {}    def wrapper(*args, **kwargs):        if cls not in instance:            instance[cls] = cls(*args, **kwargs)        return instance[cls]    return wrapper@singleclass Twins(object):    def __init__(self, name):        self.name = name    def introduction(self):        print("Hi, My name is %s." % self.name)bob = Twins("Bob")tom = Twins("Tom")bob.introduction()# outputs: Hi, My name is Bob.tom.introduction()# outputs: Hi, My name is Bob.

是的,你没有看错。Twins类实例出来的对象都显示了一样的内容。这是因为single装饰器的作用,Twins类仅仅只被真正实例化了一次。这个例子也正是设计模式(Design Pattern)中单例模式的实现原理。

 

装饰器注意事项

  • 它们是Python2.4版本新加入的,请确保你使用的版本能够运行你的代码
  • 装饰器会降低被装饰函数调用的速度,请记住这一点
  • 你不能反装饰一个函数。有一些创建可删除装饰器的技巧但是没有人使用它们。所以一旦函数被装饰过,它就固定下来了。对于所有代码都是如此。
  • 装饰器包装函数,会使得代码很难调试
  • 装饰器的优点在于,不用重写函数,就可以立刻拓展函数的功能

转载地址:http://qbsoi.baihongyu.com/

你可能感兴趣的文章
Xcode 11 报错,提示libstdc++.6 缺失,解决方案
查看>>
vue项目打包后无法运行报错空白页面
查看>>
面试题:强制类型转换
查看>>
Decorator模式
查看>>
Template模式
查看>>
Observer模式
查看>>
高性能服务器设计
查看>>
图文介绍openLDAP在windows上的安装配置
查看>>
Pentaho BI开源报表系统
查看>>
Pentaho 开发: 在eclipse中构建Pentaho BI Server工程
查看>>
android中SharedPreferences的简单例子
查看>>
android中使用TextView来显示某个网址的内容,使用<ScrollView>来生成下拉列表框
查看>>
andorid里关于wifi的分析
查看>>
Hibernate和IBatis对比
查看>>
Spring MVC 教程,快速入门,深入分析
查看>>
Ubuntu Navicat for MySQL安装以及破解方案
查看>>
在C++中如何实现模板函数的外部调用
查看>>
HTML5学习之——HTML 5 应用程序缓存
查看>>
HTML5学习之——HTML 5 服务器发送事件
查看>>
mysql中用命令行复制表结构的方法
查看>>