Warum sollte eine Funktion in einem Python-Wörterbuch gespeichert werden?

68

Ich bin ein Python-Anfänger und habe gerade eine Technik mit Wörterbüchern und Funktionen gelernt. Die Syntax ist einfach und scheint trivial zu sein, aber meine Python-Sinne kribbeln. Etwas sagt mir, dass dies ein tiefgründiges und sehr pythonisches Konzept ist, und ich verstehe seine Bedeutung nicht ganz. Kann jemand dieser Technik einen Namen geben und erklären, wie / warum sie nützlich ist?


Die Technik ist, wenn Sie ein Python-Wörterbuch und eine Funktion haben, die Sie darauf anwenden möchten. Sie fügen ein zusätzliches Element in das Diktat ein, dessen Wert der Name der Funktion ist. Wenn Sie bereit sind, die Funktion aufzurufen, geben Sie den Aufruf indirekt aus, indem Sie sich auf das dict-Element beziehen, nicht auf die Funktion nach Namen.

Das Beispiel, an dem ich arbeite, stammt von Learn Python the Hard Way, 2nd Ed. (Dies ist die Version, die verfügbar ist, wenn Sie sich über Udemy.com anmelden . Leider ist die kostenlose Live-HTML-Version derzeit Ed 3 und enthält dieses Beispiel nicht mehr.)

Umschreiben:

# 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

Dann sind die folgenden Ausdrücke äquivalent. Sie können die Funktion direkt aufrufen oder auf das dict-Element verweisen, dessen Wert die Funktion ist.

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

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

Kann jemand erklären, um welche Sprachfunktion es sich handelt und wo es beim "echten" Programmieren zu spielen kommt? Diese Spielzeugübung reichte aus, um mir die Syntax beizubringen, brachte mich aber nicht ganz hin.

mdeutschmtl
quelle
13
Warum sollte dieser Beitrag nicht zum Thema gehören? Es ist eine großartige Frage zum Algorithmus- und Datenstrukturkonzept!
Martijn Pieters
Ich habe solche Sachen in anderen Sprachen gesehen (und getan). Sie können es als switch-Anweisung betrachten, aber in ein passierbares Objekt mit O (1) -Suchzeit einschließen.
KChaloux
1
Ich hatte eine Ahnung, dass es etwas Wichtiges und Selbstreferenzielles ist, die Funktion in ein eigenes Diktat aufzunehmen ... siehe @ dietbuddhas Antwort ... aber vielleicht auch nicht?
mdeutschmtl

Antworten:

83

Mit einem Diktat können Sie den Schlüssel in einen aufrufbaren übersetzen. Der Schlüssel muss jedoch nicht fest codiert sein, wie in Ihrem Beispiel.

In der Regel handelt es sich hierbei um eine Form der Anruferversendung, bei der Sie den Wert einer Variablen verwenden, um eine Verbindung zu einer Funktion herzustellen. Angenommen, ein Netzwerkprozess sendet Ihnen Befehlscodes. Mit einer Dispatch-Zuordnung können Sie die Befehlscodes einfach in ausführbaren Code übersetzen:

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))

Beachten Sie, dass die Funktion, die wir jetzt aufrufen, vollständig von dem Wert abhängt, von dem der Wert ist command. Der Schlüssel muss auch nicht übereinstimmen. Es muss nicht einmal eine Zeichenfolge sein, Sie können alles verwenden, was als Schlüssel verwendet werden kann, und es passt zu Ihrer spezifischen Anwendung.

Die Verwendung einer Versandmethode ist sicherer als andere Techniken, wie z. B. die eval()Beschränkung der zulässigen Befehle auf das, was Sie zuvor definiert haben. ls)"; DROP TABLE Students; --Zum Beispiel wird kein Angreifer eine Injektion an einem Dispatch-Tisch vorbeischleichen.

Martijn Pieters
quelle
5
@Martjin - Könnte dies in diesem Fall nicht als Implementierung des 'Command Pattern' bezeichnet werden? Scheint, als ob dies das Konzept ist, das das OP zu erfassen versucht?
PhD
3
@PhD: Ja, das Beispiel, das ich erstellt habe, ist eine Command Pattern-Implementierung. Der dictfungiert als Dispatcher (Befehlsmanager, Aufrufer usw.).
Martijn Pieters
Tolle Erklärung auf höchstem Niveau, @Martijn, danke. Ich glaube, ich habe die Idee "Versand".
mdeutschmtl
28

@Martijn Pieters hat die Technik gut erklärt, aber ich wollte etwas aus Ihrer Frage klären.

Wichtig zu wissen ist, dass Sie NICHT "den Namen der Funktion" im Wörterbuch speichern. Sie speichern einen Verweis auf die Funktion selbst. Sie können dies mit einem printauf der Funktion sehen.

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

fist nur eine Variable, die auf die von Ihnen definierte Funktion verweist. Mit einem Wörterbuch können Sie ähnliche Dinge gruppieren, es unterscheidet sich jedoch nicht von der Zuweisung einer Funktion zu einer anderen Variablen.

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

Ebenso können Sie eine Funktion als Argument übergeben.

>>> def c(func):
...   func()
... 
>>> c(f)
1
unholysampler
quelle
5
Die Erwähnung einer erstklassigen Funktion würde definitiv helfen :-)
Florian Margaine
7

