python】装饰器与装饰"/>
【python】装饰器与装饰
介绍
装饰器很可能属于 Python 中最漂亮和最强大的设计可能性,但同时,许多人认为这个概念很复杂。准确地说,装饰器的使用非常简单,但是编写装饰器可能会很复杂,尤其是如果您对装饰器和一些函数式编程概念没有经验的话。
尽管它是相同的底层概念,但我们在 Python 中有两种不同的装饰器:
- 函数装饰器
- 类装饰器
Python 中的装饰器是用于修改函数或类的任何可调用 Python 对象。对函数“func”或类“C”的引用传递给装饰器,装饰器返回修改后的函数或类。修改后的函数或类通常包含对原始函数“func”或类“C”的调用。
你也可以参考我们关于装饰器记忆的章节。
如果你喜欢本页右侧的图片,如果你也对使用 Python、Numpy、Scipy 和 Matplotlib 进行图像处理感兴趣,你一定会喜欢我们关于图像处理技术的章节,它解释了整个制作过程——我们的装饰者和标志图片!
装饰器的第一步
我们从我们的各种 Python 培训课程中了解到,装饰器的定义有一些地方,很多初学者都会卡在那里。
因此,我们将通过重复函数的一些重要方面来介绍装饰器。首先,您必须知道或记住函数名是对函数的引用,并且我们可以为同一个函数分配多个名称:
def succ ( x ):返回 x + 1 后继者 = succ 后继者( 10 )
输出:
11
成功( 10 )
输出:
11
这意味着我们有两个名称,即同一个函数的“succ”和“successor”。下一个重要的事实是我们可以删除“succ”或“successor”而不删除函数本身。
del succ 继任者( 10 )
输出:
11
函数内的函数
在函数内部拥有或定义函数的概念对于 C 或 C++ 程序员来说是全新的:
def f (): def g (): print ( "嗨,是我'g'" ) print ( "谢谢你给我打电话" ) print ( "这是'f'函数" ) print ( "我在打电话' g' 现在:" ) g () f ()
输出:
这是函数'f' 我现在打电话给'g': 嗨,是我'g' 谢谢你给我打电话
在函数中使用“正确”返回语句的另一个示例:
def temperature ( t ): def celsius2fahrenheit ( x ): return 9 * x / 5 + 32 result = "It's " + str ( celsius2fahrenheit ( t )) + "degrees!" 返回 结果 打印(温度(20 ))
输出:
这是68.0度!
下面的例子是关于阶乘函数的,我们之前定义如下:
def factorial ( n ): """ 计算 n 的阶乘, n 应该是一个整数并且 n <= 0 """ if n == 0 : return 1 else : return n * factorial ( n - 1 )
如果有人向此函数传递负值或浮点数会发生什么?它永远不会结束。您可能会想到如下检查:
def factorial ( n ): """ 计算 n 的阶乘, n 应该是一个整数并且 n <= 0 """ if type ( n ) == int and n >= 0 : if n == 0 : return 1 else : return n * factorial ( n - 1 ) else : raise TypeError ( "n 必须是一个正整数或零" )
如果用4 '' for example, i.e.
阶乘 (4) ''调用这个函数,首先要检查的是它是否是我的正整数。原则上,这是有道理的。“问题”现在出现在递归步骤中。现在factorial (3) '' is called. This call and all others also check whether it is a positive whole number. But this is unnecessary: If you subtract the value
1 '' 从一个正整数,你又得到一个正整数或 `` 0 ''。因此,我们的函数的两个明确定义的参数值。
使用嵌套函数(局部函数)可以优雅地解决这个问题:
def factorial ( n ): """ 计算 n 的阶乘, n 应该是一个整数并且 n <= 0 """ def inner_factorial ( n ): if n == 0 : return 1 else : return n * inner_factorial ( n - 1 )如果 类型( n ) == int 并且 n >= 0 :返回 inner_factorial ( n ) else :加注 类型错误(“n应为positve int或0” )
作为参数的函数
如果你只看前面的例子,这似乎不是很有用。结合 Python 函数的两个更强大的可能性,它会变得很有用。由于函数的每个参数都是对对象的引用,函数也是对象,因此我们可以将函数——或者更好的“对函数的引用”——作为参数传递给函数。我们将在下一个简单示例中演示这一点:
def g (): print ( "嗨,是我 'g'" ) print ( "谢谢你给我打电话" ) def f ( func ): print ( "嗨,是我 'f'" ) print ( "我会打电话给我'func' now" ) func () f ( g )
输出:
嗨,我是‘f’ 我现在就叫'func' 嗨,是我'g' 谢谢你给我打电话
您可能对输出不满意。'f' 应该写成它调用的是 'g' 而不是 'func'。当然,我们需要知道 func 的“真实”名称是什么。为此,我们可以使用属性__name__
,因为它包含以下名称:
def g (): print ( "嗨,是我 'g'" ) print ( "谢谢你给我打电话" ) def f ( func ): print ( "嗨,是我 'f'" ) print ( "我会打电话给你'func' now" ) func () print ( "func 的真名是" + func . __name__ ) f ( g )
输出:
嗨,我是‘f’ 我现在就叫'func' 嗨,是我'g' 谢谢你给我打电话 func 的真名是 g
输出再次解释了正在发生的事情。另一个例子:
import math def foo ( func ): print ( "The function " + func . __name__ + " was connected to foo" ) res = 0 for x in [ 1 , 2 , 2.5 ]: res += func ( x ) return res print ( foo ( math . sin )) 打印( foo ( math . cos )))
输出:
函数 sin 被传递给 foo 2.3492405557375347 函数 cos 被传递给 foo -0.6769881462259364
函数返回函数
函数的输出也是对对象的引用。因此函数可以返回对函数对象的引用。
def f ( x ): def g ( y ): return y + x + 3 return g nf1 = f ( 1 ) nf2 = f ( 3 ) print ( nf1 ( 1 ) ) print ( nf2 ( 1 ))
输出:
5 7
前面的例子看起来很人为,完全没用。我们现在将展示另一个面向语言的示例,它显示了更实际的触感。好的,仍然不是一个有用的功能。我们用几乎不言自明的名称编写了一个函数greeting_func_gen
。因此,此函数返回(或生成)可用于创建不同语言(即德语、法语、意大利语、土耳其语和希腊语)的人的函数:
高清 greeting_func_gen (郎):DEF customized_greeting (名称):如果 郎 == “德” : #德国短语 = “Guten摩根” ELIF 郎 == “FR” :# 法国短语 = “卓悦” ELIF 郎 == “它” : # 意大利语短语 = "Buongiorno " elif lang == "tr" : # 土耳其语短语 = "Günaydın "ELIF 郎 == "gr" : # 希腊语短语 = "Καλημερα " else :短语 = "Hi "返回 短语 + 名称 + "!" 返回 customized_greeting say_hi = greeting_func_gen (“TR” ) 打印(say_hi (“Gülay” )) 的方式#这个土耳其名字的意思是“玫瑰月亮”
输出:
Günaydın Gülay!
在以下示例中,它变得越来越有用,同时也更加面向数学。我们现在将实现一个多项式“工厂”函数。我们将从编写一个可以创建 2 次多项式的版本开始。
p(X)=一个X2+乙X+C
作为多项式工厂函数的 Python 实现可以这样写:
def polynomial_creator ( a , b , c ): def polynomial ( x ):返回 a * x ** 2 + b * x + c返回 多项式 p1 = polynomial_creator ( 2 , 3 , - 1 ) p2 = polynomial_creator ( - 1 , 2 , 1 ) 为 X 在 范围( - 2 , 2 , 1 ):打印( x , p1 ( x ), p2 ( x ))
输出:
-2 1 -7 -1 -2 -2 0 -1 1 1 4 2
我们可以推广我们的工厂函数,以便它可以用于任意次数的多项式:
∑克=0n一个克⋅X克=一个n⋅Xn+一个n-1⋅Xn-1+...+一个2⋅X2+一个1⋅X+一个0
def polynomial_creator ( * coefficients ): """ 系数的形式为 a_n, ... a_1, a_0 """ def polynomial ( x ): res = 0 for index , coeff in enumerate ( coefficients [:: - 1 ]) : res += coeff * x ** index return res return polynomial p1 = polynomial_creator ( 4 ) p2 = polynomial_creator ( 2 , 4 ) p3 = polynomial_creator ( 1 , 8 , - 1 , 3 , 2 ) p4 = polynomial_creator ( - 1 , 2 , 1 ) for x in range ( - 2 , 2 , 1 ): print ( x , p1 ( x ), p2 ( x ), p3 ( x ), p4 ( x ))
输出:
-2 4 0 -56 -7 -1 4 2 -9 -2 0 4 4 2 1 1 4 6 13 2
例如,函数 p3 实现以下多项式:
p3(X)=X4+8⋅X3-X2+3⋅X+2
我们装饰器 polynomial_creator 中的多项式函数可以更有效地实现。我们可以用某种方式对它进行因式分解,这样它就不需要任何求幂。
没有求幂的一般多项式的因式分解版本:
r电子秒=(...(一个n⋅X+一个n-1)⋅X+...+一个1)⋅X+一个0
避免取幂的多项式创建器装饰器的实现:
def polynomial_creator ( * coeffs ): """ 系数的形式为 a_n, a_n_1, ... a_1, a_0 """ def polynomial ( x ): res = coeffs [ 0 ] for i in range ( 1 , len ( coeffs )): res = res * x + coeffs [ i ]返回 res返回 多项式 p1 = polynomial_creator ( 4) p2 = polynomial_creator ( 2 , 4 ) p3 = polynomial_creator ( 1 , 8 , - 1 , 3 , 2 ) p4 = polynomial_creator ( - 1 , 2 , 1 ) for x in range ( - 2 , 2 , 1 ): print ( x , p1 ( x ), p2( x ), p3 ( x ), p4 ( x ))
输出:
-2 4 0 -56 -7 -1 4 2 -9 -2 0 4 4 2 1 1 4 6 13 2
如果您想了解有关多项式以及如何创建多项式类的更多信息,可以继续阅读我们关于多项式的章节。
一个简单的装饰器
现在我们已经准备好定义我们的第一个简单装饰器了:
def our_decorator ( func ): def function_wrapper ( x ): print ( "调用前" + func . __name__ ) func ( x ) print ( "调用后" + func . __name__ ) return function_wrapper def foo ( x ): print ( "Hi , foo 已被调用 " + str ( x )) 打印("我们在装饰前调用 foo:" ) foo ( "Hi" ) print ( "我们现在用 f 装饰 foo:" ) foo = our_decorator ( foo ) print ( "我们在装饰后调用 foo:" ) foo ( 42 )
输出:
我们在装饰前调用 foo : 嗨, foo 已被称为 Hi 我们现在用 f 装饰 foo: 我们在装饰后调用 foo : 在调用 foo 之前 嗨, foo 已被调用 42 调用 foo 后
如果您查看前一个程序的输出,您可以看到发生了什么。在修饰“foo = our_decorator(foo)”之后,foo 是对“function_wrapper”的引用。'foo' 将在 'function_wrapper' 内被调用,但在调用之前和之后将执行一些额外的代码,即在我们的例子中两个打印函数。
Python 中装饰器的常用语法
Python 中的装饰通常不像我们在前面的示例中那样执行,即使符号foo = our_decorator(foo)
很吸引人且易于掌握。这就是我们使用它的原因!您还可以在我们之前的方法中看到一个设计问题。“foo”在同一个程序中存在两个版本,装饰前和装饰后。
我们现在要做一个适当的装饰。装饰出现在函数头之前的行中。“@”后跟装饰器函数名称。
我们现在将重写我们的初始示例。而不是写声明
foo = our_decorator(foo)
我们可以写
@our_decorator
但是这条线必须直接定位在装饰函数的前面。完整的例子现在看起来像这样:
def our_decorator ( func ): def function_wrapper ( x ): print ( "调用前" + func . __name__ ) func ( x ) print ( "调用后" + func . __name__ ) return function_wrapper @our_decorator def foo ( x ): print ( “嗨, foo 已被调用” + str ( x )) foo (“你好” )
输出:
在调用 foo 之前 嗨, foo 已被称为 Hi 调用 foo 后
我们可以用我们的装饰器“our_decorator”装饰每一个接受一个参数的其他函数。我们在下面演示这一点。我们稍微改变了我们的函数包装器,以便我们可以看到函数调用的结果:
def our_decorator ( func ): def function_wrapper ( x ): print ( "调用前" + func . __name__ ) res = func ( x ) print ( res ) print ( "调用后" + func . __name__ ) return function_wrapper @our_decorator def succ (ñ ):返回 ñ + 1个 SUCC (10 )
输出:
在调用 succ 之前 11 调用 succ 后
也可以装饰第三方函数,例如我们从模块导入的函数。在这种情况下,我们不能使用带有“at”符号的 Python 语法:
from math import sin , cos def our_decorator ( func ): def function_wrapper ( x ): print ( "调用前" + func . __name__ ) res = func ( x ) print ( res ) print ( "调用后" + func . __name__ ) return function_wrapper sin = our_decorator ( sin ) cos = our_decorator ( cos ) for f in [ sin , cos ]: f ( 3.1415 )
输出:
在称罪之前 9.265358966049026e-05 称罪后 调用cos之前 -0.9999999957076562 调用cos后
总而言之,我们可以说 Python 中的装饰器是一个可调用的 Python 对象,用于修改函数、方法或类定义。将要修改的原始对象作为参数传递给装饰器。装饰器返回一个修改过的对象,例如一个修改过的函数,它绑定到定义中使用的名称。
前面的 function_wrapper 仅适用于只有一个参数的函数。我们提供了 function_wrapper 的通用版本,它在以下示例中接受具有任意参数的函数:
from random import random , randint , choice def our_decorator ( func ): def function_wrapper ( * args , ** kwargs ): print ( "Before call" + func . __name__ ) res = func ( * args , ** kwargs ) print ( res )打印( “调用后” + func .__name__ ) return function_wrapper random = our_decorator ( random ) randint = our_decorator ( randint ) choice = our_decorator ( choice ) random () randint ( 3 , 8 ) choice ([ 4 , 5 , 6 ])
输出:
在调用随机之前 0.3206237466802222 随机调用后 在调用 randint 之前 6 调用randint后 呼叫选择前 5 调用后选择
装饰器的用例
使用装饰器检查参数
在关于递归函数的章节中,我们介绍了阶乘函数。我们想让函数尽可能简单,我们不想掩盖潜在的想法,所以我们没有合并任何参数检查。因此,如果有人使用负参数或浮点参数调用我们的函数,我们的函数将进入无限循环。
以下程序使用装饰器函数来确保传递给函数 factorial 的参数是正整数:
def argument_test_natural_number ( f ): def helper ( x ): if type ( x ) == int and x > 0 : return f ( x ) else : raise Exception ( "Argument is not an integer" ) return helper @argument_test_natural_number def factorial ( n ):如果 n == 1 :否则返回 1: return n * factorial ( n - 1 ) for i in range ( 1 , 10 ): print ( i , factorial ( i )) print ( factorial ( - 1 ))
输出:
1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880
使用装饰器计算函数调用
以下示例使用装饰器来计算函数被调用的次数。准确地说,我们可以仅将这个装饰器用于只有一个参数的函数:
def call_counter ( func ): def helper ( x ): helper 。调用 += 1返回 func ( x )助手。呼叫 = 0返回 辅助 @call_counter DEF SUCC (X ):返回 X + 1张 打印(SUCC 。呼叫) 用于 我 在 范围(10 ):SUCC (我) 打印(SUCC 。电话)
输出:
0 10
我们指出我们只能将我们之前的装饰器用于函数,这些函数只接受一个参数。我们将使用 *args 和 **kwargs 符号来编写装饰器,这些装饰器可以处理具有任意数量的位置和关键字参数的函数。
def call_counter ( func ): def helper ( * args , ** kwargs ): helper 。调用 += 1返回 func ( * args , ** kwargs ) helper 。call = 0 return helper @call_counter def succ ( x ): return x + 1 @call_counter def mul1 ( x , y= 1 ): return x * y + 1 print ( succ . calls ) for i in range ( 10 ): succ ( i ) mul1 ( 3 , 4 ) mul1 ( 4 ) mul1 ( y = 3 , x = 2 ) print ( SUCC 。调用) 打印(MUL1 。电话)
输出:
0 10 3
带参数的装饰器
我们在以下代码中定义了两个装饰器:
def night_greeting ( func ): def function_wrapper ( x ): print ( "Good night ," + func . __name__ + "returns:" ) return func ( x ) return function_wrapper def Morning_greeting ( func ): def function_wrapper ( x ): print ( "早上好," + func . __name__ + " 返回:" )return func ( x ) return function_wrapper @evening_greeting def foo ( x ): print ( 42 ) foo ( "Hi" )
输出:
晚上好, foo 回来了: 42
除了问候语之外,这两个装饰器几乎相同。我们想在装饰器中添加一个参数,以便在我们进行装饰时自定义问候语。我们必须在我们之前的装饰器函数周围包装另一个函数来实现这一点。我们现在可以轻松地用希腊语说“早安”:
def greeting ( expr ): def greeting_decorator ( func ): def function_wrapper ( x ): print ( expr + ", " + func . __name__ + "returns:" ) func ( x ) return function_wrapper return greeting_decorator @greeting ( "καλημερα" ) def foo ( x ):打印( 42 ) foo (“你好” )
输出:
καλημερα, foo 返回: 42
如果我们不想或不能使用“at”装饰器语法,我们可以通过函数调用来实现:
高清 问候(EXPR ):DEF greeting_decorator (FUNC ):DEF function_wrapper (X ):打印(EXPR + “ ” + FUNC 。__name__ + “回报” )返回 FUNC (X )返回 function_wrapper回报 greeting_decorator 高清 FOO (X ):打印( 42 ) 问候 2 = 问候("καλημερα" ) foo = greeting2 ( foo ) foo ( "Hi" )
输出:
καλημερα, foo 返回: 42
当然,我们不需要“greeting2”的额外定义。我们可以直接将调用“greeting(“καλημερα”)”的结果应用到“foo”上:
foo = 问候(“καλημερα”)(foo)
使用来自 functools 的包装
到目前为止,我们定义装饰器的方式还没有考虑到属性
__name__
(函数名称),__doc__
(文档字符串)和__module__
(定义函数的模块)
装修后将失去原有的功能。
以下装饰器将保存在文件 greeting_decorator.py 中:
定义问候(功能):def function_wrapper(x):""" function_wrapper 的问候 """打印(“嗨,” + func.__name__ + “返回:”)返回函数(x)返回函数_包装器
我们在以下程序中调用它:
from greeting_decorator import greeting @greeting def f ( x ): """ 只是一些愚蠢的函数 """ return x + 4 f ( 10 ) print ( "function name: " + f . __name__ ) print ( "docstring: " + f . __doc__ ) 打印( "模块名称:" + f . __module__ )
输出:
嗨,f 返回: 函数名:function_wrapper 文档字符串:问候的函数包装器 模块名称:greeting_decorator
我们得到了上面的“不需要的”结果。
如果我们在装饰器内部分配它们,我们可以保存函数 f 的原始属性。我们相应地更改我们之前的装饰器并将其保存为 greeting_decorator_manually.py:
定义问候(功能): def function_wrapper(x):""" function_wrapper 的问候 """打印(“嗨,” + func.__name__ + “返回:”)返回函数(x)function_wrapper.__name__ = func.__name__function_wrapper.__doc__ = func.__doc__function_wrapper.__module__ = func.__module__返回函数_包装器
在我们的主程序中,我们所要做的就是更改 import 语句。
从 greeting_decorator_manually 导入 问候语
幸运的是,我们不必将所有这些代码添加到我们的装饰器中以获得这些结果。我们可以从 functools 导入装饰器“包装”,并用它在装饰器中装饰我们的函数:
from functools import wraps def greeting ( func ): @wraps ( func ) def function_wrapper ( x ): """ function_wrapper of greeting """ print ( "Hi, " + func . __name__ + "returns:" ) return func ( x )返回 function_wrapper
类而不是函数
该调用方法
到目前为止,我们使用函数作为装饰器。在将装饰器定义为类之前,必须先介绍__call__
类的方法。我们已经提到装饰器只是一个将函数作为输入参数的可调用对象。函数是一个可调用对象,但很多 Python 程序员不知道还有其他可调用对象。一个可调用对象是一个可以使用并且表现得像一个函数但可能不是一个函数的对象。可以以实例是可调用对象的方式定义类。__call__
如果实例被称为“像函数一样”,即使用括号,则调用该方法。
class A : def __init__ ( self ): print ( "A 的一个实例被初始化了" ) def __call__ ( self , * args , ** kwargs ): print ( "Arguments are:" , args , kwargs ) x = A () print ( "现在调用实例:" ) x ( 3 , 4 , x = 11 , y =10 ) 打印( "让我们再次调用它:" ) x ( 3 , 4 , x = 11 , y = 10 )
输出:
A 的一个实例被初始化 现在调用实例: 参数是: (3, 4) {'x': 11, 'y': 10} 让我们再次调用它: 参数是: (3, 4) {'x': 11, 'y': 10}
我们可以使用以下__call__
方法为斐波那契函数编写一个类:
斐波那契类:def __init__ ( self ): self 。缓存 = {} DEF __call__ (自, Ñ ):如果 ñ 未 在 自我。缓存:如果 n == 0 :自我。缓存[ 0 ] = 0 elif n == 1 : self . 缓存[ 1 ] = 1否则:自己。缓存[ n ] = self 。__call__ ( n - 1 ) + self 。__call__ ( n - 2 )返回 self 。cache [ n ] fib = Fibonacci () for i in range ( 15 ): print ( fib ( i ), end = ", " )
输出:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377,
您可以__call__
在我们教程的魔术函数一章中找到有关该方法的更多信息。
使用类作为装饰器
我们将以下装饰器重写为一个类:
def decorator1 ( f ): def helper (): print ( "Decorating" , f . __name__ ) f () return helper @decorator1 def foo (): print ( "inside foo()" ) foo ()
输出:
装饰 foo 在 foo() 里面
作为类实现的以下装饰器执行相同的“工作”:
类 装饰器2 : def __init__ ( self , f ): self 。f = f def __call__ ( self ): print ( "Decorating" , self . f . __name__ ) self . f () @decorator2 def foo (): print ( "inside foo()" ) foo ()
输出:
装饰 foo 在 foo() 里面
两个版本都返回相同的输出。
更多推荐
【python】装饰器与装饰
发布评论