2011-11-08 13 views
10

A volte trovo che devo usare funzioni con nomi lunghi come os.path.abspath e os.path.dirname a lotto in poche righe di codice. Non penso che valga la pena di sporcare il namespace globale con tali funzioni, ma sarebbe estremamente utile essere in grado di definire un ambito attorno alle linee in cui ho bisogno di quelle funzioni. A titolo di esempio, questo sarebbe perfetto:Posso definire un ambito ovunque in Python?

import os, sys 

closure: 
    abspath = os.path.abspath 
    dirname = os.path.dirname 

    # 15 lines of heavy usage of those functions 

# Can't access abspath or dirname here 

Mi piacerebbe sapere se questo è fattibile in qualche modo

+0

Penso che "chiusura" non sia la parola giusta qui. Vedi http://en.wikipedia.org/wiki/Closure_(computer_science). Forse "namespace temporaneo" è ciò che intendevi? –

+1

@RaymondHettinger - Penso che la parola sia "portata". "Posso definire un ambito diverso dalla funzione, dal modulo globale o incorporato in Python?" – Omnifarious

+0

Di solito, quando si codifica qualcosa, appartiene a una funzione oa un metodo con una quantità ragionevole di righe di codice. Quindi "sporcare lo spazio dei nomi globale" non dovrebbe essere una preoccupazione, o il tuo codice probabilmente ha bisogno di riprogettare oltre "l'ambito". – MatthieuW

risposta

19

Python non ha uno strumento namespace temporanea come let in Lisp e Scheme.

La solita tecnica in Python consiste nel mettere i nomi nello spazio dei nomi corrente e quindi portarli fuori quando hai finito con loro. Questa tecnica è usata pesantemente nella libreria standard:

abspath = os.path.abspath 
dirname = os.path.dirname 
# 15 lines of heavy usage of those functions 
a = abspath(somepath) 
d = dirname(somepath) 
... 
del abspath, dirname 

Una tecnica alternativa per ridurre lo sforzo di battitura è quello di accorciare il prefisso ricorrente:

>>> import math as m 
>>> m.sin(x/2.0) + m.sin(x * m.pi) 

>>> p = os.path 
... 
>>> a = p.abspath(somepath) 
>>> d = p.dirname(somepath) 

Un'altra tecnica comunemente usata nella libreria standard è quello di non solo preoccuparsi di contaminare lo spazio dei nomi del modulo e basarsi semplicemente su __all__ per elencare i nomi che si intende rendere pubblici. L'effetto di __all__ è discusso nello docs for the import statement.

Naturalmente, è anche possibile creare il proprio spazio dei nomi memorizzando i nomi in un dizionario (anche se questa soluzione non è comune):

d = dict(abspath = os.path.abspath, 
     dirname = os.path.dirname) 
... 
a = d['abspath'](somepath) 
d = d['dirname'](somepath) 

Infine, è possibile inserire tutto il codice in una funzione (che ha il suo proprio spazio dei nomi locale), ma questo ha una serie di svantaggi:

  • la configurazione è imbarazzante (un uso atipico e misterioso delle funzioni)
  • è necessario dichiarare come globale qualsiasi incarico che si desidera eseguire che non sia temporaneo.
  • il codice non verrà eseguito fino a quando si chiama la funzione
def temp():      # disadvantage 1: awkward setup 
    global a, d      # disadvantage 2: global declarations 
    abspath = os.path.abspath 
    dirname = os.path.dirname 
    # 15 lines of heavy usage of those functions 
    a = abspath(somepath) 
    d = dirname(somepath) 
temp()        # disadvantage 3: invoking the code 
1

Basta fare una funzione?

def do_things_with_those_functions(): 
    abspath = os.path.abspath 
    dirname = os.path.dirname 
    # etc. 

È possibile farlo in qualsiasi ambito:

def outer_function(): 
    a = 5 
    def inner_function(): 
     print(a) 
    inner_function() 
+0

Questo approccio funziona ma presenta uno svantaggio significativo in quanto è necessario dichiarare come * globale * qualsiasi cosa utile si desideri fare quando si utilizzano tali funzioni. E, nell'esempio nidificato esterno/interno, avresti bisogno della parola chiave nonlocal * di Python 3 per scrivere su variabili esterne all'ambito temporaneo. –

