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

发布于 2024-08-30 03:04:20 字数 646 浏览 3 评论 0原文

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

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) 或其他任何内容才能使其正常工作。为什么?

I have a class that dynamically overloads basic arithmetic operators like so...

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

But if I change the class to inherit from the dictionary (class IshyNum(dict):) it doesn't work. I need to explicitly def __add__(self, other) or whatever in order for this to work. Why?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

葮薆情 2024-09-06 03:04:20

答案可以在 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

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

The answer is found in the two types of class that Python has.

The first code-snippet you provided uses a legacy "old-style" class (you can tell because it doesn't subclass anything - there's nothing before the colon). Its semantics are peculiar. In particular, you can add a special method to an instance:

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

and get a valid response:

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

But, subclassing dict means you are generating a new-style class. And the semantics of operator overloading are different:

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'

To make this work with new-style classes (which includes subclasses of dict or just about any other type you will find), you have to make sure the special method is defined on the class. You can do this through a metaclass:

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

Also, the semantic difference means that in the very first case I could define my local add method with one argument (the self it uses is captured from the surrounding scope in which it is defined), but with new-style classes, Python expects to pass in both values explicitly, so the inner function has two arguments.

As a previous commenter mentioned, best to avoid old-style classes if possible and stick with new-style classes (old-style classes are removed in Python 3+). Its unfortunate that the old-style classes happened to work for you in this case, where new-style classes will require more code.


Edit:

You can also do this more in the way you originally tried by setting the method on the class rather than the 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

I'm afraid I sometimes think in Metaclasses, where simpler solutions would be better :)

陌上青苔 2024-09-06 03:04:20

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

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

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

In general, never set __ methods on the instance -- they're only supported on the class. (In this instance, the problem is that they happen to work on old-style classes. Don't use old-style classes).

You probably want to use a metaclass, not the weird thing you're doing here.

Here's a metaclass tutorial: http://www.voidspace.org.uk/python/articles/metaclasses.shtml

美人迟暮 2024-09-06 03:04:20

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

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

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

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

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

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

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

这是您真正想知道的:

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

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

I do not understand what you are trying to accomplish, but I am almost certain you are going about it in the wrong way. Some of my observations:

  • I don't see why you're trying to dynamically generate those arithmetic methods. You don't do anything instance-specific with them, so I don't see why you would not just define them on the class.

    • The only reason they work at all is because IshyNum is an old-style class; this isn't a good thing, since old-style classes are long-deprecated and not as nice as new-style classes. (I'll explain later why you should be especially interested in this.)

    • If you wanted to automate the process of doing the same thing for multiple methods (probably not worth it in this case), you could just do this right after the class definition block.

      • Don't use map to do that. map is for making a list; using it for side effects is silly. Just use a normal for loop.
  • If you want to use composition to refer lots of methods to the same attribute automatedly when using composition, use __getattr__ and redirect to that attribute's methods.

  • Don't inherit dict. There is nothing much to gain from inheriting built-in types. It turns out it is more confusing than it's worth, and you don't get to re-use much.

    • If your code above is anything close to the stuff in your post, you really don't want to inherit dict. If it's not, try posting your real use case.

Here is what you really wanted to know:

  • When you inherit dict, you are making a new-style class. IshyNum is an old-style class because it doesn't inherit object (or one of its subclasses).

    New-style classes have been Python's flagship kind of class for a decade and are what you want to use. In this case, they actually cause your technique no longer to work. This is fine, though, since there is no reason in the code you posted to set magic methods on a per-instance level and little reason ever to want to.

瀞厅☆埖开 2024-09-06 03:04:20

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

For new-style classes, Python does not check the instance for an __add__ method when performing an addition, it checks the class instead. The problem is that you are binding the __add__ method (and all the others) to the instance as a bound method and not to the class as an unbound method. (This is true to other special methods as well, you can attach them only to the class, not to an instance). So, you'll probably want to use a metaclass to achieve this functionality (although I think this is a very awkward thing to do as it is much more readable to spell out these methods explicitly). Anyway, here is an example with metaclasses:

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文