Jak złe są definicje cieniowania w zewnętrznych zakresach?

208

Właśnie przeszedłem na Pycharm i bardzo się cieszę z wszystkich ostrzeżeń i wskazówek, które pozwalają mi ulepszyć swój kod. Z wyjątkiem tego, którego nie rozumiem:

This inspection detects shadowing names defined in outer scopes.

Wiem, że złą praktyką jest dostęp do zmiennej z zakresu zewnętrznego, ale jaki jest problem z zaciemnieniem zakresu zewnętrznego?

Oto przykład, w którym Pycharm przekazuje mi komunikat ostrzegawczy:

data = [4, 5, 6]

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

print_data(data)
Framester
źródło
1
Szukałem również ciągu „Ta inspekcja wykrywa ...”, ale nic nie znalazłem w pomocy online pycharm
Framester
1
Aby wyłączyć tę wiadomość w PyCharm: <Ctrl> + <Alt> + s (ustawienia), Edytor , Inspekcje , „ Cieniowanie nazw z zewnętrznych zakresów ”. Odznacz.
ChaimG

Odpowiedzi:

222

Nie ma nic wielkiego w powyższym fragmencie, ale wyobraź sobie funkcję z kilkoma dodatkowymi argumentami i całkiem kilkoma wierszami kodu. Następnie decydujesz się zmienić nazwę swojego dataargumentu, ponieważ yaddabrakuje jednego z miejsc, w których jest on używany w ciele funkcji ... Teraz dataodnosi się do globalnego i zaczynasz mieć dziwne zachowanie - w którym miałbyś o wiele bardziej oczywiste, NameErrorgdybyś tego nie zrobił mają globalną nazwę data.

Pamiętaj również, że w Pythonie wszystko jest obiektem (w tym moduły, klasy i funkcje), więc nie ma odrębnych przestrzeni nazw dla funkcji, modułów lub klas. Innym scenariuszem jest importowanie funkcji foou góry modułu i używanie jej gdzieś w treści funkcji. Następnie dodajesz nowy argument do swojej funkcji i nazywasz go - pech - foo.

Wreszcie, wbudowane funkcje i typy również działają w tej samej przestrzeni nazw i mogą być w ten sam sposób cieniowane.

Nic z tego nie stanowi większego problemu, jeśli masz krótkie funkcje, dobre nazewnictwo i przyzwoity, niezakłócony zasięg, ale cóż, czasami trzeba zachować mniej niż doskonały kod i ostrzeżenie o takich możliwych problemach może pomóc.

bruno desthuilliers
źródło
21
Na szczęście PyCharm (używany przez OP) ma bardzo przyjemną operację zmiany nazwy, która zmienia nazwę zmiennej wszędzie tam, gdzie jest używana w tym samym zakresie, co sprawia, że ​​błędy zmiany nazwy są mniej prawdopodobne.
wojtów
Oprócz operacji zmiany nazwy PyCharm chciałbym mieć specjalne wyróżnienia składniowe dla zmiennych odnoszących się do zakresu zewnętrznego. Te dwa elementy powinny sprawić, że ta czasochłonna gra z rozdzielczością cienia stanie się nieistotna.
Leo
Uwaga dodatkowa: możesz użyć tego nonlocalsłowa kluczowego, aby wyraźnie wskazać zewnętrzny wynik odsyłający (jak w zamknięciach). Zauważ, że różni się to od cieniowania, ponieważ wyraźnie nie ukrywa zmiennych z zewnątrz.
Felix D.
149

Obecnie najlepiej oceniona i zaakceptowana odpowiedź oraz większość odpowiedzi tutaj nie ma sensu.

Nie ma znaczenia, jak długa jest twoja funkcja ani jak nazywasz swoją zmienną opisowo (aby, mam nadzieję, zminimalizować ryzyko potencjalnej kolizji nazwy).

Fakt, że zmienna lokalna twojej funkcji lub jej parametr mają tę samą nazwę w zasięgu globalnym, jest całkowicie nieistotny. I faktycznie, bez względu na to, jak ostrożnie wybierzesz nazwę zmiennej lokalnej, twoja funkcja nigdy nie będzie w stanie przewidzieć „czy moja fajna nazwa yaddabędzie również używana w przyszłości jako zmienna globalna?”. Rozwiązanie? Po prostu nie przejmuj się tym! Prawidłowym sposobem myślenia jest zaprojektowanie funkcji tak, aby pobierała dane wejściowe tylko z parametrów w sygnaturze sposobem , w ten sposób nie musisz dbać o to, co jest (lub będzie) w zasięgu globalnym, a wtedy cieniowanie w ogóle nie będzie problemem.

Innymi słowy, problem zacienienia ma znaczenie tylko wtedy, gdy twoja funkcja musi używać tej samej nazwy zmiennej lokalnej ORAZ zmiennej globalnej. Ale przede wszystkim należy unikać takiego projektu. Kod OP nie ma tak naprawdę problemu projektowego. Po prostu PyCharm nie jest wystarczająco inteligentny i na wszelki wypadek wydaje ostrzeżenie. Tak więc, aby uszczęśliwić PyCharm, a także oczyścić nasz kod, zobacz to rozwiązanie cytowane z odpowiedzi Silyevska, aby całkowicie usunąć zmienną globalną.

def print_data(data):
    print data

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

main()

