Escopo do bloco em Python

93

Ao codificar em outras linguagens, você às vezes criará um escopo de bloco, como este:

statement
...
statement
{
    statement
    ...
    statement
}
statement
...
statement

Um dos objetivos (de muitos) é melhorar a legibilidade do código: mostrar que certas instruções formam uma unidade lógica ou que certas variáveis ​​locais são usadas apenas naquele bloco.

Existe uma maneira idiomática de fazer a mesma coisa em Python?

Johan Råde
fonte
2
One purpose (of many) is to improve code readability- O código Python, escrito corretamente (ou seja, seguindo o zen do python ) não precisa desse enfeite para ser legível. Na verdade, é uma das (muitas) coisas de que gosto no Python.
Burhan Khalid,
Eu tentei jogar com __exit__e withdeclaração, mudando o globals()mas eu falhei.
Ruggero Turra
1
seria muito útil definir o tempo de vida variável, conectado à aquisição de recursos
Ruggero Turra
24
@BurhanKhalid: Isso não é verdade. O zen do Python não impede que você polua um escopo local com uma variável temporária aqui e ali. Se você transformar todo uso de uma única variável temporária em, por exemplo, definir uma função aninhada que é chamada imediatamente, o zen do Python também não ficará feliz. Limitar explicitamente o escopo de uma variável é uma ferramenta para melhorar a legibilidade, porque responde diretamente "esses identificadores são usados ​​abaixo?" - uma questão que pode surgir lendo até mesmo o código Python mais elegante.
bluenote10
17
@BurhanKhalid Não há problema em não ter um recurso. Mas chamar isso de "zen" é nojento.
Phil

Respostas:

80

Não, não há suporte de idioma para a criação de escopo de bloco.

As seguintes construções criam escopo:

  • módulo
  • classe
  • função (incl. lambda)
  • expressão geradora
  • compreensões (dict, set, list (em Python 3.x))
ThomasH
fonte
37

A maneira idiomática em Python é manter suas funções curtas. Se você acha que precisa disso, refatore seu código! :)

Python cria um novo escopo para cada módulo, classe, função, expressão geradora, compreensão de dicionário, compreensão de conjunto e em Python 3.x também para cada compreensão de lista. Além desses, não há escopos aninhados dentro das funções.

Sven Marnach
fonte
12
"O mais importante na programação é a capacidade de dar um nome a algo. A segunda coisa mais importante é não ser obrigado a dar um nome a algo." Na maior parte, Python requer que os escopos (para variáveis, etc.) recebam nomes. A este respeito, as variáveis ​​Python são o segundo teste mais importante.
Krazy Glew
19
O mais importante na programação é a capacidade de gerenciar as dependências de seu aplicativo e gerenciar escopos de blocos de código. Blocos anônimos permitem que você limite o tempo de vida dos retornos de chamada, ao passo que, caso contrário, seus retornos de chamada são usados ​​apenas uma vez, mas ao vivo durante a duração do programa, isso causa confusão no escopo global e prejudica a legibilidade do código.
Dmitry
Acabei de notar que as variáveis ​​também são locais para ditar / definir compreensões. Tentei Python 2.7 e 3.3, mas não tenho certeza se ele depende da versão.
wjandrea
1
@wjandrea Você tem razão - adicionado à lista. Não deve haver nenhuma diferença entre as versões do Python para eles.
Sven Marnach
3
Eu reformularia a última frase, pois você pode muito bem criar funções dentro de funções. Portanto, existem escopos aninhados dentro das funções.
ThomasH
17

Você pode fazer algo semelhante a um escopo de bloco C ++ em Python declarando uma função dentro de sua função e, em seguida, chamando-a imediatamente. Por exemplo:

def my_func():
    shared_variable = calculate_thing()

    def do_first_thing():
        ... = shared_variable
    do_first_thing()

    def do_second_thing():
        foo(shared_variable)
        ...
    do_second_thing()

Se você não tem certeza por que deseja fazer isso, este vídeo pode convencê-lo.

O princípio básico é definir o escopo de tudo o mais estreitamente possível, sem introduzir qualquer 'lixo' (tipos / funções extras) em um escopo mais amplo do que o absolutamente necessário - Nada mais deseja usar o do_first_thing()método, por exemplo, então ele não deve ter escopo fora do função de chamada.

Ben
fonte
É também a maneira que está sendo usada pelos desenvolvedores do Google em tutoriais do TensorFlow, como pode ser visto por exemplo aqui
Nino Filiu
13

Eu concordo que não há escopo de bloco. Mas um lugar no python 3 o torna PARECIDO como se tivesse um escopo de bloco.

o que aconteceu que deu esse visual? Isso estava funcionando corretamente no python 2. mas para fazer o vazamento de variável parar no python 3, eles fizeram esse truque e essa mudança faz com que pareça que há um escopo de bloco aqui.

