摘要:在开始使用 python 装饰器之前,我们需要了解 Python 函数是如何工作的。Python 函数被视为一等函数,这意味着它们可以被视为对象并随心所欲地传递。
在开始使用 python 装饰器之前,我们需要了解 Python 函数是如何工作的。Python 函数被视为一等函数,这意味着它们可以被视为对象并随心所欲地传递。
Python 可以在函数中定义函数,称为内部函数。一个函数也可以从另一个函数返回(这是在 Python 中实现 switch 运算符的一种方式)。
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 装饰器语法用更好的语法进行糖衣炮衣。让我们看看如何操作。
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_functiondef 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_functiondef divide(a, b): """ This is a function to divide two numbers """ return a/b请注意粗体代码。我们导入 functools 并装饰我们的包装器。这个 functiontools.wraps 装饰器将 docstring 和 name 属性注入到包装器中,以便我们在打印 __name__ 和 __doc__ 时获得正确的属性。
>>> print(divide.__name__)divide>>> print(divide.__doc__)This is a function to divide two numbersPython 接受有序参数,后跟关键字参数。这可以演示如下。
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_argsdef test_function(*args, **kwargs): return a/b调用上述 test_function 函数将得到以下结果。
>>> test_function('name', 'age', height=150, weight=50)0 name1 ageheight 150weight 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 指定的幂。您可能会找到参数化装饰器的更多示例如下。
验证输入字段、JSON 字符串、文件存在等。实现 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_safeclass Calculator: def __init__(self, a, b): self.ratio = a/b在包装函数的过程中,可以将状态注入到函数中。让我们看看下面的例子。
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@recorddef test: print("Running")运行上面的示例会得到以下输出。
>>> testRan for 1 time(s)Running>>> testRan for 2 time(s)Running>>> testRan for 3 time(s)Running这在使用 decorator 创建单例时很有用。接下来让我们看看。
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@singletonclass SingularObject: def __init__(self): print("The object is being created")我们可以按如下方式确认功能。
>>> first = SingularObjectThe object is being created>>> second = SingularObject>>> second is firstTrue这些对象引用同一实例。因此,我们可以保证不会创建更多对象。因此,单例!
到目前为止,我们只将函数视为包装器或装饰器。但是,在 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)@Recorddef test: print("Run")请注意粗体部分。我们正在使用 functools 来更新函数属性。我们使用 __call__ 重载来定义函数调用的操作。构造函数__init__启动变量,类似于我们在前面的有状态装饰器示例中的包装器函数之后所做的操作。上述示例的输出如下所示。
>>> testRan for 1 time(s)Run>>> testRan for 2 time(s)Run>>> testRan for 2 time(s)Run来源:自由坦荡的湖泊AI