Wie schlecht sind Schattennamen in äußeren Bereichen definiert?

207

Ich bin gerade zu Pycharm gewechselt und freue mich sehr über alle Warnungen und Hinweise, die es mir gibt, um meinen Code zu verbessern. Außer diesem, den ich nicht verstehe:

This inspection detects shadowing names defined in outer scopes.

Ich weiß, dass es eine schlechte Praxis ist, auf Variablen vom äußeren Bereich aus zuzugreifen, aber was ist das Problem beim Abschatten des äußeren Bereichs?

Hier ist ein Beispiel, in dem Pycharm mir die Warnmeldung gibt:

data = [4, 5, 6]

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

print_data(data)
Framester
quelle
1
Außerdem habe ich nach der Zeichenfolge "Diese Inspektion erkennt ..." gesucht, aber in der Online-Hilfe von pycharm
Framester
1
So deaktivieren Sie diese Meldung in PyCharm: <Strg> + <Alt> + s (Einstellungen), Editor , Inspektionen , " Namen aus äußeren Bereichen beschatten ". Deaktivieren Sie.
ChaimG

Antworten:

222

Keine große Sache in Ihrem obigen Snippet, aber stellen Sie sich eine Funktion mit ein paar weiteren Argumenten und einigen weiteren Codezeilen vor. Dann beschließen Sie, Ihr dataArgument umzubenennen, yaddaaber eine der Stellen zu verpassen, an denen es im Funktionskörper verwendet wird ... dataBezieht sich nun auf das Globale, und Sie beginnen, sich seltsam zu verhalten - wo Sie ein viel offensichtlicheres hätten, NameErrorwenn Sie es nicht getan hätten einen globalen Namen haben data.

Denken Sie auch daran, dass in Python alles ein Objekt ist (einschließlich Module, Klassen und Funktionen), sodass es keine eindeutigen Namespaces für Funktionen, Module oder Klassen gibt. Ein weiteres Szenario besteht darin, dass Sie die Funktion foooben in Ihrem Modul importieren und irgendwo in Ihrem Funktionskörper verwenden. Dann fügen Sie Ihrer Funktion ein neues Argument hinzu und nennen es - Pech - foo.

Schließlich befinden sich integrierte Funktionen und Typen ebenfalls im selben Namespace und können auf dieselbe Weise schattiert werden.

Nichts davon ist ein großes Problem, wenn Sie kurze Funktionen, eine gute Benennung und eine anständige, uneinheitliche Abdeckung haben, aber manchmal müssen Sie weniger als perfekten Code pflegen, und wenn Sie vor solchen möglichen Problemen gewarnt werden, kann dies hilfreich sein.

bruno desthuilliers
quelle
21
Glücklicherweise hat PyCharm (wie vom OP verwendet) eine sehr schöne Umbenennungsoperation, die die Variable überall dort umbenennt, wo sie im selben Bereich verwendet wird, was das Umbenennen von Fehlern weniger wahrscheinlich macht.
Wojtow
Zusätzlich zur Umbenennungsoperation von PyCharm hätte ich gerne spezielle Syntax-Highlights für Variablen, die sich auf den äußeren Bereich beziehen. Diese beiden sollten dieses zeitaufwändige Spiel mit Schattenauflösung irrelevant machen.
Leo
Randnotiz: Sie können das nonlocalSchlüsselwort verwenden, um die Bezugnahme auf die äußere Punktzahl (wie in Abschlüssen) explizit zu machen. Beachten Sie, dass dies anders ist als das Abschatten, da Variablen von außen explizit nicht beschattet werden.
Felix D.
147

Die derzeit am besten bewertete und akzeptierte Antwort und die meisten Antworten hier verfehlen den Punkt.

Es spielt keine Rolle, wie lange Ihre Funktion dauert oder wie Sie Ihre Variable beschreibend benennen (um hoffentlich die Wahrscheinlichkeit einer möglichen Namenskollision zu minimieren).

Die Tatsache, dass die lokale Variable oder der Parameter Ihrer Funktion zufällig einen Namen im globalen Bereich gemeinsam hat, ist völlig irrelevant. Unabhängig davon, wie sorgfältig Sie den Namen Ihrer lokalen Variablen auswählen, kann Ihre Funktion niemals vorhersehen, ob mein cooler Name in yaddaZukunft auch als globale Variable verwendet wird. Die Lösung? Mach dir darüber einfach keine Sorgen! Die richtige Einstellung besteht darin, Ihre Funktion so zu gestalten, dass Eingaben von und nur von ihren Parametern in der Signatur verwendet werden. Auf diese Weise müssen Sie sich nicht darum kümmern, was im globalen Bereich liegt (oder sein wird), und dann wird das Abschatten überhaupt kein Problem.

Mit anderen Worten, das Shadowing-Problem ist nur dann von Bedeutung, wenn Ihre Funktion dieselbe lokale Variable UND die globale Variable verwenden muss. Aber Sie sollten ein solches Design in erster Linie vermeiden. Der OP-Code hat KEIN solches Designproblem. Es ist nur so, dass PyCharm nicht klug genug ist und für alle Fälle eine Warnung ausgibt. Um PyCharm glücklich zu machen und unseren Code sauber zu machen, wird diese Lösung aus der Antwort von Silyevsk zitiert , um die globale Variable vollständig zu entfernen.

def print_data(data):
    print data

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

main()

Dies ist der richtige Weg, um dieses Problem zu "lösen", indem Sie Ihr globales Problem beheben / entfernen und Ihre aktuelle lokale Funktion nicht anpassen.

