`super` в подклассе` typing.NamedTuple` завершается с ошибкой в ​​python 3.8 - PullRequest
9 голосов
/ 01 мая 2020

У меня есть код, который работал в 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 волшебно в этом отношении?

Ответы [ 2 ]

4 голосов
/ 01 мая 2020

К сожалению, я не очень знаком с 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__
2 голосов
/ 01 мая 2020

Я был немного неправ в другой вопрос (который я только что обновил). По-видимому, такое поведение проявляется в обоих случаях 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__ напрямую, как предложено Азатом, делает хитрость.

как это может произойти во время определения

Так же, как и следующее:

class Foo(metaclass=1): pass

Многие проверки выполняются во время создания класса. Среди них - проверка, передал ли метакласс __classcell__ до type_new.

...