Apakah percobaan coba / kecuali yang bersarang di python merupakan praktik pemrograman yang baik?

201

Saya sedang menulis wadah saya sendiri, yang perlu memberikan akses ke kamus di dalam oleh panggilan atribut. Penggunaan khas wadah akan seperti ini:

dict_container = DictContainer()
dict_container['foo'] = bar
...
print dict_container.foo

Saya tahu mungkin bodoh menulis sesuatu seperti ini, tapi itulah fungsi yang harus saya sediakan. Saya sedang berpikir untuk mengimplementasikan ini dengan cara berikut:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        try:
            return self.dict[item]
        except KeyError:
            print "The object doesn't have such attribute"

Saya tidak yakin apakah percobaan bersarang / kecuali blok adalah praktik yang baik sehingga cara lain adalah dengan menggunakan hasattr()dan has_key():

def __getattribute__(self, item):
        if hasattr(self, item):
            return object.__getattribute__(item)
        else:
            if self.dict.has_key(item):
                return self.dict[item]
            else:
                raise AttributeError("some customised error")

Atau untuk menggunakan salah satunya dan satu coba tangkap blok seperti ini:

def __getattribute__(self, item):
    if hasattr(self, item):
        return object.__getattribute__(item)
    else:
        try:
            return self.dict[item]
        except KeyError:
            raise AttributeError("some customised error")

Opsi mana yang paling pythonic dan elegan?

Michal
sumber
Akan menghargai pemberian dewa python if 'foo' in dict_container:. Amin.
gseattle

Jawaban:

180

Contoh pertama Anda baik-baik saja. Bahkan dokumen Python resmi merekomendasikan gaya ini yang dikenal sebagai EAFP .

Secara pribadi, saya lebih suka menghindari bersarang saat tidak perlu:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        pass  # fallback to dict
    try:
        return self.dict[item]
    except KeyError:
        raise AttributeError("The object doesn't have such attribute") from None

PS. has_key()telah lama tidak digunakan dalam Python 2. Gunakan item in self.dictsebagai gantinya.

lqc
sumber
2
return object.__getattribute__(item)tidak benar dan akan menghasilkan a TypeErrorkarena jumlah argumen yang salah disahkan. Seharusnya sebaliknya return object.__getattribute__(self, item).
martineau
13
PEP 20: flat lebih baik daripada bersarang.
Ioannis Filippidis
7
Apa from Noneartinya di baris terakhir?
niklas
2
@niklas Ini pada dasarnya menekan konteks pengecualian ("selama menangani pengecualian ini, pengecualian lain terjadi" - pesan yang tidak diinginkan). Lihat di sini
Kade
Fakta bahwa Python docs merekomendasikan mencoba bersarang adalah semacam gila. Itu jelas gaya menghebohkan. Cara yang benar menangani rantai operasi di mana hal-hal dapat gagal seperti itu akan menggunakan semacam konstruksi monadik, yang tidak didukung oleh Python.
Henry Henrinson
19

Sementara di Jawa itu memang praktik yang buruk untuk menggunakan Pengecualian untuk kontrol aliran (terutama karena pengecualian memaksa jvm untuk mengumpulkan sumber daya ( lebih lanjut di sini )), dengan Python Anda memiliki 2 prinsip penting: Mengetik Bebek dan EAFP . Ini pada dasarnya berarti bahwa Anda didorong untuk mencoba menggunakan objek seperti yang Anda pikir akan berhasil, dan menangani ketika hal-hal tidak seperti itu.

Singkatnya satu-satunya masalah adalah kode Anda mendapatkan terlalu banyak indentasi. Jika Anda menyukainya, coba sederhanakan beberapa sarang seperti yang disarankan lqc

Bruno Penteado
sumber
10

Untuk contoh spesifik Anda, Anda sebenarnya tidak perlu membuat sarang. Jika ekspresi di tryblok berhasil, fungsi akan kembali, jadi kode apa pun setelah seluruh blok coba / kecuali hanya akan dijalankan jika upaya pertama gagal. Jadi Anda bisa melakukannya:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        pass
    # execution only reaches here when try block raised AttributeError
    try:
        return self.dict[item]
    except KeyError:
        print "The object doesn't have such attribute"

Bersarang mereka tidak buruk, tetapi saya merasa seperti membiarkannya rata membuat struktur lebih jelas: Anda secara berurutan mencoba serangkaian hal dan mengembalikan yang pertama yang berhasil.