+0

@RaymondHettinger Oppure potresti passare le variabili alla funzione. –

+0

Come sarebbe di aiuto con le variabili non temporanee a cui vuoi scrivere? Le "15 linee di pesante utilizzo di tali funzioni" dell'OP stanno presumibilmente facendo qualcosa con quelle funzioni abbreviate. –

0

È possibile definire le funzioni da nessuna parte, li chiamano, quindi eliminarli. Ma non c'è modo di avere una funzione anonima che possa fare uso di dichiarazioni.

-3

In generale, la digitazione non è la parte più difficile del software di scrittura, ma se insistete:

class LocalNamespace(object): 
    def __enter__(self, *args, **kwargs): 
     pass 
    def __exit__(self, *args, **kwargs): 
     pass 
with LocalNamespace() as foo: 
    abspath = os.path.abspath 
    dirname = os.path.dirname 
    # etc. 

Spero che questo aiuti.

+0

Lo hai testato prima di pubblicarlo? 'with' non crea un ambito locale in Python; quei nomi sono ancora disponibili al di fuori del blocco 'with'. – agf

+0

Questo non funziona. Scusate. –

+0

Sì, ok. Giusto.Hai ragione, non l'ho provato a fondo prima di pubblicare. Scusate. –

1

La risposta breve è "No".

Python ha tre ambiti. Ha portata di funzione, portata globale (aka modulo) e portata incorporata. È possibile dichiarare nessun altro ambito.

Una dichiarazione class sembra un po 'come un ambito, ma non lo è. In pratica è una scorciatoia per l'assegnazione di un mucchio di campi su un oggetto. Le funzioni di quella classe non possono accedere a quei campi senza passare attraverso l'oggetto su cui sono definiti.

Questo suona un po 'più limitante di quello che è. In Python puoi anche nidificare le definizioni di funzione. Una definizione di funzione nidificata ottiene l'accesso in sola lettura all'ambito esterno. Questo è 'dinamico'. Il nome non deve essere menzionato prima che la funzione sia definita. Ecco un esempio:

def joe(x): 
    def bar(): 
     return y 
    def baz(z): 
     y = x + 20 
     return x 
    y = x+5 
    return bar, baz 

>>> a, b = joe(5) 
>>> b(20) 
5 
>>> a() 
10 

Così, si può sorta di ottenere questo effetto senza sacrificare troppo località definendo una funzione annidata che crea i valori necessari, li usa, e restituisce un risultato.

Ricordo che quando imparavo Python, abituarmi alle regole di scoping piuttosto strane era una delle parti più difficili. Quando sono state introdotte funzioni annidate, a mio parere, hanno reso le regole di scoping ancora più strane a causa della semantica di sola lettura per gli ambiti esterni e l'ambito dinamico delle chiusure.

A quanto pare, in python3 c'è un modo di 'importazione' una variabile dal campo di applicazione racchiude utilizzando la parola chiave nonlocal (analogo alla parola global) in modo da poter utilizzare in un contesto di lettura/scrittura:

def joe(x): 
    def bar(): 
     return y 
    def baz(z): 
     nonlocal y 
     y = x + 20 
     return x 
    y = x+5 
    return bar, baz 

>>> a, b = joe(5) 
>>> b(20) 
5 
>>> a() 
25 

Altrimenti, ogni volta che Python vede una variabile sul lato sinistro di un segno =, si presuppone che si stia creando una nuova variabile locale. Le parole chiave global e nonlocal sono un modo per affermare che si intende modificare una variabile che non rientra nell'ambito della funzione.

5

Questo tipo di fa ciò che si vuole, ma bisogna ripetere i nomi

try: 
    abspath = os.path.abspath 
    dirname = os.path.dirname 
    # fifteen lines of code 
finally: 
    del abspath 
    del dirname 

questo modo si evita l'inquinamento dello spazio dei nomi, se v'è un'eccezione in una situazione, come di seguito

try: 
    ... 
    try: 
     abspath = os.path.abspath 
     dirname = os.path.dirname 
     # fifteen lines of code 
    finally: 
     del abspath 
     del dirname 

    ... # don't want abspath or dirname in scope here even if there was 
    ... # an exception in the above block 