RayLuo
quelle
11
Nun, sicher, in einer perfekten Welt machen Sie immer einen Tippfehler oder vergessen einen Ihrer Such-Ersetzungen, wenn Sie den Parameter ändern, aber Fehler passieren und das ist, was PyCharm sagt - "Warnung - technisch gesehen ist nichts falsch, aber dies könnte leicht zu einem Problem werden "
Dwanderson
1
@dwanderson Die von Ihnen erwähnte Situation ist nichts Neues, sie wird in der aktuell gewählten Antwort klar beschrieben. Der Punkt, den ich versuche, ist jedoch, dass wir globale Variablen vermeiden sollten, nicht vermeiden, globale Variablen zu beschatten. Letzterer verfehlt den Punkt. Kapiert? Verstanden?
RayLuo
4
Ich stimme voll und ganz der Tatsache zu, dass Funktionen so "rein" wie möglich sein sollten, aber Sie verpassen die beiden wichtigen Punkte völlig: Es gibt keine Möglichkeit, Python daran zu hindern, einen Namen in den umschließenden Bereichen nachzuschlagen, wenn er nicht lokal definiert ist, und alles (Module) , Funktionen, Klassen usw.) ist ein Objekt und befindet sich im selben Namespace wie jede andere "Variable". In Ihrem obigen Snippet ist print_dataIS eine globale Variable. Denken Sie darüber nach ...
Bruno Desthuilliers
2
Ich bin in diesem Thread gelandet, weil ich in Funktionen definierte Funktionen verwende, um die äußere Funktion besser lesbar zu machen, ohne den globalen Namespace zu überladen, oder um separate Dateien zu verwenden. Dieses Beispiel gilt hier nicht für den allgemeinen Fall, dass nicht lokale nicht globale Variablen schattiert werden.
Micseydel
2
Zustimmen. Das Problem hierbei ist das Python-Scoping. Der nicht explizite Zugriff auf Objekte außerhalb des aktuellen Bereichs führt zu Problemen. Wer würde das wollen! Schade, denn sonst ist Python eine ziemlich gut durchdachte Sprache (trotz einer ähnlichen Mehrdeutigkeit bei der Benennung von Modulen).
CodeCabbie
24

In einigen Fällen kann eine gute Problemumgehung darin bestehen, den vars + -Code in eine andere Funktion zu verschieben:

def print_data(data):
    print data

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

main()
Silyevsk
quelle
Ja. Ich denke, eine gute Idee kann mit lokalen und globalen Variablen durch Refactoring umgehen. Ihr Tipp hilft wirklich, solche potenziellen Risiken für primitive ide
stanleyxu2005
5

Es kommt darauf an, wie lange die Funktion dauert. Je länger die Funktion ist, desto größer ist die Chance, dass jemand, der sie in Zukunft modifiziert, schreibt data, dass dies das Globale bedeutet. Tatsächlich bedeutet es das Lokale, aber weil die Funktion so lang ist, ist es für sie nicht offensichtlich, dass es ein Lokales mit diesem Namen gibt.

Für Ihre Beispielfunktion denke ich, dass das Abschatten des Globalen überhaupt nicht schlecht ist.

Steve Jessop
quelle
5

Mach das:

data = [4, 5, 6]

def print_data():
    global data
    print(data)

print_data()

quelle
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
quelle
47
Ich jedenfalls bin nicht verwirrt. Es ist ziemlich offensichtlich der Parameter.
2
@delnan Sie mögen in diesem trivialen Beispiel nicht verwirrt sein, aber was ist, wenn andere in der Nähe definierte Funktionen das Globale verwenden data, alles tief in ein paar hundert Codezeilen?
John Colanduoni
13
@HevyLight Ich muss mir keine anderen Funktionen in der Nähe ansehen. Ich schaue nur auf diese Funktion und kann sehen, dass dataes sich um einen lokalen Namen in dieser Funktion handelt. Daher mache ich mir nicht einmal die Mühe, zu überprüfen / mich daran zu erinnern, ob ein gleichnamiger Global existiert , geschweige denn, was er enthält.
4
Ich denke nicht, dass diese Argumentation gültig ist, nur weil Sie zur Verwendung eines globalen "globale Daten" innerhalb der Funktion definieren müssten. Andernfalls ist der globale nicht zugänglich.
CodyF
1
@CodyF False- wenn Sie nicht definieren, sondern nur versuchen , zu verwenden data, sieht es durch Bereiche , bis es eine findet, so es tut die globale finden data. data = [1, 2, 3]; def foo(): print(data); foo()
Dwanderson
3

Ich sehe gerne ein grünes Häkchen in der oberen rechten Ecke in Pycharm. Ich füge die Variablennamen mit einem Unterstrich hinzu, um diese Warnung zu löschen, damit ich mich auf die wichtigen Warnungen konzentrieren kann.

data = [4, 5, 6]

def print_data(data_): 
    print(data_)

print_data(data)
Baz
quelle
2

Es sieht aus wie es 100% pytest Code-Muster

sehen:

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

Ich hatte das gleiche Problem mit, deshalb habe ich diesen Beitrag gefunden;)

# ./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

Und es wird mit warnen This inspection detects shadowing names defined in outer scopes.

Um dies zu beheben, bewegen Sie einfach Ihr twitterGerät in./tests/conftest.py

# ./tests/conftest.py
import pytest

from syntropy import db


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

Und entfernen Sie das twitterGerät wie in./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

Dies wird QA, Pycharm und alle glücklich machen

Andrei.Danciuc
quelle