在元类上拦截运算符查找
我有一堂课,需要让每个运算符(例如__add__
,__sub__
等等)发挥一些魔力。
我没有在类中创建每个函数,而是有一个元类,它定义了operator模块中的每个运算符。
import operator
class MetaFuncBuilder(type):
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
attr = '__{0}{1}__'
for op in (x for x in dir(operator) if not x.startswith('__')):
oper = getattr(operator, op)
# ... I have my magic replacement functions here
# `func` for `__operators__` and `__ioperators__`
# and `rfunc` for `__roperators__`
setattr(self, attr.format('', op), func)
setattr(self, attr.format('r', op), rfunc)
该方法很好用,但是我认为如果仅在需要时生成替换运算符会更好。
运营商的查询应该是元类,因为x +
1
是为已完成type(x).__add__(x,1)
,而不是x.__add__(x,1)
,但它不会被抓到__getattr__
,也没有__getattribute__
方法。
那行不通:
class Meta(type):
def __getattr__(self, name):
if name in ['__add__', '__sub__', '__mul__', ...]:
func = lambda:... #generate magic function
return func
同样,结果“函数”必须是绑定到所用实例的方法。
关于如何拦截此查询的任何想法?我不知道我想做什么。
对于那些质疑为什么我需要这种东西的人,请在此处查看完整的代码。那是一个生成函数的工具(
只是为了好玩 ),可以代替lambda
s。
例:
>>> f = FuncBuilder()
>>> g = f ** 2
>>> g(10)
100
>>> g
<var [('pow', 2)]>
仅作记录,我不想知道另一种做同一件事的方法(我不会在类上声明每个运算符……这将很无聊,而我所采用的方法也相当不错:)。
我想知道如何拦截来自运算符的属性查找 。
-
一些黑魔法可以帮助您实现目标:
operators = ["add", "mul"] class OperatorHackiness(object): """ Use this base class if you want your object to intercept __add__, __iadd__, __radd__, __mul__ etc. using __getattr__. __getattr__ will called at most _once_ during the lifetime of the object, as the result is cached! """ def __init__(self): # create a instance-local base class which we can # manipulate to our needs self.__class__ = self.meta = type('tmp', (self.__class__,), {}) # add operator methods dynamically, because we are damn lazy. # This loop is however only called once in the whole program # (when the module is loaded) def create_operator(name): def dynamic_operator(self, *args): # call getattr to allow interception # by user func = self.__getattr__(name) # save the result in the temporary # base class to avoid calling getattr twice setattr(self.meta, name, func) # use provided function to calculate result return func(self, *args) return dynamic_operator for op in operators: for name in ["__%s__" % op, "__r%s__" % op, "__i%s__" % op]: setattr(OperatorHackiness, name, create_operator(name)) # Example user class class Test(OperatorHackiness): def __init__(self, x): super(Test, self).__init__() self.x = x def __getattr__(self, attr): print "__getattr__(%s)" % attr if attr == "__add__": return lambda a, b: a.x + b.x elif attr == "__iadd__": def iadd(self, other): self.x += other.x return self return iadd elif attr == "__mul__": return lambda a, b: a.x * b.x else: raise AttributeError ## Some test code: a = Test(3) b = Test(4) # let's test addition print(a + b) # this first call to __add__ will trigger # a __getattr__ call print(a + b) # this second call will not! # same for multiplication print(a * b) print(a * b) # inplace addition (getattr is also only called once) a += b a += b print(a.x) # yay!
输出量
__getattr__(__add__) 7 7 __getattr__(__mul__) 12 12 __getattr__(__iadd__) 11
现在,您可以通过继承我的
OperatorHackiness
基类来使用第二个代码示例。您甚至可以获得额外的好处:__getattr__
每个实例和每个运算符仅被调用一次,并且缓存不涉及额外的递归层。我们在此规避了方法调用比方法查找慢的问题(正如Paul
Hankin正确注意到的那样)。注意 :添加运算符方法的循环在整个程序中仅执行一次,因此准备工作在毫秒范围内会产生恒定的开销。