在元类上拦截运算符查找

时间:2011-12-26 16:02:07

标签: python python-3.x operators 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 + 1type(x).__add__(x,1)而不是x.__add__(x,1)完成,但它不会被__getattr__或{{1方法。

这不起作用:

__getattribute__

此外,生成的“函数”必须是绑定到所用实例的方法。

关于如何拦截此查找的任何想法?我不知道我的目标是否清楚。


对于那些质疑为什么我需要这样的事情,请检查完整的代码here。 这是一个生成函数的工具( 只是为了好玩 ),可以替代class Meta(type): def __getattr__(self, name): if name in ['__add__', '__sub__', '__mul__', ...]: func = lambda:... #generate magic function return func

示例:

lambda

仅仅为了记录,我不想知道另一种方法来做同样的事情(我不会在课堂上宣布每一个操作员......这将是无聊的,我的方法非常好: )。 我想知道如何拦截来自运营商的属性查找

3 个答案:

答案 0 :(得分:5)

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

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

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

答案 1 :(得分:3)

手头的问题是Python在对象的类上查找__xxx__方法,而不是在对象本身上查找 - 如果找不到它,它不会回退到__getattr__也不会{ {1}}。

拦截此类调用的唯一方法是已经有一个方法。它可以是一个存根功能,如Niklas Baumstark的答案,或者它可以是完整的替换功能;无论哪种方式,必须已经存在,否则您将无法拦截此类电话。

如果你正在仔细阅读,你会注意到你将最终方法绑定到实例的要求不是一个可能的解决方案 - 你可以做到,但Python永远不会调用它,因为Python正在查看__getattribute__方法的实例的类,而不是实例的类。 Niklas Baumstark为每个实例制作一个独特的临时类的解决方案尽可能接近这个要求。

答案 2 :(得分:1)

看起来你的事情太复杂了。您可以定义一个mixin类并从中继承。这比使用元类更简单,并且比使用__getattr__运行得更快。

class OperatorMixin(object):
    def __add__(self, other):
        return func(self, other)
    def __radd__(self, other):
        return rfunc(self, other)
    ... other operators defined too

然后,您希望拥有这些运算符的每个类都继承自OperatorMixin。

class Expression(OperatorMixin):
    ... the regular methods for your class

在需要时生成运算符方法不是一个好主意:与常规方法查找相比,__getattr__速度慢,并且由于方法存储一次(在mixin类上),因此几乎不会节省任何内容