¿Por qué no puedo subclasificar tuple en python3?

Vamos a comenzar esta pregunta diciendo que debe usar __new__ lugar de __init__ para subclasificar objetos inmutables .

Dicho esto, veamos el siguiente código:

 class MyTuple(tuple): def __init__(self, *args): super(MyTuple, self).__init__(*args) mytuple = MyTuple([1,2,3]) 

Esto funciona en python2, pero en python3 obtengo:

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

¿Por qué pasó esto? ¿Qué cambió en python3?

Python 3 cambió la forma en que el object.__new__ y el object.__init__ reactjsn a los argumentos cuando ambos se anulan. Si una clase anula (o hereda métodos que anulan) tanto object.__init__ como object.__new__ , object.__init__ y object.__new__ lanzarán una excepción si reciben algún argumento en exceso. En Python 2, eso habría dado una Advertencia de Depredación (suprimida por defecto).

tuple no tiene su propio __init__ . Hereda el object.__init__ , por lo que en realidad está pasando un montón de argumentos al object.__init__ que el object.__init__ no toma. Python 2 le estaba dando una advertencia (suprimida), y Python 3 está cometiendo un error.

El código tiene un comentario que explica bien el manejo sutil de los argumentos adicionales del object.__init__ y del 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 (ie, 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. */ 

He estado investigando el código base de C y no he encontrado ninguna pista real (aún) sobre qué ha cambiado para no permitir este comportamiento en python3. He probado en python2.7, python3.3, python3.5 y python3.6. La única vez que su código funciona sin una excepción es en python2.7. Tampoco he encontrado ninguna referencia en la documentación sobre por qué cambió esto, sin embargo, tengo algunas ideas …

Primero, tuple.__init__ de acuerdo en que tuple.__init__ no puede hacer nada ya que tuple es inmutable. Cuando se llama a __init__ , la tupla ya está congelada. Por lo tanto, esto nos lleva a suponer que, dado que tuple.__init__ no hace nada, los desarrolladores consideraron que es engañoso permitir que acepte cualquier argumento. Al evitar que la clase base acepte argumentos, alientan a las personas a anular __new__ (y, por lo tanto, alientan la herencia adecuada para objetos inmutables).