Mengapa menyimpan fungsi di dalam kamus python?

68

Saya pemula python, dan saya baru belajar teknik yang melibatkan kamus dan fungsi. Sintaksnya mudah dan sepertinya hal yang sepele, tetapi indera python saya kesemutan. Sesuatu mengatakan kepada saya bahwa ini adalah konsep yang dalam dan sangat pythonic dan saya tidak begitu memahami pentingnya. Dapatkah seseorang memberi nama pada teknik ini dan menjelaskan bagaimana / mengapa ini berguna?


Tekniknya adalah ketika Anda memiliki kamus python dan fungsi yang ingin Anda gunakan di dalamnya. Anda memasukkan elemen tambahan ke dalam dict, yang nilainya adalah nama fungsi. Ketika Anda siap untuk memanggil fungsi Anda mengeluarkan panggilan secara tidak langsung dengan merujuk pada elemen dict, bukan fungsi dengan nama.

Contoh yang saya kerjakan adalah dari Learn Python the Hard Way, 2nd Ed. (Ini adalah versi yang tersedia ketika Anda mendaftar melalui Udemy.com ; sayangnya versi HTML gratis langsung saat ini Ed 3, dan tidak lagi menyertakan contoh ini).

Mengutip:

# 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

Kemudian ekspresi berikut ini setara. Anda dapat memanggil fungsi secara langsung, atau dengan merujuk elemen dict yang nilainya adalah fungsi.

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

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

Dapatkah seseorang menjelaskan fitur bahasa apa ini, dan mungkin di mana ia bisa bermain dalam pemrograman "nyata"? Latihan mainan ini sudah cukup untuk mengajari saya sintaks, tetapi tidak membawa saya ke sana.

mdeutschmtl
sumber
13
Mengapa posting ini di luar topik? Ini pertanyaan algoritma dan struktur data konsep yang hebat!
Martijn Pieters
Saya telah melihat (dan melakukan) beberapa hal seperti ini dalam bahasa lain. Anda bisa mengurutkannya sebagai pernyataan switch, tetapi membungkusnya dengan baik pada objek yang lumayan dengan waktu pencarian O (1).
KChaloux
1
Saya punya firasat ada sesuatu yang penting & referensi diri tentang termasuk fungsi di dalam diktanya sendiri ... lihat jawaban @ dietbuddha ... tapi mungkin tidak?
mdeutschmtl

Jawaban:

83

Dengan menggunakan dikt, Anda dapat menerjemahkan kunci menjadi callable. Kuncinya tidak perlu di-hardcode, seperti pada contoh Anda.

Biasanya, ini adalah bentuk pengiriman penelepon, di mana Anda menggunakan nilai variabel untuk terhubung ke suatu fungsi. Katakanlah suatu proses jaringan mengirimkan kode perintah kepada Anda, pemetaan pengiriman memungkinkan Anda menerjemahkan kode perintah dengan mudah ke dalam kode yang dapat dieksekusi:

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

Perhatikan bahwa fungsi apa yang kita sebut sekarang sepenuhnya bergantung pada apa nilainya command. Kuncinya juga tidak harus cocok; bahkan tidak harus berupa string, Anda dapat menggunakan apa pun yang dapat digunakan sebagai kunci, dan cocok dengan aplikasi spesifik Anda.

Menggunakan metode pengiriman lebih aman daripada teknik lain, seperti eval(), karena membatasi perintah yang diijinkan untuk apa yang Anda tetapkan sebelumnya. Tidak ada penyerang yang akan menyelundupkan ls)"; DROP TABLE Students; --injeksi melewati meja pengiriman, misalnya.

Martijn Pieters
sumber
5
@ Martjin - Tidak bisakah ini disebut implementasi dari 'Pola Perintah' dalam kasus itu? Sepertinya itulah konsep yang coba dipahami oleh OP?
PhD
3
@ PDF: Ya, contoh yang saya buat adalah implementasi Pola Perintah; yang dictbertindak sebagai operator (manajer perintah, Invoker, dll).
Martijn Pieters
Penjelasan tingkat atas yang bagus, @ Martijn, terima kasih. Saya pikir saya mendapatkan ide "pengiriman".
mdeutschmtl
28

@ Martijn Pieters melakukan pekerjaan dengan baik menjelaskan teknik ini, tetapi saya ingin mengklarifikasi sesuatu dari pertanyaan Anda.

Yang penting untuk diketahui adalah bahwa Anda TIDAK menyimpan "nama fungsi" dalam kamus. Anda menyimpan referensi ke fungsi itu sendiri. Anda dapat melihat ini menggunakan printfungsi.

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

fhanyalah variabel yang mereferensikan fungsi yang Anda tetapkan. Menggunakan kamus memungkinkan Anda untuk mengelompokkan hal-hal yang disukai, tetapi tidak ada bedanya dengan menetapkan fungsi ke variabel yang berbeda.

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

Demikian pula, Anda bisa melewatkan fungsi sebagai argumen.

