系统学习Python——装饰器:函数装饰器-[初探用户定义函数装饰器]

分类目录:《系统学习Python》总目录


尽管Python提供了很多可以用作装饰器的内置函数,但其实我们也可以自己编写定制的装饰器。由于它们的广泛用途,我们准备在后面的文章来宣布详细介绍装饰器的编写。不过作为一个快速的示例,让我们看看一个简单的用户定义的装饰器的应用。

回顾之前的文章,__call__运算符重载方法为类实例实现了一个函数调用的接口。下面的代码通过这种方式定义了一个调用代理类,在实例中储存被装饰的函数,并捕获对原来嵫数名的调用。因为这是一个类,所以也拥有状态信息(作为调用次数的计数器):

class tracer:
	def __init__(self, func):
		self.calls = 0
		self.func = func
	
	def __call__(self, *args):
		self.calls += 1
		print('call %s to %s' % (self.calls, self.func.__name__))
		return self.func(*args)

@tracer
def spam(a, b, c):
	print(a + b + c)

因为spam函数是通过tracer装饰器执行的,所以当原来的函数名spam被调用时,实际上触发的是类中的__call__方法。这个方法会计数并记录该次调用,然后委托给被包装的原来的函数。注意*name带星号参数语法是如何用来打包并解包传人的参数的。因此,该装饰器可以包装带有任意多基于位置的参数的任何函数。

最终结果就是新增了一层逻辑到原有的spam函数。下面是该脚本第一行来自tracer类,第二行是spam函数本身的返回值:
输入:

spam(1,2,3)
spam('a', 'b', 'c')

输出:

call 1 to spam
6
call 2 to spam
abc

我们可以仔细学习一下这个例子的代码来加深理解。这个装饰器对于任何接受基于位置参数的函数都有效,但是它不能处理关键字参数,而且不能装饰类一级的方法函数(简而言之,对于其__call__方法方法将只传人一个tracer实例)。正如我们将在前面的文章中见到的,有各种各样的方式来编写函数装饰器,包括嵌套def语句;其中的一些替代方案比这里给出的版本更适用于方法。

例如,通过使用带有外层作用域状态的内嵌函数,而不是带有属性的可调用类实例,函数装饰器通常能变得更加广泛地适用于类一级的方法。我们将推迟讲述该内容的全部细节,但下面是一个这种基于闭包的编码模型的简单例子。它出于可移植性的目的,为计数器状态使用了函数属性,不过仅在Python3.X中还能另外利用变量和nonlocal

def tracer(func):
    def oncall(*args):
        oncall.calls += 1
        print('call %s to %s' % (oncall.calls, func.__name__))
        return func(*args)
    
    oncall.calls = 0
    return oncall

class C:
    @tracer
    def spam(self, a, b, c):
        print(a + b + c)

输入:

x = C()
x.spam(1,2,3)
x.spam('a', 'b', 'c')

输出:

call 1 to spam
6
call 2 to spam
abc
``
`
参考文献:
[1] Mark Lutz. Python学习手册[M]. 机械工业出版社, 2018.

你可能感兴趣的:(系统学习Python,Python,python,装饰器,函数,类,对象,方法)