`super` in einer` typing.NamedTuple`-Unterklasse schlägt in Python 3.8 fehl

9

Ich habe Code, der in Python 3.6 funktioniert hat und in Python 3.8 fehlschlägt. Es scheint sich darauf zu beschränken, die superUnterklasse wie folgt aufzurufen typing.NamedTuple:

<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

Der Zweck dieses super(object, self).__repr__Aufrufs besteht darin, den Standard zu verwenden, '<__main__.Test object at 0x7fa109953cf8>' __repr__anstatt den gesamten Inhalt der Tupelelemente auszudrucken (was standardmäßig der Fall wäre). Es gibt einige Fragen zusuper , was zu ähnlichen Fehlern , aber sie:

  1. Siehe die parameterlose Version super()
  2. Fehler bereits in Python 3.6 (es hat bei mir vor 3.6 funktioniert -> 3.8 Upgrade)
  3. Ich verstehe sowieso nicht, wie ich das beheben kann, da es sich nicht um eine benutzerdefinierte Metaklasse handelt, über die ich die Kontrolle habe, sondern um die von stdlib bereitgestellte typing.NamedTuple .

Meine Frage ist, wie ich dies beheben kann, während die Abwärtskompatibilität mit Python 3.6 erhalten bleibt (ansonsten würde ich nur verwenden @dataclasses.dataclass anstatt von zu erben typing.NamedTuple).

Eine Nebenfrage ist, wie dies zum Zeitpunkt der Definition fehlschlagen kann , da sich der fehlerhafte superAufruf in einer Methode befindet, die noch nicht einmal ausgeführt wurde. Zum Beispiel:

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

funktioniert (bis wir das tatsächlich aufrufen __repr__), obwohl fooes sich um eine undefinierte Referenz handelt. Ist superdiesbezüglich magisch?

Jatentaki
quelle
Wenn ich Ihren Code in Python 3.6 ausführe, erhalte ich eine Darstellung des superObjekts selbst, keine Darstellung Ihrer TestInstanz.
Chepper
Es scheint auch, dass die Magie der Kompilierungszeit, die implizite Argumente zulässt, immer noch auftritt, um explizite Argumente zu validieren.
Chepper
2
Die Verwendung __repr__ = object.__repr__in Ihrer Klassendefinition funktioniert für mich unter Python3.6 und Python3.8
Azat Ibrakov
@chepner in der Tat, jetzt fange ich an, verwirrt darüber zu werden, warum es vorher funktioniert hat. Aber es tat ...
Jatentaki
Dieses Problem wird durch die Metaklasse von verursacht typing.NamedTuple. typing.NamedTupleMeta, das macht ein paar Spielereien. super()muss __class__zur Kompilierungszeit verfügbar sein , was hier anscheinend nicht der Fall ist. Siehe auch: Geben Sie ein __classcell__Beispiel für die Python 3.6-Metaklasse
L3viathan

Antworten:

2

Ich habe mich in der anderen Frage (die ich gerade aktualisiert habe) etwas geirrt . Anscheinend manifestiert sich dieses Verhalten in beiden Fällen von super. Im Nachhinein hätte ich das testen sollen.

Was hier passiert, ist, dass die Metaklasse NamedTupleMetatatsächlich nicht übergeht __classcell__, type.__new__weil sie im laufenden Betrieb ein benanntes Tupel erstellt und dieses zurückgibt. In Pythons 3.6 und 3.7 (wo dies immer noch a ist DeprecationWarning) tritt das __classcell__Leck in das Klassenwörterbuch ein, da es nicht von entfernt wird 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>

Die object.__repr__direkte Verwendung, wie von Azat vorgeschlagen, reicht aus.

Wie kann dies zum Zeitpunkt der Definition fehlschlagen?

Auf die gleiche Weise schlägt auch Folgendes fehl:

class Foo(metaclass=1): pass

Während der Erstellung der Klasse werden viele Überprüfungen durchgeführt. Unter diesen ist die Überprüfung , ob die Metaklasse die bestanden hat __classcell__über type_new.

Dimitris Fasarakis Hilliard
quelle
Ist das dann ein Fehler in stdlib?
Jatentaki
@Jatentaki Das Auslaufen von __classcell__, hätte ich mir gedacht. Unterstützung super, weiß nicht, genauso wie Mehrfachvererbung kürzlich geändert wurde, um jetzt einen Fehler auszulösen. Es könnte sich dennoch lohnen, ein Problem zu erstellen.
Dimitris Fasarakis Hilliard
4

Leider bin ich mit CPython-Interna und Klassengenerierung nicht so vertraut, um zu sagen, warum dies fehlschlägt, aber es gibt dieses CPython-Bug-Tracker-Problem, das verwandt zu sein scheint, und einige Wörter in Python-Dokumenten

Detail der CPython-Implementierung: In CPython 3.6 und höher wird die __class__Zelle als __classcell__Eintrag im Klassennamensraum an die Metaklasse übergeben . Wenn vorhanden, muss dies bis zum type.__new__Aufruf weitergegeben werden, damit die Klasse korrekt initialisiert wird. Andernfalls wird RuntimeErrorin Python 3.8 ein.

Wahrscheinlich namedtuplehaben wir irgendwo während der eigentlichen Erstellung einen Anruf type.__new__ohne __classcell__Propagierung, aber ich weiß nicht, ob dies der Fall ist.

Aber dieser spezielle Fall scheint lösbar zu sein, wenn man den super()Aufruf nicht verwendet und explizit sagt, dass "wir eine __repr__Methode der objectKlasse haben müssen" wie

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