JBe*_*rdo 8 python metaclass operators python-3.x
我有一个课程需要为每个操作员制作一些魔法,比如__add__,__sub__等等.
我没有在类中创建每个函数,而是使用了一个元类来定义运算符模块中的每个运算符.
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)
Run Code Online (Sandbox Code Playgroud)
该方法工作正常,但我认为如果我只在需要时生成替换运算符会更好.
运营商的查询应该是元类,因为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
Run Code Online (Sandbox Code Playgroud)
此外,生成的"函数"必须是绑定到所用实例的方法.
关于如何拦截此查找的任何想法?我不知道我的目标是否清楚.
对于那些质疑为什么我需要这样的事情,请在这里查看完整的代码.这是一个生成函数的工具(只是为了好玩),可以替代lambdas.
例:
>>> f = FuncBuilder()
>>> g = f ** 2
>>> g(10)
100
>>> g
<var [('pow', 2)]>
Run Code Online (Sandbox Code Playgroud)
仅仅为了记录,我不想知道另一种方法来做同样的事情(我不会在课堂上宣布每一个操作员......这将是无聊的,我的工作方法很好:).我想知道如何拦截来自运营商的属性查找.
一些黑魔法让你实现你的目标:
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!
Run Code Online (Sandbox Code Playgroud)
产量
__getattr__(__add__)
7
7
__getattr__(__mul__)
12
12
__getattr__(__iadd__)
11
Run Code Online (Sandbox Code Playgroud)
现在,您可以通过从我的OperatorHackiness基类继承来逐字地使用您的第二个代码示例.您甚至可以获得额外的好处:__getattr__每个实例和运算符只调用一次,并且缓存不涉及额外的递归层.我们特此避免了与方法查找相比方法调用缓慢的问题(正如Paul Hankin正确注意到的那样).
注意:添加操作符方法的循环仅在整个程序中执行一次,因此准备工作在毫秒范围内持续开销.