Secara kebetulan, Anda mungkin ingin memikirkan apakah Anda benar-benar ingin menggunakannya __getattribute__daripada di __getattr__sini. Menggunakan __getattr__akan menyederhanakan hal-hal karena Anda akan tahu bahwa proses pencarian atribut normal telah gagal.

BrenBarn
sumber
10

Hanya hati-hati - dalam hal ini pertama kali finallydisentuh TAPI dilewati juga.

def a(z):
    try:
        100/z
    except ZeroDivisionError:
        try:
            print('x')
        finally:
            return 42
    finally:
        return 1


In [1]: a(0)
x
Out[1]: 1
Sławomir Lenart
sumber
Wow, itu mengejutkan saya ... Bisakah Anda mengarahkan saya ke beberapa fragmen dokumentasi yang menjelaskan perilaku ini?
Michal
1
@Michal: fyi: kedua finallyblok dieksekusi a(0), tetapi hanya orangtua finally-returnyang dikembalikan.
Sławomir Lenart
7

Menurut pendapat saya ini akan menjadi cara yang paling Pythonic untuk menanganinya, meskipun dan karena itu membuat pertanyaan Anda diperdebatkan. Perhatikan bahwa ini mendefinisikan __getattr__()alih-alih __getattribute__()karena melakukan itu berarti hanya berurusan dengan atribut "khusus" yang disimpan dalam kamus internal.

def __getattr__(self, name):
    """only called when an attribute lookup in the usual places has failed"""
    try:
        return self.my_dict[name]
    except KeyError:
        raise AttributeError("some customized error message")
martineau
sumber
2
Perhatikan bahwa menaikkan pengecualian di exceptblok dapat memberikan hasil membingungkan dengan Python 3. Itu karena (per PEP 3134) Python 3 melacak pengecualian pertama (the KeyError) sebagai "konteks" dari pengecualian kedua (the AttributeError), dan jika mencapai tingkat atas, ia akan mencetak traceback yang mencakup kedua pengecualian. Ini bisa membantu ketika pengecualian kedua tidak diharapkan, tetapi jika Anda sengaja menaikkan pengecualian kedua, itu tidak diinginkan. Untuk Python 3.3, PEP 415 menambahkan kemampuan untuk menekan konteks dengan menggunakan raise AttributeError("whatever") from None.
Blckknght
3
@ Blckknght: Mencetak traceback yang mencakup kedua pengecualian akan baik-baik saja dalam kasus ini. Dengan kata lain, saya tidak berpikir pernyataan selimut Anda bahwa itu selalu tidak diinginkan itu benar. Dalam penggunaan di sini, itu menutup KeyErrormenjadi AttributeErrordan menunjukkan bahwa ini apa yang terjadi di traceback akan berguna dan yang sesuai.
martineau
Untuk situasi yang lebih rumit, Anda mungkin benar, tetapi saya pikir ketika Anda mengonversi antara jenis pengecualian, Anda sering tahu bahwa detail pengecualian pertama tidak masalah bagi pengguna luar. Yaitu, jika __getattr__memunculkan pengecualian, bug tersebut mungkin salah ketik pada akses atribut, bukan bug implementasi dalam kode kelas saat ini. Menampilkan pengecualian sebelumnya karena konteks dapat mengacaukannya. Dan bahkan ketika Anda menekan konteks dengan raise Whatever from None, Anda masih bisa mendapatkan pengecualian sebelumnya jika perlu via ex.__context__.
Blckknght
1
Saya ingin menerima jawaban Anda namun dalam pertanyaan saya lebih ingin tahu apakah menggunakan blok try / catch bersarang atau tidak adalah praktik yang baik. Di sisi lain itu adalah solusi paling elegan dan saya akan menggunakannya dalam kode saya. Banyak terima kasih Martin.
Michal
Michal: Sama-sama. Ini juga lebih cepat daripada menggunakan __getattribute__().
martineau
4

Dalam Python lebih mudah untuk meminta maaf daripada izin. Jangan memusingkan penanganan pengecualian bersarang.

(Selain itu, has*hampir selalu menggunakan pengecualian di bawah penutup.)

Ignacio Vazquez-Abrams
sumber
4

Menurut dokumentasi , lebih baik untuk menangani beberapa pengecualian melalui tuple atau seperti ini:

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError as e:
    print "I/O error({0}): {1}".format(e.errno, e.strerror)
except ValueError:
    print "Could not convert data to an integer."
except:
    print "Unexpected error:", sys.exc_info()[0]
    raise
