Como você acessa outras variáveis de classe a partir de uma compreensão de lista na definição de classe? O seguinte funciona no Python 2, mas falha no Python 3:
class Foo:
x = 5
y = [x for i in range(1)]
O Python 3.2 fornece o erro:
NameError: global name 'x' is not defined
Tentar Foo.x
também não funciona. Alguma idéia de como fazer isso no Python 3?
Um exemplo motivador um pouco mais complicado:
from collections import namedtuple
class StateDatabase:
State = namedtuple('State', ['name', 'capital'])
db = [State(*args) for args in [
['Alabama', 'Montgomery'],
['Alaska', 'Juneau'],
# ...
]]
Neste exemplo, apply()
teria sido uma solução decente, mas infelizmente foi removida do Python 3.
python
python-3.x
scope
list-comprehension
python-internals
Mark Lodato
fonte
fonte
NameError: global name 'x' is not defined
em Python 3.2 e 3.3, que é o que eu esperaria.Respostas:
Escopo de classe e compreensão de lista, conjunto ou dicionário, bem como expressões geradoras, não se misturam.
O porquê; ou, a palavra oficial neste
No Python 3, as compreensões de lista receberam um escopo próprio (espaço de nome local) próprio, para impedir que suas variáveis locais se espalhem pelo escopo ao redor (consulte Compreensão de lista do Python para religar nomes mesmo após o escopo de compreensão. Isso está certo? ). Isso é ótimo quando se usa essa compreensão de lista em um módulo ou em uma função, mas nas classes o escopo é um pouco, uhm, estranho .
Isso está documentado no pep 227 :
e na
class
documentação da declaração composta :Ênfase minha; o quadro de execução é o escopo temporário.
Como o escopo é redirecionado como os atributos em um objeto de classe, permitir que ele seja usado como um escopo não local também leva a um comportamento indefinido; o que aconteceria se um método de classe referido
x
como variável de escopo aninhado também manipulasseFoo.x
, por exemplo? Mais importante, o que isso significaria para as subclasses deFoo
? O Python precisa tratar um escopo de classe de maneira diferente, pois é muito diferente de um escopo de função.Por fim, mas definitivamente não menos importante, a seção Nomeação e ligação vinculada na documentação do modelo de Execução menciona explicitamente os escopos de classe:
Portanto, para resumir: você não pode acessar o escopo da classe a partir de funções, compreensões de lista ou expressões geradoras incluídas nesse escopo; eles agem como se esse escopo não existisse. No Python 2, as compreensões da lista foram implementadas usando um atalho, mas no Python 3 elas têm seu próprio escopo de função (como deveriam ter sido o tempo todo) e, portanto, seu exemplo é interrompido. Outros tipos de compreensão têm seu próprio escopo, independentemente da versão do Python; portanto, um exemplo semelhante com uma compreensão de conjunto ou de dict seria interrompido no Python 2.
A (pequena) exceção; ou por que uma parte ainda pode funcionar
Há uma parte de uma expressão de compreensão ou gerador que é executada no escopo circundante, independentemente da versão do Python. Essa seria a expressão para o iterável mais externo. No seu exemplo, é o
range(1)
:Portanto, o uso
x
dessa expressão não geraria um erro:Isso se aplica apenas ao iterável mais externo; se uma compreensão tiver várias
for
cláusulas, os iteráveis parafor
cláusulas internas serão avaliados no escopo da compreensão:Essa decisão de design foi tomada para gerar um erro no momento da criação do genexp, em vez do tempo da iteração ao criar o iterável mais externo de uma expressão de gerador gera um erro ou quando o iterável mais externo acaba não sendo iterável. As compreensões compartilham esse comportamento para obter consistência.
Olhando sob o capô; ou, muito mais detalhes do que você sempre desejou
Você pode ver tudo isso em ação usando o
dis
módulo . Estou usando o Python 3.3 nos exemplos a seguir, porque ele adiciona nomes qualificados que identificam os objetos de código que queremos inspecionar. O bytecode produzido é funcionalmente idêntico ao Python 3.2.Para criar uma classe, o Python pega essencialmente todo o conjunto que compõe o corpo da classe (então tudo recuou um nível mais do que a
class <name>:
linha) e executa isso como se fosse uma função:O primeiro
LOAD_CONST
carrega um objeto de código para oFoo
corpo da classe, transforma isso em uma função e o chama. O resultado dessa chamada é então usado para criar o espaço para nome da classe, its__dict__
. Por enquanto, tudo bem.O que deve ser observado aqui é que o bytecode contém um objeto de código aninhado; no Python, definições de classe, funções, compreensões e geradores são todos representados como objetos de código que contêm não apenas bytecode, mas também estruturas que representam variáveis locais, constantes, variáveis extraídas de globais e variáveis extraídas do escopo aninhado. O bytecode compilado se refere a essas estruturas e o intérprete python sabe como acessar os dados fornecidos pelos bytecodes apresentados.
O importante a lembrar aqui é que o Python cria essas estruturas em tempo de compilação; o
class
conjunto é um objeto de código (<code object Foo at 0x10a436030, file "<stdin>", line 2>
) que já está compilado.Vamos inspecionar o objeto de código que cria o próprio corpo da classe; objetos de código têm uma
co_consts
estrutura:O bytecode acima cria o corpo da classe. A função é executada e o
locals()
namespace resultante , contendox
ey
é usado para criar a classe (exceto que ela não funciona porquex
não está definida como global). Note que depois de armazenar5
emx
, ele carrega um outro objeto de código; essa é a compreensão da lista; está envolto em um objeto de função exatamente como o corpo da classe; a função criada recebe um argumento posicional, orange(1)
iterável a ser usado para seu código de loop, convertido em um iterador. Conforme mostrado no bytecode,range(1)
é avaliado no escopo da classe.A partir disso, você pode ver que a única diferença entre um objeto de código para uma função ou gerador e um objeto de código para uma compreensão é que o último é executado imediatamente quando o objeto de código pai é executado; o bytecode simplesmente cria uma função rapidamente e a executa em algumas pequenas etapas.
Em vez disso, o Python 2.x usa o bytecode embutido, aqui está o resultado do Python 2.7:
Nenhum objeto de código é carregado, em vez disso, um
FOR_ITER
loop é executado em linha. Portanto, no Python 3.x, o gerador de listas recebeu um objeto de código próprio, o que significa que ele tem seu próprio escopo.No entanto, a compreensão foi compilada juntamente com o restante do código-fonte python quando o módulo ou script foi carregado pela primeira vez pelo intérprete, e o compilador não considera um conjunto de classes um escopo válido. Quaisquer variáveis referenciadas em uma compreensão de lista devem procurar no escopo em torno da definição de classe, recursivamente. Se a variável não foi encontrada pelo compilador, ela a marca como global. A desmontagem do objeto de código de compreensão da lista mostra que
x
é realmente carregado como um global:Esse pedaço de bytecode carrega o primeiro argumento passado (o
range(1)
iterador) e, assim como a versão do Python 2.x usaFOR_ITER
para fazer um loop sobre ele e criar sua saída.Se tivéssemos definido
x
nafoo
função,x
seria uma variável de célula (as células se referem a escopos aninhados):O
LOAD_DEREF
carregamento indiretox
dos objetos da célula do objeto de código:A referência real procura o valor das estruturas de dados do quadro atual, que foram inicializadas a partir do
.__closure__
atributo de um objeto de função . Como a função criada para o objeto do código de compreensão é descartada novamente, não conseguimos inspecionar o fechamento dessa função. Para ver um fechamento em ação, teríamos que inspecionar uma função aninhada:Então, para resumir:
Uma solução alternativa; ou, o que fazer sobre isso
Se você criar um escopo explícito para a
x
variável, como em uma função, poderá usar variáveis de escopo de classe para entender a lista:A
y
função 'temporária' pode ser chamada diretamente; nós o substituímos quando o fazemos com seu valor de retorno. Seu escopo é considerado ao resolverx
:Obviamente, as pessoas que leem o seu código vão pensar um pouco sobre isso; convém colocar um grande comentário explicando por que você está fazendo isso.
A melhor solução é usar apenas
__init__
para criar uma variável de instância:e evite todo o esforço e perguntas para se explicar. Para seu próprio exemplo concreto, eu nem armazenaria o material
namedtuple
na classe; use a saída diretamente (não armazene a classe gerada) ou use um global:fonte
y = (lambda x=x: [x for i in range(1)])()
lambda
são apenas funções anônimas, afinal.Na minha opinião, é uma falha no Python 3. Espero que eles mudem.
Old Way (funciona em 2.7, joga
NameError: name 'x' is not defined
em 3 ou mais):OBSERVAÇÃO: o simples escopo com
A.x
ele não resolveriaNova maneira (funciona em mais de 3):
Como a sintaxe é tão feia, eu apenas inicializo todas as minhas variáveis de classe no construtor.
fonte
def
para criar uma função).python -c "import IPython;IPython.embed()"
. Execute o IPython diretamente usando o sayipython
e o problema desaparecerá.A resposta aceita fornece informações excelentes, mas parece haver algumas outras rugas aqui - diferenças entre a compreensão da lista e as expressões geradoras. Uma demonstração com a qual brinquei:
fonte
Este é um erro no Python. As compreensões são anunciadas como equivalentes a loops, mas isso não é verdade nas classes. Pelo menos até Python 3.6.6, em uma compreensão usada em uma classe, apenas uma variável de fora da compreensão é acessível dentro da compreensão e deve ser usada como o iterador mais externo. Em uma função, essa limitação de escopo não se aplica.
Para ilustrar por que isso é um erro, vamos retornar ao exemplo original. Isso falha:
Mas isso funciona:
A limitação é declarada no final desta seção no guia de referência.
fonte
Como o iterador mais externo é avaliado no escopo circundante, podemos usá-lo em
zip
conjuntoitertools.repeat
para transportar as dependências para o escopo da compreensão:Também é possível usar
for
loops aninhados na compreensão e incluir as dependências no iterável mais externo:Para o exemplo específico do OP:
fonte