>>> def c(func):
...   func()
... 
>>> c(f)
1
unholysampler
sumber
5
Menyebutkan fungsi kelas satu pasti akan membantu :-)
Florian Margaine
7

Perhatikan bahwa kelas Python sebenarnya hanyalah gula sintaksis untuk kamus. Saat kamu melakukan:

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

ketika Anda menelepon

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

benar-benar sama dengan:

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

yang, setelah resolusi nama, sama seperti:

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

Salah satu teknik yang berguna adalah untuk memetakan input pengguna ke callback. Sebagai contoh:

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

Ini bisa juga ditulis di kelas:

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

sintaks callback mana yang lebih baik tergantung pada aplikasi tertentu dan selera programmer. Yang pertama adalah gaya yang lebih fungsional, yang terakhir lebih berorientasi objek. Yang pertama mungkin terasa lebih alami jika Anda perlu memodifikasi entri dalam kamus fungsi secara dinamis (mungkin berdasarkan input pengguna); yang terakhir mungkin terasa lebih alami jika Anda memiliki satu set pemetaan preset berbeda yang dapat dipilih secara dinamis.

Lie Ryan
sumber
Saya pikir pertukaran ini adalah apa yang membuat saya berpikir "pythonic", fakta bahwa apa yang Anda lihat di permukaan hanyalah cara konvensional untuk menyajikan sesuatu yang jauh lebih dalam. Mungkin itu tidak khusus untuk python, meskipun latihan python (dan programmer python?) Tampaknya berbicara banyak sekali tentang fitur bahasa dengan cara ini.
mdeutschmtl
Pikiran lain, apakah ada sesuatu yang khusus untuk python dalam cara bersedia mengevaluasi apa yang tampak seperti dua "istilah" hanya duduk berdekatan satu sama lain, referensi dikt dan daftar argumen? Apakah bahasa lain mengizinkannya? Ini semacam pemrograman setara dengan lompatan dalam aljabar dari 5 * xke 5x(maafkan analogi sederhana).
mdeutschmtl
@mdeutschmtl: itu tidak benar-benar unik untuk Python, meskipun bahasa yang tidak memiliki fungsi kelas pertama atau objek fungsi mungkin tidak pernah memiliki situasi di mana akses kamus segera diikuti oleh panggilan fungsi bisa dimungkinkan.
Lie Ryan
2
@mdeutschmtl "fakta bahwa apa yang Anda lihat di permukaan hanyalah cara konvensional untuk menyajikan sesuatu yang jauh lebih dalam." - Itu disebut gula sintaksis dan ada di semua tempat
Izkata
6

Ada 2 teknik yang melompat ke pikiran saya yang mungkin Anda rujuk, tidak ada yang Pythonic karena mereka lebih luas dari satu bahasa.

1. Teknik penyembunyian informasi / enkapsulasi dan kohesi (mereka biasanya berjalan beriringan sehingga saya menyatukannya).

Anda memiliki objek yang memiliki data, dan Anda melampirkan metode (perilaku) yang sangat kohesif dengan data. Jika Anda perlu mengubah fungsinya, perluas fungsionalitasnya atau buat perubahan lain yang tidak perlu diubah oleh penelepon (dengan asumsi tidak ada data tambahan yang perlu diteruskan).

2. Pengiriman tabel

Bukan case klasik, karena hanya ada satu entri dengan fungsi. Namun, tabel pengiriman digunakan untuk mengatur perilaku yang berbeda dengan kunci sedemikian rupa sehingga mereka dapat memandang ke atas dan dipanggil secara dinamis. Saya tidak yakin apakah Anda memikirkan hal ini karena Anda tidak mengacu pada fungsi dengan cara yang dinamis, namun demikian Anda masih mendapatkan ikatan yang efektif (panggilan "tidak langsung").

Pengorbanan

Satu hal yang perlu diperhatikan adalah apa yang Anda lakukan akan bekerja dengan baik dengan ruang nama kunci yang dikenal. Namun, Anda menjalankan risiko tabrakan antara data dan fungsi dengan ruang nama kunci yang tidak diketahui.

dietbuddha
sumber
Enkapsulasi karena data dan fungsi yang disimpan dalam dikt (ionary) terkait, dan dengan demikian memiliki kohesi. Data dan fungsinya berasal dari dua kejadian yang sangat berbeda, jadi pada pandangan pertama contohnya tampaknya menyatukan entitas yang sangat berbeda secara bersamaan.
ChuckCottrill
0

Saya memposting solusi ini yang menurut saya cukup umum dan dapat berguna karena dibuat sederhana dan mudah untuk beradaptasi dengan kasus-kasus tertentu:

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)

kita juga dapat mendefinisikan daftar di mana setiap elemen adalah objek fungsi dan menggunakan __call__metode bawaan. Penghargaan untuk semua untuk inspirasi dan kerja sama.

"Artis hebat adalah penyederhanaan", Henri Frederic Amiel

Emanuele
sumber