Po co przechowywać funkcję w słowniku Pythona?

68

Jestem początkującym Pythonem i właśnie nauczyłem się techniki obejmującej słowniki i funkcje. Składnia jest łatwa i wydaje się trywialna, ale moje zmysły pytona mrowią. Coś mi mówi, że jest to głęboka i bardzo pytoniczna koncepcja i nie do końca rozumiem jej znaczenie. Czy ktoś może nazwać tę technikę i wyjaśnić, w jaki sposób / dlaczego jest ona przydatna?


Technika polega na tym, że masz słownik Pythona i funkcję, której zamierzasz używać. Wstawiasz dodatkowy element do słownika, którego wartością jest nazwa funkcji. Kiedy jesteś gotowy, aby wywołać funkcję, wywołujesz połączenie pośrednio , odwołując się do elementu dict, a nie funkcji według nazwy.

Przykład, z którego pracuję, pochodzi z Learn Python the Hard Way, 2nd Ed. (Jest to wersja dostępna po rejestracji przez Udemy.com ; niestety bezpłatna wersja HTML na żywo to obecnie Ed 3 i nie zawiera już tego przykładu).

Parafrazować:

# 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

Następnie następujące wyrażenia są równoważne. Możesz wywołać funkcję bezpośrednio lub przez odwołanie się do elementu dict, którego wartością jest funkcja.

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

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

Czy ktoś może wyjaśnić, co to jest funkcja językowa i może gdzie jest grać w „prawdziwym” programowaniu? To ćwiczenie z zabawkami wystarczyło, by nauczyć mnie składni, ale nie zabrało mnie tam.

mdeutschmtl
źródło
13
Dlaczego ten post byłby nie na temat? To świetne pytanie dotyczące algorytmu i struktury danych!
Martijn Pieters
Widziałem (i zrobiłem) takie rzeczy w innych językach. Można by na to spojrzeć jak na instrukcję przełączającą, ale ładnie opakowaną w przejezdny obiekt z czasem wyszukiwania O (1).
KChaloux
1
Miałem przeczucie, że było coś ważnego i samodzielnego w włączeniu funkcji do własnego słownika ... patrz odpowiedź @ dietbuddha ... ale może nie?
mdeutschmtl

Odpowiedzi:

83

Za pomocą słownika możesz przetłumaczyć klucz na wywoływalny. Klucz nie musi być jednak zakodowany na stałe, jak w twoim przykładzie.

Zwykle jest to forma wysyłania dzwoniącego, w której wartość zmiennej jest używana do łączenia się z funkcją. Powiedzmy, że proces sieciowy wysyła Ci kody poleceń, mapowanie wysyłki pozwala łatwo przetłumaczyć kody poleceń na kod wykonywalny:

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

Zauważ, że funkcja, którą teraz nazywamy, zależy całkowicie od wartości command. Klucz nie musi się również zgadzać; nie musi to być nawet ciąg znaków, możesz użyć wszystkiego, co może być użyte jako klucz i pasuje do twojej konkretnej aplikacji.

Korzystanie z metody wysyłki jest bezpieczniejsze niż inne techniki, takie jak eval(), ponieważ ogranicza ona polecenia dozwolone do tego, co zdefiniowałeś wcześniej. Na przykład żaden atakujący nie będzie mógł wymknąć się ls)"; DROP TABLE Students; --zastrzyku poza tabelę wysyłki.

Martijn Pieters
źródło
5
@Martjin - Czy nie można tego nazwać implementacją „Wzorca poleceń” w takim przypadku? Wydaje się, że to jest koncepcja, którą OP próbuje zrozumieć?
doktorat
3
@PhD: Tak, przykład, który zbudowałem, jest implementacją Wzorca Poleceń; że dictdziała jako dyspozytora (menedżer poleceń Invoker, etc.).
Martijn Pieters,
Świetne wyjaśnienie na najwyższym poziomie, @Martijn, dzięki. Myślę, że dostaję pomysł „wysyłki”.
mdeutschmtl
28

@Martijn Pieters wykonał kawał dobrej roboty wyjaśniając technikę, ale chciałem wyjaśnić coś z twojego pytania.

Ważne jest, aby wiedzieć, że NIE przechowujesz w nazwie „nazwy funkcji”. Przechowujesz odwołanie do samej funkcji. Możesz to zobaczyć za pomocą printfunkcji a.

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

fjest tylko zmienną, która odwołuje się do zdefiniowanej funkcji. Korzystanie ze słownika pozwala grupować podobne rzeczy, ale nie różni się niczym od przypisania funkcji do innej zmiennej.

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

Podobnie możesz przekazać funkcję jako argument.

>>> def c(func):
...   func()
... 
>>> c(f)
1
unholysampler
źródło
5
Wspomnienie funkcji pierwszej klasy zdecydowanie by pomogło :-)
Florian Margaine,
7

