【问题标题】:Why can't I subclass tuple in python3?为什么我不能在 python3 中子类化元组?
【发布时间】:2017-05-14 09:48:25
【问题描述】:

让我们先说you should use __new__ instead of __init__ for subclassing immutable objects

话虽如此,让我们看看下面的代码:

class MyTuple(tuple):
    def __init__(self, *args):
        super(MyTuple, self).__init__(*args)

mytuple = MyTuple([1,2,3])

这在 python2 中有效,但在 python3 中我得到:

Traceback (most recent call last):
  File "tmp.py", line 5, in <module>
    mytuple = MyTuple([1,2,3])
  File "tmp.py", line 3, in __init__
    super(MyTuple, self).__init__(*args)
TypeError: object.__init__() takes no parameters

为什么会这样? python3有什么变化?

【问题讨论】:

  • 请问您为什么不使用__new__?这似乎是要走的路。
  • @TigerhawkT3 我现在是 :) 但我仍然对此感到好奇

标签: python python-3.x tuples subclass


【解决方案1】:

Python 3 改变了 object.__new__object.__init__ 在两者都被覆盖时对参数的反应方式。如果一个类覆盖(或继承覆盖的方法)object.__init__object.__new__object.__init__object.__new__ 将在收到任何多余的参数时抛出异常。在 Python 2 中,这会给出一个 DeprecationWarning (默认禁止)。

tuple 没有自己的__init__。它继承了object.__init__,因此您实际上将一堆参数传递给object.__init__,而object.__init__ 不接受这些参数。 Python 2 给你一个(被抑制的)警告,而 Python 3 让它成为一个错误。

代码中有一个注释很好地解释了object.__init__object.__new__ 对额外参数的微妙处理:

/* You may wonder why object.__new__() only complains about arguments
   when object.__init__() is not overridden, and vice versa.

   Consider the use cases:

   1. When neither is overridden, we want to hear complaints about
      excess (i.e., any) arguments, since their presence could
      indicate there's a bug.

   2. When defining an Immutable type, we are likely to override only
      __new__(), since __init__() is called too late to initialize an
      Immutable object.  Since __new__() defines the signature for the
      type, it would be a pain to have to override __init__() just to
      stop it from complaining about excess arguments.

   3. When defining a Mutable type, we are likely to override only
      __init__().  So here the converse reasoning applies: we don't
      want to have to override __new__() just to stop it from
      complaining.

   4. When __init__() is overridden, and the subclass __init__() calls
      object.__init__(), the latter should complain about excess
      arguments; ditto for __new__().

   Use cases 2 and 3 make it unattractive to unconditionally check for
   excess arguments.  The best solution that addresses all four use
   cases is as follows: __init__() complains about excess arguments
   unless __new__() is overridden and __init__() is not overridden
   (IOW, if __init__() is overridden or __new__() is not overridden);
   symmetrically, __new__() complains about excess arguments unless
   __init__() is overridden and __new__() is not overridden
   (IOW, if __new__() is overridden or __init__() is not overridden).

   However, for backwards compatibility, this breaks too much code.
   Therefore, in 2.6, we'll *warn* about excess arguments when both
   methods are overridden; for all other cases we'll use the above
   rules.

*/

【讨论】:

  • 我什至看到了这条评论,但我想我没有仔细阅读它,因为它也在 python2.x 代码库中。
【解决方案2】:

我一直在研究 C 代码库,但我还没有找到任何真正的线索,说明在 python3 中发生了什么改变以禁止这种行为。我已经在 python2.7、python3.3、python3.5 和 python3.6 上进行了测试。您的代码唯一可以正常工作的时间是在 python2.7 上。我也没有在文档中找到任何关于为什么会发生变化的参考资料,但是我确实有一些想法......

首先,让我们同意tuple.__init__ 不能做任何事情,因为tuple 是不可变的。当__init__ 被调用时,元组已经被冻结了。所以,这让我们得出了我的猜测——因为tuple.__init__ 什么都不做,开发人员认为允许它接受任何参数是一种误导。通过阻止基类接受参数,它们鼓励人们覆盖__new__(因此,鼓励对不可变对象进行适当的继承)。

【讨论】:

  • 这是一个很好的猜测。有趣的是,这段代码实际上适用于 python2.7:print mytuple 产生 (1, 2, 3)
  • @goncalopp -- 当然。这是因为__new__ 创建了正确的元组并将其传递给__init__(连同传递给tuple 的初始参数)。
猜你喜欢
  • 1970-01-01
  • 2012-11-24
  • 2013-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多