【问题标题】:Dynamic Operator Overloading on dict classes in PythonPython中dict类的动态运算符重载
【发布时间】:2010-04-22 21:23:52
【问题描述】:

我有一个类可以像这样动态重载基本算术运算符...

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) 或其他任何东西才能使它工作。为什么?

【问题讨论】:

    标签: python dictionary operator-overloading


    【解决方案1】:

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

    您提供的第一个 code-sn-p 使用了一个遗留的“旧式”类(您可以知道,因为它没有任何子类 - 冒号之前没有任何内容)。它的语义很奇特。特别是,您可以为实例添加特殊方法:

    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 而不是 instance 上设置方法,以最初尝试的方式执行此操作:

    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
    

    恐怕我有时会在元类中思考,更简单的解决方案会更好:)

    【讨论】:

    • 谢谢。这个解释是我需要的。它也使我很容易以错误的方式完成我想要的事情,只需更改原始代码中的一行。 :P 我敢肯定人们会因此而对我大发雷霆。
    • 是的,没错,很抱歉我第一次错过了那个更简单的版本!
    【解决方案2】:

    一般来说,永远不要在实例上设置 __ 方法——它们只在类上受支持。 (在这种情况下,问题在于它们恰好适用于旧式类。不要使用旧式类)。

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

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

    【讨论】:

    • @Erin,他做到了,但不是超级直接。由于dict 是内置类型,因此继承它会生成一个新样式的类。
    • @Erin:我做到了。当你在做一些“不受支持”的事情时,奇怪的事情就会发生。解决的办法是不做不支持的事情,不了解什么情况会导致什么奇怪的事情......
    【解决方案3】:

    我不明白你想要完成什么,但我几乎可以肯定你的做法是错误的。我的一些观察:

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

      • 它们起作用的唯一原因是因为IshyNum 是一个老式类;这不是一件好事,因为旧式类早已被弃用,而且不如新式类好。 (我稍后会解释为什么你应该对此特别感兴趣。)

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

        • 不要使用map 这样做。 map 用于制作列表;用它来治疗副作用是愚蠢的。只需使用普通的 for 循环即可。
    • 如果您想在使用组合时使用组合将许多方法自动引用到同一属性,请使用 __getattr__ 并重定向到该属性的方法。

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

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

    这是你真正想知道的:

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

      十年来,新型类一直是 Python 的旗舰类,并且是您想要使用的。在这种情况下,它们实际上会导致您的技术不再起作用。不过,这很好,因为您发布的代码中没有理由在每个实例级别上设置魔术方法,也没有理由想要这样做。

    【讨论】:

      【解决方案4】:

      对于新型类,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
      

      【讨论】:

      • 这可以通过 Mixin 或装饰器来完成,而无需借助元类。
      猜你喜欢
      • 1970-01-01
      • 2015-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多