Python装饰器

本文详细介绍了Python装饰器的概念,如何作为对象操作、通过函数返回函数、装饰方法和类,以及注意事项。通过实例演示了装饰器的创建、参数传递和应用,包括装饰器链和装饰类的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

函数即对象

函数参考

手工制作装饰器

装饰器解密

实际解答开头的问题

向被装饰函数传递参数

装饰方法

给装饰器传递参数

装饰类

装饰器注意事项


函数即对象

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

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

print(shout())
# outputs: "Yes!"

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

# 注意这里并没有使用括号:我们没有调用函数,只是把函数名shout放进了变量scream中。这意味
# 着,你可以通过scream来调用shout
print(scream())
# outputs: "Yes!"

# 更近一步说,这意味着你可以删除原来的函数名shout,仍然可以通过scream来调用函数。
del shout
try:
    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: <function shout at 0xb7ea817c>

# 这个对象是函数的返回值之一
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_decorator
def 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 wrapper


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


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


sandwich()
# outputs: --ham--

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

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

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


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

实际解答开头的问题

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

# 构造黑体的装饰器
def make_bold(func):
    def wrapper():
        return "<b>" + func() + "</b>"
    return wrapper


# 构造斜体的装饰器
def make_italic(func):
    def wrapper():
        return "<i>" + func() + "</i>"
    return wrapper


@make_bold
@make_italic
def say():
    return "hello"


print(say())
# outputs: <b><i>hello</i></b>


# 它完全等同于下面这段代码:
def say2():
    return "hello"


say2 = make_bold(make_italic(say2))
print(say2())
# outputs: <b><i>hello</i></b>

向被装饰函数传递参数

# 这不是黑魔法,有时你不得不让包装层传递参数
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")
# 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 wrapper


class 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_arguments
def 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_arguments
def 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_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!"
)
# 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_decorator
def 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


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值