Python: Bagaimana saya bisa tahu pengecualian mana yang mungkin terlempar dari pemanggilan metode

89

Apakah ada cara untuk mengetahui (pada waktu pengkodean) pengecualian mana yang diharapkan saat menjalankan kode python? Saya akhirnya menangkap kelas Pengecualian dasar 90% dari waktu karena saya tidak tahu jenis pengecualian mana yang mungkin dilempar (dan tidak memberitahu saya untuk membaca dokumentasi. Berkali-kali pengecualian dapat disebarkan dari dalam. Dan banyak lagi kali dokumentasi tidak diperbarui atau benar). Apakah ada semacam alat untuk memeriksa ini? (seperti dengan membaca kode python dan libs)?

GabiMe
sumber
2
Ingatlah bahwa di Python <2.6, Anda juga bisa membuat raisestring, bukan hanya BaseExceptionsubclass. Jadi jika Anda memanggil ke kode perpustakaan yang di luar kendali Anda, bahkan except Exceptiontidak cukup, karena tidak akan menangkap pengecualian string. Seperti yang ditunjukkan orang lain, Anda menggonggong pohon yang salah di sini.
Daniel Pryden
Saya tidak tahu itu. Saya pikir kecuali Pengecualian: .. menangkap hampir semuanya.
GabiMe
2
except Exceptionberfungsi dengan baik untuk menangkap pengecualian string di Python 2.6 dan yang lebih baru.
Jeffrey Harris

Jawaban:

22

Saya kira solusi hanya bisa tidak tepat karena kurangnya aturan pengetikan statis.

Saya tidak mengetahui beberapa alat yang memeriksa pengecualian, tetapi Anda dapat menemukan alat Anda sendiri yang sesuai dengan kebutuhan Anda (peluang bagus untuk bermain sedikit dengan analisis statis).

Sebagai upaya pertama, Anda bisa menulis fungsi yang membangun AST, menemukan semua Raisenode, dan kemudian mencoba mencari pola umum untuk memunculkan pengecualian (misalnya memanggil konstruktor secara langsung)

Biarlah xprogram berikut ini:

x = '''\
if f(x):
    raise IOError(errno.ENOENT, 'not found')
else:
    e = g(x)
    raise e
'''

Bangun AST menggunakan compilerpaket:

tree = compiler.parse(x)

Kemudian tentukan Raisekelas pengunjung:

class RaiseVisitor(object):
    def __init__(self):
        self.nodes = []
    def visitRaise(self, n):
        self.nodes.append(n)

Dan berjalan di Raisenode pengumpul AST :

v = RaiseVisitor()
compiler.walk(tree, v)

>>> print v.nodes
[
    Raise(
        CallFunc(
            Name('IOError'),
            [Getattr(Name('errno'), 'ENOENT'), Const('not found')],
            None, None),
        None, None),
    Raise(Name('e'), None, None),
]

Anda dapat melanjutkan dengan menyelesaikan simbol menggunakan tabel simbol kompilator, menganalisis dependensi data, dll. Atau Anda dapat menyimpulkan, CallFunc(Name('IOError'), ...)"seharusnya berarti menaikkan IOError", yang cukup OK untuk hasil praktis yang cepat :)

Andrey Vlasovskikh
sumber
Terima kasih atas jawaban yang menarik ini. Tidak mengerti mengapa saya harus mencari sesuatu yang lebih dari semua node yang dinaikkan. Mengapa saya harus "menyelesaikan simbol menggunakan tabel simbol compiler, menganalisis dependensi data"? Bukankah satu-satunya cara untuk meningkatkan eksepsi adalah dengan menaikkan ()?
GabiMe
1
Dengan v.nodesnilai di atas, Anda tidak bisa benar-benar mengatakan, apa itu Name('IOError')atau Name('e'). Anda tidak tahu apa nilai itu IOErrordan ebisa menunjuk, karena mereka disebut variabel bebas. Bahkan jika konteks pengikatannya diketahui (di sini tabel simbol ikut bermain), Anda harus melakukan beberapa jenis analisis ketergantungan data untuk menyimpulkan nilai pastinya (ini harus sulit dilakukan dengan Python).
Andrey Vlasovskikh
Saat Anda mencari solusi semi-otomatis yang praktis, daftar yang ['IOError(errno.ENOENT, "not found")', 'e']ditampilkan kepada pengguna sudah cukup. Tetapi Anda tidak dapat menyimpulkan kelas aktual dari nilai variabel yang diwakili oleh string :) (maaf telah memposting ulang)
Andrey Vlasovskikh
1
Iya. Metode ini, meskipun pintar, tidak benar-benar memberi Anda cakupan yang lengkap. Karena sifat dinamis Python, sangat mungkin (meskipun jelas ide yang buruk) untuk kode yang Anda panggil untuk melakukan sesuatu seperti exc_class = raw_input(); exec "raise " + exc_class. Intinya adalah bahwa analisis statis semacam ini tidak benar-benar mungkin dilakukan dalam bahasa dinamis seperti Python.
Daniel Pryden
7
Omong-omong, Anda bisa saja find /path/to/library -name '*.py' | grep 'raise 'mendapatkan hasil yang serupa :)
Andrey Vlasovskikh
23

