在元类上拦截运算符查找(Intercept operator lookup on metaclass)

编程入门 行业动态 更新时间:2024-10-28 08:29:19
在元类上拦截运算符查找(Intercept operator lookup on metaclass)

我有一个需要为每个运算符创造一些魔力的类,比如__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)

该方法工作正常,但我认为如果仅在需要时生成替换运算符会更好。

查找运算符应该放在元类上,因为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)]>

只是为了记录,我不想知道另一种做同样事情的方法(我不会在课堂上宣布每一个操作员......这会很无聊,而且我的工作方式很不错:)。 我想知道如何拦截来自操作员的属性查找

I have a class that need to make some magic with every operator, like __add__, __sub__ and so on.

Instead of creating each function in the class, I have a metaclass which defines every operator in the operator module.

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)

The approach works fine, but I think It would be better if I generate the replacement operator only when needed.

Lookup of operators should be on the metaclass because x + 1 is done as type(x).__add__(x,1) instead of x.__add__(x,1), but it doesn't get caught by __getattr__ nor __getattribute__ methods.

That doesn't work:

class Meta(type): def __getattr__(self, name): if name in ['__add__', '__sub__', '__mul__', ...]: func = lambda:... #generate magic function return func

Also, the resulting "function" must be a method bound to the instance used.

Any ideas on how can I intercept this lookup? I don't know if it's clear what I want to do.


For those questioning why do I need to this kind of thing, check the full code here. That's a tool to generate functions (just for fun) that could work as replacement for lambdas.

Example:

>>> f = FuncBuilder() >>> g = f ** 2 >>> g(10) 100 >>> g <var [('pow', 2)]>

Just for the record, I don't want to know another way to do the same thing (I won't declare every single operator on the class... that will be boring and the approach I have works pretty fine :). I want to know how to intercept attribute lookup from an operator.

最满意答案

一些黑魔法让你实现你的目标:

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注意到的那样)。

注意 :添加操作符方法的循环只在整个程序中执行一次,因此准备在毫秒范围内持续开销。

Some black magic let's you achieve your goal:

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!

Output

__getattr__(__add__) 7 7 __getattr__(__mul__) 12 12 __getattr__(__iadd__) 11

Now you can use your second code sample literally by inheriting from my OperatorHackiness base class. You even get an additional benefit: __getattr__ will only be called once per instance and operator and there is no additional layer of recursion involved for the caching. We hereby circumvent the problem of method calls being slow compared to method lookup (as Paul Hankin noticed correctly).

NOTE: The loop to add the operator methods is only executed once in your whole program, so the preparation takes constant overhead in the range of milliseconds.

更多推荐

本文发布于:2023-08-05 19:37:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1439474.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:运算符   Intercept   元类上   metaclass   lookup

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!