Saya ingin tahu apakah mungkin untuk mengontrol definisi fungsi Python berdasarkan pengaturan global (misalnya OS). Contoh:
@linux
def my_callback(*args, **kwargs):
print("Doing something @ Linux")
return
@windows
def my_callback(*args, **kwargs):
print("Doing something @ Windows")
return
Kemudian, jika seseorang menggunakan Linux, definisi pertama my_callback
akan digunakan dan yang kedua akan diabaikan.
Ini bukan tentang menentukan OS, ini tentang definisi fungsi / dekorator.
my_callback = windows(<actual function definition>)
- sehingga namanyamy_callback
akan ditimpa, terlepas dari apa yang dekorator lakukan. Satu-satunya cara fungsi versi Linux bisa berakhir di variabel itu adalah jikawindows()
mengembalikannya - tetapi fungsi tersebut tidak memiliki cara untuk mengetahui tentang versi Linux. Saya pikir cara yang lebih khas untuk mencapai ini adalah memiliki definisi fungsi OS-spesifik dalam file terpisah, dan secara kondisionalimport
hanya satu dari mereka.functools.singledispatch
, yang melakukan sesuatu yang mirip dengan yang Anda inginkan. Di sana,register
dekorator tahu tentang dispatcher (karena itu adalah atribut dari fungsi dispatcher, dan khusus untuk dispatcher tertentu), sehingga dapat mengembalikan dispatcher dan menghindari masalah dengan pendekatan Anda.uuid.getnode()
,. (Konon, jawaban Todd di sini cukup bagus.)Jawaban:
Jika tujuannya adalah untuk memiliki efek yang sama dalam kode Anda yang memiliki #ifdef WINDOWS / #endif .. inilah cara untuk melakukannya (Saya menggunakan mac btw).
Kasus Sederhana, Tanpa Rantai
Jadi dengan implementasi ini Anda mendapatkan sintaks yang sama dengan yang Anda miliki dalam pertanyaan Anda.
Apa yang dilakukan kode di atas, pada dasarnya, adalah menugaskan zulu ke zulu jika platform cocok. Jika platform tidak cocok, itu akan mengembalikan zulu jika sudah ditentukan sebelumnya. Jika tidak ditentukan, ia mengembalikan fungsi placeholder yang menimbulkan pengecualian.
Penghias secara konsep mudah untuk mencari tahu jika Anda ingat itu
analog dengan:
Berikut ini implementasi menggunakan dekorator berparameter:
Dekorator berparameter analog dengan
foo = mydecorator(param)(foo)
.Saya sudah memperbarui sedikit jawabannya. Menanggapi komentar, saya telah memperluas cakupan aslinya untuk memasukkan aplikasi ke metode kelas dan untuk mencakup fungsi yang didefinisikan dalam modul lain. Dalam pembaruan terakhir ini, saya dapat mengurangi kompleksitas yang terlibat dalam menentukan apakah suatu fungsi telah ditentukan.
[Sedikit pembaruan di sini ... Saya tidak bisa menghentikannya - ini merupakan latihan yang menyenangkan] Saya telah melakukan beberapa pengujian lagi, dan ternyata ini berfungsi secara umum pada kartu panggil - bukan hanya fungsi biasa; Anda juga bisa mendekorasi deklarasi kelas apakah dapat dipanggil atau tidak. Dan itu mendukung fungsi fungsi dalam, jadi hal-hal seperti ini dimungkinkan (walaupun mungkin bukan gaya yang baik - ini hanya kode uji):
Di atas menunjukkan mekanisme dasar dekorator, cara mengakses ruang lingkup pemanggil, dan bagaimana menyederhanakan banyak dekorator yang memiliki perilaku serupa dengan memiliki fungsi internal yang berisi algoritma umum yang ditentukan.
Dukungan Chaining
Untuk mendukung rantai dekorator ini menunjukkan apakah suatu fungsi berlaku untuk lebih dari satu platform, dekorator dapat diimplementasikan seperti:
Dengan begitu Anda mendukung rantai:
sumber
macos
danwindows
didefinisikan dalam modul yang sama denganzulu
. Saya percaya ini juga akan menghasilkan fungsi yang dibiarkan seolah-None
olah fungsi tersebut tidak didefinisikan untuk platform saat ini, yang akan menyebabkan beberapa kesalahan runtime yang sangat membingungkan.Meskipun
@decorator
sintaks terlihat bagus, Anda mendapatkan perilaku yang sama persis seperti yang diinginkan dengan sederhanaif
.Jika diperlukan, ini juga memungkinkan untuk dengan mudah menegakkan bahwa beberapa kasus cocok.
sumber
def callback_windows(...)
dandef callback_linux(...)
, kemudianif windows: callback = callback_windows
, dll. Namun cara ini adalah cara yang lebih mudah untuk membaca, men-debug, dan memelihara.elif
, karena tidak akan pernah menjadi kasus yang diharapkan bahwa lebih dari satulinux
/windows
/macOS
akan benar. Bahkan, saya mungkin hanya mendefinisikan satu variabelp = platform.system()
, lalu gunakanif p == "Linux"
, dll daripada beberapa boolean flags. Variabel yang tidak ada tidak dapat disinkronkan.elif
tentu memiliki kelebihan - khususnya, trailingelse
+raise
untuk memastikan bahwa setidaknya satu kasus melakukan pertandingan. Sedangkan untuk mengevaluasi predikat, saya lebih suka memiliki mereka dievaluasi - itu menghindari duplikasi dan memisahkan definisi dan penggunaan. Bahkan jika hasilnya tidak disimpan dalam variabel, sekarang ada nilai-nilai hardcoded yang bisa keluar dari sinkronisasi sama saja. Saya tidak pernah dapat mengingat berbagai string sihir untuk cara yang berbeda, misalnyaplatform.system() == "Windows"
versussys.platform == "win32"
, ...Enum
atau hanya seperangkat konstanta.Di bawah ini adalah salah satu implementasi yang mungkin untuk mekanik ini. Seperti disebutkan dalam komentar, mungkin lebih baik untuk mengimplementasikan antarmuka "master dispatcher", seperti yang terlihat di
functools.singledispatch
, untuk melacak keadaan yang terkait dengan beberapa definisi kelebihan beban. Harapan saya adalah implementasi ini setidaknya akan menawarkan beberapa wawasan tentang masalah yang mungkin harus Anda hadapi ketika mengembangkan fungsi ini untuk basis kode yang lebih besar.Saya hanya menguji bahwa implementasi di bawah ini berfungsi sebagaimana ditentukan pada sistem Linux, jadi saya tidak dapat menjamin bahwa solusi ini memungkinkan terciptanya fungsi khusus platform. Tolong jangan gunakan kode ini dalam pengaturan produksi tanpa mengujinya sendiri terlebih dahulu.
Untuk menggunakan dekorator ini, kita harus bekerja melalui dua tingkat tipuan. Pertama, kita harus menentukan platform apa yang kita ingin dekorator merespons. Ini dilakukan oleh garis
implement_linux = implement_for_os('Linux')
dan mitra Window-nya di atas. Selanjutnya, kita perlu menyampaikan definisi fungsi yang kelebihan beban. Langkah ini harus dilakukan di situs definisi, seperti yang ditunjukkan di bawah ini.Untuk mendefinisikan fungsi khusus platform, Anda sekarang dapat menulis yang berikut:
Panggilan untuk
some_function()
akan dikirim secara tepat ke definisi spesifik platform yang disediakan.Secara pribadi, saya tidak akan menyarankan menggunakan teknik ini dalam kode produksi. Menurut pendapat saya, lebih baik eksplisit tentang perilaku yang bergantung pada platform di setiap lokasi di mana perbedaan ini terjadi.
sumber
implement_for_os
tidak mengembalikan dekorator itu sendiri, melainkan mengembalikan fungsi yang akan menghasilkan dekorator yang pernah dilengkapi dengan definisi fungsi sebelumnya yang dimaksud.Saya menulis kode sebelum membaca jawaban lain. Setelah saya menyelesaikan kode saya, saya menemukan kode @ Todd adalah jawaban terbaik. Pokoknya saya memposting jawaban saya karena saya merasa senang ketika saya sedang memecahkan masalah ini. Saya belajar hal-hal baru berkat pertanyaan yang bagus ini. Kelemahan dari kode saya adalah bahwa ada overhead untuk mengambil kamus setiap kali fungsi dipanggil.
sumber
Solusi bersih adalah dengan membuat fungsi registri khusus yang dikirim
sys.platform
. Ini sangat mirip denganfunctools.singledispatch
. Kode sumber fungsi ini menyediakan titik awal yang baik untuk mengimplementasikan versi khusus:Sekarang dapat digunakan mirip dengan
singledispatch
:Registrasi juga berfungsi langsung pada nama fungsi:
sumber