Anda seharusnya hanya menangkap pengecualian yang akan Anda tangani.

Menangkap semua pengecualian berdasarkan jenis konkretnya adalah omong kosong. Anda harus menangkap pengecualian khusus yang dapat dan akan Anda tangani. Untuk pengecualian lain, Anda dapat menulis tangkapan umum yang menangkap "pengecualian dasar", mencatatnya (menggunakan str()fungsi) dan menghentikan program Anda (atau melakukan sesuatu yang sesuai dalam situasi macet).

Jika Anda benar-benar akan menangani semua pengecualian dan yakin tidak ada yang fatal (misalnya, jika Anda menjalankan kode di semacam lingkungan kotak pasir), maka pendekatan Anda untuk menangkap BaseException generik sesuai dengan tujuan Anda.

Anda mungkin juga tertarik dengan referensi pengecualian bahasa , bukan referensi untuk perpustakaan yang Anda gunakan.

Jika referensi library benar-benar buruk dan tidak menampilkan kembali pengecualiannya sendiri saat menangkap sistem, satu-satunya pendekatan yang berguna adalah menjalankan pengujian (mungkin menambahkannya ke rangkaian pengujian, karena jika ada sesuatu yang tidak terdokumentasi, itu dapat berubah!) . Hapus file penting untuk kode Anda dan periksa pengecualian apa yang dilempar. Berikan terlalu banyak data dan periksa kesalahan apa yang dihasilkannya.

Anda tetap harus menjalankan pengujian, karena, meskipun metode untuk mendapatkan pengecualian dengan kode sumber ada, itu tidak akan memberi Anda ide bagaimana Anda harus menangani semua itu . Mungkin Anda harus menampilkan pesan kesalahan "File needful.txt tidak ditemukan!" kapan kamu menangkap IndexError? Hanya tes yang tahu.

P Shved
sumber
27
Tentu, Tapi bagaimana seseorang bisa memutuskan pengecualian mana yang harus dia tangani jika dia tidak tahu apa yang mungkin dilempar ??
GabiMe
@ bugspy.net, memperbaiki jawaban saya untuk mencerminkan masalah ini
P Shved
Mungkin sudah waktunya untuk penganalisis kode sumber yang bisa mengetahui ini? Seharusnya tidak terlalu sulit untuk dikembangkan menurut saya
GabiMe
@ bugspy.net, saya menguatkan klausul mengapa ini mungkin bukan waktunya untuk itu.
P Shved
Tentu kamu benar. Namun mungkin masih menarik - selama pengembangan - untuk mengetahui jenis pengecualian yang mungkin terjadi.
hek2mgl
11

Alat yang tepat untuk mengatasi masalah ini adalah unittests. Jika Anda memiliki pengecualian yang dimunculkan oleh kode nyata yang tidak dimunculkan oleh unittests, maka Anda memerlukan lebih banyak unittests.

Pertimbangkan ini

def f(duck):
    try:
        duck.quack()
    except ??? could be anything

bebek bisa menjadi benda apapun

Tentunya Anda dapat memiliki AttributeErroritik if tidak memiliki dukun, TypeErrorbebek if memiliki dukun tetapi tidak dapat dipanggil. Anda tidak tahu apa yang duck.quack()mungkin timbul, bahkan mungkin a DuckErroratau sesuatu

Sekarang misalkan Anda memiliki kode seperti ini

arr[i] = get_something_from_database()

Jika memunculkan IndexErrorAnda tidak tahu apakah itu berasal dari arr [i] atau dari dalam fungsi database. biasanya tidak terlalu menjadi masalah di mana pengecualian terjadi, melainkan sesuatu yang salah dan apa yang Anda inginkan tidak terjadi.

Teknik praktis adalah menangkap dan mungkin menghidupkan kembali pengecualian seperti ini

except Exception as e
    #inspect e, decide what to do
    raise
John La Rooy
sumber
Mengapa harus menangkapnya jika Anda akan "menghidupkannya kembali"?
Tarnay Kálmán
Anda tidak perlu mengulanginya, itulah yang seharusnya ditunjukkan oleh komentar tersebut.
John La Rooy
2
Anda juga dapat memilih untuk mencatat pengecualian di suatu tempat dan kemudian mengaktifkan kembali
John La Rooy
3
Saya tidak berpikir bahwa menulis tes unit adalah jawabannya. Pertanyaannya adalah "bagaimana saya bisa mengetahui pengecualian mana yang diharapkan" dan menulis unit test tidak akan membantu Anda menemukan ini. Nyatanya, untuk menulis tes unit Anda sudah harus tahu pengecualian mana yang diharapkan sehingga untuk menulis tes unit yang benar Anda harus menjawab pertanyaan aslinya juga.
Bruno Ranschaert
6

