前端开发入门到精通的在线学习网站

网站首页 > 资源文章 正文

如何使用 python 装饰器语法进行更简洁编码

qiguaw 2025-03-12 19:56:15 资源文章 49 ℃ 0 评论


Python 生成器

使用 yield 关键字开发 python 生成器函数

在开始使用 python 装饰器之前,我们需要了解 Python 函数是如何工作的。Python 函数被视为一等函数,这意味着它们可以被视为对象并随心所欲地传递。

Python 可以在函数中定义函数,称为内部函数。一个函数也可以从另一个函数返回(这是在 Python 中实现 switch 运算符的一种方式)。


switch case 实现

Python 字典是一种对象构造,其中对象将返回给引用它的键。由于 Python 没有显式的 switch 运算符,因此我们使用 dict 构造来创建一个。请参阅此示例。

op_switch = {
    'sqr': lambda x: x**2,
    'sqrt': lambda x: x**0.5,
    'abs': lambda x: abs(x)
}

我们的 switch case 基于一个字符串来选择操作。字典返回一个函数。为了简化代码,我使用了 lambda 函数定义。它们的行为与函数的行为类似(不完全相同!可以按如下方式访问它们。

>>> switch['sqr'](12)
144
>>> switch['sqrt'](25)
5.0

将函数传递给另一个函数

考虑需要包装另一个函数的情况。想象一下,包装器函数可以在许多其他函数之间共享。在我告诉你一个真实的工作例子之前,让我们先来看看这个宠物场景。

假设你需要一个函数,该函数将执行该函数并返回一个答案。如果引发异常,将返回 None

def deco_function(func, *args):
    try:
        return func(*args)
    except:
        print("Error occured")
        return None
    
def divide(a, b):
    return a/b

我们的 deco_function 函数将执行传递的函数,其中包含作为 *args 传递的 args 集。为简单起见,我省略了关键字参数。如果我们运行此命令,我们将看到我们给出的每个参数的以下输出。

>>> deco_function(divide, 10, 2)
5.0
>>> deco_function(divide, 10, 0)
Error occured

很整洁,对吧!

这种方法的主要问题是包装函数的函数签名必须众所周知。我们需要传递函数本身和参数。在复杂场景中,这不太容易维护!

Python 装饰器简介

上面的相同函数可以使用 python 装饰器语法用更好的语法进行糖衣炮衣。让我们看看如何操作。

def deco_function(func):
    def wrapped(*args):
        """
        This is the wrapper for a function to be fail safe
        """
        try:
            return func(*args)
        except:
            print("Error occured")
            return None
    return wrapped
    
@deco_function
def divide(a, b):    
    """
    This is a function to divide two numbers
    """
    return a/b

在这个例子中,我们使用 deco_function 装饰器来装饰 divide 函数。在 decorator 中,将包装器放置在传递的函数周围并返回包装器。这与以下语句类似。

divide = deco_function(divide)

但是,我们现在可以自由地忘记 call_function 实现。这真是太棒了!

现实世界的装饰器用法!


Flask 是 python 的服务器实现。屏幕截图显示了如何使用 decorator 在 Flask 中实现路由。我们只需要提到应激活该功能的路线。到目前为止,我们还没有讨论如何将参数传递给装饰器。我们很快就会讨论这个问题!

正确使用装饰器和函数名称

要记住的一点是,包装函数可能会导致其标识混淆。这是因为一旦我们用其他东西包装它,函数就不再是它自己。

>>> divide.__name__
wrapped
>>> print(divide.__doc__)This is the wrapper for a function to be fail safe

函数的 __name__ 属性返回函数名称本身,打印 __doc__ 返回文档字符串。但是,我们可以看到,对于这两个属性,我们从包装函数获取值,而不是从引用的函数获取值。这可能会在大型软件中造成严重的混淆。这是解决方法。

import functoolsdef deco_function(func):
    @functools.wraps(func)
    def wrapped(*args):
        """
        This is the wrapper for a function to be fail safe
        """
        try:
            return func(*args)
        except:
            print("Error occured")
            return None
    return wrapped
    
@deco_function
def divide(a, b):    
    """
    This is a function to divide two numbers
    """
    return a/b

请注意粗体代码。我们导入 functools 并装饰我们的包装器。这个 functiontools.wraps 装饰器将 docstringname 属性注入到包装器中,以便我们在打印 __name____doc__ 时获得正确的属性。

>>> print(divide.__name__)
divide
>>> print(divide.__doc__)This is a function to divide two numbers

了解参数和关键字参数的工作原理

Python 接受有序参数,后跟关键字参数。这可以演示如下。

import functoolsdef print_args(func):
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        args_arr = [(n, a) for n, a in enumerate(args)]                      
        kwargs_arr = [(k, v) for k, v in kwargs.items()]for k, v in args_arr + kwargs_arr:
            print(k, v)
    return wrapped
    
@print_args
def test_function(*args, **kwargs):    
    return a/b

调用上述 test_function 函数将得到以下结果。

>>> test_function('name', 'age', height=150, weight=50)
0 name
1 age
height 150
weight 50

根据上述观察,必须注意,参数是在 keyword 参数之前组织的。顺序必须在参数中保留,并且在关键字参数中无关紧要。在包装器中,参数和关键字参数都必须传递到被包装的函数中。这确保了包装器更广泛的可用性。

带参数的装饰器

现在我们已经清楚地了解了装饰器的工作原理,让我们看看如何使用带有参数的装饰器。

def powered(power):
    def powered_decorator(func):
        def wrapper(*args):
            return func(*args)**power
        return wrapper
    return powered_decorator@powered(2)
def add(*args):
    return sum(args)

在上面的示例中,我们有一个带有 attribute 的包装器。简单地说,这个包装器要求将答案提升为由 attribute 指定的幂。您可能会找到参数化装饰器的更多示例如下。

  1. 验证输入字段、JSON 字符串、文件存在等。
  2. 实现 switch cased 装饰器

装饰器的高级用例

装饰器可以以类似的方式用于类。但是,在这里我们可以讨论两种使用 decorator 的方法;在类中,以及为类。

类中的装饰器

在下面的示例中,我对类 Calculator 的函数使用修饰器。这有助于我在操作失败时正常地获取操作的值。

import functoolsdef try_safe(func):
    @functools.wraps(func)
    def wrapped(*args):
        try:
            return func(*args)
        except:
            print("Error occured")
            return None
    return wrappedclass Calculator:
    
    def __init__(self):
        pass
    
    @try_safe
    def add(self, *args):
        return sum(args)
    
    @try_safe
    def divide(self, a, b):
        return a/b

上面的代码可以按如下方式使用。

>>> calc = Calculator()
>>> calc.divide(10, 2)
5.0

类的装饰器

对类使用装饰器将在函数实例化期间激活装饰器。例如,下面的代码将使用 constructor 参数检查对象是否正常创建。如果操作失败,将返回 None 来代替 Calculator 类中的对象。

import functoolsdef try_safe(cls):
    @functools.wraps(cls)
    def wrapped(*args):
        try:
            return cls(*args)
        except:
            print("Error occured")
            return None
    return wrapped@try_safe
class Calculator:
    
    def __init__(self, a, b):
        self.ratio = a/b

使用 Decorator 为 Function 注入 State

在包装函数的过程中,可以将状态注入到函数中。让我们看看下面的例子。

import functoolsdef record(func):
    @functools.wraps(func)
    def wrapped(*args):
        wrapped.record += 1
        print(f"Ran for {wrapped.record} time(s)")
        return func(*args)
    wrapped.record = 0
    return wrapped@record
def test():
    print("Running")

运行上面的示例会得到以下输出。

>>> test()
Ran for 1 time(s)
Running
>>> test()
Ran for 2 time(s)
Running
>>> test()
Ran for 3 time(s)
Running

这在使用 decorator 创建单例时很有用。接下来让我们看看。

使用 Python 装饰器的单例

Singleton 引用在调用之间共享的实例,并且不会出于任何原因进行复制。简单来说,首先创建一个实例。在以下创建实例的调用中,将返回现有实例。

让我们看看如何使用装饰器实现单例。

import functoolsdef singleton(cls):
    @functools.wraps(cls)
    def wrapped(*args, **kwargs):
        if not wrapped.object:
            wrapped.object = cls(*args, **kwargs)
        return wrapped.object
    wrapped.object = None
    return wrapped@singleton
class SingularObject:
    def __init__(self):
        print("The object is being created")

我们可以按如下方式确认功能。

>>> first = SingularObject()
The object is being created
>>> second = SingularObject()
>>> second is first
True

这些对象引用同一实例。因此,我们可以保证不会创建更多对象。因此,单例!

制作包装器/装饰器类

到目前为止,我们只将函数视为包装器或装饰器。但是,在 OOP 程序中,类可能是首选。我们只需对 record 示例进行一些修改即可实现此目的。让我们看看代码。

import functoolsclass Record:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.record = 0def __call__(self, *args, **kwargs):
        self.record += 1
        print(f"Ran for {self.record} time(s)")
        return self.func(*args, **kwargs)@Record
def test():
    print("Run")

请注意粗体部分。我们正在使用 functools 来更新函数属性。我们使用 __call__ 重载来定义函数调用的操作。构造函数__init__启动变量,类似于我们在前面的有状态装饰器示例中的包装器函数之后所做的操作。上述示例的输出如下所示。

>>> test()
Ran for 1 time(s)
Run
>>> test()
Ran for 2 time(s)
Run
>>> test()
Ran for 2 time(s)
Run

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表