`super` en una subclase` typing.NamedTuple` falla en python 3.8

9

Tengo un código que funcionó en Python 3.6 y falla en Python 3.8. Parece reducirse a llamar superen la subclase de typing.NamedTuple, como se muestra a continuación:

<ipython-input-2-fea20b0178f3> in <module>
----> 1 class Test(typing.NamedTuple):
      2     a: int
      3     b: float
      4     def __repr__(self):
      5         return super(object, self).__repr__()

RuntimeError: __class__ not set defining 'Test' as <class '__main__.Test'>. Was __classcell__ propagated to type.__new__?
In [3]: class Test(typing.NamedTuple): 
   ...:     a: int 
   ...:     b: float 
   ...:     #def __repr__(self): 
   ...:     #    return super(object, self).__repr__() 
   ...:                                                                         

>>> # works

El propósito de esta super(object, self).__repr__llamada es usar el estándar en '<__main__.Test object at 0x7fa109953cf8>' __repr__lugar de imprimir todo el contenido de los elementos de tupla (lo que sucedería de manera predeterminada). Hay algunas preguntas sobre el superresultado de errores similares pero:

  1. Consulte la versión sin parámetros super()
  2. Fallo ya en Python 3.6 (funcionó para mí antes de la actualización 3.6 -> 3.8)
  3. No entiendo cómo solucionar esto de todos modos, dado que no es una metaclase personalizada sobre la que tengo control sino el stdlib proporcionado typing.NamedTuple.

Mi pregunta es ¿cómo puedo solucionar esto mientras mantengo la compatibilidad con Python 3.6 (de lo contrario, solo usaría en @dataclasses.dataclasslugar de heredar de typing.NamedTuple)?

Una pregunta secundaria es cómo puede fallar esto en el momento de la definición dado que la superllamada infractora está dentro de un método que aún no se ha ejecutado. Por ejemplo:

In [3]: class Test(typing.NamedTuple): 
   ...:     a: int 
   ...:     b: float 
   ...:     def __repr__(self): 
   ...:         return foo 

funciona (hasta que realmente llamemos al __repr__) aunque foosea ​​una referencia indefinida. ¿Es supermágico en ese sentido?

Jatentaki
fuente
Cuando ejecuto su código en Python 3.6, obtengo una representación del superobjeto en sí, no una representación de su Testinstancia.
Chepner
Además, parece que cualquier magia en tiempo de compilación que permita argumentos implícitos todavía ocurre para validar algunos argumentos explícitos.
Chepner
2
usar __repr__ = object.__repr__en su definición de clase me funciona en Python3.6 y Python3.8
Azat Ibrakov
@chepner de hecho, ahora estoy empezando a confundirme sobre por qué funcionó antes. Pero lo hizo ...
Jatentaki
Este problema es causado por la metaclase de typing.NamedTuple; typing.NamedTupleMeta, eso está haciendo algunas travesuras. super()requiere __class__estar disponible en tiempo de compilación , que no es el caso aquí, aparentemente. Vea también: Proporcione un __classcell__ejemplo para la metaclase Python 3.6
L3viathan

Respuestas:

2

Estaba un poco equivocado en la otra pregunta (que acabo de actualizar). Aparentemente, este comportamiento se manifiesta en ambos casos de super. En retrospectiva, debería haber probado esto.

¿Qué está pasando aquí es la metaclase NamedTupleMetade hecho no pasa __classcell__a type.__new__porque crea un namedtuple sobre la marcha y rendimientos. En realidad, en Python 3.6 y 3.7 (donde esto todavía es a DeprecationWarning), se __classcell__filtra en el diccionario de la clase ya que no se elimina NamedTupleMeta.__new__.

class Test(NamedTuple):
    a: int
    b: float
    def __repr__(self):
        return super().__repr__()

# isn't removed by NamedTupleMeta
Test.__classcell__
<cell at 0x7f956562f618: type object at 0x5629b8a2a708>

Usar object.__repr__directamente como lo sugiere Azat hace el truco.

¿Cómo puede fallar esto en el momento de la definición?

De la misma manera, lo siguiente también falla:

class Foo(metaclass=1): pass

Se realizan muchas verificaciones mientras se construye la clase. Entre estos, está verificando si la metaclase ha pasado __classcell__a type_new.

Dimitris Fasarakis Hilliard
fuente
¿Es esto un error en stdlib entonces?
Jatentaki
La filtración de @Jatentaki __classcell__, habría pensado así. Apoyo super, no sé, de la misma manera que la herencia múltiple cambiada recientemente para generar un error. Sin embargo, podría valer la pena crear un problema.
Dimitris Fasarakis Hilliard
4

Desafortunadamente, no estoy tan familiarizado con la generación interna de clases y CPython para decir por qué falla, pero existe este problema del rastreador de errores de CPython que parece estar relacionado y algunas palabras en documentos de Python

Detalle de implementación de CPython: en CPython 3.6 y versiones posteriores, la __class__celda se pasa a la metaclase como una __classcell__entrada en el espacio de nombres de la clase. Si está presente, esto debe propagarse hasta la type.__new__llamada para que la clase se inicialice correctamente. De lo contrario, se generará un RuntimeErrorPython 3.8.

así que probablemente en algún lugar durante la namedtuplecreación real tengamos una llamada type.__new__sin __classcell__propagación, pero no sé si es el caso.

Pero este caso particular parece resolverse al no utilizar la super()llamada con decir explícitamente que "necesitamos tener un __repr__método de la objectclase" como

class Test(typing.NamedTuple):
    a: int
    b: float
    __repr__ = object.__repr__
Azat Ibrakov
fuente