Saya tahu bahwa argumen default dibuat pada waktu inisialisasi fungsi dan tidak setiap kali fungsi dipanggil. Lihat kode berikut:
def ook (item, lst=[]):
lst.append(item)
print 'ook', lst
def eek (item, lst=None):
if lst is None: lst = []
lst.append(item)
print 'eek', lst
max = 3
for x in xrange(max):
ook(x)
for x in xrange(max):
eek(x)
Yang tidak saya dapatkan adalah mengapa ini diterapkan dengan cara ini. Apa manfaat yang ditawarkan perilaku ini daripada inisialisasi pada setiap waktu panggilan?
Jawaban:
Saya pikir alasannya adalah kesederhanaan implementasi. Biarkan saya uraikan.
Nilai default dari fungsi adalah ekspresi yang perlu Anda evaluasi. Dalam kasus Anda, ini adalah ekspresi sederhana yang tidak bergantung pada penutupan, tetapi bisa berupa sesuatu yang berisi variabel bebas -
def ook(item, lst = something.defaultList())
. Jika Anda mendesain Python, Anda akan memiliki pilihan - apakah Anda mengevaluasinya sekali ketika fungsi didefinisikan atau setiap kali ketika fungsi dipanggil. Python memilih yang pertama (tidak seperti Ruby, yang cocok dengan opsi kedua).Ada beberapa manfaat untuk ini.
Pertama, Anda mendapatkan beberapa peningkatan kecepatan dan memori. Dalam kebanyakan kasus, Anda akan memiliki argumen default yang tidak dapat diubah dan Python dapat membangunnya hanya sekali, alih-alih pada setiap pemanggilan fungsi. Ini menghemat (sebagian) memori dan waktu. Tentu saja, itu tidak bekerja dengan baik dengan nilai-nilai yang bisa berubah, tetapi Anda tahu bagaimana Anda bisa berkeliling.
Manfaat lain adalah kesederhanaan. Sangat mudah untuk memahami bagaimana ekspresi dievaluasi - ia menggunakan ruang lingkup leksikal ketika fungsi didefinisikan. Jika mereka pergi ke arah lain, ruang lingkup leksikal mungkin berubah antara definisi dan doa dan membuatnya sedikit lebih sulit untuk di-debug. Python akan sangat mudah dalam kasus-kasus tersebut.
sumber
Salah satu cara untuk mengatakannya adalah bahwa parameter
lst.append(item)
tidak bermutasilst
.lst
masih referensi daftar yang sama. Hanya saja isi dari daftar itu telah dimutasi.Pada dasarnya, Python tidak memiliki (yang saya ingat) variabel konstan atau tidak berubah sama sekali - tetapi ia memiliki beberapa tipe konstan dan tidak dapat diubah. Anda tidak dapat mengubah nilai integer, Anda hanya bisa menggantinya. Tetapi Anda dapat memodifikasi konten daftar tanpa menggantinya.
Seperti integer, Anda tidak dapat mengubah referensi, Anda hanya bisa menggantinya. Tetapi Anda dapat memodifikasi konten objek yang direferensikan.
Sedangkan untuk membuat objek default sekali, saya membayangkan itu sebagian besar sebagai optimasi, untuk menghemat overhead pembuatan objek dan pengumpulan sampah.
sumber
Ini memungkinkan Anda memilih perilaku yang Anda inginkan, seperti yang Anda tunjukkan dalam contoh Anda. Jadi jika Anda ingin argumen default tidak dapat diubah, Anda menggunakan nilai yang tidak dapat diubah , seperti
None
atau1
. Jika Anda ingin membuat argumen default bisa berubah, Anda menggunakan sesuatu yang bisa berubah, seperti[]
. Itu hanya fleksibilitas, meskipun harus diakui, itu bisa menggigit jika Anda tidak mengetahuinya.sumber
Saya pikir jawaban sebenarnya adalah: Python ditulis sebagai bahasa prosedural dan hanya mengadopsi aspek fungsional setelah-fakta. Apa yang Anda cari adalah untuk default parameter yang harus dilakukan sebagai closure, dan closure di Python benar-benar hanya setengah matang. Untuk bukti coba ini:
yang memberi di
[2, 2, 2]
mana Anda akan mengharapkan penutupan sejati untuk menghasilkan[0, 1, 2]
.Ada cukup banyak hal yang saya suka jika Python memiliki kemampuan untuk membungkus parameter defaulting di penutupan. Sebagai contoh:
Akan sangat berguna, tetapi "a" tidak berada dalam ruang lingkup pada waktu definisi fungsi, jadi Anda tidak dapat melakukan itu dan sebaliknya harus melakukan dengan kikuk:
Yang merupakan hal yang hampir sama ... hampir.
sumber
Manfaat yang sangat besar adalah memoisasi. Ini adalah contoh standar:
dan untuk perbandingan:
Pengukuran waktu dalam ipython:
sumber
Itu terjadi karena kompilasi dengan Python dilakukan dengan mengeksekusi kode deskriptif.
Kalau ada yang bilang
akan sangat jelas bahwa Anda menginginkan array baru setiap kali.
Tetapi bagaimana jika saya katakan:
Di sini saya kira saya ingin membuat barang ke berbagai daftar, dan memiliki global catch-all ketika saya tidak menentukan daftar.
Tapi bagaimana kompiler akan menebak ini? Jadi mengapa mencoba? Kita bisa mengandalkan apakah ini dinamai atau tidak, dan itu kadang-kadang bisa membantu, tetapi sebenarnya itu hanya dugaan saja. Pada saat yang sama, ada alasan bagus untuk tidak mencoba - konsistensi.
Seperti itu, Python hanya menjalankan kode. Variabel list_of_all sudah diberi objek, sehingga objek dilewatkan dengan referensi ke dalam kode yang default x dengan cara yang sama bahwa panggilan ke fungsi apa pun akan mendapatkan referensi ke objek lokal yang disebutkan di sini.
Jika kita ingin membedakan yang tidak disebutkan namanya dari case yang disebutkan, itu akan melibatkan kode pada kompilasi yang menjalankan tugas dengan cara yang sangat berbeda dari yang dijalankan saat run-time. Jadi kami tidak membuat kasus khusus.
sumber
Ini terjadi karena fungsi dalam Python adalah objek kelas satu :
Selanjutnya dijelaskan bahwa mengedit nilai parameter memodifikasi nilai default untuk panggilan berikutnya, dan bahwa solusi sederhana menggunakan Tidak Ada sebagai default, dengan tes eksplisit di fungsi tubuh, adalah semua yang diperlukan untuk memastikan tidak ada kejutan.
Yang berarti
def foo(l=[])
menjadi instance dari fungsi itu ketika dipanggil, dan akan digunakan kembali untuk panggilan lebih lanjut. Pikirkan parameter fungsi sebagai terpisah dari atribut objek.Pro's dapat mencakup pengungkitan ini agar kelas memiliki variabel statis C-like. Jadi yang terbaik adalah mendeklarasikan nilai default Tidak Ada dan menginisialisasi mereka sesuai kebutuhan:
hasil:
bukannya yang tak terduga:
sumber