Quanto sono definiti i nomi shadowing negli ambiti esterni?


208

Sono appena passato a Pycharm e sono molto contento di tutti gli avvisi e i suggerimenti che mi fornisce per migliorare il mio codice. Tranne questo, che non capisco:

This inspection detects shadowing names defined in outer scopes.

So che è una cattiva pratica accedere alla variabile dall'ambito esterno ma qual è il problema con l'ombra dell'ambito esterno?

Ecco un esempio, in cui Pycharm mi dà il messaggio di avviso:

data = [4, 5, 6]

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

print_data(data)

1
Ho anche cercato la stringa "Questa ispezione rileva ..." ma non ho trovato nulla nella guida in linea di Pycharm
Framester

1
Per disattivare questo messaggio in PyCharm: <Ctrl> + <Alt> + s (impostazioni), Editor , Ispezioni , " Ombreggiatura di nomi da ambiti esterni ". Deselezionare.
ChaimG

Risposte:


222

Non è un grosso problema nel tuo frammento di cui sopra, ma immagina una funzione con qualche argomento in più e qualche altra riga di codice. Quindi decidi di rinominare il tuo dataargomento come yaddama perdi uno dei luoghi in cui viene utilizzato nel corpo della funzione ... Ora si datariferisce al globale e inizi ad avere comportamenti strani - dove avresti un aspetto molto più ovvio NameErrorse non lo facessi avere un nome globale data.

Ricorda inoltre che in Python tutto è un oggetto (inclusi moduli, classi e funzioni), quindi non esistono spazi dei nomi distinti per funzioni, moduli o classi. Un altro scenario è che si importa la funzione foonella parte superiore del modulo e la si utilizza da qualche parte nel corpo della funzione. Quindi aggiungi un nuovo argomento alla tua funzione e lo chiami - sfortuna - foo.

Infine, anche le funzioni e i tipi incorporati vivono nello stesso spazio dei nomi e possono essere ombreggiati allo stesso modo.

Niente di tutto questo è un grosso problema se si dispone di funzioni brevi, una buona denominazione e una copertura discreta e discreta, ma bene, a volte è necessario mantenere un codice non perfetto ed essere avvertiti di tali possibili problemi potrebbe essere d'aiuto.


