ʻIf key di dict` vs. `try / kecuali` - idiom mana yang lebih mudah dibaca?

95

Saya punya pertanyaan tentang idiom dan keterbacaan, dan tampaknya ada benturan filosofi Python untuk kasus khusus ini:

Saya ingin membangun kamus A dari kamus B. Jika kunci tertentu tidak ada di B, maka jangan lakukan apa-apa dan lanjutkan.

Cara mana yang lebih baik?

try:
    A["blah"] = B["blah"]
except KeyError:
    pass

atau

if "blah" in B:
    A["blah"] = B["blah"]

"Lakukan dan minta maaf" vs. "kesederhanaan dan kesederhanaan".

Mana yang lebih baik dan mengapa?

LeeMobile
sumber
1
Contoh kedua mungkin lebih baik ditulis sebagai if "blah" in B.keys(), atau if B.has_key("blah").
girasquid
2
tidak A.update(B)tidak bekerja untuk Anda?
SilentGhost
21
@ Luke: has_keytidak digunakan lagi indan memeriksa B.keys()perubahan operasi O (1) menjadi O (n).
kindall
4
@ Luke: bukan itu tidak. .has_keytidak digunakan lagi dan keysmembuat daftar yang tidak dibutuhkan di py2k, dan berlebihan di py3k
SilentGhost
2
'build' A, seperti dalam, A kosong untuk memulai? Dan kami hanya menginginkan kunci tertentu? Gunakan pemahaman a: A = dict((k, v) for (k, v) in B if we_want_to_include(k)).
Karl Knechtel

Jawaban:

77

Pengecualian tidak bersyarat.

Versi bersyarat lebih jelas. Itu wajar: ini adalah kontrol aliran langsung, yang dirancang untuk kondisional, bukan pengecualian.

Versi pengecualian terutama digunakan sebagai pengoptimalan saat melakukan pencarian ini dalam satu putaran: untuk beberapa algoritme, versi ini memungkinkan penghapusan pengujian dari loop dalam. Tidak ada manfaat itu di sini. Ini memiliki keuntungan kecil yang menghindari keharusan mengatakan "blah"dua kali, tetapi jika Anda melakukan banyak hal ini Anda mungkin harus memiliki move_keyfungsi pembantu .

Secara umum, saya sangat menyarankan untuk tetap menggunakan versi bersyarat secara default kecuali Anda memiliki alasan khusus untuk tidak melakukannya. Kondisional adalah cara yang jelas untuk melakukan ini, yang biasanya merupakan rekomendasi kuat untuk memilih satu solusi daripada yang lain.

Glenn Maynard
sumber
3
Saya tidak setuju. Jika Anda mengatakan "lakukan X, dan jika tidak berhasil, lakukan Y". Alasan utama terhadap solusi bersyarat di sini, Anda harus menulis "blah"lebih sering, yang mengarah ke situasi yang lebih rawan kesalahan.
glglgl
6
Dan, khususnya di Python, EAFP sangat banyak digunakan.
glglgl
8
Jawaban ini akan benar untuk bahasa apa pun yang saya tahu kecuali untuk Python.
Tomáš Zato - Kembalikan Monica
3
Jika Anda menggunakan pengecualian seolah-olah mereka bersyarat dalam Python, saya harap tidak ada orang lain yang harus membacanya.
Glenn Maynard
Jadi, apa keputusan akhirnya? :)
floatingpurr
62

Ada juga cara ketiga untuk menghindari pengecualian dan pencarian ganda, yang dapat menjadi penting jika pencarian mahal:

value = B.get("blah", None)
if value is not None: 
    A["blah"] = value

Jika Anda mengharapkan kamus berisi Nonenilai, Anda dapat menggunakan beberapa konstanta esoterik seperti NotImplemented, Ellipsisatau membuat yang baru:

MyConst = object()
def update_key(A, B, key):
    value = B.get(key, MyConst)
    if value is not MyConst: 
        A[key] = value

Bagaimanapun, menggunakan update()adalah opsi yang paling mudah dibaca bagi saya:

a.update((k, b[k]) for k in ("foo", "bar", "blah") if k in b)
lqc
sumber
14

Dari apa yang saya pahami, Anda ingin memperbarui dict A dengan kunci, pasangan nilai dari dict B

update adalah pilihan yang lebih baik.

A.update(B)

Contoh:

>>> A = {'a':1, 'b': 2, 'c':3}
>>> B = {'d': 2, 'b':5, 'c': 4}
>>> A.update(B)
>>> A
{'a': 1, 'c': 4, 'b': 5, 'd': 2}
>>> 
pyfunc
sumber
"Jika kunci tertentu tidak ada di B" Maaf, seharusnya lebih jelas, tapi saya hanya ingin menyalin nilai jika kunci tertentu di B ada. Tidak semua di B.
LeeMobile
1
@Lee Mobile -A.update({k: v for k, v in B.iteritems() if k in specificset})
Omnifarious
8

Kutipan langsung dari wiki kinerja Python:

Kecuali untuk pertama kalinya, setiap kali sebuah kata terlihat, pengujian pernyataan if gagal. Jika Anda menghitung kata dalam jumlah besar, banyak kata mungkin akan muncul berkali-kali. Dalam situasi di mana inisialisasi nilai hanya akan terjadi sekali dan augmentasi nilai tersebut akan terjadi berkali-kali, lebih murah menggunakan pernyataan percobaan.

Jadi tampaknya kedua opsi tersebut dapat dijalankan tergantung dari situasi. Untuk lebih jelasnya Anda mungkin ingin memeriksa tautan ini: Coba-kecuali-kinerja

Sami Lehtinen
sumber
itu bacaan yang menarik, tapi menurut saya agak tidak lengkap. Dikt yang digunakan hanya memiliki 1 elemen dan saya menduga dicts yang lebih besar akan berdampak signifikan pada kinerja
user2682863
3

Saya pikir aturan umum di sini adalah A["blah"]biasanya akan ada, jika demikian coba-kecuali bagus jika tidak maka gunakanif "blah" in b:

Saya pikir "mencoba" itu murah pada waktunya tetapi "kecuali" lebih mahal.

neil
sumber
10
Jangan mendekati kode dari perspektif pengoptimalan secara default; mendekatinya dari perspektif keterbacaan dan pemeliharaan. Kecuali jika tujuannya adalah pengoptimalan khusus, ini adalah kriteria yang salah (dan jika pengoptimalan, jawabannya adalah pembandingan, bukan menebak-nebak).
Glenn Maynard
Saya mungkin seharusnya meletakkan poin terakhir dalam tanda kurung atau entah bagaimana lebih samar - poin utama saya adalah yang pertama dan saya pikir itu memiliki keuntungan tambahan dari yang kedua.
Neil
3

Saya pikir contoh kedua adalah apa yang harus Anda lakukan kecuali kode ini masuk akal:

try:
    A["foo"] = B["foo"]
    A["bar"] = B["bar"]
    A["baz"] = B["baz"]
except KeyError:
    pass

Ingatlah bahwa kode akan dibatalkan segera setelah ada kunci yang tidak ada di dalamnya B . Jika kode ini masuk akal, maka Anda harus menggunakan metode pengecualian, jika tidak gunakan metode pengujian. Menurut pendapat saya, karena lebih pendek dan dengan jelas mengungkapkan maksudnya, jauh lebih mudah dibaca daripada metode pengecualian.

Tentu saja, orang yang menyuruh Anda menggunakan updateitu benar. Jika Anda menggunakan versi Python yang mendukung pemahaman kamus, saya sangat menyukai kode ini:

updateset = {'foo', 'bar', 'baz'}
A.update({k: B[k] for k in updateset if k in B})
Beraneka ragam
sumber
"Ingatlah bahwa kode akan dibatalkan segera setelah ada kunci yang tidak ada di B." - inilah mengapa praktik terbaiknya adalah menempatkan hanya minimum absolut dalam blok try: biasanya ini adalah satu baris. Contoh pertama akan lebih baik sebagai bagian dari loop, sepertifor key in ["foo", "bar", "baz"]: try: A[key] = B[key]
Zim
2

Aturan dalam bahasa lain adalah mencadangkan pengecualian untuk kondisi luar biasa, yaitu kesalahan yang tidak terjadi dalam penggunaan biasa. Tidak tahu bagaimana aturan itu berlaku untuk Python, karena StopIteration seharusnya tidak ada pada aturan itu.

Mark Ransom
sumber
Menurut saya, kastanye ini berasal dari bahasa di mana penanganan pengecualian mahal sehingga dapat berdampak signifikan pada kinerja. Saya belum pernah melihat pembenaran atau alasan nyata di baliknya.
John La Rooy
@JohnLaRooy Tidak, kinerja bukanlah alasannya. Pengecualian adalah sejenis goto non-lokal , yang oleh beberapa orang dianggap menghalangi pembacaan kode. Namun, penggunaan pengecualian dengan cara ini dianggap idiomatis di Python sehingga hal di atas tidak berlaku.
Ian Goldby
pengembalian bersyarat juga "kebagian non-lokal" dan banyak orang lebih memilih gaya itu daripada memeriksa penjaga di akhir blok kode.
cowbert
1

Secara pribadi, saya condong ke metode kedua (tetapi menggunakan has_key):

if B.has_key("blah"):
  A["blah"] = B["blah"]

Dengan begitu, setiap operasi penugasan hanya dua baris (bukan 4 dengan coba / kecuali), dan pengecualian apa pun yang terlempar akan menjadi kesalahan nyata atau hal-hal yang Anda lewatkan (bukan hanya mencoba mengakses kunci yang tidak ada) .

Ternyata (lihat komentar pada pertanyaan Anda), has_keysudah usang - jadi saya rasa lebih baik ditulis sebagai

if "blah" in B:
  A["blah"] = B["blah"]
girasquid.dll
sumber
1

Memulai Python 3.8, dan pengenalan ekspresi penugasan (PEP 572) ( :=operator), kita dapat menangkap nilai kondisi dictB.get('hello', None)dalam variabel valueuntuk memeriksa apakah tidak None(sebagai dict.get('hello', None)mengembalikan nilai terkait atau None) dan kemudian menggunakannya di dalam tubuh variabel. kondisi:

# dictB = {'hello': 5, 'world': 42}
# dictA = {}
if value := dictB.get('hello', None):
  dictA["hello"] = value
# dictA is now {'hello': 5}
Xavier Guihot
sumber
1
Ini gagal jika nilai == 0
Eric
0

Meskipun jawaban yang diterima menekankan pada prinsip "lihat sebelum Anda melompat" mungkin berlaku untuk sebagian besar bahasa, lebih banyak pythonic mungkin merupakan pendekatan pertama, berdasarkan prinsip python. Belum lagi itu adalah gaya pengkodean yang sah di python. Hal yang penting adalah memastikan Anda menggunakan blok coba kecuali dalam konteks yang benar dan mengikuti praktik terbaik. Misalnya. melakukan terlalu banyak hal dalam blok percobaan, menangkap pengecualian yang sangat luas, atau lebih buruk lagi - klausa kosong kecuali dll.

Lebih mudah meminta maaf daripada izin. (EAFP)

Lihat referensi dokumen python di sini .

Juga, blog ini dari Brett ini, salah satu pengembang inti, membahas sebagian besar secara singkat.

Lihat diskusi SO lainnya di sini :

AJ
sumber