¿Por qué object .__ new__ funciona de manera diferente en estos tres casos

de la pregunta ¿Por qué, o más bien, cómo funciona el objeto .__ nuevo__ funciona de manera diferente en estos dos casos?

El autor no estaba interesado en el por qué, sino en el cómo.

Me gustaría mucho entender por qué, particularmente:

  1. ¿por qué no es el object.__init__ no se imprime ningún parámetro en lugar del object.__new__ (in testclass1)

  2. ¿Por qué no se genera ningún error para testclass3? (ya que no tiene más argumentos que el yo)

código

 >>> class testclass1(object): ... pass ... >>> class testclass2(object): ... def __init__(self,param): ... pass ... >>> a = object.__new__(testclass1, 56) Traceback (most recent call last): File "", line 1, in  TypeError: object.__new__() takes no parameters >>> b = object.__new__(testclass2, 56) >>> b  >>> class testclass3(object): ... def __init__(self): ... pass ... >>> c = object.__new__(testclass3, 56) >>> c  >>> c1 = object.__new__(testclass3) >>> c1  

Estás utilizando una versión anterior de Python; el mensaje de error se ha actualizado desde entonces:

 >>> object.__new__(testclass1, 56) Traceback (most recent call last): File "", line 1, in  TypeError: object() takes no parameters 

Python solo se quejará de que __init__ no respalde los argumentos si ni __new__ ni __init__ han sido anulados; por ejemplo, cuando se hereda de ambos object . testclass1 ajusta a ese caso, testclass3 no porque tiene un método __init__ .

Esto es para admitir la implementación de tipos inmutables que no tienen un uso para __init__ (que en este caso se heredaría del object ), y tipos mutables, donde __new__ no debería preocuparse por los argumentos que __init__ espera (que generalmente serían más argumentos) .

Vea el número 1683368 donde Guido van Rossum explica sus motivaciones para esto.

El código fuente de typeobject.c tiene esto para decir:

Tal vez se pregunte por qué object.__new__() solo se queja de los argumentos.
cuando el object.__init__() no se anula, y viceversa.

Considere los casos de uso:

  1. Cuando ninguno de ellos está anulado, queremos escuchar quejas sobre argumentos en exceso (es decir, cualquier argumento), ya que su presencia podría indicar que hay un error.

  2. Al definir un tipo de Inmutable, es probable que __new__() solo __new__() , ya que __init__() se llama demasiado tarde para inicializar un objeto Inmutable. Dado que __new__() define la firma para el tipo, sería un dolor tener que anular __init__() solo para evitar que se queje sobre los argumentos en exceso.

  3. Al definir un tipo Mutable, es probable que __init__() solo __init__() . Así que aquí se aplica el razonamiento inverso: no queremos tener que anular __new__() solo para evitar que se queje.

  4. Cuando __init__() se reemplaza, y la subclase __init__() llama al object.__init__() , este último debe quejarse de los argumentos en exceso; lo mismo para __new__() .

Los casos de uso 2 y 3 hacen que sea poco atractivo verificar incondicionalmente los argumentos en exceso. La mejor solución que aborda los cuatro casos de uso es la siguiente: __init__() queja de los argumentos en exceso, a menos que __new__() esté anulada y __init__() no esté anulada (IOW, si __init__() esté anulada o __new__() no esté anulada) ; simétricamente, __new__() queja de los argumentos en exceso a menos que __init__() esté sobreescrito y __new__() no esté sobreescrito (IOW, si __new__() esté sobreescrito o __init__() no esté sobreescrito).

Sin embargo, para compatibilidad con versiones anteriores, esto rompe demasiado código. Por lo tanto, en 2.6, advertiremos sobre los argumentos en exceso cuando ambos métodos se sobrescriban; Para todos los demás casos usaremos las reglas anteriores.

Tenga en cuenta que el método .__init__() sí mismo se quejará! Cuando creas una instancia, se llama a __new__ y __init__ ; su código solo llama a __new__ directamente y no invoca a __init__ ! La creación de una instancia de testclass1 y testclass3 falla si pasas los argumentos:

 >>> testclass1(56) Traceback (most recent call last): File "", line 1, in  TypeError: object() takes no parameters >>> testclass3(56) Traceback (most recent call last): File "", line 1, in  TypeError: __init__() takes exactly 1 argument (2 given) 

La única diferencia es que para testclass1 son los métodos predeterminados para object() que se quejan de un error específico para el __init__ personalizado.