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 :
Os nomes no escopo da classe não estão acessíveis. Os nomes são resolvidos no escopo da função mais interna. Se uma definição de classe ocorrer em uma cadeia de escopos aninhados, o processo de resolução ignorará as definições de classe.
e na class
documentação da declaração composta :
O conjunto da classe é então executado em um novo quadro de execução (consulte a seção Nomeando e vinculando ), usando um espaço para nome local recém-criado e o espaço para nome global original. (Geralmente, o conjunto contém apenas definições de funções.) Quando o conjunto da classe termina a execução, seu quadro de execução é descartado, mas seu espaço de nomes local é salvo . [4] Um objeto de classe é criado usando a lista de herança para as classes base e o espaço de nomes local salvo para o dicionário de atributo.
Ê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 manipulasse Foo.x
, por exemplo? Mais importante, o que isso significaria para as subclasses de Foo
? 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:
O escopo dos nomes definidos em um bloco de classes é limitado ao bloco de classes; não se estende aos blocos de código dos métodos - isso inclui compreensões e expressões geradoras, pois são implementadas usando um escopo de função. Isso significa que o seguinte falhará:
class A:
a = 42
b = list(a + i for i in range(10))
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.
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
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)
:
y = [x for i in range(1)]
# ^^^^^^^^
Portanto, o uso x
dessa expressão não geraria um erro:
# Runs fine
y = [i for i in range(x)]
Isso se aplica apenas ao iterável mais externo; se uma compreensão tiver várias for
cláusulas, os iteráveis para for
cláusulas internas serão avaliados no escopo da compreensão:
# NameError
y = [i for i in range(1) for j in range(x)]
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:
>>> import dis
>>> def foo():
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo)
2 0 LOAD_BUILD_CLASS
1 LOAD_CONST 1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>)
4 LOAD_CONST 2 ('Foo')
7 MAKE_FUNCTION 0
10 LOAD_CONST 2 ('Foo')
13 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
16 STORE_FAST 0 (Foo)
5 19 LOAD_FAST 0 (Foo)
22 RETURN_VALUE
O primeiro LOAD_CONST
carrega um objeto de código para o Foo
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:
>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
2 0 LOAD_FAST 0 (__locals__)
3 STORE_LOCALS
4 LOAD_NAME 0 (__name__)
7 STORE_NAME 1 (__module__)
10 LOAD_CONST 0 ('foo.<locals>.Foo')
13 STORE_NAME 2 (__qualname__)
3 16 LOAD_CONST 1 (5)
19 STORE_NAME 3 (x)
4 22 LOAD_CONST 2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>)
25 LOAD_CONST 3 ('foo.<locals>.Foo.<listcomp>')
28 MAKE_FUNCTION 0
31 LOAD_NAME 4 (range)
34 LOAD_CONST 4 (1)
37 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
40 GET_ITER
41 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
44 STORE_NAME 5 (y)
47 LOAD_CONST 5 (None)
50 RETURN_VALUE
O bytecode acima cria o corpo da classe. A função é executada e o locals()
namespace resultante , contendo x
e y
é usado para criar a classe (exceto que ela não funciona porque x
não está definida como global). Note que depois de armazenar 5
em x
, 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, o range(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:
2 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
3 6 LOAD_CONST 0 (5)
9 STORE_NAME 2 (x)
4 12 BUILD_LIST 0
15 LOAD_NAME 3 (range)
18 LOAD_CONST 1 (1)
21 CALL_FUNCTION 1
24 GET_ITER
>> 25 FOR_ITER 12 (to 40)
28 STORE_NAME 4 (i)
31 LOAD_NAME 2 (x)
34 LIST_APPEND 2
37 JUMP_ABSOLUTE 25
>> 40 STORE_NAME 5 (y)
43 LOAD_LOCALS
44 RETURN_VALUE
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:
>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
4 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_GLOBAL 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Esse pedaço de bytecode carrega o primeiro argumento passado (o range(1)
iterador) e, assim como a versão do Python 2.x usa FOR_ITER
para fazer um loop sobre ele e criar sua saída.
Se tivéssemos definido x
na foo
função, x
seria uma variável de célula (as células se referem a escopos aninhados):
>>> def foo():
... x = 2
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
5 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_DEREF 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
O LOAD_DEREF
carregamento indireto x
dos objetos da célula do objeto de código:
>>> foo.__code__.co_cellvars # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars # Refers to `x` in foo
('x',)
>>> foo().y
[2]
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:
>>> def spam(x):
... def eggs():
... return x
... return eggs
...
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5
Então, para resumir:
- As compreensões de lista obtêm seus próprios objetos de código no Python 3 e não há diferença entre os objetos de código para funções, geradores ou compreensões; objetos de código de compreensão são agrupados em um objeto de função temporário e chamados imediatamente.
- Os objetos de código são criados em tempo de compilação e as variáveis não locais são marcadas como variáveis globais ou livres, com base nos escopos aninhados do código. O corpo da classe não é considerado um escopo para procurar essas variáveis.
- Ao executar o código, o Python precisa apenas examinar os globais, ou o fechamento do objeto em execução no momento. Como o compilador não incluiu o corpo da classe como um escopo, o espaço para nome da função temporária não é considerado.
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:
>>> class Foo:
... x = 5
... def y(x):
... return [x for i in range(1)]
... y = y(x)
...
>>> Foo.y
[5]
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 resolver x
:
>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)
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:
def __init__(self):
self.y = [self.x for i in range(1)]
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:
from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])
class StateDatabase:
db = [State(*args) for args in [
('Alabama', 'Montgomery'),
('Alaska', 'Juneau'),
# ...
]]
NameError: global name 'x' is not defined
em Python 3.2 e 3.3, que é o que eu esperaria.