Bagaimana saya bisa membuat dua dekorator dengan Python yang akan melakukan hal berikut?
@makebold
@makeitalic
def say():
return "Hello"
... yang seharusnya kembali:
"<b><i>Hello</i></b>"
Saya tidak mencoba membuat HTML
cara ini dalam aplikasi nyata - hanya mencoba memahami cara kerja dekorator dan rantai dekorator.
__name__
dan, berbicara tentang paket dekorator, tanda tangan fungsi).*args
dan**kwargs
harus ditambahkan dalam jawabannya. Fungsi yang didekorasi dapat memiliki argumen, dan mereka akan hilang jika tidak ditentukan.*args
,**kwargs
. Cara mudah untuk menyelesaikan 3 masalah ini sekaligus adalah menggunakandecopatch
seperti yang dijelaskan di sini . Anda juga dapat menggunakandecorator
sebagaimana telah disebutkan oleh Marius Gedminas, untuk menyelesaikan poin 2 dan 3.Jika Anda tidak menyukai penjelasan panjang, lihat jawaban Paolo Bergantino .
Dasar-Dasar Dekorator
Fungsi Python adalah objek
Untuk memahami dekorator, Anda harus terlebih dahulu memahami bahwa fungsi adalah objek dengan Python. Ini memiliki konsekuensi penting. Mari kita lihat mengapa dengan contoh sederhana:
Ingatlah ini. Kami akan segera kembali ke sana.
Properti lain yang menarik dari fungsi Python adalah mereka dapat didefinisikan di dalam fungsi lain!
Referensi fungsi
Oke, masih di sini? Sekarang bagian yang menyenangkan ...
Anda telah melihat bahwa fungsi adalah objek. Oleh karena itu, fungsinya:
Itu artinya suatu fungsi dapat
return
fungsi lain .Masih ada lagi!
Jika Anda dapat
return
suatu fungsi, Anda dapat mengirimkannya sebagai parameter:Nah, Anda hanya memiliki semua yang diperlukan untuk memahami dekorator. Anda lihat, dekorator adalah "pembungkus", yang berarti bahwa mereka membiarkan Anda mengeksekusi kode sebelum dan sesudah fungsi yang mereka hias tanpa memodifikasi fungsi itu sendiri.
Dekorator buatan tangan
Bagaimana Anda akan melakukannya secara manual:
Sekarang, Anda mungkin ingin itu setiap kali Anda menelepon
a_stand_alone_function
,a_stand_alone_function_decorated
dipanggil sebagai gantinya. Itu mudah, cukup timpaa_stand_alone_function
dengan fungsi yang dikembalikan olehmy_shiny_new_decorator
:Dekorator didemistifikasi
Contoh sebelumnya, menggunakan sintaksis dekorator:
Ya, itu saja, sesederhana itu.
@decorator
hanyalah jalan pintas ke:Dekorator hanyalah varian pythonic dari pola desain dekorator . Ada beberapa pola desain klasik yang tertanam di Python untuk memudahkan pengembangan (seperti iterators).
Tentu saja, Anda dapat mengakumulasikan dekorator:
Menggunakan sintaks dekorator Python:
Urutan Anda mengatur dekorator MASALAH:
Sekarang: untuk menjawab pertanyaan ...
Sebagai kesimpulan, Anda dapat dengan mudah melihat bagaimana menjawab pertanyaan:
Anda sekarang dapat meninggalkan bahagia, atau membakar otak Anda sedikit lebih banyak dan melihat penggunaan dekorator canggih.
Membawa dekorator ke tingkat berikutnya
Melewati argumen ke fungsi yang didekorasi
Metode dekorasi
Satu hal bagus tentang Python adalah metode dan fungsi benar-benar sama. Satu-satunya perbedaan adalah metode berharap bahwa argumen pertama mereka adalah referensi ke objek saat ini (
self
).Itu berarti Anda dapat membangun dekorator untuk metode dengan cara yang sama! Ingatlah untuk
self
mempertimbangkan:Jika Anda membuat dekorator serba guna - yang akan Anda terapkan pada fungsi atau metode apa pun, apa pun argumennya - maka gunakan saja
*args, **kwargs
:Melewati argumen ke dekorator
Hebat, sekarang apa yang akan Anda katakan tentang memberikan argumen kepada dekorator itu sendiri?
Ini bisa agak bengkok, karena dekorator harus menerima fungsi sebagai argumen. Oleh karena itu, Anda tidak dapat meneruskan argumen fungsi yang didekorasi langsung ke dekorator.
Sebelum bergegas ke solusinya, mari kita menulis sedikit pengingat:
Persis sama. "
my_decorator
" Disebut. Jadi saat Anda@my_decorator
, Anda memberi tahu Python untuk memanggil fungsi 'dilabeli oleh variabel "my_decorator
"'.Ini penting! Label yang Anda berikan dapat langsung mengarah ke dekorator— atau tidak .
Mari kita menjadi jahat. ☺
Tidak mengherankan di sini.
Mari kita lakukan PERSIS hal yang sama, tetapi lewati semua variabel perantara sial:
Mari kita buat lebih pendek :
Hei, apakah kamu melihat itu? Kami menggunakan panggilan fungsi dengan
@
sintaks " "! :-)Jadi, kembalilah ke dekorator dengan argumen. Jika kita dapat menggunakan fungsi untuk menghasilkan dekorator dengan cepat, kita dapat memberikan argumen ke fungsi itu, kan?
Ini dia: dekorator dengan argumen. Argumen dapat diatur sebagai variabel:
Seperti yang Anda lihat, Anda bisa meneruskan argumen ke dekorator seperti fungsi apa pun menggunakan trik ini. Anda bahkan dapat menggunakan
*args, **kwargs
jika Anda mau. Tapi ingat dekorator dipanggil hanya sekali . Tepat ketika Python mengimpor skrip. Anda tidak dapat menetapkan argumen secara dinamis setelahnya. Ketika Anda melakukan "impor x", fungsinya sudah didekorasi , jadi Anda tidak dapat mengubah apa pun.Ayo berlatih: mendekorasi dekorator
Oke, sebagai bonus, saya akan memberi Anda cuplikan untuk membuat dekorator mana pun menerima argumen apa pun secara umum. Lagi pula, untuk menerima argumen, kami membuat dekorator kami menggunakan fungsi lain.
Kami membungkus dekorator.
Adakah yang kita lihat baru-baru ini yang berfungsi?
Oh ya, dekorator!
Mari bersenang-senang dan menulis dekorator untuk dekorator:
Dapat digunakan sebagai berikut:
Saya tahu, terakhir kali Anda memiliki perasaan ini, itu setelah mendengarkan seorang pria berkata: "sebelum memahami rekursi, Anda harus terlebih dahulu memahami rekursi". Tetapi sekarang, tidakkah Anda merasa senang menguasai hal ini?
Praktik terbaik: dekorator
The
functools
Modul diperkenalkan pada Python 2.5. Ini termasuk fungsifunctools.wraps()
, yang menyalin nama, modul, dan mendokumentasikan fungsi yang didekorasi ke pembungkusnya.(Fakta menyenangkan:
functools.wraps()
adalah dekorator! ☺)Bagaimana dekorator bisa berguna?
Sekarang pertanyaan besarnya: Untuk apa saya bisa menggunakan dekorator?
Tampak keren dan kuat, tetapi contoh praktisnya akan bagus. Ya, ada 1000 kemungkinan. Penggunaan klasik memperluas perilaku fungsi dari lib eksternal (Anda tidak dapat memodifikasinya), atau untuk debugging (Anda tidak ingin memodifikasinya karena bersifat sementara).
Anda dapat menggunakannya untuk memperluas beberapa fungsi dengan cara KERING, seperti:
Tentu saja hal baik dengan dekorator adalah Anda dapat menggunakannya langsung pada hampir semua hal tanpa menulis ulang. KERING, saya berkata:
Python sendiri menyediakan beberapa dekorator:
property
,staticmethod
, dllIni benar-benar taman bermain besar.
sumber
__closure__
atributnya) untuk mengeluarkan fungsi asli yang tidak didekorasi. Salah satu contoh penggunaan didokumentasikan dalam jawaban ini yang mencakup bagaimana mungkin untuk menyuntikkan fungsi dekorator di tingkat yang lebih rendah dalam keadaan terbatas.@decorator
Sintaksis Python mungkin paling sering digunakan untuk mengganti fungsi dengan penutupan wrapper (seperti yang dijelaskan jawabannya). Tapi itu juga bisa menggantikan fungsinya dengan sesuatu yang lain. Buildinproperty
,classmethod
danstaticmethod
dekorator menggantikan fungsi dengan deskriptor, misalnya. Dekorator juga dapat melakukan sesuatu dengan suatu fungsi, seperti menyimpan referensi ke dalamnya dalam semacam registri, lalu mengembalikannya, tidak dimodifikasi, tanpa pembungkus apa pun.Atau, Anda dapat menulis fungsi pabrik yang mengembalikan dekorator yang membungkus nilai pengembalian fungsi yang didekorasi dalam tag yang diteruskan ke fungsi pabrik. Sebagai contoh:
Ini memungkinkan Anda untuk menulis:
atau
Secara pribadi saya akan menulis dekorator agak berbeda:
yang akan menghasilkan:
Jangan lupa konstruksi yang merupakan sintaksis dekorator:
sumber
def wrap_in_tag(*kwargs)
itu@wrap_in_tag('b','i')
Sepertinya orang lain sudah memberi tahu Anda cara mengatasi masalah. Saya harap ini akan membantu Anda memahami apa itu dekorator.
Dekorator hanyalah gula sintaksis.
Ini
meluas ke
sumber
@decorator()
(bukan@decorator
) itu adalah gula sintaksis untukfunc = decorator()(func)
. Ini juga praktik umum ketika Anda perlu menghasilkan dekorator "on the fly"Dan tentu saja Anda dapat mengembalikan lambdas juga dari fungsi dekorator:
sumber
makebold = lambda f : lambda "<b>" + f() + "</b>"
makebold = lambda f: lambda: "<b>" + f() + "</b>"
makebold = lambda f: lambda *a, **k: "<b>" + f(*a, **k) + "</b>"
functools.wraps
agar tidak membuang docstring / tanda tangan / namasay
@wraps
tempat lain di halaman ini tidak akan membantu saya ketika saya mencetakhelp(say)
dan mendapatkan "Help on function <lambda>` alih-alih "Help on function say" .Dekorator Python menambahkan fungsionalitas tambahan ke fungsi lain
Bisa jadi dekorator cetak miring
Perhatikan bahwa suatu fungsi didefinisikan di dalam suatu fungsi. Apa yang pada dasarnya dilakukannya adalah mengganti fungsi dengan yang baru didefinisikan. Sebagai contoh, saya memiliki kelas ini
Sekarang katakan, saya ingin kedua fungsi mencetak "---" setelah dan sebelum selesai. Saya bisa menambahkan cetakan "---" sebelum dan sesudah setiap pernyataan cetak. Tetapi karena saya tidak suka mengulang sendiri, saya akan membuat dekorator
Jadi sekarang saya bisa mengubah kelas saya menjadi
Untuk informasi lebih lanjut tentang dekorator, lihat http://www.ibm.com/developerworks/linux/library/l-cpdecor.html
sumber
self
Argumen diperlukan karenanewFunction()
didefinisikan dalamaddDashes()
dirancang khusus untuk menjadi dekorator metode bukan dekorator fungsi umum. Theself
Argumen merupakan contoh kelas dan diteruskan ke metode kelas apakah mereka menggunakan atau tidak - lihat bagian berjudul Dekorasi metode dalam jawaban @ e-satis ini.functools.wraps
Anda dapat membuat dua dekorator terpisah yang melakukan apa yang Anda inginkan seperti yang diilustrasikan langsung di bawah ini. Perhatikan penggunaan
*args, **kwargs
dalam deklarasiwrapped()
fungsi yang mendukung fungsi yang didekorasi memiliki beberapa argumen (yang tidak benar-benar diperlukan untuksay()
fungsi contoh , tetapi disertakan untuk umum).Untuk alasan yang sama,
functools.wraps
dekorator digunakan untuk mengubah atribut meta dari fungsi yang dibungkus menjadi yang didekorasi. Ini membuat pesan kesalahan dan dokumentasi fungsi yang disematkan (func.__doc__
) menjadi pesan dari fungsi yang didekorasi alih-alihwrapped()
.Perbaikan
Seperti yang Anda lihat ada banyak kode duplikat di dua dekorator ini. Dengan kesamaan ini, lebih baik bagi Anda untuk membuat yang generik yang sebenarnya merupakan pabrik dekorator — dengan kata lain, fungsi dekorator yang membuat dekorator lain. Dengan begitu akan ada lebih sedikit pengulangan kode - dan memungkinkan prinsip KERING untuk diikuti.
Untuk membuat kode lebih mudah dibaca, Anda dapat menetapkan nama yang lebih deskriptif untuk dekorator yang dibuat oleh pabrik:
atau bahkan menggabungkannya seperti ini:
Efisiensi
Sementara contoh di atas melakukan semua pekerjaan, kode yang dihasilkan melibatkan cukup banyak overhead dalam bentuk panggilan fungsi asing ketika beberapa dekorator diterapkan sekaligus. Ini mungkin tidak masalah, tergantung penggunaan yang tepat (yang mungkin terikat I / O, misalnya).
Jika kecepatan fungsi yang didekorasi penting, overhead dapat disimpan ke satu panggilan fungsi tambahan dengan menulis fungsi pabrik dekorator yang sedikit berbeda yang mengimplementasikan penambahan semua tag sekaligus, sehingga dapat menghasilkan kode yang menghindari panggilan fungsi tambahan yang dikeluarkan dengan menggunakan dekorator terpisah untuk setiap tag.
Ini membutuhkan lebih banyak kode dalam dekorator itu sendiri, tetapi ini hanya berjalan ketika diterapkan pada definisi fungsi, tidak lebih lama ketika mereka sendiri dipanggil. Ini juga berlaku saat membuat nama yang lebih mudah dibaca dengan menggunakan
lambda
fungsi seperti yang diilustrasikan sebelumnya. Sampel:sumber
Cara lain untuk melakukan hal yang sama:
Atau, lebih fleksibel:
sumber
functools.update_wrapper
untuk menjagasayhi.__name__ == "sayhi"
Anda ingin fungsi berikut, ketika dipanggil:
Mengembalikan:
Solusi sederhana
Untuk melakukan hal ini, buat dekorator yang mengembalikan lambdas (fungsi anonim) yang menutup fungsi (penutupan) dan menyebutnya:
Sekarang gunakan sesuai keinginan:
dan sekarang:
Masalah dengan solusi sederhana
Tapi sepertinya kita hampir kehilangan fungsi aslinya.
Untuk menemukannya, kita perlu menggali ke dalam penutupan setiap lambda, yang salah satunya dikubur di yang lain:
Jadi jika kita menempatkan dokumentasi pada fungsi ini, atau ingin dapat menghias fungsi yang membutuhkan lebih dari satu argumen, atau kita hanya ingin tahu fungsi apa yang kita lihat dalam sesi debugging, kita perlu melakukan sedikit lebih banyak dengan pembungkus.
Solusi berfitur lengkap - mengatasi sebagian besar masalah ini
Kami memiliki dekorator
wraps
darifunctools
modul di perpustakaan standar!Sangat disayangkan bahwa masih ada beberapa boilerplate, tapi ini sesederhana yang kita bisa.
Di Python 3, Anda juga mendapatkan
__qualname__
dan__annotations__
ditugaskan secara default.Jadi sekarang:
Dan sekarang:
Kesimpulan
Jadi kita melihat bahwa
wraps
membuat fungsi pembungkus melakukan hampir semua hal kecuali memberi tahu kami apa yang dibutuhkan fungsi sebagai argumen.Ada modul lain yang mungkin berusaha untuk mengatasi masalah, tetapi solusinya belum ada di perpustakaan standar.
sumber
Untuk menjelaskan dekorator dengan cara sederhana:
Dengan:
Kapan:
Anda benar-benar melakukannya:
sumber
Dekorator mengambil definisi fungsi dan membuat fungsi baru yang menjalankan fungsi ini dan mengubah hasilnya.
setara dengan:
Contoh:
Ini
setara dengan ini
65 <=> 'a'
Untuk memahami dekorator, penting untuk diperhatikan, bahwa dekorator membuat fungsi baru do yang merupakan bagian dalam yang menjalankan fungsi dan mengubah hasilnya.
sumber
print(do(65))
danprint(do2(65))
beA
danA
?Anda juga dapat menulis dekorator di Kelas
sumber
Jawaban ini telah lama dijawab, tetapi saya pikir saya akan membagikan kelas Dekorator saya yang membuat menulis dekorator baru menjadi mudah dan ringkas.
Untuk satu saya pikir ini membuat perilaku dekorator sangat jelas, tetapi juga membuatnya mudah untuk mendefinisikan dekorator baru dengan sangat singkat. Untuk contoh yang tercantum di atas, Anda dapat menyelesaikannya sebagai:
Anda juga dapat menggunakannya untuk melakukan tugas yang lebih kompleks, seperti misalnya dekorator yang secara otomatis membuat fungsi diterapkan secara rekursif ke semua argumen di iterator:
Yang mencetak:
Perhatikan bahwa contoh ini tidak termasuk
list
jenis di Instantiation dari dekorator, sehingga dalam pernyataan cetak akhir metode akan diterapkan ke daftar itu sendiri, bukan elemen daftar.sumber
Berikut adalah contoh sederhana dari rantai dekorasi. Perhatikan baris terakhir - ini menunjukkan apa yang terjadi di bawah selimut.
Outputnya terlihat seperti:
sumber
Berbicara tentang contoh penghitung - seperti yang diberikan di atas, penghitung akan dibagikan di antara semua fungsi yang menggunakan dekorator:
Dengan begitu, dekorator Anda dapat digunakan kembali untuk fungsi yang berbeda (atau digunakan untuk menghias fungsi yang sama beberapa kali :)
func_counter1 = counter(func); func_counter2 = counter(func)
, dan variabel penghitung akan tetap pribadi untuk masing-masing.sumber
Hiasi fungsi dengan jumlah argumen yang berbeda:
Hasil:
sumber
def wrapper(*args, **kwargs):
danfn(*args, **kwargs)
.Jawaban Paolo Bergantino memiliki keuntungan besar hanya dengan menggunakan stdlib, dan berfungsi untuk contoh sederhana ini di mana tidak ada argumen dekorator atau argumen fungsi yang didekorasi .
Namun ia memiliki 3 batasan utama jika Anda ingin menangani kasus yang lebih umum:
makestyle(style='bold')
dekorator adalah hal yang tidak sepele.@functools.wraps
tidak mempertahankan tanda tangan , jadi jika argumen buruk diberikan mereka akan mulai mengeksekusi, dan mungkin menimbulkan jenis kesalahan yang berbeda dari biasanyaTypeError
.@functools.wraps
untuk mengakses argumen berdasarkan namanya . Memang argumen tersebut dapat muncul di*args
, di**kwargs
, atau mungkin tidak muncul sama sekali (jika itu opsional).Saya menulis
decopatch
untuk memecahkan masalah pertama, dan menulismakefun.wraps
untuk memecahkan dua lainnya. Perhatikan bahwamakefun
memanfaatkan trik yang sama daridecorator
lib terkenal .Ini adalah bagaimana Anda akan membuat dekorator dengan argumen, mengembalikan pembungkus yang benar-benar melindungi tanda tangan:
decopatch
memberi Anda dua gaya pengembangan lain yang menyembunyikan atau menampilkan berbagai konsep python, tergantung pada preferensi Anda. Gaya paling ringkas adalah sebagai berikut:Dalam kedua kasus Anda dapat memeriksa bahwa dekorator berfungsi seperti yang diharapkan:
Lihat dokumentasi untuk detailnya.
sumber