Zauważ, że klasa Python jest tak naprawdę tylko cukrem składniowym dla słownika. Kiedy to zrobisz:

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

kiedy zadzwonisz

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

jest naprawdę taki sam jak:

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

który po rozpoznaniu nazwy jest taki sam jak:

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

Jedną przydatną techniką jest mapowanie danych wejściowych użytkownika na wywołania zwrotne. Na przykład:

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

Można to alternatywnie zapisać w klasie:

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

dowolna lepsza składnia wywołania zwrotnego zależy od konkretnej aplikacji i gustu programisty. Pierwszy z nich jest bardziej funkcjonalny, drugi bardziej zorientowany obiektowo. Ten pierwszy może wydawać się bardziej naturalny, jeśli trzeba dynamicznie modyfikować wpisy w słowniku funkcji (być może na podstawie danych wprowadzonych przez użytkownika); to drugie może wydawać się bardziej naturalne, jeśli masz zestaw różnych mapowań ustawień wstępnych, które można wybierać dynamicznie.

Lie Ryan
źródło
Myślę, że ta wymienność sprawiła, że ​​pomyślałem „pytoniczny”, fakt, że to, co widzisz na powierzchni, to po prostu konwencjonalny sposób przedstawienia czegoś znacznie głębszego. Może to nie jest szczególne dla Pythona, chociaż ćwiczenia Pythona (i programiści Pythona?) Wydają się w ten sposób mówić dużo o funkcjach językowych.
mdeutschmtl
Inna myśl, czy jest coś specyficznego dla Pythona w sposobie, w jaki jest on skłonny ocenić, co wygląda jak dwa „warunki” po prostu sąsiadujące ze sobą, odniesienie do dykt i lista argumentów? Czy pozwalają na to inne języki? To coś w rodzaju odpowiednika programowania skoku w algebrze od 5 * xdo 5x(wybacz prostą analogię).
mdeutschmtl
@mdeutschmtl: nie jest to tak naprawdę wyjątkowe w Pythonie, chociaż języki, w których brakuje funkcji pierwszej klasy lub obiektu funkcji, mogą nigdy nie mieć żadnych sytuacji, w których możliwy byłby dostęp do słownika bezpośrednio po wywołaniu funkcji.
Lie Ryan,
2
@mdeutschmtl „fakt, że to, co widzisz na powierzchni, to po prostu konwencjonalny sposób prezentowania czegoś głębszego”. - to się nazywa cukier syntaktyczny i istnieje wszędzie
Izkata
6

Są w mojej głowie 2 techniki, do których możesz się odwoływać, z których żadna nie jest Pythoniczna, ponieważ są szersze niż jeden język.

1. Technika ukrywania / enkapsulacji informacji i spójności (zwykle idą w parze, więc łączę je razem).

Masz obiekt, który ma dane, i dołączasz metodę (zachowanie), która jest bardzo spójna z danymi. Jeśli musisz zmienić funkcję, rozszerzyć funkcjonalność lub dokonać innych zmian, dzwoniący nie będą musieli zmieniać (zakładając, że nie trzeba przekazywać żadnych dodatkowych danych).

2. Tabele wysyłki

Nie jest to klasyczny przypadek, ponieważ jest tylko jedna pozycja z funkcją. Jednak tabele wysyłki służą do organizowania różnych zachowań według klucza, dzięki czemu można je wyszukiwać i wywoływać dynamicznie. Nie jestem pewien, czy myślisz o tym, ponieważ nie odwołujesz się do funkcji w sposób dynamiczny, ale mimo to nadal uzyskujesz skuteczne późne wiązanie (wywołanie „pośrednie”).

Kompromisy

Jedną z rzeczy, na które należy zwrócić uwagę, jest to, co robisz, będzie działać dobrze ze znaną przestrzenią nazw kluczy. Istnieje jednak ryzyko kolizji między danymi a funkcjami z nieznaną przestrzenią nazw kluczy.

dietbuddha
źródło
Hermetyzacja, ponieważ dane i funkcja przechowywane w dykcie (jonowym) są powiązane, a zatem mają spójność. Dane i funkcje pochodzą z dwóch bardzo różnych dommains, więc na pierwszy rzut oka wydaje się, że zestawia ze sobą bardzo różnorodne byty.
ChuckCottrill
0

Publikuję to rozwiązanie, które moim zdaniem jest dość ogólne i może być przydatne, ponieważ jest proste i łatwe do dostosowania do konkretnych przypadków:

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)

można także zdefiniować listę, w której każdy element jest obiektem funkcji, i użyć __call__wbudowanej metody. Podziękowania dla wszystkich za inspirację i współpracę.

„Wielki artysta jest prostotą”, Henri Frederic Amiel

Emanuele
źródło