Perché memorizzare una funzione all'interno di un dizionario Python?

65

Sono un principiante pitone e ho appena imparato una tecnica che coinvolge dizionari e funzioni. La sintassi è facile e sembra una cosa banale, ma i miei sensi pitone sono pizzicanti. Qualcosa mi dice che questo è un concetto profondo e molto pitonico e non ne sto proprio afferrando l'importanza. Qualcuno può dare un nome a questa tecnica e spiegare come / perché è utile?

La tecnica è quando hai un dizionario Python e una funzione che intendi usare su di esso. Si inserisce un elemento extra nel dict, il cui valore è il nome della funzione. Quando sei pronto a chiamare la funzione, emetti la chiamata indirettamente facendo riferimento all'elemento dict, non alla funzione per nome.

L'esempio da cui lavoro proviene da Learn Python the Hard Way, 2nd Ed. (Questa è la versione disponibile quando ti iscrivi attraverso Udemy.com ; purtroppo versione HTML gratuita in tempo reale è attualmente in Ed 3 e non include più questo esempio).

Per parafrasare:

# make a dictionary of US states and major cities
cities = {'San Diego':'CA', 'New York':'NY', 'Detroit':'MI'}

# define a function to use on such a dictionary
def find_city (map, city):
    # does something, returns some value
    if city in map:
        return map[city]
    else:
        return "Not found"

# then add a final dict element that refers to the function
cities['_found'] = find_city

Quindi le seguenti espressioni sono equivalenti. Puoi chiamare direttamente la funzione o facendo riferimento all'elemento dict il cui valore è la funzione.

>>> find_city (cities, 'New York')
NY

>>> cities['_found'](cities, 'New York')
NY

Qualcuno può spiegare quale caratteristica linguistica è questa, e forse dove si tratta di giocare nella programmazione "reale"? Questo esercizio giocattolo è stato sufficiente per insegnarmi la sintassi, ma non mi ha portato fino in fondo.

    
posta mdeutschmtl 09.01.2013 - 21:10
fonte

5 risposte

75

Usando un dict, traduciamo la chiave in un callable. La chiave non deve essere necessariamente codificata, come nel tuo esempio.

Di solito, questa è una forma di invio del chiamante, in cui si utilizza il valore di una variabile per connettersi a una funzione. Supponiamo che un processo di rete invii codici di comando, una mappatura di dispatch ti consente di tradurre facilmente i codici di comando in codice eseguibile:

def do_ping(self, arg):
    return 'Pong, {0}!'.format(arg)

def do_ls(self, arg):
    return '\n'.join(os.listdir(arg))

dispatch = {
    'ping': do_ping,
    'ls': do_ls,
}

def process_network_command(command, arg):
    send(dispatch[command](arg))

Nota che la funzione che chiamiamo ora dipende interamente dal valore di command . La chiave non deve corrispondere a nessuno dei due; non deve nemmeno essere una stringa, puoi usare qualsiasi cosa che possa essere usata come chiave e adatta alla tua specifica applicazione.

L'uso di un metodo di invio è più sicuro di altre tecniche, come eval() , poiché limita i comandi consentiti a ciò che hai definito in precedenza. Ad esempio, nessun hacker introdurrà un'iniezione ls)"; DROP TABLE Students; -- oltre una tabella di distribuzione.

    
risposta data 09.01.2013 - 21:13
fonte
27

@Martijn Pieters ha fatto un buon lavoro spiegando la tecnica, ma volevo chiarire qualcosa dalla tua domanda.

La cosa importante da sapere è che NON memorizza "il nome della funzione" nel dizionario. Stai memorizzando un riferimento alla funzione stessa. Puoi visualizzarlo utilizzando print sulla funzione.

>>> def f():
...   print 1
... 
>>> print f
<function f at 0xb721c1b4>

