Blocca l'ambito in Python


93

Quando si codifica in altre lingue, a volte creerai un ambito di blocco, come questo:

statement
...
statement
{
    statement
    ...
    statement
}
statement
...
statement

Uno degli scopi (tra i tanti) è migliorare la leggibilità del codice: mostrare che certe istruzioni formano un'unità logica o che certe variabili locali sono usate solo in quel blocco.

C'è un modo idiomatico di fare la stessa cosa in Python?


2
One purpose (of many) is to improve code readability- Il codice Python, scritto correttamente (cioè seguendo lo zen di python ) non avrebbe bisogno di tali guarnizioni per essere leggibile. In effetti, è una delle (molte) cose che mi piacciono di Python.
Burhan Khalid

Ho provato a giocare con __exit__e withdichiarazione, cambiando il globals()ma non ci sono riuscito.
Ruggero Turra

1
sarebbe molto utile definire durata variabile, connessa all'acquisizione della risorsa
Ruggero Turra

25
@ BurhanKhalid: non è vero. Lo zen di Python non ti impedisce di inquinare uno scope locale con una variabile temporanea qua e là. Se trasformi ogni utilizzo di una singola variabile temporanea in, ad esempio, la definizione di una funzione annidata che viene chiamata immediatamente, neanche lo zen di Python sarà felice. Limitare esplicitamente l'ambito di una variabile è uno strumento per migliorare la leggibilità, perché risponde direttamente "sono questi identificatori utilizzati di seguito?" - una domanda che può sorgere leggendo anche il codice Python più elegante.
bluenote10

18
@BurhanKhalid Va bene non avere una funzione. Ma chiamarlo "zen" è semplicemente disgustoso.
Phil

Risposte:


82

No, non esiste alcun supporto linguistico per la creazione dell'ambito del blocco.

I seguenti costrutti creano l'ambito:

  • modulo
  • classe
  • funzione (incl. lambda)
  • espressione del generatore
  • comprensione (dict, set, list (in Python 3.x))

38

Il modo idiomatico in Python è di mantenere le funzioni brevi. Se pensi di averne bisogno, effettua il refactoring del tuo codice! :)

Python crea un nuovo ambito per ogni modulo, classe, funzione, espressione del generatore, comprensione dei dettati, comprensione degli insiemi e in Python 3.x anche per ogni comprensione delle liste. Oltre a questi, non ci sono ambiti annidati all'interno delle funzioni.


12
"La cosa più importante nella programmazione è la capacità di dare un nome a qualcosa. La seconda cosa più importante è non essere obbligati a dare un nome a qualcosa." Per la maggior parte, Python richiede che agli ambiti (per le variabili, ecc.) Vengano assegnati nomi. A questo proposito, le variabili Python sono il secondo test più importante.
Krazy Glew

19
La cosa più importante nella programmazione è la capacità di gestire le dipendenze dell'applicazione e gestire gli ambiti dei blocchi di codice. I blocchi anonimi ti consentono di limitare la durata dei callback, mentre altrimenti i tuoi callback vengono utilizzati solo una volta, ma rimangono attivi per la durata del programma, questo causa confusione nell'ambito globale e danneggia la leggibilità del codice.
Dmitry

Ho appena notato che le variabili sono anche locali per dettare / impostare le comprensioni. Ho provato Python 2.7 e 3.3, ma non sono sicuro che dipenda dalla versione.
wjandrea

1
@wjandrea Hai ragione - aggiunto alla lista. Non dovrebbe esserci alcuna differenza tra le versioni di Python per questi.
Sven Marnach

4
Vorrei riformulare l'ultima frase, poiché puoi benissimo creare funzioni all'interno di funzioni. Quindi ci sono ambiti nidificati all'interno delle funzioni.
ThomasH

19

Puoi fare qualcosa di simile all'ambito di un blocco C ++ in Python dichiarando una funzione all'interno della tua funzione e quindi chiamandola immediatamente. Per esempio:

def my_func():
    shared_variable = calculate_thing()

    def do_first_thing():
        ... = shared_variable
    do_first_thing()

    def do_second_thing():
        foo(shared_variable)
        ...
    do_second_thing()

Se non sei sicuro del motivo per cui potresti volerlo fare, questo video potrebbe convincerti.

Il principio di base è quello di definire l'ambito di tutto il più strettamente possibile senza introdurre alcuna "spazzatura" (tipi / funzioni extra) in un ambito più ampio di quanto sia assolutamente richiesto - Nient'altro vuole utilizzare il do_first_thing()metodo, ad esempio, quindi non dovrebbe essere definito al di fuori del funzione di chiamata.


È anche il modo in cui viene utilizzato dagli sviluppatori di Google nei tutorial di TensorFlow, come mostrato ad esempio qui
Nino Filiu,

13

Sono d'accordo che non esiste un ambito di blocco. Ma un punto in Python 3 lo fa sembrare come se avesse un ambito di blocco.

cosa è successo che ha dato questo aspetto? Questo funzionava correttamente in python 2. ma per fermare la dispersione delle variabili in python 3 hanno fatto questo trucco e questa modifica fa sembrare che qui abbia un ambito di blocco.

