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

Python中dict类的动态运算符重载,python,dictionary,operator-overloading,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: setatt

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

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\uuuu添加(self,other)
或其他任何东西,以使其工作。为什么?

通常,不要在实例上设置方法——它们只在类上受支持。(在本例中,问题是它们恰好在旧式类上工作。不要使用旧式类)

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


这里有一个元类教程:

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

  • 我不明白您为什么要尝试动态生成这些算术方法。您没有对它们执行任何特定于实例的操作,因此我不明白您为什么不在类中定义它们

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

    • 如果您想自动化对多个方法执行相同操作的过程(在本例中可能不值得),您可以在类定义块之后立即执行此操作

      • 不要使用
        map
        来执行此操作<代码>地图用于制作列表;用它做副作用是愚蠢的。只需使用普通for循环
  • 如果要在使用合成时使用合成自动地将许多方法引用到同一属性,请使用
    \uuu getattr\uuu
    并重定向到该属性的方法

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

    • 如果上面的代码与文章中的内容非常接近,那么您确实不想继承
      dict
      。如果不是,试着发布你真正的用例
以下是您真正想知道的:

  • 当您继承
    dict
    时,您正在创建一个
    IshyNum
    是一个旧式类,因为它不继承
    对象(或其子类之一)

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


答案可以在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希望显式地传递这两个值,因此内部函数有两个参数

正如前面的一位评论员所提到的,如果可能的话,最好避免使用旧样式的类,并坚持使用新样式的类(旧样式的类在Python3+中被删除)。不幸的是,在这种情况下,旧样式的类恰好适合您,而新样式的类将需要更多的代码


编辑:

您还可以按照最初尝试的方式,通过在类而不是实例上设置方法来实现这一点:

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

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

对于新样式的类,Python在执行加法时不检查
\uuuuuuuuuuuuuuuuuuuuuuu>方法的实例,而是检查类。问题是,您将
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。(其他特殊方法也是如此,您只能将它们附加到类,而不能附加到实例)。因此,您可能希望使用元类来实现此功能(尽管我认为这是一件非常尴尬的事情,因为显式地阐述这些方法更具可读性)。无论如何,下面是一个元类示例:

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

@艾琳,他说了,但不是很直接。由于
dict
是一个内置类型,继承它会生成一个新样式的类。@Erin:是的。当你做一些“不受支持”的事情时,奇怪的事情就会发生。解决方法不是做不受支持的事情,也不是去理解什么情况导致了什么奇怪的事情……谢谢。这就是我需要的解释。这也让我很容易以错误的方式完成我想要的任务,只需更改原始代码中的一行即可P我相信人们会为此对我大发雷霆。是的,没错,很抱歉我第一次错过了那个简单的版本!这可以通过Mixin或decorator来完成,而无需借助元类。