Deixe-me explicar.


Pela ideia de escopo, quando introduzimos variáveis ​​com os mesmos nomes dentro do mesmo escopo, seu valor deve ser modificado.

isso é o que está acontecendo em python 2

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'W'

Mas em python 3, embora a variável com o mesmo nome seja introduzida, ela não sobrescreve, a compreensão da lista age como uma caixa de areia por algum motivo e parece criar um novo escopo nela.

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'OLD'

e essa resposta vai contra a declaração do respondente @Thomas. O único meio de criar escopo são funções, classes ou módulos, porque isso se parece com outro local de criação de um novo escopo.

Harish Kayarohanam
fonte
0

Módulos (e pacotes) são uma ótima maneira Pythônica de dividir seu programa em namespaces separados, o que parece ser um objetivo implícito desta questão. Na verdade, enquanto estava aprendendo o básico do Python, me senti frustrado com a falta de um recurso de escopo de bloco. No entanto, uma vez que entendi os módulos Python, pude realizar com mais elegância meus objetivos anteriores sem a necessidade de escopo de bloco.

Como motivação, e para apontar as pessoas para a direção certa, acho que é útil fornecer exemplos explícitos de algumas das construções de escopo do Python. Primeiro, explico minha tentativa fracassada de usar classes Python para implementar o escopo do bloco. A seguir, explico como consegui algo mais útil usando módulos Python. No final, descrevo uma aplicação prática de pacotes para carregar e filtrar dados.

Tentando escopo de bloco com classes

Por alguns momentos, pensei que havia alcançado o escopo do bloco colocando o código dentro de uma declaração de classe:

x = 5
class BlockScopeAttempt:
    x = 10
    print(x) # Output: 10
print(x) # Output: 5

Infelizmente, isso falha quando uma função é definida:

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(x) 
    printx2() # Output: 5!!!

Isso ocorre porque as funções definidas em uma classe usam escopo global. A maneira mais fácil (embora não a única) de corrigir isso é especificar explicitamente a classe:

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(BlockScopeAttempt.x)  # Added class name
    printx2() # Output: 10

Isso não é tão elegante porque é preciso escrever funções de maneira diferente, dependendo se estão ou não contidas em uma classe.

Melhores resultados com módulos Python

Módulos são muito semelhantes às classes estáticas, mas os módulos são muito mais limpos na minha experiência. Para fazer o mesmo com os módulos, crio um arquivo chamado my_module.pyno diretório de trabalho atual com o seguinte conteúdo:

x = 10
print(x) # (A)

def printx():
    global x
    print(x) # (B)

Então, em meu arquivo principal ou sessão interativa (por exemplo, Jupyter), eu faço

x = 5
import my_module # Output: 10 from (A)
my_module.printx() # Output: 10 from (B)
print(x) # Output: 5

Como explicação, cada arquivo Python define um módulo que possui seu próprio namespace global. Importar um módulo permite que você acesse as variáveis ​​neste namespace com o. sintaxe.

Se você estiver trabalhando com módulos em uma sessão interativa, você pode executar essas duas linhas no início

%load_ext autoreload
%autoreload 2

e os módulos serão recarregados automaticamente quando seus arquivos correspondentes forem modificados.

Pacotes para carregar e filtrar dados

A ideia de pacotes é uma pequena extensão do conceito de módulos. Um pacote é um diretório que contém um __init__.pyarquivo (possivelmente em branco) , que é executado na importação. Módulos / pacotes dentro deste diretório podem ser acessados ​​com a .sintaxe.

Para análise de dados, geralmente preciso ler um grande arquivo de dados e, em seguida, aplicar interativamente vários filtros. Ler um arquivo leva vários minutos, então quero fazer isso apenas uma vez. Com base no que aprendi na escola sobre programação orientada a objetos, costumava acreditar que se deveria escrever o código para filtrar e carregar como métodos em uma classe. Uma grande desvantagem dessa abordagem é que, se eu redefinir meus filtros, a definição de minha classe muda, então tenho que recarregar a classe inteira, incluindo os dados.

Hoje em dia com Python, defino um pacote chamado my_dataque contém submódulos chamados loade filter. Dentro de filter.pyeu posso fazer uma importação relativa:

from .load import raw_data

Se eu modificar filter.py, autoreloaddetectarei as mudanças. Ele não recarrega load.py, então não preciso recarregar meus dados. Dessa forma, posso criar um protótipo de meu código de filtragem em um bloco de notas Jupyter, envolvê-lo como uma função e, em seguida, cortar e colar de meu bloco de notas diretamente no filter.py. Descobrir isso revolucionou meu fluxo de trabalho e me transformou de um cético em um crente no "Zen de Python".

Ben Mares
fonte