Python 装饰器详解
Python 装饰器是 Python 语言中一种极其强大的工具。 通过使用装饰器,我们能够修改函数的行为,而无需直接更改函数本身的代码。 这种机制通过将函数包装在另一个函数中实现,从而允许我们编写更加简洁、可维护且易于重用的代码。 本文不仅会详细介绍如何使用装饰器,还会深入探讨如何创建自定义的装饰器。
必备知识
为了更好地理解 Python 装饰器,你需要具备一些基础知识。 以下是一些你需要熟悉的概念,并附带相关资源以便回顾。
基础 Python 知识
装饰器属于中高级主题,因此在学习之前,你需要对 Python 的基础知识有充分的掌握,例如数据类型、函数、对象和类。 此外,了解面向对象编程中的 getter、setter 和构造函数等概念也是必要的。 如果你不熟悉 Python 编程,可以参考一些入门教程。
函数作为一等公民
除了基础知识,你还需要理解 Python 中一个更高级的概念:函数是一等公民。 这意味着函数在 Python 中被视为对象,就像整数、字符串一样。 由于它们是对象,因此可以进行以下操作:
- 函数可以作为参数传递给其他函数。
- 函数可以作为返回值从其他函数返回。
- 函数可以被存储在变量中。
函数对象与其他对象的唯一区别是它包含魔术方法 __call__()
。 希望你已经对这些预备知识感到满意,接下来,我们将深入探讨装饰器的核心概念。
什么是 Python 装饰器?
简单来说,Python 装饰器是一个函数,它接收一个函数作为输入,并返回一个经过修改的新函数。 换句话说,如果函数 decorate
接收函数 original
作为参数,那么 decorate
就是一个装饰器,它会返回一个基于 original
修改的函数 modified
。modified
函数在调用 original
函数之前或之后可以执行其他操作。以下代码示例能够更直观地说明这个过程:
# decorate 是一个装饰器,它接收另一个函数 original 作为参数 def decorate(original): # 创建 modified,这是 original 的修改版本 # modified 会调用 original,并且可以在调用前后执行其他操作 def modified(): # 在调用 original 之前,打印一些信息 print("Before Original function") # 调用 original original() # 在调用 original 之后,打印一些信息 print("After Original function") # 最后,decorate 返回 modified,这是 original 的修改版本 return modified
如何在 Python 中创建装饰器?
为了更好地理解如何在 Python 中创建和使用装饰器,我们将通过一个简单的日志记录装饰器示例来演示。 此装饰器将在每次运行时记录被装饰的函数的名称。
首先,我们需要创建装饰器函数。装饰器函数接收一个名为 func 的参数,该参数表示被装饰的函数。
def create_logger(func): # 函数体在这里
在装饰器函数中,我们将创建一个经过修改的函数。此修改后的函数将在调用原始函数之前,记录原始函数的名称。
# 在 create_logger 内部 def modified_func(): print("Calling: ", func.__name__) func()
接下来,create_logger
函数将返回修改后的函数。因此,完整的 create_logger
函数如下所示:
def create_logger(func): def modified_func(): print("Calling: ", func.__name__) func() return modified_func
至此,我们已经成功创建了一个装饰器。 create_logger
函数是一个简单的装饰器示例。 它接收要装饰的函数 func
,并返回另一个函数 modified_func
。 modified_func
在运行 func
之前会记录 func
的名称。
如何在 Python 中使用装饰器
要使用装饰器,我们需要使用 @
语法,如下所示:
@create_logger def say_hello(): print("Hello, World!")
现在,当我们在脚本中调用 say_hello()
时,输出应该是:
Calling: say_hello "Hello, World"
但 @create_logger
实际上在做什么呢? 它正在将装饰器应用到 say_hello
函数。为了更好地理解其背后的机制,下面的代码实现的效果与在 say_hello
前面加上 @create_logger
是一样的。
def say_hello(): print("Hello, World!") say_hello = create_logger(say_hello)
换句话说,在 Python 中,使用装饰器的一种方式是显式地调用装饰器函数,并将要装饰的函数作为参数传入,就像我们在上面的代码中所做的那样。另一种更加简洁的方式是使用 @
语法。
在本节中,我们已经介绍了如何在 Python 中创建装饰器。
稍微复杂一点的例子
上面的例子是一个简单的示例。还有一些稍显复杂的例子,例如当被装饰的函数接收参数时。另一种更复杂的情况是当你想装饰整个类时。我将在这里涵盖这两种情况。
当函数接受参数时
当被装饰的函数接收参数时,修改后的函数也应该接收这些参数,并在最终调用原始函数时将它们传递过去。如果这让你感到困惑,我们可以用 foo-bar 术语来解释一下。
回想一下,foo
是装饰器函数,bar
是我们正在装饰的函数,而 baz
是被装饰后的 bar
。在这种情况下,bar
将接收参数,并在调用 baz
时将它们传递给 baz
。以下代码示例能够帮助巩固这个概念:
def foo(bar): def baz(*args, **kwargs): # 你可以在这里做一些事情 ... # 然后,调用 bar,并将 args 和 kwargs 传递给它 bar(*args, **kwargs) # 你也可以在这里做一些事情 ... return baz
如果 *args
和 **kwargs
看起来不太熟悉,它们分别是指向位置参数和关键字参数的指针。
需要注意的是,baz
可以访问参数,因此可以在调用 bar
之前对参数执行一些验证。
举个例子,如果我们有一个装饰器函数 ensure_string
,它将确保传递给它所装饰的函数的参数是一个字符串,我们可以像这样实现它:
def ensure_string(func): def decorated_func(text): if type(text) is not str: raise TypeError('argument to ' + func.__name__ + ' must be a string.') else: func(text) return decorated_func
我们可以像这样装饰 say_hello
函数:
@ensure_string def say_hello(name): print('Hello', name)
然后,我们可以使用以下代码进行测试:
say_hello('John') # 应该可以正常运行 say_hello(3) # 应该抛出异常
它应该会产生以下输出:
Hello John Traceback (most recent call last): File "/home/anesu/Documents/python-tutorial/./decorators.py", line 20, in <module> say hello(3) # should throw an exception File "/home/anesu/Documents/python-tu$ ./decorators.pytorial/./decorators.py", line 7, in decorated_func raise TypeError('argument to + func._name_ + must be a string.') TypeError: argument to say hello must be a string. $0
正如预期的那样,该脚本成功打印了 “Hello John”,因为 “John” 是一个字符串。而尝试打印 “Hello 3” 时会抛出异常,因为 “3” 不是字符串。 ensure_string
装饰器可用于验证任何需要字符串的函数的参数。
装饰类
除了装饰函数,我们还可以装饰类。 当你给类添加装饰器时,装饰方法会替换类的构造函数/启动器方法 (__init__
)。
回到 foo-bar 示例,假设 foo 是我们的装饰器,Bar 是我们要装饰的类,那么 foo 会装饰 Bar.__init__
。 这在你希望在实例化 Bar 类型的对象之前执行某些操作时会非常有用。
这意味着下面的代码:
def foo(func): def new_func(*args, **kwargs): print('Doing some stuff before instantiation') func(*args, **kwargs) return new_func @foo class Bar: def __init__(self): print("In initiator")
等同于:
def foo(func): def new_func(*args, **kwargs): print('Doing some stuff before instantiation') func(*args, **kwargs) return new_func class Bar: def __init__(self): print("In initiator") Bar.__init__ = foo(Bar.__init__)
实际上,实例化 Bar 类的对象(使用这两种方式中的任何一种定义)都会得到相同的输出:
Doing some stuff before instantiation In initiator
Python 中的示例装饰器
虽然你可以定义自己的装饰器,但 Python 中已经内置了一些装饰器。 以下是一些在 Python 中你可能会遇到的常见装饰器:
@staticmethod
静态方法用于类上,表示它修饰的方法是一个静态方法。 静态方法是无需实例化类即可运行的方法。 在下面的代码示例中,我们创建了一个带有静态方法 bark
的 Dog
类。
class Dog: @staticmethod def bark(): print('Woof, woof!')
现在,可以像这样访问 bark
方法:
Dog.bark()
运行代码会产生以下输出:
Woof, woof!
正如我在“如何使用装饰器”一节中提到的,有两种方法可以使用装饰器。 @
语法更加简洁,是其中一种。 另一种方法是调用装饰器函数,并将我们要装饰的函数作为参数传入。 这意味着上面的代码实现了与下面的代码相同的功能:
class Dog: def bark(): print('Woof, woof!') Dog.bark = staticmethod(Dog.bark)
我们仍然可以用同样的方式使用 bark
方法:
Dog.bark()
它会产生相同的输出:
Woof, woof!
如你所见,第一种方法更加清晰,而且在开始阅读代码之前,就更明显地看出该函数是一个静态函数。 因此,对于其余示例,我将使用第一种方法。 但请记住,第二种方法也是一种选择。
@classmethod
此装饰器用于表示它修饰的方法是一个类方法。 类方法类似于静态方法,因为它们都不需要在调用它们之前实例化类。
然而,主要的区别在于类方法可以访问类属性,而静态方法则不能。 这是因为无论何时调用类方法,Python 都会自动将类作为第一个参数传递给类方法。要在 Python 中创建类方法,我们可以使用 classmethod
装饰器。
class Dog: @classmethod def what_are_you(cls): print("I am a " + cls.__name__ + "!")
要运行代码,我们只需要调用方法,而无需实例化类:
Dog.what_are_you()
输出是:
I am a Dog!
@property
property
装饰器用于将方法标记为属性的获取器。 回到我们的 Dog
示例,让我们创建一个方法来检索 Dog
的名字。
class Dog: # 创建一个构造函数方法,接收狗的名字作为参数 def __init__(self, name): # 创建一个私有属性 name # 双下划线使该属性变为私有 self.__name = name @property def name(self): return self.__name
现在,我们可以像访问普通属性一样访问狗的名字:
# 创建类的一个实例 foo = Dog('foo') # 访问 name 属性 print("The dog's name is:", foo.name)
运行代码的结果是:
The dog's name is: foo
@property.setter
property.setter
装饰器用于为我们的属性创建设置器方法。 要使用 @property.setter
装饰器,你需要将 property
替换为你正在为其创建设置器的属性的名称。 例如,如果你正在为属性 foo
的方法创建设置器,那么你的装饰器将是 @foo.setter
。 这里有一个 Dog 的例子来阐明:
class Dog: # 创建一个构造函数方法,接收狗的名字作为参数 def __init__(self, name): # 创建一个私有属性 name # 双下划线使该属性变为私有 self.__name = name @property def name(self): return self.__name # 为我们的 name 属性创建一个设置器 @name.setter def name(self, new_name): self.__name = new_name
要测试设置器,我们可以使用以下代码:
# 创建一条新的狗 foo = Dog('foo') # 更改狗的名字 foo.name="bar" # 将狗的新名字打印到屏幕上 print("The dog's new name is:", foo.name)
运行代码将产生以下输出:
The dogs's new name is: bar
装饰器在 Python 中的重要性
现在,我们已经了解了装饰器的本质,并且你已经看到了一些装饰器的示例,接下来我们可以讨论为什么装饰器在 Python 中如此重要。 装饰器之所以重要,有以下几个原因:
- 它们支持代码的重用性:在上面给出的日志记录示例中,我们可以在任何我们想要函数上使用
@create_logger
。 这允许我们为所有函数添加日志记录功能,而无需为每个函数手动编写。 - 它们允许你编写模块化的代码:再次回到日志示例,通过使用装饰器,你可以将核心功能(在本例中为
say_hello
)与你需要的其他功能(在本例中为日志记录)分开。 - 它们增强了框架和库:装饰器广泛用于 Python 框架和库中,以提供附加的功能。 例如,在 Flask 或 Django 等 Web 框架中,装饰器用于定义路由、处理身份验证或将中间件应用于特定视图。
总结
装饰器非常有用,你可以使用它们来扩展功能,而无需更改它们的核心实现。 这在你想要为函数的性能计时、记录调用函数的时间、在调用函数之前验证参数,或在运行函数之前验证权限时非常有用。 掌握装饰器之后,你将能够以更加简洁的方式编写代码。
接下来,你可能想阅读我们关于元组和在 Python 中使用 cURL 的文章。