Jest to właściwy sposób na „rozwiązanie” tego problemu poprzez naprawienie / usunięcie globalnej rzeczy, a nie dostosowanie bieżącej funkcji lokalnej.

RayLuo
źródło
11
No cóż, jasne, w idealnym świecie zawsze popełnisz literówkę lub zapomnisz jednego z twoich kryteriów wyszukiwania po zmianie parametru, ale zdarzają się błędy i tak mówi PyCharm - „Ostrzeżenie - nic nie jest technicznie błędne, ale to może łatwo stać się problemem ”
dwanderson
1
@dwanderson Wspomniana sytuacja nie jest niczym nowym, jest wyraźnie opisana w aktualnie wybranej odpowiedzi. Chodzi mi jednak o to, że powinniśmy unikać zmiennych globalnych, a nie zaciemniać zmiennych globalnych. Ten ostatni nie ma sensu. Zdobyć? Rozumiem?
RayLuo
4
Całkowicie zgadzam się z tym, że funkcje powinny być tak „czyste”, jak to możliwe, ale całkowicie pomijasz dwa ważne punkty: nie ma sposobu, aby ograniczyć Pythonowi wyszukiwanie nazwy w obejmujących zakresach, jeśli nie jest ona zdefiniowana lokalnie, i wszystko (moduły , funkcje, klasy itp.) jest obiektem i żyje w tej samej przestrzeni nazw, co każda inna „zmienna”. W powyższym fragmencie print_dataJEST zmienną globalną. Pomyśl o tym ...
Bruno Desthuilliers,
2
Skończyło się na tym wątku, ponieważ używam funkcji zdefiniowanych w funkcjach, aby uczynić funkcję zewnętrzną bardziej czytelną bez zaśmiecania globalnej przestrzeni nazw lub ciężkiego używania osobnych plików. Ten przykład tutaj nie dotyczy tego ogólnego przypadku, w którym nielokalne zmienne nieglobalne są zasłaniane.
micseydel,
2
Zgodzić się. Problemem jest tutaj zakres Pythona. Brak jawnego dostępu do obiektów poza bieżącym zakresem wymaga problemów. Kto by tego chciał! Szkoda, bo inaczej Python jest dość dobrze przemyślanym językiem (nie znoszącym podobnej dwuznaczności w nazewnictwie modułów).
CodeCabbie
24

Dobrym rozwiązaniem w niektórych przypadkach może być przeniesienie kodu vars + do innej funkcji:

def print_data(data):
    print data

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

main()
silyevsk
źródło
Tak. Myślę, że dobry ide jest w stanie obsłużyć zmienne lokalne i zmienne globalne poprzez refaktoryzację. Twoja wskazówka naprawdę pomaga wyeliminować takie potencjalne ryzyko dla prymitywnych ide
stanleyxu2005
5

To zależy od długości tej funkcji. Im dłuższa funkcja, tym większa szansa, że ​​ktoś ją zmodyfikuje w przyszłości, datamyśląc, że oznacza to globalny. W rzeczywistości oznacza to lokalny, ale ponieważ funkcja jest tak długa, nie jest dla nich oczywiste, że istnieje lokalny o tej nazwie.

W przypadku twojej przykładowej funkcji myślę, że cieniowanie globalnego wcale nie jest złe.

Steve Jessop
źródło
5

Zrób to:

data = [4, 5, 6]

def print_data():
    global data
    print(data)

print_data()

źródło
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
źródło
47
Po pierwsze nie jestem zdezorientowany. Jest to oczywiście parametr.
2
@delnan Nie możesz się mylić w tym trywialnym przykładzie, ale co, jeśli inne zdefiniowane w pobliżu funkcje wykorzystały globalny data, wszystkie w obrębie kilkuset wierszy kodu?
John Colanduoni
13
@HevyLight Nie muszę patrzeć na inne funkcje w pobliżu. Patrzę na tylko tej funkcji i można zobaczyć, że datato lokalna nazwa w tej funkcji, więc nawet nie sprawdzając / pamiętając czy globalna o tej samej nazwie istnieje , nie mówiąc już, co on zawiera.
4
Nie sądzę, aby to rozumowanie było poprawne, wyłącznie dlatego, że aby użyć globalnego, musisz zdefiniować „globalne dane” wewnątrz funkcji. W przeciwnym razie globalny nie będzie dostępny.
CodyF
1
@CodyF False- jeśli nie definiują, ale po prostu spróbować użyć data, to patrzy się przez zakresy, aż znajdzie jeden, więc nie znaleźć globalny data. data = [1, 2, 3]; def foo(): print(data); foo()
dwanderson
3

Lubię widzieć zielony haczyk w prawym górnym rogu w pycharm. Dołączam nazwy zmiennych podkreśleniem, aby wyczyścić to ostrzeżenie, dzięki czemu mogę skupić się na ważnych ostrzeżeniach.

data = [4, 5, 6]

def print_data(data_): 
    print(data_)

print_data(data)
Baz
źródło
2

Wygląda na to, że jest to 100% wzorcowy kod zapytania

widzieć:

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

Miałem ten sam problem, dlatego znalazłem ten 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

I to ostrzeże This inspection detects shadowing names defined in outer scopes.

Aby to naprawić, po prostu przenieś twitterurządzenie do./tests/conftest.py

# ./tests/conftest.py
import pytest

from syntropy import db


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

I usuń twitterurządzenie jak w./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

To sprawi radość QA, Pycharm i wszystkim

Andrei.Danciuc
źródło