Quelle est la gravité des noms d'observation dans les étendues externes?

208

Je viens de passer à Pycharm et je suis très heureux de tous les avertissements et astuces qu'il me donne pour améliorer mon code. Sauf pour celui-ci que je ne comprends pas:

This inspection detects shadowing names defined in outer scopes.

Je sais que c'est une mauvaise pratique d'accéder aux variables depuis la portée externe, mais quel est le problème avec l'observation de la portée externe?

Voici un exemple, où Pycharm me donne le message d'avertissement:

data = [4, 5, 6]

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

print_data(data)
Framester
la source
1
J'ai également recherché la chaîne "Cette inspection détecte ..." mais je n'ai rien trouvé dans l'aide en ligne de pycharm
Framester
1
Pour désactiver ce message dans PyCharm: <Ctrl> + <Alt> + s (paramètres), Editor , Inspections , " Shadowing names from external scopes ". Décochez.
ChaimG

Réponses:

222

Pas grand-chose dans votre extrait ci-dessus, mais imaginez une fonction avec quelques arguments supplémentaires et quelques lignes de code supplémentaires. Ensuite, vous décidez de renommer votre dataargument comme yaddamais manquez l'un des endroits où il est utilisé dans le corps de la fonction ... Désormais se dataréfère au global, et vous commencez à avoir un comportement étrange - où vous auriez beaucoup plus évident NameErrorsi vous ne le faisiez pas avoir un nom global data.

Souvenez-vous également qu'en Python, tout est un objet (y compris les modules, les classes et les fonctions), il n'y a donc pas d'espaces de noms distincts pour les fonctions, les modules ou les classes. Un autre scénario consiste à importer une fonction fooen haut de votre module et à l'utiliser quelque part dans le corps de votre fonction. Ensuite, vous ajoutez un nouvel argument à votre fonction et le nommez - pas de chance - foo.

Enfin, les fonctions et types intégrés vivent également dans le même espace de noms et peuvent être masqués de la même manière.

Rien de tout cela n'est un problème si vous avez des fonctions courtes, un bon nom et une couverture décente, mais bien, parfois, vous devez maintenir un code moins que parfait et être averti de ces problèmes possibles pourrait aider.

bruno desthuilliers
la source
21
Heureusement, PyCharm (tel qu'utilisé par l'OP) a une très belle opération de changement de nom qui renomme la variable partout où elle est utilisée dans la même portée, ce qui rend les erreurs de changement de nom moins probables.
wojtow
En plus de l'opération de changement de nom de PyCharm, j'aimerais avoir des points forts de syntaxe spéciaux pour les variables qui se réfèrent à la portée externe. Ces deux devraient rendre ce jeu de résolution d'ombre fastidieux non pertinent.
Leo
Remarque: vous pouvez utiliser le nonlocalmot - clé pour rendre explicite le référencement externe (comme dans les fermetures). Notez que cela est différent de l'observation, car il n'observe pas explicitement les variables de l'extérieur.
Felix D.
149

La réponse actuellement la plus votée et acceptée et la plupart des réponses ici manquent de pertinence.

Peu importe la durée de votre fonction ou la façon dont vous nommez votre variable de manière descriptive (pour, espérons-le, minimiser les risques de collision de noms potentiels).

Le fait que la variable locale de votre fonction ou son paramètre partage un nom dans la portée globale est complètement hors de propos. Et en fait, peu importe le soin avec lequel vous choisissez votre nom de variable locale, votre fonction ne peut jamais prévoir "si mon nom génial yaddasera également utilisé comme variable globale à l'avenir?". La solution? Ne vous en faites pas!La mentalité correcte est de concevoir votre fonction pour consommer les entrées de et uniquement à partir de ses paramètres dans la signature , de cette façon, vous n'avez pas besoin de vous soucier de ce qui est (ou sera) de portée mondiale, puis l'observation ne devient plus un problème.

En d'autres termes, le problème de l'observation n'a d'importance que lorsque votre fonction doit utiliser la même variable locale ET la variable globale. Mais vous devez éviter une telle conception en premier lieu. Le code de l'OP n'a PAS vraiment un tel problème de conception. C'est juste que PyCharm n'est pas assez intelligent et il émet un avertissement au cas où. Donc, juste pour rendre PyCharm heureux, et aussi pour rendre notre code propre, voir cette solution citant la réponse de silyevsk pour supprimer complètement la variable globale.

def print_data(data):
    print data

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

main()

C'est la bonne façon de "résoudre" ce problème, en corrigeant / supprimant votre problème global, et non en ajustant votre fonction locale actuelle.

