Quão ruins são os nomes de sombra definidos nos escopos externos?

208

Acabei de mudar para Pycharm e estou muito feliz com todos os avisos e dicas que ele fornece para melhorar meu código. Exceto por este que eu não entendo:

This inspection detects shadowing names defined in outer scopes.

Sei que é uma má prática acessar variáveis ​​do escopo externo, mas qual é o problema de sombrear o escopo externo?

Aqui está um exemplo, onde Pycharm me dá a mensagem de aviso:

data = [4, 5, 6]

def print_data(data): # <-- Warning: "Shadows 'data' from outer scope
    print data

print_data(data)
Framester
fonte
1
Também procurei a string "Esta inspeção detecta ...", mas não encontrei nada na ajuda on-line do pycharm: jetbrains.com/pycharm/webhelp/getting-help.html
Framester
1
Para desativar esta mensagem no PyCharm: <Ctrl> + <Alt> + s (configurações), Editor , Inspeções , " Sombreando nomes de escopos externos ". Desmarque.
ChaimG

Respostas:

222

Não é grande coisa no seu snippet acima, mas imagine uma função com mais alguns argumentos e muito mais linhas de código. Então você decide renomear seu dataargumento, yaddamas perde um dos lugares em que é usado no corpo da função ... Agora datase refere ao global, e você começa a ter um comportamento estranho - onde você teria muito mais óbvio NameErrorse não o fizesse. tem um nome global data.

Lembre-se também de que no Python tudo é um objeto (incluindo módulos, classes e funções), portanto não há espaços de nomes distintos para funções, módulos ou classes. Outro cenário é que você importa a função foona parte superior do seu módulo e a utiliza em algum lugar do seu corpo. Então você adiciona um novo argumento à sua função e o denomina - azar - foo.

Por fim, funções e tipos internos também vivem no mesmo espaço para nome e podem ser sombreados da mesma maneira.

Nada disso é muito problemático se você tiver funções curtas, boa nomeação e uma cobertura unittest decente, mas, às vezes, é necessário manter um código menos que perfeito e ser avisado sobre esses possíveis problemas pode ajudar.

bruno desthuilliers
fonte
21
Felizmente, o PyCharm (conforme usado pelo OP) possui uma operação de renomeação muito boa que renomeia a variável em todos os lugares em que é usada no mesmo escopo, o que torna menos provável a ocorrência de erros de renomeação.
Wojtow 6/04/2017
Além da operação de renomeação do PyCharm, eu adoraria ter destaques especiais de sintaxe para variáveis ​​que se referem ao escopo externo. Esses dois devem tornar esse jogo demorado e demorado irrelevante.
Leo
Nota lateral: você pode usar a nonlocalpalavra-chave para tornar explícita a referência externa (como nos fechamentos). Observe que isso é diferente de sombreamento, pois explicitamente não oculta variáveis ​​externas.
Felix D.
149

A resposta atualmente mais votada e aceita e a maioria das respostas aqui não atendem .

Não importa quanto tempo sua função seja, ou como você nomeará sua variável descritivamente (para minimizar a chance de uma possível colisão de nomes).

O fato de a variável local da sua função ou seu parâmetro compartilhar um nome no escopo global é completamente irrelevante. E, de fato, não importa com que cuidado você escolha o nome da variável local, sua função nunca poderá prever "se meu nome legal yaddatambém será usado como variável global no futuro?". A solução? Simplesmente não se preocupe com isso! A mentalidade correta é projetar sua função para consumir informações de e somente de seus parâmetros na assinatura , para que você não precise se importar com o que é (ou será) no escopo global e, em seguida, o sombreamento não se torna um problema.

Em outras palavras, o problema de sombreamento só importa quando sua função precisa usar o mesmo nome variável local E a variável global. Mas você deve evitar esse design em primeiro lugar. O código do OP realmente não tem esse problema de design. Só que o PyCharm não é inteligente o suficiente e emite um aviso. Portanto, apenas para deixar o PyCharm feliz e também para limpar o nosso código, veja esta solução citando a resposta de silyevsk para remover completamente a variável global.

def print_data(data):
    print data

def main():
    data = [4, 5, 6]
    print_data(data)

main()

Esta é a maneira correta de "resolver" esse problema, corrigindo / removendo sua coisa global, não ajustando sua função local atual.