except: 
    ... 
+0

+1 Il try/finally è stato un bel tocco :-) –

+0

@gnibbler Non sono sicuro di capire chiaramente la motivazione del tuo codice. Penso che si debba usare il fatto che la clausola "fuinally" viene sempre eseguita, il che garantisce che i nomi abspath e dirnames vengano cancellati dopo la loro definizione e implicazioni nelle istruzioni. Ho ragione ? Se è così, non vedo in realtà perché ci sarebbe un vantaggio supplementare con questo codice relativamente allo snippet simile nel codice di Raymond Hettinger che rende solo la cancellazione dei nomi per seguire la definizione e utilizzare le linee. – eyquem

+0

@eyquem, se v'è un altro tentativo/aspettarci blocco fuori nello stesso ambito, il try/finally garantisce che le variabili non può fuoriuscire anche se v'è un'eccezione all'interno del try/finally –

0

E ' non è chiaro se sei più imbarazzato dalla lunghezza delle espressioni di identificazione da scrivere, o dalla traccia di identificatori rimanenti in uno spazio dei nomi dopo il loro uso.

  • Per la prima, la tecnica di definire un alias per un lungo prefisso, come descritto da Raymond Hettinger, è quella di impiegare.

  • Per il secondo, sono sorpreso che nessuno abbia fatto ricorso all'importazione di un modulo in cui vengono consegnate le istruzioni e le linee che considerate pesanti e sporchi.

A proposito, se si accede alle funzioni da os.path.abspath e os.path.dirnames, non è corretto dire che le funzioni (suppongo si intende i loro nomi) lettiera lo spazio dei nomi. Dal momento che appartengono al modulo os o os.percorso (dipende da quale è stato importato), ci sono solo il nome del modulo 'os' o 'os.path' nello spazio dei nomi e l'oggetto modulo nella memoria, ma non i nomi delle funzioni direttamente nel namespace.

Quindi, si può creare uno script di nome "heavy_code.py":

def doing(x): 
    from os.path import abspath as a,dirname as d 
    ## Execute all the desired processes and creations 
    def fufu(s,t): 
     return s+t 
    dedex = d[x] 
    #......... 
    #........... 
    #........ 
    #............ 
    #.......... 

    ## Return to the calling scope all that is needed there 
    return (dedex,fufu) 

E nel modulo principale, prendendo acount della risposta di gnibbler:

one_path = 'I:/all/magala/zeru/kiol.py' 
try: 
    from pp.bududu import doing 
    w,ff = doing(one_path) 
finally: 
    del doing 

.

Per vedere come funziona:

one_path = 'I:/all/magala/zeru/kiol.py' 
try: 
    from pp.bududu.heavy_code import doing 
    print "in try : " 
    print dir() 
    print "executing doing()" 
    w,ff = doing(one_path) 
    print dir() 
finally: 
    del doing 

print "\nafter finally : " 
print dir() 
print '\nw ==',w 
print 'ff(10,12) ==',ff(10,12) 

produce come risultato:

in try : 
['__builtins__', '__doc__', '__name__', '__package__', 'doing', 'one_path'] 
executing doing() 
['__builtins__', '__doc__', '__name__', '__package__', 'doing', 'ff', 'one_path', 'w'] 

after finally : 
['__builtins__', '__doc__', '__name__', '__package__', 'ff', 'one_path', 'w'] 

w == I:/all/magala/zeru 
ff(10,12) == 22 

Dopo l'esecuzione del frammento, la funzione facendo() non esiste più nel modulo principale, ma gli oggetti creati dall'esecuzione di doing() si trovano ora senza una confusione di nomi nello spazio dei nomi del modulo principale. Inoltre, tutti gli identificatori necessari all'interno della funzione doing() sono locali.

La creazione di tutti gli oggetti desiderati e necessari possono essere delegate al modulo heavy_code, qualunque sia quanti sono, durante l'importazione e l'esecuzione della funzione facendo() prende solo due righe nel modulo principale, e funzione doing() in codice_alto più la sua linea chiamante può essere facilmente modificata.

Non è quello a cui è destinato il modulo?

Problemi correlati