Mengapa meminta maaf lebih mudah daripada mendapatkan izin dengan Python?

Mengapa "Meminta Maaf Lebih Mudah daripada Mendapatkan Izin" (EAFP) dianggap sebagai praktik yang baik dengan Python? Sebagai pemula pemrograman, saya mendapat kesan bahwa menggunakan banyak try...except rutinitas akan menyebabkan kode membengkak dan kurang dapat dibaca dibandingkan dengan menggunakan pemeriksaan lainnya.

Apa keuntungan dari pendekatan EAFP?

NB: Saya tahu ada pertanyaan serupa di sini, tapi kebanyakan merujuk pada beberapa contoh spesifik, sedangkan saya lebih tertarik pada filosofi di balik prinsip tersebut.


person n1000    schedule 02.10.2015    source sumber
comment
Pernyataan bukan untuk aliran kode.   -  person Ignacio Vazquez-Abrams    schedule 02.10.2015
comment
@ IgnacioVazquez-Abrams Bisakah Anda menjelaskannya lebih lanjut? Menurut pemahaman saya, ini adalah alternatif dari try...except? Haruskah saya mengubah pertanyaannya?   -  person n1000    schedule 02.10.2015
comment
Mereka seharusnya digunakan untuk memastikan bahwa program Anda tidak melakukan hal bodoh selama situasi berbentuk buah pir, bukan untuk memeriksa apakah seseorang meneruskan bilangan bulat dan bukan string.   -  person Ignacio Vazquez-Abrams    schedule 02.10.2015
comment
Oke - saya menghapus assert. Rupanya saya tidak menggunakannya dengan benar di masa lalu...   -  person n1000    schedule 02.10.2015


Jawaban (4)


LBYL, pendekatan balasan terhadap EAFP tidak ada hubungannya dengan pernyataan, itu hanya berarti Anda menambahkan tanda centang sebelum mencoba untuk mengakses sesuatu yang mungkin tidak ada di sana.

Alasan mengapa Python adalah EAFP adalah karena tidak seperti bahasa lain (misalnya Java) - dalam Python, menangkap pengecualian adalah operasi yang relatif murah, dan itulah mengapa Anda dianjurkan untuk menggunakannya.

Contoh untuk EAFP:

try:
    snake = zoo['snake']
except KeyError as e:
    print "There's no snake in the zoo"
    snake = None

Contoh untuk LBYL:

if 'snake' in zoo:
    snake = zoo['snake']
else:
    snake = None