RayLuo
la source
11
Eh bien, bien sûr, dans un monde parfait, vous pouvez faire une faute de frappe, ou oublier une de vos recherches-remplacer lorsque vous modifiez le paramètre, mais des erreurs se produisent et c'est ce que PyCharm dit - "Attention - rien n'est techniquement erroné, mais cela pourrait facilement devenir un problème "
dwanderson
1
@dwanderson La situation que vous avez mentionnée n'est pas nouvelle, elle est clairement décrite dans la réponse actuellement choisie. Cependant, le point que j'essaie de faire valoir, c'est que nous devons éviter la variable globale, et non éviter l'observation d'une variable globale. Ce dernier manque le point. Tu piges? Je l'ai?
RayLuo
4
Je suis entièrement d'accord sur le fait que les fonctions doivent être aussi "pures" que possible mais vous manquez totalement les deux points importants: il n'y a aucun moyen d'empêcher Python de rechercher un nom dans les étendues qui l'entourent s'il n'est pas défini localement, et tout (modules , fonctions, classes, etc.) est un objet et vit dans le même espace de noms que toute autre "variable". Dans votre extrait ci-dessus, print_dataEST une variable globale. Pensez-y ...
bruno desthuilliers
2
Je me suis retrouvé sur ce fil parce que j'utilise des fonctions définies dans des fonctions, pour rendre la fonction externe plus lisible sans encombrer l'espace de noms global ou lourdement en utilisant des fichiers séparés. Cet exemple ici ne s'applique pas à ce cas général, où des variables non locales non globales sont masquées.
micseydel
2
Se mettre d'accord. Le problème ici est la portée de Python. L'accès non explicite à des objets en dehors de la portée actuelle pose problème. Qui voudrait ça! Dommage car sinon Python est un langage assez bien pensé (malgré une ambiguïté similaire dans la dénomination des modules).
CodeCabbie
24

Une bonne solution de contournement dans certains cas peut être de déplacer le code vars + vers une autre fonction:

def print_data(data):
    print data

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

main()
silyevsk
la source
Oui. Je pense qu'une bonne idée est capable de gérer les variables locales et globales en refactorisant. Votre astuce aide vraiment à éliminer ces risques potentiels pour les idées primitives
stanleyxu2005
5

Cela dépend de la durée de la fonction. Plus la fonction est longue, plus il est probable que quelqu'un la modifiant à l'avenir écriradata pensant que cela signifie le global. En fait, cela signifie le local, mais comme la fonction est si longue, il n'est pas évident pour eux qu'il existe un local portant ce nom.

Pour votre exemple de fonction, je pense que l'observation du global n'est pas mal du tout.

Steve Jessop
la source
5

Faites ceci:

data = [4, 5, 6]

def print_data():
    global data
    print(data)

print_data()

la source
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
la source
47
Pour ma part, je ne suis pas confus. C'est assez évidemment le paramètre.
2
@delnan Vous ne pouvez pas être confus dans cet exemple trivial, mais que se passe-t-il si d'autres fonctions définies à proximité utilisent le global data, le tout dans quelques centaines de lignes de code?
John Colanduoni
13
@HevyLight Je n'ai pas besoin de regarder d'autres fonctions à proximité. Je ne regarde que cette fonction et je vois que datac'est un nom local dans cette fonction, donc je ne me soucie même pas de vérifier / me souvenir si un global du même nom existe , et encore moins ce qu'il contient.
4
Je ne pense pas que ce raisonnement soit valide, uniquement parce que pour utiliser un global, il faudrait définir des "données globales" à l'intérieur de la fonction. Sinon, le global n'est pas accessible.
CodyF
1
@CodyF False- si vous ne définissez pas, mais juste essayer d'utiliser data, il regarde à travers champs jusqu'à ce qu'il en trouve un, il ne trouve mondial data. data = [1, 2, 3]; def foo(): print(data); foo()
dwanderson
3

J'aime voir une coche verte dans le coin supérieur droit de pycharm. J'ajoute les noms de variables avec un trait de soulignement juste pour effacer cet avertissement afin que je puisse me concentrer sur les avertissements importants.

data = [4, 5, 6]

def print_data(data_): 
    print(data_)

print_data(data)
Baz
la source
2

Il ressemble à un modèle de code 100% pytest

voir:

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

J'ai eu le même problème, c'est pourquoi j'ai trouvé ce 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

Et il avertira This inspection detects shadowing names defined in outer scopes.

Pour résoudre ce problème, déplacez simplement votre twitterappareil./tests/conftest.py

# ./tests/conftest.py
import pytest

from syntropy import db


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

Et retirez le twitterluminaire comme dans./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

Ce sera heureux QA, Pycharm et tout le monde

Andrei.Danciuc
la source