`super` em uma subclasse` typing.NamedTuple` falha no python 3.8


9

Eu tenho um código que funcionou no Python 3.6 e falha no Python 3.8. Parece resumir-se a chamar a supersubclasse de typing.NamedTuple, como abaixo:

<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

O objetivo desta super(object, self).__repr__chamada é usar o padrão em '<__main__.Test object at 0x7fa109953cf8>' __repr__vez de imprimir todo o conteúdo dos elementos da tupla (o que aconteceria por padrão). Existem algumas perguntas sobre como superresultar em erros semelhantes, mas eles:

  1. Consulte a versão sem parâmetros super()
  2. Já falhei no Python 3.6 (funcionou para mim antes da atualização 3.6 -> 3.8)
  3. Não consigo entender como corrigir isso de qualquer maneira, já que não é uma metaclasse personalizada sobre a qual tenho controle, mas a fornecida pelo stdlib typing.NamedTuple.

Minha pergunta é como posso corrigir isso, mantendo a compatibilidade com o Python 3.6 (caso contrário, eu usaria em @dataclasses.dataclassvez de herdar typing.NamedTuple)?

Uma questão paralela é como isso pode falhar no momento da definição, uma vez que a superchamada incorreta está dentro de um método que ainda nem foi executado. Por exemplo:

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

funciona (até chamarmos de __repr__) mesmo que fooseja uma referência indefinida. É supermágico a esse respeito?


Quando executo seu código no Python 3.6, recebo uma representação do superpróprio objeto, não uma representação da sua Testinstância.
— chepner

Além disso, parece que qualquer mágica em tempo de compilação que permita argumentos implícitos ainda ocorre para validar argumentos explícitos.
— chepner

2
usando __repr__ = object.__repr__em sua definição de classe funciona para mim em Python3.6 e Python3.8
— Azat Ibrakov

@chepner de fato, agora estou começando a ficar confuso sobre o porquê de ter funcionado antes. Mas ...
— Jatentaki

Esse problema é causado pela metaclasse de typing.NamedTuple; typing.NamedTupleMeta, isso está fazendo algumas travessuras. super()requer __class__estar disponível em tempo de compilação , o que não é o caso aqui, aparentemente. Veja também: Forneça um __classcell__exemplo para a metaclasse do Python 3.6
— L3viathan

Respostas:


2

Eu estava um pouco errado na outra pergunta (que acabei de atualizar). Aparentemente, esse comportamento se manifesta nos dois casos de super. Em retrospectiva, eu deveria ter testado isso.

O que está acontecendo aqui é que a metaclasse NamedTupleMetanão passa __classcell__de type.__new__fato para ela, porque cria um duplo nomeado em tempo real e o devolve. Na verdade, nas versões 3.6 e 3.7 do Python (onde ainda é um DeprecationWarning), o __classcell__vazamento no dicionário de classes, uma vez que não é removido por 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__diretamente como sugerido por Azat faz o truque.

como isso pode falhar no momento da definição

Da mesma maneira que o seguinte também falha:

class Foo(metaclass=1): pass

Muitas verificações são realizadas enquanto a classe está sendo construída. Entre eles, está verificando se a metaclasse passou __classcell__para o type_new.


Isso é um bug no stdlib?
— Jatentaki

@ Katentaki O vazamento de __classcell__, eu teria pensado assim. Suporte super, não sei, da mesma maneira que a herança múltipla recentemente alterada para agora gerar um erro. Ainda pode valer a pena criar um problema.
— Dimitris Fasarakis Hilliard

4

Infelizmente, eu não estou tão familiarizado com o interno do CPython e a geração de classes para dizer por que ele falha, mas há esse problema do rastreador de erros do CPython que parece estar relacionado e algumas palavras nos documentos do Python

Detalhes da implementação do CPython: No CPython 3.6 e posterior, a __class__célula é passada para a metaclasse como uma __classcell__entrada no espaço de nomes da classe. Se presente, isso deve ser propagado até a type.__new__chamada para que a classe seja inicializada corretamente. Não fazer isso resultará em um RuntimeErrorno Python 3.8.

então, provavelmente em algum lugar durante a namedtuplecriação real , temos uma chamada type.__new__sem __classcell__propagação, mas não sei se é o caso.

Mas esse caso em particular parece ser solucionável ao não usar super()call dizendo explicitamente que "precisamos ter o __repr__método da objectclasse" como

class Test(typing.NamedTuple):
    a: int
    b: float
    __repr__ = object.__repr__
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.