动态运算符在Python中的dict类上重载

时间:2010-04-22 21:23:53

标签: python dictionary operator-overloading

我有一个动态重载基本算术运算符的类,如...

import operator

class IshyNum:
    def __init__(self, n):
        self.num=n
        self.buildArith()

    def arithmetic(self, other, o):
        return o(self.num, other)

    def buildArith(self):
        map(lambda o: setattr(self, "__%s__"%o,lambda f: self.arithmetic(f, getattr(operator, o))), ["add", "sub", "mul", "div"])

if __name__=="__main__":
    number=IshyNum(5)
    print number+5
    print number/2
    print number*3
    print number-3

但是,如果我将类更改为继承自字典(class IshyNum(dict):),则它不起作用。我需要明确def __add__(self, other)或其他什么才能使它工作。为什么呢?

4 个答案:

答案 0 :(得分:4)

答案可以在Python的两种类型中找到。

你提供的第一个代码片段使用了一个遗留的“旧式”类(你可以告诉它,因为它没有任何子类 - 冒号前没有任何内容)。它的语义是特殊的。特别是,您可以向实例添加特殊方法:

class Foo:
   def __init__(self, num):
      self.num = num
      def _fn(other):
         return self.num + other.num
      self.__add__ = _fn

并获得有效回复:

>>> f = Foo(2)
>>> g = Foo(1)
>>> f + g
3

但是,子类化dict意味着您正在生成一个新式的类。并且运算符重载的语义不同:

class Foo (object):
   def __init__(self, num):
      self.num = num
      def _fn(other):
         return self.num + other.num
      self.__add__ = _fn
>>> f = Foo(2)
>>> g = Foo(1)
>>> f + g
Traceback ...
TypeError: unsupported operand type(s) for +: 'Foo' and 'Foo'

为了使这种方法适用于新式类(包括dict的子类或几乎所有其他类型),您必须确保在类上定义了特殊方法。您可以通过元类执行此操作:

class _MetaFoo(type):
    def __init__(cls, name, bases, args):
        def _fn(self, other):
            return self.num + other.num
        cls.__add__ = _fn

class Foo(object):
    __metaclass__ = _MetaFoo
    def __init__(self, num):
        self.num = num

>>> f = Foo(2)
>>> g = Foo(1)
>>> f+g
3

此外,语义差异意味着在第一种情况下,我可以使用一个参数定义我的本地add方法(它使用的self是从定义它的周围范围捕获的),但是新的样式类,Python期望显式传递两个值,因此内部函数有两个参数。

正如之前提到的那样,最好尽可能避免使用旧式类,并坚持使用新式类(旧式类在Python 3+中删除)。不幸的是,在这种情况下,旧式的课程恰好适合你,新式课程需要更多的代码。


编辑:

您还可以通过在而不是实例上设置方法,以最初尝试的方式执行此操作:

class Foo(object):
    def __init__(self, num):
        self.num = num
setattr(Foo, '__add__', (lambda self, other: self.num + other.num))
>>> f = Foo(2)
>>> g = Foo(1)
>>> f+g
3

我担心我有时会在Metaclasses中思考,更简单的解决方案会更好:)

答案 1 :(得分:3)

通常,永远不要在实例上设置__方法 - 它们仅在类上受支持。 (在这种情况下,问题是它们碰巧在旧式类上工作。不要使用旧式类。)

你可能想要使用元类,而不是你在这里做的奇怪事情。

这是一个元类教程:http://www.voidspace.org.uk/python/articles/metaclasses.shtml

答案 2 :(得分:2)

我不明白你想要完成什么,但我几乎肯定你会以错误的方式解决它。我的一些观察结果:

  • 我不明白你为什么要动态生成这些算术方法。你没有做任何特定于实例的事情,所以我不明白为什么你不会在课堂上定义它们。

    • 他们工作的唯一原因是因为IshyNum是一个旧式的课程;这不是一件好事,因为旧式类已被长期弃用,并不像新式类那样好。 (我稍后会解释为什么你应该对此特别感兴趣。)

    • 如果你想自动化为多个方法做同样事情的过程(在这种情况下可能不值得),你可以在类定义块之后立即执行此操作。

      • 请勿使用map来执行此操作。 map用于列表;用它来副作用是愚蠢的。只需使用正常的for循环。
  • 如果您想使用合成在使用合成时自动将大量方法引用到同一属性,请使用__getattr__并重定向到该属性的方法。

  • 不要继承dict。继承内置类型没有什么好处。事实证明它比它的价值更令人困惑,而且你不会重复使用它。

    • 如果上面的代码与帖子中的内容很接近,那么 确实不想继承dict。如果不是,请尝试发布您的真实用例。

这是你真正想知道的:

  • 当您继承dict时,您正在制作new-style classIshyNum是旧式类,因为它不继承object(或其子类之一)。

    新风格的课程已经成为Python十年来的旗舰类,并且是你想要使用的。在这种情况下,它们实际上导致您的技术不再起作用。但这很好,因为您发布的代码中没有任何理由在每个实例级别上设置魔术方法,而且几乎没有理由要这样做。

答案 3 :(得分:2)

对于新式类,Python在执行添加时不检查实例的__add__方法,而是检查类。问题是您将__add__方法(以及所有其他方法)绑定到实例作为绑定方法,而不是作为未绑定方法绑定到类。 (这也适用于其他特殊方法,您只能将它们附加到类,而不是实例)。因此,您可能希望使用元类来实现此功能(尽管我认为这是一件非常尴尬的事情,因为明确说明这些方法更具可读性)。无论如何,这是一个元类的例子:

import operator

class OperatorMeta(type):
    def __new__(mcs, name, bases, attrs):
        for opname in ["add", "sub", "mul", "div"]:
            op = getattr(operator, opname)
            attrs["__%s__" % opname] = mcs._arithmetic_func_factory(op)
        return type.__new__(mcs, name, bases, attrs)

    @staticmethod
    def _arithmetic_func_factory(op):
        def func(self, other):
            return op(self.num, other)
        return func

class IshyNum(dict):
    __metaclass__ = OperatorMeta

    def __init__(self, n):
        dict.__init__(self)
        self.num=n

if __name__=="__main__":
    number=IshyNum(5)
    print number+5
    print number/2
    print number*3
    print number-3