person Nir Alfasi    schedule 02.10.2015
comment
Perhatikan bahwa yang kedua sebaiknya menggunakan objek sentinel yang unik, jika tidak maka akan gagal pada nilai yang salah. - person Ignacio Vazquez-Abrams; 02.10.2015
comment
@ IgnacioVazquez-Abrams Anda benar, tetapi dalam contoh ini saya rasa saya dapat berasumsi bahwa False, 0 atau nilai palsu lainnya tidak akan mewakili ular di kebun binatang :) - person Nir Alfasi; 02.10.2015
comment
Jika Anda menggunakan dict.get, maka Anda sebaiknya meneruskan None sebagai nilai defaultnya: zoo.get('snake', None). Ini sebenarnya bukan contoh yang baik untuk LBYL karena penggunaan kata dict.get yang pemaaf. - person poke; 02.10.2015
comment
@poke benar, bisa disingkat menjadi: snake = zoo.get('snake', None) tapi intinya menunjukkan contoh LBYL bukan cara menyiasatinya :) - person Nir Alfasi; 02.10.2015
comment
Kemudian lakukan bagian "mencari" dengan benar dan periksa apakah kunci tersebut ada di kamus tanpa meminta maaf untuk mendapatkan nilai darinya dan memeriksa kebenaran nilai tersebut. - person poke; 02.10.2015
comment
Alasan mengapa Python adalah EAFP adalah karena tidak seperti bahasa lain (misalnya Java) - dalam Python menangkap pengecualian adalah operasi yang murah: ya, itu salah. Menyiapkan blok coba/kecuali itu murah tetapi menangkap pengecualian tidaklah murah. Coba kode ini untuk memeriksanya gist.github.com/RealBigB/38f4579c543261f1400f - di sini (python 2.7.x) Saya mendapatkan 1.3156080246 untuk eafp dan 0.368113994598 untuk lbyl. - person bruno desthuilliers; 02.10.2015
comment
@brunodesthuilliers apakah Anda menganggap contoh yang memunculkan jutaan pengecualian dan menganggapnya sebagai contoh bagus yang dapat digunakan sebagai model untuk program normal? :) - person Nir Alfasi; 02.10.2015
comment
@brunodesthuilliers Saat saya mencoba menjelaskan dalam jawaban saya, menangkap pengecualian dengan Python lebih murah dibandingkan dengan bahasa lain, tapi tentu saja tidak gratis. Pengecualian masih berlaku untuk kasus luar biasa, dalam hal ini, jika Anda mengharapkan kuncinya selalu ada. Lalu, cek tambahan untuk LBYL sedikit lebih mahal. - person poke; 02.10.2015
comment
@brunodesthuilliers ambil contoh Anda: (1.3156080246-0.368113994598)/1000000 = 0.00000094749403 yang merupakan biaya rata-rata untuk menangkap satu pengecualian dalam contoh yang Anda berikan. Itu kurang dari satu mikrodetik, atau 947 nano detik jika lebih akurat... - person Nir Alfasi; 02.10.2015
comment
@alfasin wrt/ kode itu hanya contoh Anda sendiri - dan menyatakan bahwa dalam Python menangkap pengecualian adalah operasi yang murah jelas menyesatkan. Ini mungkin lebih murah dibandingkan bahasa lain tetapi masih jauh lebih mahal daripada pencarian kunci. - person bruno desthuilliers; 02.10.2015
comment
@brunodesthuilliers Anda mengambil kata-kata saya di luar konteks, tidak mahal dibandingkan dengan bahasa lain dan karenanya dianjurkan untuk digunakan. Saya tidak mengatakan itu 'gratis'. - person Nir Alfasi; 02.10.2015
comment
@brunodesthuilliers Namun adalah operasi yang murah. Itu tidak berarti Anda dapat membandingkannya dengan produk lain yang lebih murah dan mengatakan bahwa itu salah. Ini seperti mengatakan print itu mahal karena tidak mencetaknya jauh lebih murah. Coba jalankan contoh yang sama di mis. Java dan Anda dapat melihat mengapa pengecualian tidak mahal di sini. - person poke; 02.10.2015
comment
Saya pernah harus memperbaiki kode orang lain untuk tugas komputasi harian yang agak rumit dan memakan banyak waktu. Dia menggunakan EAFP di semua tempat. Hanya dengan mengubahnya menjadi LBYL hampir mengurangi separuh waktu eksekusi. Pada contoh di atas, versi EAFP hanya 3,5x kali lebih lambat, tentu saja itu tidak mahal. Dan itu akan menjadi komentar terakhir saya mengenai hal ini. - person bruno desthuilliers; 02.10.2015
comment
@brunodesthuilliers 3,5x lebih lambat ini menjadikannya 1 detik lebih lambat per juta eksekusi. Ketika Anda memperbaiki kodenya, Anda pasti telah melakukan hal-hal lain yang bahkan tidak Anda sadari... - person Nir Alfasi; 02.10.2015

Anda mencampurkan dua hal di sini: Pernyataan dan logika berbasis EAFP.

Pernyataan digunakan untuk memvalidasi kontrak fungsi, yaitu kondisi sebelum dan sesudahnya dan terkadang juga invariannya. Mereka memastikan bahwa suatu fungsi akan digunakan sebagaimana mestinya. Ini bukan untuk aliran kode, karena mereka benar-benar mengganggu eksekusi jika terjadi kesalahan. Contoh umum adalah pemeriksaan argumen None dalam pemanggilan fungsi.

Di Python, Anda biasanya menghindari penggunaan pernyataan terlalu banyak. Secara umum, Anda harus mengharapkan pengguna kode Anda untuk menggunakannya dengan benar. Misalnya, jika Anda mendokumentasikan suatu fungsi untuk mengambil argumen yang bukan None, maka tidak perlu ada pernyataan yang memvalidasi argumen tersebut. Sebaliknya, berharap saja ada nilainya. Jika ada kesalahan karena nilai Tidak Ada, maka kesalahan tersebut akan tetap muncul, sehingga pengguna mengetahui bahwa mereka melakukan kesalahan. Namun Anda tidak harus memeriksa semuanya setiap saat.

Sekarang, EAFP adalah sesuatu yang berbeda. Ini digunakan dalam aliran kontrol, atau lebih tepatnya, ini menghindari aliran kontrol tambahan demi mengharapkan segala sesuatunya benar dan menangkap pengecualian jika ternyata tidak benar. Contoh umum yang menunjukkan perbedaannya adalah akses kunci dalam kamus:

# LBYL
if key in dic:
    print(dic[key])
else:
    handleError()

# EAFP
try:
    print(dic[key])
except KeyError:
    handleError()

