截取元类的运算符 [英] Intercept operator lookup on metaclass

查看:71
本文介绍了截取元类的运算符的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一堂课,需要让每个运算符(例如__add____sub__等)都具有一些魔力.

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.

运算符的查找应在元类上,因为x + 1是作为type(x).__add__(x,1)而不是x.__add__(x,1)完成的,但不会被__getattr____getattribute__方法捕获.

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.

那行不通:

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.

对于那些质疑我为什么需要这种东西的人,请在此处查看完整代码. 这是一个生成函数( 只是为了好玩 )的工具,可以代替lambda s.

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.

示例:

>>> 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.

推荐答案

一些黑魔法可以帮助您实现目标:

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!

输出

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

现在,您可以通过从我的OperatorHackiness基类继承而直接使用第二个代码示例.您甚至可以获得额外的好处:__getattr__在每个实例和运算符中仅被调用一次,并且缓存不涉及额外的递归层.我们在此规避了方法调用比方法查找慢的问题(正如Paul Hankin正确注意到的那样).

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.

这篇关于截取元类的运算符的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