Blairg23
sumber
2
Jawaban ini tidak benar-benar menjawab pertanyaan awal tetapi bagi siapa pun yang membacanya, perhatikan "telanjang" kecuali pada akhirnya adalah ide yang mengerikan (biasanya) karena akan menangkap semua hal termasuk misalnya NameError & KeyboardInterrupt - yang biasanya tidak seperti yang Anda maksudkan!
kalah
Mengingat bahwa kode kembali memunculkan pengecualian yang sama tepat setelah pernyataan cetak, apakah ini benar-benar masalah besar. Dalam hal ini dapat memberikan lebih banyak konteks pengecualian tanpa menyembunyikannya. Jika itu tidak naik kembali maka saya akan sepenuhnya setuju, tetapi saya tidak berpikir ada risiko untuk menyembunyikan pengecualian yang tidak Anda inginkan.
NimbusScale
4

Contoh yang bagus dan sederhana untuk percobaan bersarang / kecuali adalah sebagai berikut:

import numpy as np

def divide(x, y):
    try:
        out = x/y
    except:
        try:
            out = np.inf * x / abs(x)
        except:
            out = np.nan
    finally:
        return out

Sekarang coba berbagai kombinasi dan Anda akan mendapatkan hasil yang benar:

divide(15, 3)
# 5.0

divide(15, 0)
# inf

divide(-15, 0)
# -inf

divide(0, 0)
# nan

[Tentu saja kita punya numpy jadi kita tidak perlu membuat fungsi ini]

Perawatan Merusak
sumber
2

Satu hal yang saya ingin hindari adalah menaikkan pengecualian baru saat menangani yang lama. Itu membuat pesan kesalahan membingungkan untuk dibaca.

Misalnya, dalam kode saya, saya awalnya menulis

try:
    return tuple.__getitem__(self, i)(key)
except IndexError:
    raise KeyError(key)

Dan saya mendapat pesan ini.

>>> During handling of above exception, another exception occurred.

Yang saya inginkan adalah ini:

try:
    return tuple.__getitem__(self, i)(key)
except IndexError:
    pass
raise KeyError(key)

Itu tidak memengaruhi bagaimana pengecualian ditangani. Di salah satu blok kode, KeyError akan ditangkap. Ini hanya masalah mendapatkan poin gaya.

Steve Zelaznik
sumber
Lihat penggunaan jawaban kenaikan diterima from None, untuk poin gaya yang lebih banyak lagi . :)
Pianosaurus
1

Jika coba-kecuali-akhirnya bersarang di dalam akhirnya blok, hasil dari "anak" akhirnya dipertahankan. Saya belum menemukan expaination resmi, tetapi cuplikan kode berikut menunjukkan perilaku ini dalam Python 3.6.

def f2():
    try:
        a = 4
        raise SyntaxError
    except SyntaxError as se:
        print('log SE')
        raise se from None
    finally:
        try:
            raise ValueError
        except ValueError as ve:
            a = 5
            print('log VE')
            raise ve from None
        finally:
            return 6       
        return a

In [1]: f2()
log SE
log VE
Out[2]: 6
Guanghua Shu
sumber
Perilaku ini berbeda dari contoh yang diberikan oleh @ Sławomir Lenart ketika akhirnya bersarang di dalam kecuali blok.
Guanghua Shu
0

Saya tidak berpikir itu masalah menjadi pythonic atau elegan. Ini masalah mencegah pengecualian sebanyak yang Anda bisa. Pengecualian dimaksudkan untuk menangani kesalahan yang mungkin terjadi dalam kode atau acara yang tidak dapat Anda kendalikan. Dalam hal ini Anda memiliki kontrol penuh ketika memeriksa apakah suatu item adalah atribut atau dalam kamus, jadi hindari pengecualian bersarang dan tetap dengan upaya kedua Anda.

burung hantu
sumber
Dari dokumen: Dalam lingkungan multi-utas, pendekatan LBYL (Look Before You Leap) dapat berisiko memperkenalkan kondisi ras antara "yang melihat" dan "yang melompat". Misalnya, kode, jika kunci dalam pemetaan: kembali pemetaan [kunci] dapat gagal jika utas lain menghapus kunci dari pemetaan setelah pengujian, tetapi sebelum pencarian. Masalah ini dapat diselesaikan dengan kunci atau dengan menggunakan pendekatan EAFP (Lebih mudah untuk meminta maaf daripada izin) .
Nuno André