21
Fortunatamente PyCharm (come usato dall'OP) ha una bella operazione di ridenominazione che rinomina la variabile ovunque sia usata nello stesso ambito, il che rende meno probabile la ridenominazione degli errori.
Wojtow,

Oltre all'operazione di ridenominazione di PyCharm, mi piacerebbe avere speciali momenti salienti della sintassi per le variabili che si riferiscono all'ambito esterno. Questi due dovrebbero rendere irrilevante questo gioco che richiede molto tempo nella risoluzione delle ombre.
Leone,

Nota a margine: puoi usare la nonlocalparola chiave per rendere esplicito il riferimento del punteggio esterno (come nelle chiusure). Si noti che questo è diverso dall'ombreggiatura, poiché non ombreggia esplicitamente le variabili dall'esterno.
Felix D.

149

La risposta attualmente più votata e accettata e la maggior parte delle risposte qui mancano il punto.

Non importa quanto sia lunga la tua funzione o come dai nomi descrittivi alla tua variabile (per ridurre al minimo la possibilità di potenziali collisioni di nomi).

Il fatto che la variabile locale della sua funzione o il suo parametro capiti di condividere un nome nell'ambito globale è completamente irrilevante. E infatti, non importa con quale cura scegli il nome della tua variabile locale, la tua funzione non potrà mai prevedere "se il mio bel nome yaddaverrà usato anche come variabile globale in futuro?". La soluzione? Semplicemente non ti preoccupare di questo! La mentalità corretta è progettare la propria funzione in modo da consumare input da e solo dai suoi parametri in firma , in questo modo non è necessario preoccuparsi di ciò che è (o sarà) nell'ambito globale, e quindi l'ombreggiatura non diventa affatto un problema.

In altre parole, il problema dell'ombreggiatura è importante solo quando la tua funzione deve utilizzare la stessa variabile locale E la variabile globale. Ma dovresti evitare tale design in primo luogo. Il codice dell'OP NON presenta davvero questo problema di progettazione. È solo che PyCharm non è abbastanza intelligente e dà un avvertimento per ogni evenienza. Quindi, solo per rendere felice PyCharm e anche per rendere pulito il nostro codice, vedere questa soluzione citando la risposta di silyevsk per rimuovere completamente la variabile globale.

def print_data(data):
    print data

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

main()

Questo è il modo corretto di "risolvere" questo problema, riparando / rimuovendo la tua cosa globale, non regolando la tua attuale funzione locale.


11
Bene, certo, in un mondo perfetto, devi sempre fare un refuso, o dimenticare una delle tue sostituzioni di ricerca quando cambi il parametro, ma si verificano errori ed è quello che dice PyCharm - "Avvertenza - nulla è tecnicamente in errore, ma questo potrebbe facilmente diventare un problema "
dwanderson il

1
@dwanderson La situazione che hai citato non è una novità, è chiaramente descritta nella risposta attualmente scelta. Tuttavia, il punto che cerco di sottolineare è che dovremmo evitare la variabile globale, non evitare la variabile globale in ombra. Quest'ultimo manca il punto. Prendilo? Fatto?
RayLuo,

4
Concordo pienamente sul fatto che le funzioni dovrebbero essere il più "pure" possibile ma ti mancano totalmente i due punti importanti: non c'è modo di impedire a Python di cercare un nome negli ambiti racchiusi se non è definito localmente e tutto (moduli , funzioni, classi ecc.) è un oggetto e vive nello stesso spazio dei nomi di qualsiasi altra "variabile". Nel frammento di cui sopra, print_dataÈ una variabile globale. Pensaci ...
Bruno desthuilliers,

2
Ho finito con questo thread perché sto usando le funzioni definite nelle funzioni, per rendere più leggibile la funzione esterna senza ingombrare lo spazio dei nomi globale o usare pesantemente file separati. Questo esempio qui non si applica a quel caso generale, in cui le variabili non globali non locali vengono ombreggiate.
micseydel,

2
Essere d'accordo. Il problema qui è l'ambito di Python. L'accesso non esplicito agli oggetti al di fuori dell'ambito attuale richiede problemi. Chi lo vorrebbe! Un peccato perché altrimenti Python è un linguaggio abbastanza ben congegnato (nonostante una simile ambiguità nella denominazione dei moduli).
CodeCabbie

24

Una buona soluzione in alcuni casi potrebbe essere quella di spostare il codice + var su un'altra funzione:

def print_data(data):
    print data

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

main()

Sì. Penso che un buon ide sia in grado di gestire le variabili locali e le variabili globali mediante refactoring. Il tuo consiglio aiuta davvero ad eliminare tali potenziali rischi per l'ide primitiva
Stanleyxu2005,

5

Dipende da quanto tempo è la funzione. Più lunga è la funzione, maggiori sono le possibilità che qualcuno la modifichi in futuro scriverà datapensando che significhi il globale. In realtà significa locale, ma poiché la funzione è così lunga non è ovvio per loro che esiste un locale con quel nome.

Per la tua funzione di esempio, penso che l'ombra del globale non sia affatto male.


5

Fai questo:

data = [4, 5, 6]

def print_data():
    global data
    print(data)

print_data()

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)

47
Io per primo non sono confuso. È abbastanza ovviamente il parametro.

2
@delnan In questo banale esempio potresti non essere confuso, ma cosa succederebbe se altre funzioni definite nelle vicinanze usassero il globale data, tutto in profondità entro poche centinaia di righe di codice?
John Colanduoni,

13
@HevyLight Non ho bisogno di guardare altre funzioni nelle vicinanze. Guardo solo questa funzione e vedo che dataè un nome locale in questa funzione, quindi non mi preoccupo nemmeno di controllare / ricordare se esiste un globale con lo stesso nome , per non parlare di ciò che contiene.

4
Non penso che questo ragionamento sia valido, solo perché per usare un globale, dovresti definire "dati globali" all'interno della funzione. Altrimenti, il globale non è accessibile.
CodyF,

1
@CodyF False- se non si definisce, ma solo cercare di utilizzo data, si guarda attraverso gli ambiti fino a quando ne trova uno, in modo da non trovare il globale data. data = [1, 2, 3]; def foo(): print(data); foo()
Dwanderson,

3

Mi piace vedere un segno di spunta verde nell'angolo in alto a destra in Pycharm. Aggiungo i nomi delle variabili con un carattere di sottolineatura solo per cancellare questo avviso in modo da poter concentrarmi sugli avvisi importanti.

data = [4, 5, 6]

def print_data(data_): 
    print(data_)

print_data(data)

2

Sembra che sia un modello di codice pytest al 100%

vedere:

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

Ho avuto lo stesso problema, ecco perché ho trovato questo 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 lo avvertirà This inspection detects shadowing names defined in outer scopes.

Per risolvere il problema basta spostare il twitterdispositivo 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_

E rimuovi il twitterdispositivo come 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

Questo renderà felici QA, Pycharm e tutti

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.