RayLuo
fonte
11
Bem, com certeza, em um mundo perfeito, você pode digitar um erro de digitação ou esquecer uma de suas substituições de pesquisa quando altera o parâmetro, mas erros acontecem e é isso que PyCharm está dizendo - "Aviso - nada tecnicamente está errado, mas isso poderia facilmente se tornar um problema "
dwanderson 22/02
1
@dwanderson A situação que você mencionou não é novidade, está claramente descrita na resposta atualmente escolhida. No entanto, o argumento que tento enfatizar é que devemos evitar a variável global, não a sombra da variável global. Este último erra o ponto. Pegue? Entendi?
22417 RayLuo
4
Concordo plenamente com o fato de que as funções devem ser tão "puras" quanto possível, mas você perde totalmente os dois pontos importantes: não há como restringir o Python de procurar um nome nos escopos anexos se não estiver definido localmente e tudo mais (módulos , funções, classes etc) é um objeto e vive no mesmo espaço de nome que qualquer outra "variável". No seu snippet acima, print_dataÉ uma variável global. Pense nisso ...
bruno desthuilliers
2
Eu terminei neste segmento porque estou usando funções definidas em funções, para tornar a função externa mais legível sem bagunçar o espaço para nome global ou pesadamente usando arquivos separados. Este exemplo aqui não se aplica a esse caso geral, de variáveis ​​não globais não locais sendo sombreadas.
22818 michaeldel
2
Aceita. O problema aqui é o escopo do Python. O acesso não explícito a objetos fora do escopo atual está causando problemas. Quem iria querer isso! Uma pena, porque, caso contrário, o Python é uma linguagem muito bem pensada (não obstante uma ambiguidade semelhante na nomeação de módulos).
CodeCabbie
24

Uma boa solução alternativa em alguns casos pode ser mover o código vars + para outra função:

def print_data(data):
    print data

def main():
    data = [4, 5, 6]
    print_data(data)

main()
silyevsk
fonte
Sim. Eu acho que um bom ide é capaz de lidar com variáveis ​​locais e variáveis ​​globais refatorando. Sua dica realmente ajuda a eliminar tais riscos potenciais para ide primitiva
stanleyxu2005
5

Depende de quanto tempo a função é. Quanto maior a função, maior a chance de que alguém a modifique no futuro escreva datapensando que isso significa global. Na verdade, significa local, mas como a função é longa, não é óbvio para eles que existe um local com esse nome.

Para sua função de exemplo, acho que sombrear o global não é nada ruim.

Steve Jessop
fonte
5

Faça isso:

data = [4, 5, 6]

def print_data():
    global data
    print(data)

print_data()

fonte
3
data = [4, 5, 6] #your global variable

def print_data(data): # <-- Pass in a parameter called "data"
    print data  # <-- Note: You can access global variable inside your function, BUT for now, which is which? the parameter or the global variable? Confused, huh?

print_data(data)
JoeC
fonte
47
Eu, pelo menos não estou confuso. Obviamente, é o parâmetro.
2
@ delnan Você pode não se confundir neste exemplo trivial, mas e se outras funções definidas nas proximidades usassem o global data, tudo dentro de algumas centenas de linhas de código?
precisa saber é o seguinte
13
@HevyLight Não preciso olhar para outras funções próximas. Eu olho apenas para esta função e posso ver que dataé um nome local nessa função, então nem me incomodo em verificar / lembrar se existe um global com o mesmo nome , sem falar no que ele contém.
4
Eu não acho que esse raciocínio seja válido, apenas porque para usar um global, você precisaria definir "dados globais" dentro da função. Caso contrário, o global não estará acessível.
CodyF
1
@CodyF False- se você não definir, mas apenas tentar uso data, parece-se através de escopos até encontrar um, por isso faz encontrar o mundial data. data = [1, 2, 3]; def foo(): print(data); foo()
dwanderson
3

Eu gosto de ver uma marca verde no canto superior direito em pycharm. Anexo os nomes das variáveis ​​com um sublinhado apenas para limpar este aviso, para que eu possa me concentrar nos avisos importantes.

data = [4, 5, 6]

def print_data(data_): 
    print(data_)

print_data(data)
Baz
fonte
2

Parece 100% padrão de código pytest

Vejo:

https://docs.pytest.org/en/latest/fixture.html#conftest-py-sharing-fixture-functions

Eu tive o mesmo problema com, é por isso que encontrei este post;)

# ./tests/test_twitter1.py
import os
import pytest

from mylib import db
# ...

@pytest.fixture
def twitter():
    twitter_ = db.Twitter()
    twitter_._debug = True
    return twitter_

@pytest.mark.parametrize("query,expected", [
    ("BANCO PROVINCIAL", 8),
    ("name", 6),
    ("castlabs", 42),
])
def test_search(twitter: db.Twitter, query: str, expected: int):

    for query in queries:
        res = twitter.search(query)
        print(res)
        assert res

E avisará com This inspection detects shadowing names defined in outer scopes.

Para corrigir isso, apenas mova seu twitterdispositivo para./tests/conftest.py

# ./tests/conftest.py
import pytest

from syntropy import db


@pytest.fixture
def twitter():
    twitter_ = db.Twitter()
    twitter_._debug = True
    return twitter_

E remova o twitteraparelho como em./tests/test_twitter2.py

# ./tests/test_twitter2.py
import os
import pytest

from mylib import db
# ...

@pytest.mark.parametrize("query,expected", [
    ("BANCO PROVINCIAL", 8),
    ("name", 6),
    ("castlabs", 42),
])
def test_search(twitter: db.Twitter, query: str, expected: int):

    for query in queries:
        res = twitter.search(query)
        print(res)
        assert res

Isso fará com que o controle de qualidade seja feliz, Pycharm e todos

Andrei.Danciuc
fonte