Lasciatemi spiegare.


Secondo l'idea di ambito, quando introduciamo variabili con lo stesso nome all'interno dello stesso ambito, il suo valore dovrebbe essere modificato.

questo è ciò che sta accadendo in python 2

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'W'

Ma in python 3, anche se la variabile con lo stesso nome è stata introdotta, non ha la precedenza, la comprensione dell'elenco agisce come una sandbox per qualche motivo e sembra creare un nuovo ambito al suo interno.

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'OLD'

e questa risposta va contro l'affermazione di @ Thomas del rispondente L'unico mezzo per creare un ambito sono funzioni, classi o moduli perché questo sembra un altro posto per creare un nuovo ambito.


0

I moduli (e i pacchetti) sono un ottimo modo Pythonic per dividere il tuo programma in spazi dei nomi separati, che sembra essere un obiettivo implicito di questa domanda. In effetti, mentre stavo imparando le basi di Python, mi sono sentito frustrato dalla mancanza di una funzionalità di ambito di blocco. Tuttavia, una volta compresi i moduli Python, ho potuto realizzare in modo più elegante i miei obiettivi precedenti senza la necessità di un ambito di blocco.

Come motivazione e per indirizzare le persone verso la giusta direzione, penso che sia utile fornire esempi espliciti di alcuni costrutti di scoping di Python. Per prima cosa spiego il mio tentativo fallito di utilizzare le classi Python per implementare l'ambito del blocco. Successivamente spiego come ho ottenuto qualcosa di più utile utilizzando i moduli Python. Alla fine delineo una pratica applicazione dei pacchetti al caricamento e al filtraggio dei dati.

Tentativo di ambito di blocco con le classi

Per alcuni istanti ho pensato di aver raggiunto l'ambito del blocco inserendo il codice all'interno di una dichiarazione di classe:

x = 5
class BlockScopeAttempt:
    x = 10
    print(x) # Output: 10
print(x) # Output: 5

Purtroppo questo si interrompe quando viene definita una funzione:

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(x) 
    printx2() # Output: 5!!!

Questo perché le funzioni definite all'interno di una classe utilizzano l'ambito globale. Il modo più semplice (sebbene non l'unico) per risolvere questo problema è specificare esplicitamente la classe:

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(BlockScopeAttempt.x)  # Added class name
    printx2() # Output: 10

Non è così elegante perché si devono scrivere funzioni in modo diverso a seconda che siano contenute o meno in una classe.

Risultati migliori con i moduli Python

I moduli sono molto simili alle classi statiche, ma secondo la mia esperienza i moduli sono molto più puliti. Per fare lo stesso con i moduli, creo un file chiamato my_module.pynella directory di lavoro corrente con i seguenti contenuti:

x = 10
print(x) # (A)

def printx():
    global x
    print(x) # (B)

Quindi nel mio file principale o nella sessione interattiva (ad esempio Jupyter), lo faccio

x = 5
import my_module # Output: 10 from (A)
my_module.printx() # Output: 10 from (B)
print(x) # Output: 5

Come spiegazione, ogni file Python definisce un modulo che ha il proprio spazio dei nomi globale. L'importazione di un modulo consente di accedere alle variabili in questo spazio dei nomi con la .sintassi.

Se stai lavorando con i moduli in una sessione interattiva, puoi eseguire queste due righe all'inizio

%load_ext autoreload
%autoreload 2

ei moduli verranno ricaricati automaticamente quando i file corrispondenti vengono modificati.

Pacchetti per il caricamento e il filtraggio dei dati

L'idea dei pacchetti è una leggera estensione del concetto di moduli. Un pacchetto è una directory contenente un file (possibilmente vuoto) __init__.py, che viene eseguito durante l'importazione. È possibile accedere ai moduli / pacchetti all'interno di questa directory con la .sintassi.

Per l'analisi dei dati, spesso ho bisogno di leggere un file di dati di grandi dimensioni e quindi applicare in modo interattivo vari filtri. La lettura di un file richiede diversi minuti, quindi voglio farlo solo una volta. Sulla base di ciò che ho imparato a scuola sulla programmazione orientata agli oggetti, credevo che si dovesse scrivere il codice per il filtraggio e il caricamento come metodi in una classe. Uno dei principali svantaggi di questo approccio è che se poi ridefinisco i miei filtri, la definizione della mia classe cambia, quindi devo ricaricare l'intera classe, inclusi i dati.

Oggigiorno con Python definisco un pacchetto chiamato my_datache contiene sottomoduli denominati loade filter. All'interno di filter.pyposso fare un'importazione relativa:

from .load import raw_data

Se modifico filter.py, autoreloadrileverà le modifiche. Non si ricarica load.py, quindi non ho bisogno di ricaricare i miei dati. In questo modo posso prototipare il mio codice di filtraggio in un notebook Jupyter, avvolgerlo come funzione e quindi tagliare e incollare dal mio notebook direttamente in filter.py. Capirlo ha rivoluzionato il mio flusso di lavoro e mi ha convertito da scettico a credente nello "Zen di Python".

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.