Beachten Sie, dass die Python-Klasse eigentlich nur ein Syntaxzucker für das Wörterbuch ist. Wenn Sie das tun:

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

wenn du anrufst

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

ist wirklich genauso wie:

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

Das ist nach der Namensauflösung genau das Gleiche wie:

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

Eine nützliche Technik besteht darin, Benutzereingaben Rückrufen zuzuordnen. Zum Beispiel:

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

Dies kann alternativ in der Klasse geschrieben werden:

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

Welche Rückrufsyntax besser ist, hängt von der jeweiligen Anwendung und dem Geschmack des Programmierers ab. Ersteres ist funktionaler, letzteres objektorientierter. Ersteres fühlt sich möglicherweise natürlicher an, wenn Sie die Einträge im Funktionswörterbuch dynamisch ändern müssen (möglicherweise basierend auf Benutzereingaben). Letzteres fühlt sich möglicherweise natürlicher an, wenn Sie über eine Reihe unterschiedlicher Voreinstellungszuordnungen verfügen, die dynamisch ausgewählt werden können.

Lüge Ryan
quelle
Ich denke, diese Austauschbarkeit brachte mich dazu, "pythonisch" zu denken, die Tatsache, dass das, was Sie auf der Oberfläche sehen, nur eine konventionelle Art ist, etwas viel Tieferes zu präsentieren. Vielleicht ist das nicht speziell für Python, obwohl Python-Übungen (und Python-Programmierer?) Auf diese Weise sehr viel über Sprachfunktionen zu sagen scheinen.
mdeutschmtl
Ein weiterer Gedanke: Gibt es für Python eine Besonderheit in der Art und Weise, in der es bereit ist zu bewerten, wie zwei "Begriffe" aussehen, die nur nebeneinander sitzen, die Diktatreferenz und die Argumentliste? Lassen andere Sprachen das zu? Es ist eine Art Programmieräquivalent zum Sprung in der Algebra von 5 * xnach 5x(verzeihen Sie die einfache Analogie).
mdeutschmtl
@mdeutschmtl: Es ist nicht wirklich einzigartig für Python, obwohl Sprachen, denen eine erstklassige Funktion oder ein erstklassiges Funktionsobjekt fehlt, möglicherweise niemals Situationen aufweisen, in denen ein Wörterbuchzugriff unmittelbar gefolgt von einem Funktionsaufruf möglich sein könnte.
Lie Ryan
2
@mdeutschmtl "Die Tatsache, dass das, was Sie auf der Oberfläche sehen, nur eine konventionelle Art ist, etwas viel Tieferes zu präsentieren." - das nennt sich syntaktischer Zucker und existiert überall
Izkata
6

Es gibt zwei Techniken, die mir in den Sinn kommen und auf die Sie sich vielleicht beziehen, von denen keine pythonisch ist, da sie breiter sind als eine Sprache.

1. Die Technik des Versteckens / Einkapselns von Informationen und des Zusammenhalts (normalerweise gehen sie Hand in Hand, sodass ich sie zusammenfasse).

Sie haben ein Objekt, das Daten enthält, und Sie fügen eine Methode (ein Verhalten) hinzu, die in hohem Maße mit den Daten übereinstimmt. Sollten Sie die Funktion ändern, die Funktionalität erweitern oder sonstige Änderungen vornehmen müssen, müssen die Anrufer keine Änderungen vornehmen (vorausgesetzt, es müssen keine zusätzlichen Daten übergeben werden).

2. Versandtabellen

Nicht der klassische Fall, da es nur einen Eintrag mit einer Funktion gibt. Versandtabellen werden jedoch verwendet, um unterschiedliche Verhaltensweisen anhand eines Schlüssels so zu organisieren, dass sie nachgeschlagen und dynamisch aufgerufen werden können. Ich bin mir nicht sicher, ob Sie darüber nachdenken, da Sie sich nicht dynamisch auf die Funktion beziehen, aber dennoch eine effektive späte Bindung erhalten (der "indirekte" Aufruf).

Kompromisse

Beachten Sie, dass Ihre Vorgehensweise bei einem bekannten Schlüssel-Namespace einwandfrei funktioniert. Es besteht jedoch die Gefahr einer Kollision zwischen den Daten und den Funktionen mit einem unbekannten Schlüssel-Namespace.

dietbuddha
quelle
Verkapselung, weil die im Diktat (ionary) gespeicherten Daten und die Funktion miteinander in Beziehung stehen und somit eine Kohäsion aufweisen. Die Daten und Funktionen stammen aus zwei sehr unterschiedlichen Bereichen, so dass das Beispiel auf den ersten Blick wild unterschiedliche Einheiten zusammenzufassen scheint.
ChuckCottrill
0

Ich poste diese Lösung, die meiner Meinung nach ziemlich allgemein ist und nützlich sein kann, da sie einfach gehalten und leicht an bestimmte Fälle angepasst werden kann:

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)

Man kann auch eine Liste definieren, in der jedes Element ein Funktionsobjekt ist, und die __call__eingebaute Methode verwenden. Wir danken allen für die Inspiration und Zusammenarbeit.

"Der große Künstler ist der Vereinfacher", Henri Frederic Amiel

Emanuele
quelle