f è solo una variabile che fa riferimento alla funzione che hai definito. L'uso di un dizionario ti consente di raggruppare cose simili, ma non è diverso dall'assegnare una funzione a una variabile diversa.

>>> a = f
>>> a
<function f at 0xb721c3ac>
>>> a()
1

Allo stesso modo, puoi passare una funzione come argomento.

>>> def c(func):
...   func()
... 
>>> c(f)
1
    
risposta data 09.01.2013 - 21:25
fonte
7

Si noti che la classe Python è in realtà solo uno zucchero di sintassi per il dizionario. Quando lo fai:

class Foo(object):
    def find_city(self, city):
        ...

quando chiami

f = Foo()
f.find_city('bar')

è proprio lo stesso di:

getattr(f, 'find_city')('bar')

che, dopo la risoluzione del nome, è lo stesso di:

f.__class__.__dict__['find_city'](f, 'bar')

Una tecnica utile è per mappare l'input dell'utente ai callback. Ad esempio:

def cb1(...): 
    ...
funcs = {
    'cb1': cb1,
    ...
}
while True:
    input = raw_input()
    funcs[input]()

Questo può essere scritto in alternativa in classe:

class Funcs(object):
    def cb1(self, a): 
        ...
funcs = Funcs()
while True:
    input = raw_input()
    getattr(funcs, input)()

qualsiasi sintassi di callback è migliore dipende dalla particolare applicazione e dal gusto del programmatore. Il primo è uno stile più funzionale, il secondo è più orientato agli oggetti. Il primo potrebbe sembrare più naturale se è necessario modificare dinamicamente le voci nel dizionario delle funzioni (magari in base all'input dell'utente); quest'ultimo potrebbe sembrare più naturale se si dispone di un insieme di mappature di preset differenti che possono essere scelti dinamicamente.

    
risposta data 10.01.2013 - 04:23
fonte
6

Ci sono 2 tecniche che mi vengono in mente a cui potresti riferirti, nessuna delle quali è Pythonic in quanto è più ampia di una lingua.

1. La tecnica di nascondere / incapsulare informazioni e coesione (di solito vanno di pari passo così li sto raggruppando insieme).

Hai un oggetto con dati e un metodo (comportamento) altamente coeso con i dati. Se è necessario modificare la funzione, estendere la funzionalità o apportare altre modifiche, non è necessario che i chiamanti cambino (supponendo che non sia necessario inoltrare dati aggiuntivi).

2. Tabelle di spedizione

Non è il caso classico, perché c'è una sola voce con una funzione. Tuttavia, le tabelle di invio vengono utilizzate per organizzare comportamenti diversi mediante una chiave in modo che possano essere consultati e richiamati dinamicamente. Non sono sicuro che tu pensi a questo dal momento che non ti riferisci alla funzione in modo dinamico, ma ciononostante ottieni comunque un'associazione tardiva efficace (la chiamata "indiretta").

Compromessi

Una cosa da notare è che ciò che farai funzionerà bene con uno spazio dei nomi noto delle chiavi. Tuttavia, si corre il rischio di collisione tra i dati e le funzioni con uno spazio dei nomi sconosciuto di chiavi.

    
risposta data 10.01.2013 - 02:34
fonte
0

Inserisco questa soluzione che ritengo abbastanza generica e può essere utile in quanto è semplice e facile da adattare a casi specifici:

def p(what):
    print 'playing', cmpsr

def l(what):
    print 'listening', cmpsr

actions = {'Play' : p, 'Listen' : l}

act = 'Listen'
cmpsr = 'Vivaldi'

actions[act].__call__(cmpsr)

si può anche definire una lista in cui ogni elemento è un oggetto funzione e utilizzare il metodo __call__ incorporato. Crediti a tutti per l'ispirazione e la cooperazione.

"Il grande artista è il semplice", Henri Frederic Amiel

    
risposta data 02.12.2016 - 16:07
fonte

Leggi altre domande sui tag