Sejauh ini tidak ada yang menjelaskan, mengapa Anda tidak dapat memiliki daftar pengecualian yang lengkap dan 100% benar, jadi saya pikir perlu dikomentari. Salah satu alasannya adalah fungsi kelas satu. Katakanlah Anda memiliki fungsi seperti ini:

def apl(f,arg):
   return f(arg)

Sekarang apldapat memunculkan pengecualian apa pun yang ftimbul. Meskipun tidak banyak fungsi seperti itu di pustaka inti, apa pun yang menggunakan pemahaman daftar dengan filter khusus, peta, pengurangan, dll. Akan terpengaruh.

Dokumentasi dan penganalisis sumber adalah satu-satunya sumber informasi yang "serius" di sini. Ingatlah apa yang tidak bisa mereka lakukan.

viraptor
sumber
5

Saya mengalami ini ketika menggunakan soket, saya ingin mengetahui semua kondisi kesalahan yang akan saya hadapi (jadi daripada mencoba membuat kesalahan dan mencari tahu soket apa yang saya hanya ingin daftar ringkas). Akhirnya saya akhirnya grep'ing "/usr/lib64/python2.4/test/test_socket.py" untuk "meningkatkan":

$ grep raise test_socket.py
Any exceptions raised by the clients during their tests
        raise TypeError, "test_func must be a callable function"
    raise NotImplementedError, "clientSetUp must be implemented."
    def raise_error(*args, **kwargs):
        raise socket.error
    def raise_herror(*args, **kwargs):
        raise socket.herror
    def raise_gaierror(*args, **kwargs):
        raise socket.gaierror
    self.failUnlessRaises(socket.error, raise_error,
    self.failUnlessRaises(socket.error, raise_herror,
    self.failUnlessRaises(socket.error, raise_gaierror,
        raise socket.error
    # Check that setting it to an invalid value raises ValueError
    # Check that setting it to an invalid type raises TypeError
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,

Yang merupakan daftar kesalahan yang cukup ringkas. Sekarang tentu saja ini hanya bekerja berdasarkan kasus per kasus dan tergantung pada tes yang akurat (yang biasanya memang demikian). Jika tidak, Anda perlu menangkap semua pengecualian, mencatatnya dan membedahnya dan mencari cara untuk menanganinya (yang dengan pengujian unit tidak akan sulit).

Kurt
sumber
4
This strengthen my argument, that exception handling in Python is very problematic, if we need to use grep or source analyzers to deal with something so basic (which for example in java existed from day one. Sometimes verbosity is a good thing. Java is verbose but at least there are no nasty surprises)
GabiMe
@GabiMe, It's not like this ability (or static typing in general) is a silver bullet to prevent all bugs. Java is full of nasty surprises. That's why eclipse regularly crashes.
John La Rooy
2

There are two ways that I found informative. The first one, run the code in iPython, which will display the exception type.

n = 2
str = 'me '
str + 2
TypeError: unsupported operand type(s) for +: 'int' and 'str'

In the second way we settle for catching too much and improve on it over time. Include a try expression in your code and catch except Exception as err. Print sufficient data to know what exception was thrown. As exceptions are thrown improve your code by adding a more precise except clause. When you feel that you have caught all relevant exceptions remove the all inclusive one. A good thing to do anyway because it swallows programming errors.

try:
   so something
except Exception as err:
   print "Some message"
   print err.__class__
   print err
   exit(1)
Rahav
sumber
1

biasanya, Anda hanya perlu menangkap pengecualian di sekitar beberapa baris kode. Anda tidak ingin memasukkan seluruh mainfungsi Anda ke dalam try exceptklausa. untuk setiap beberapa baris Anda harus selalu sekarang (atau dapat dengan mudah memeriksa) jenis pengecualian apa yang mungkin dimunculkan.

docs memiliki daftar lengkap pengecualian bawaan . jangan mencoba untuk kecuali pengecualian yang tidak Anda harapkan, mereka mungkin ditangani / diharapkan dalam kode panggilan.

edit : apa yang mungkin terlempar tergantung pada apa yang Anda lakukan! mengakses elemen acak dari sebuah urutan IndexError:, elemen acak dari sebuah dict:, KeyErrordll.

Coba saja jalankan beberapa baris itu di IDLE dan buat pengecualian. Tapi unittest akan menjadi solusi yang lebih baik, tentu saja.

SilentGhost
sumber
1
This not answering my simple question. I don't ask on how to design my exception handling, or when or how to catch. I ask how to find out what might be thrown
GabiMe
1
@bugspy.net: It is impossible to do what you ask, and this is a perfectly valid workaround.
Daniel Pryden