介绍
在您完成我们的元类简介一章之后,您可能已经问过自己有关元类的可能用例。有一些有趣的用例,但它不是 - 就像有人说的 - 一个等待问题的解决方案。我们已经提到了一些例子。
在 Python 教程的这一章中,我们想详细说明一个示例元类,它将装饰子类的方法。装饰器返回的装饰函数可以计算子类的每个方法被调用的次数。
这通常是我们期望分析器完成的任务之一。所以我们可以将这个元类用于简单的分析目的。当然,为进一步的分析任务扩展我们的元类会很容易。
初步说明
在我们真正深入研究问题之前,我们想提醒我们如何访问类的属性。我们将用列表类来演示这一点。我们可以使用以下构造获取类的所有非私有属性的列表 - 在我们的示例中是随机类。
import random
cls = "random" # 类的名称作为字符串
all_attributes = [ x for x in dir ( eval ( cls )) 如果 不是 x 。开始(“__” ) ]
打印(all_attributes )
输出:
['BPF', 'LOG4', 'NV_MAGICCONST', 'RECIP_BPF', 'Random', 'SG_MAGICCONST', 'SystemRandom', 'TWOPI', '_BuiltinMethodType', '_MethodType', '_Sequence', '_Set', ' _acos', '_ceil', '_cos', '_e', '_exp', '_inst', '_log', '_pi', '_random', '_sha512', '_sin', '_sqrt', '_test' , '_test_generator', '_urandom', '_warn', 'betavariate', 'choice', 'exovariate', 'gammavariate', 'gauss', 'getrandbits', 'getstate', 'lognormvariate', 'normalvariate', '帕累托变量', 'randint', 'random', 'randrange', 'sample', 'seed', 'setstate', 'shuffle', 'triangular', 'uniform', 'vonmisesvariate', 'weibullvariate']
现在,我们正在过滤可调用的属性,即类的公共方法。
方法 = [ x for x in dir ( eval ( cls )) 如果 不是 x 。开始(“__” )
和 可调用(eval (cls + “。” + x ))]
打印(方法)
输出:
['Random', 'SystemRandom', '_BuiltinMethodType', '_MethodType', '_Sequence', '_Set', '_acos', '_ceil', '_cos', '_exp', '_log', '_sha512', ' _sin', '_sqrt', '_test', '_test_generator', '_urandom', '_warn', 'betavariate', 'choice', 'expovariate', 'gammavariate', 'gauss', 'getrandbits', 'getstate' , 'lognormvariate', 'normalvariate', 'paretovariate', 'randint', 'random', 'randrange', 'sample', 'seed', 'setstate', 'shuffle', 'triangular', 'uniform', ' vonmisesvariate', 'weibullvariate']
通过否定可调用,即添加“not”,可以轻松获得类的不可调用属性:
non_callable_attributes = [ x for x in dir ( eval ( cls )) 如果 不是 x 。startswith ( "__" )
并且 不可 调用( eval ( cls + "." + x ))]
打印( non_callable_attributes )
输出:
['BPF', 'LOG4', 'NV_MAGICCONST', 'RECIP_BPF', 'SG_MAGICCONST', 'TWOPI', '_e', '_inst', '_pi', '_random']
在正常的 Python 编程中,既不推荐也不需要按以下方式应用方法,但它是可能的:
lst = [ 3 , 4 ]
列表。__dict__ [ "append" ]( lst , 42 )
lst
输出:
[3, 4, 42]
请注意 Python 文档中的注释:
“因为 dir() 主要是为了在交互式提示下使用方便,所以它尝试提供一组有趣的名称,而不是尝试提供一组严格或一致定义的名称,并且它的详细行为可能会随着版本的不同而改变. 例如,当参数是一个类时,元类属性不在结果列表中。”
用于计算函数调用的装饰器
最后,我们将开始设计元类,这是我们在本章开头提到的目标。它将用装饰器装饰其子类的所有方法,该装饰器计算调用次数。我们在Memoization 和 Decorators一章中定义了这样一个装饰器:
def call_counter ( func ):
def helper ( * args , ** kwargs ):
helper 。调用 += 1
返回 func ( * args , ** kwargs )
helper 。呼叫 = 0
助手。__name__ = 函数。__名称__
返回 帮手
我们可以按照通常的方式使用它:
@call_counter
def f ():
通过
打印(f .调用)
for _ in range ( 10 ):
f ()
打印(f .调用)
输出:
0
10
如果您为装饰功能添加替代符号会更好。我们将在最终的元类中用到它:
def f ():
通过
f = call_counter ( f )
打印( f . call )
for _ in range ( 10 ):
f ()
打印(f .调用)
输出:
0
10
“计数呼叫”元类
现在我们拥有了所有必要的“成分”来编写我们的元类。我们将 call_counter 装饰器包含为一个静态方法:
class FuncCallCounter ( type ):
"""
使用 call_counter 作为装饰器装饰子类的
所有方法的元类 """
@staticmethod
def call_counter ( func ):
""" 装饰器,用于计算函数
或方法调用函数或方法的次数 func
"""
def helper ( * args , ** kwargs ):
helper 。调用 += 1
返回 func ( * args , ** kwargs )
helper 。呼叫 = 0
助手。__name__ = 函数。__名称__
返回 帮手
def __new__ ( cls , clsname , superclasses , attributedict ):
""" 每个方法都用装饰器 call_counter 装饰,
它会
在attributedict 中对attr进行实际调用计数 """
:如果可调用( attributedict [ attr ])而不是attr . 开始(“__” ):attributedict [ attr ] = cls 。呼叫计数器(
attributedict [ attr ])
返回 类型。__new__ ( cls , clsname , superclasses , attributedict )
类 甲(元类= FuncCallCounter ):
def foo ( self ):
通过
def bar ( self ):
通过
if __name__ == "__main__" :
x = A ()
print ( x . foo . call , x . bar . calls )
x . FOO ()
打印(X ,FOO ,通话, X ,酒吧,呼叫)
X 。富()
x 。bar ()
打印( x . foo .调用, x 。酒吧。电话)
输出:
0 0
1 0
2 1