Sekarang ini terlihat sangat mirip, meskipun Anda harus ingat bahwa solusi LBYL memeriksa kamus dua kali. Seperti semua kode yang menangkap pengecualian, Anda hanya boleh melakukannya jika tidak adanya kunci adalah kasus luar biasa. Jadi jika biasanya kunci yang diberikan dikecualikan untuk ada di kamus, maka itu adalah EAFP dan Anda sebaiknya mengaksesnya secara langsung. Jika Anda tidak mengharapkan kuncinya ada di kamus, Anda mungkin harus memeriksa keberadaannya terlebih dahulu (meskipun pengecualian lebih murah di Python, pengecualian tersebut tetap tidak gratis, jadi simpanlah untuk kasus luar biasa).

Manfaat EAFP di sini juga adalah bahwa lebih dalam logika perpustakaan atau aplikasi Anda, di mana key berasal dari atas, Anda dapat berasumsi bahwa kunci yang valid telah diteruskan di sini. Jadi Anda tidak perlu menangkap pengecualian di sini tetapi biarkan saja pengecualian tersebut muncul ke titik yang lebih tinggi dalam kode Anda sehingga Anda dapat menangani kesalahan tersebut. Hal ini memungkinkan Anda untuk memiliki fungsi tingkat rendah yang sepenuhnya bebas dari pemeriksaan semacam ini.

person poke    schedule 02.10.2015
comment
Terima kasih. +1 Besar untuk mengilustrasikan dan menjelaskan berbagai keuntungan pendekatan EAFP dengan Python. - person n1000; 02.10.2015

Pertanyaan yang bagus! Ada sedikit pertanyaan di StackOverflow yang menanyakan tentang "filosofi di balik prinsip".

Mengenai definisi EAFP dalam glosarium Python, saya bahkan akan melangkah lebih jauh dan mengatakan bahwa penyebutan "pengecualian cache jika asumsi terbukti salah" agak menyesatkan dalam konteks ini. Karena jujur ​​saja, cuplikan kode ke-2 berikut TIDAK terlihat lebih "bersih dan cepat" (istilah yang digunakan dalam definisi di atas). Pantas saja OP menanyakan pertanyaan ini.

# LBYL
if key in dic:
    print(dic[key])
else:
    handleError()

# EAFP
try:
    print(dic[key])
except KeyError:
    handleError()

Menurut saya, momen sebenarnya ketika EAFP bersinar adalah Anda TIDAK menulis try ... except ... sama sekali, setidaknya tidak di sebagian besar basis kode dasar Anda. Karena, aturan pertama penanganan pengecualian: jangan lakukan penanganan pengecualian. Dengan mengingat hal tersebut, sekarang, mari kita tulis ulang cuplikan ke-2 menjadi ini:

# Real EAFP
print(dic[key])

Sekarang, bukankah pendekatan EAFP yang sebenarnya bersih dan cepat?

person RayLuo    schedule 23.04.2019

Saya akan memperluas jawaban dari @RayLuo.

Masalahnya dengan LBYL, sebenarnya tidak berfungsi secara umum. Kecuali Anda memiliki aplikasi single-thread, selalu ada kemungkinan kondisi balapan:

# LBYL
if key in dic:
    # RACE CONDITION
    print(dic[key])
else:
    handleError()

# EAFP
try:
    print(dic[key])
except KeyError:
    handleError()

Kunci dapat ditambahkan ke dikt antara tanda centang if dan dan print. Hal ini kedengarannya tidak mungkin terjadi dalam kasus khusus ini. Namun kemungkinannya lebih besar jika pemeriksaan yang Anda lakukan bertentangan dengan DB, API, atau sumber data eksternal apa pun yang dapat berubah secara asinkron dengan data aplikasi Anda.

Artinya cara yang tepat untuk mengimplementasikan LBYL adalah:

if key in dic:
    try:
        print(dic[key])
    except KeyError:
        handleError()
else:
    handleError()

Perhatikan klausa try / except sama persis dengan pendekatan EAFP.

Karena Anda harus menangani pengecualian gaya EAFP bahkan saat menggunakan pendekatan LBYL, Anda sebaiknya menggunakan pendekatan EAFP terlebih dahulu.

Satu-satunya keadaan di mana saya akan menggunakan pemeriksaan if adalah jika tindakan berikutnya (print dalam kasus ini) sangat mahal/memakan waktu untuk memulai. Itu jarang terjadi dan bukan alasan untuk menggunakan tanda centang if setiap saat.

Intinya: LBYL tidak berfungsi dalam kasus umum, tetapi EAFP bisa. Pengembang yang baik fokus pada pola solusi umum yang dapat mereka gunakan dengan percaya diri dalam berbagai masalah. Belajar menggunakan EAFP secara konsisten.

person Chris Johnson    schedule 01.03.2021