`typing.NamedTuple`サブクラスの` super`はPython 3.8で失敗します


9

Python 3.6では機能し、Python 3.8では失敗するコードがあります。次のように要約するとsuper、のサブクラスを呼び出す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

このsuper(object, self).__repr__呼び出しの目的は'<__main__.Test object at 0x7fa109953cf8>' __repr__、タプル要素のすべてのコンテンツを出力する代わりに標準を使用することです(デフォルトで発生します)。同様のエラーが発生することについていくつかの 質問がありますがsuper、それらは:

  1. パラメータなしバージョンを参照してください super()
  2. Python 3.6で失敗しました(3.6-> 3.8アップグレードの前に動作しました)
  3. 私が制御しているカスタムメタクラスではなく、stdlibが提供するものであることを考えると、どうしてもこれを修正する方法を理解できませんtyping.NamedTuple

私の質問は、Python 3.6との下位互換性を維持しながらこれを修正するにはどうすればよいですか(それ以外の場合は@dataclasses.dataclass、から継承する代わりに使用するだけtyping.NamedTupleです)?

副次的な問題は、問題のある呼び出しがまだ実行されていないメソッド内にある場合、定義時にこれがどのように失敗するかsuperです。例えば:

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

が未定義の参照で__repr__あっても、(実際にを呼び出すまで)機能しfooます。であるsuperという点で魔法?


Python 3.6でコードを実行するsuperと、Testインスタンスの表現ではなく、オブジェクト自体の表現が表示されます。
chepner

また、暗黙的な引数を許可するコンパイル時のマジックでは、明示的な引数の検証を行うために引き続き発生するようです。
chepner

2
__repr__ = object.__repr__クラス定義での使用は、Python3.6とPython3.8で動作します
Azat Ibrakov

@chepner確かに、今はなぜそれが以前に機能したのかについて混乱し始めています。しかし、それは…
Jatentaki

この問題はのメタクラスが原因typing.NamedTupleです。typing.NamedTupleMeta、それはいくつかの厄介なことをしています。どうやら、コンパイル時には利用可能であるsuper()必要__class__がありますがここではそうではありません。参照:Python 3.6メタクラスの例を提供__classcell__
L3viathan

回答:


2

私は他の質問(私が更新したばかり)で少し間違っていまし。明らかに、この動作はの両方の場合に現れsuperます。振り返ってみると、私はこれをテストするべきでした。

ここで起こっているのは、メタクラスがNamedTupleMeta実際に渡さ__classcell__れないtype.__new__ことです。これは、その場で名前付きタプルを作成し、それを返すためです。実際、Python 3.6および3.7(これはまだであるDeprecationWarning)では、__classcell__によって削除されないため、クラスディクショナリにリークし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>

object.__repr__Azatによって提案されたように直接使用することがトリックです。

定義時にこれがどのように失敗するか

同じように、次も失敗します。

class Foo(metaclass=1): pass

クラスの構築中に多くのチェックが実行されます。これらの中で、メタクラスがに渡されたかどうかを確認__classcell__していtype_newます。


これはstdlibのバグですか?
Jatentaki

@Jatentakiのリーク__classcell__、私はそう思ったでしょう。最近のsuper多重継承がエラーを発生せるように変更されたのと同じ方法で、をサポートしています。ただし、問題を作成する価値はまだあります。
Dimitris Fasarakis Hilliard

4

残念ながら、私はCPythonの内部とクラスの生成にあまり詳しくないので、なぜ失敗するのかはわかりませんが、このCPythonバグトラッカーの問題と関連しているようで、Pythonのドキュメントにいくつかの単語があります

CPython実装の詳細:CPython 3.6以降では、__class__セルは__classcell__クラス名前空間のエントリとしてメタクラスに渡されます。存在type.__new__する場合、クラスを正しく初期化するために、これを呼び出しまで伝播する必要があります。そうしないとRuntimeError、Python 3.8でが発生します。

おそらく実際のnamedtuple作成中にどこかに伝播type.__new__せずに呼び出しがありますが、それが事実__classcell__かどうかはわかりません。

しかし、この特定のケースはsuper()、「クラスの__repr__メソッドが必要」のように明示的に言って呼び出しを使用しないことで解決できるobjectようです

class Test(typing.NamedTuple):
    a: int
    b: float
    __repr__ = object.__repr__
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.