Python: Bir yöntem çağrısından hangi istisnaların atılabileceğini nasıl bilebilirim


89

Python kodunu çalıştırırken hangi istisnaların bekleneceğini bilmenin bir yolu var mı (kodlama zamanında)? Hangi istisna türünün atılabileceğini bilmediğimden (ve bana belgeleri okumamı söylemeyin. Çoğu kez derinden bir istisna yayılabilir. belgelerin güncellenmediği veya doğru olmadığı zamanlar). Bunu kontrol etmek için bir tür araç var mı? (python kodunu ve kitaplıkları okumak gibi)?


2
Python <2.6'da raisedizeler de yapabileceğinizi unutmayın, sadece BaseExceptionalt sınıflar değil . Dolayısıyla, kontrolünüz dışındaki kitaplık kodunu çağırıyorsanız except Exception, dize istisnalarını yakalayamayacağı için bile yeterli değildir. Başkalarının da belirttiği gibi, burada yanlış ağaca havlıyorsunuz.
Daniel Pryden

Bunu bilmiyordum. İstisna dışında düşündüm: .. hemen hemen her şeyi yakalar.
GabiMe

2
except ExceptionPython 2.6 ve sonraki sürümlerde dize istisnalarını yakalamak için iyi çalışır.
Jeffrey Harris

Yanıtlar:


22

Sanırım bir çözüm, statik yazım kurallarının olmaması nedeniyle kesin olmayabilir.

İstisnaları kontrol eden bir araçtan haberdar değilim, ancak ihtiyaçlarınıza uygun kendi aracınızı bulabilirsiniz (statik analizle biraz oynamak için iyi bir şans).

İlk girişim olarak, bir AST oluşturan, tüm Raisedüğümleri bulan ve ardından istisnaları artırmanın ortak modellerini bulmaya çalışan bir işlev yazabilirsiniz (örneğin, bir kurucuyu doğrudan çağırmak)

xAşağıdaki program olalım :

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

AST'yi aşağıdaki compilerpaketi kullanarak oluşturun :

tree = compiler.parse(x)

Ardından bir Raiseziyaretçi sınıfı tanımlayın :

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

Ve AST toplama Raisedüğümlerinde yürüyün :

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),
]

Derleyici sembol tablolarını kullanarak sembolleri çözerek, veri bağımlılıklarını analiz ederek, vb. Devam edebilirsiniz. Ya da sadece CallFunc(Name('IOError'), ...)"kesinlikle yükseltme anlamına gelmelidir" sonucuna varabilirsiniz, bu IOErrorhızlı pratik sonuçlar için oldukça uygundur :)


Bu ilginç cevap için teşekkürler. Yine de neden tüm yükseltme düğümlerinden başka bir şey aramam gerektiğini anlamadım. Neden "derleyici sembol tablolarını kullanarak sembolleri çözmeli, veri bağımlılıklarını analiz etmeliyim"? İstisna yaratmanın tek yolu, yükseltmek () değil mi?
GabiMe

1
Verilen v.nodesdeğer yukarıdaki, aslında şeyin ne diyemem Name('IOError')ya Name('e'). Sen ya bunlar ne değer (ler) bilmiyorum IOErrorve eonlar sözde serbest değişkenler olarak, işaret edebilir. Bağlama bağlamları bilinse bile (burada sembol tabloları devreye giriyor), kesin değerlerini çıkarmak için bir tür veri bağımlılığı analizi yapmalısınız (bu Python'da zor olmalı).
Andrey Vlasovskikh

Pratik bir yarı otomatik çözüm ararken ['IOError(errno.ENOENT, "not found")', 'e'], kullanıcıya gösterilen bir liste gayet iyi. Ancak dizelerle temsil edilen değişkenlerin gerçek değer sınıflarını çıkaramazsınız :) (yeniden
yayınladığım

1
Evet. Bu yöntem, akıllıca olsa da, aslında size tam bir koruma sağlamaz. Python'un dinamik doğası nedeniyle, böyle bir şey yapmak için aradığınız kodun mükemmel bir şekilde mümkün (ama açıkçası kötü bir fikir) exc_class = raw_input(); exec "raise " + exc_class. Mesele şu ki, bu tür bir statik analiz Python gibi dinamik bir dilde gerçekten mümkün değildir.
Daniel Pryden

7
Bu arada, sadece find /path/to/library -name '*.py' | grep 'raise 'benzer sonuçlar elde etmek için yapabilirsiniz :)
Andrey Vlasovskikh

23

Yalnızca halledeceğiniz istisnaları yakalamalısınız.

Tüm istisnaları somut türlerine göre yakalamak saçma. Eğer belirli durumları yakalamak gerektiğini olabilir ve olacaktır işlemek. Diğer istisnalar için, "temel İstisna" str()yı yakalayan, günlüğe kaydeden ( işlev kullanın ) ve programınızı sonlandıran (veya çökme durumunda uygun olan başka bir şey yapan) genel bir yakalama yazabilirsiniz .

Tüm istisnaları gerçekten ele alacaksanız ve hiçbirinin ölümcül olmadığından eminseniz (örneğin, kodu bir tür korumalı ortamda çalıştırıyorsanız), o zaman genel BaseException'ı yakalama yaklaşımınız hedeflerinize uyar.

Kullandığınız kitaplık için bir referans değil, dil istisnası referansı da ilginizi çekebilir .

Kütüphane referansı gerçekten zayıfsa ve sistem olanları yakalarken kendi istisnalarını yeniden atmazsa , tek yararlı yaklaşım testleri çalıştırmaktır (belki bunu test paketine ekleyin, çünkü bir şey belgelenmemişse değişebilir!) . Kodunuz için çok önemli bir dosyayı silin ve hangi istisnanın atıldığını kontrol edin. Çok fazla veri sağlayın ve hangi hatayı verdiğini kontrol edin.

Yine de testler yapmanız gerekecek, çünkü istisnaları kaynak koda göre alma yöntemi mevcut olsa bile, bunlardan herhangi birini nasıl ele almanız gerektiği konusunda size herhangi bir fikir vermeyecektir . Belki de "Needful.txt dosyası bulunamadı!" Hata mesajını göstermelisiniz. Eğer yakalamak zaman IndexError? Sadece test söyleyebilir.


27
Elbette, ama neyin fırlatılacağını bilmiyorsa, hangi istisnalarla baş etmesi gerektiğine nasıl karar verilir?
GabiMe

@ bugspy.net, cevabımı bu konuyu yansıtacak şekilde düzeltti
P Shved

Belki de bunu öğrenebilecek kaynak kodu analizcisinin zamanı gelmiştir? Bence geliştirmek çok zor olmamalı
GabiMe

@ bugspy.net, neden bunun zamanı olmayabileceği maddesini cesaretlendirdim.
P Shved

Tabii haklısın. Ancak - geliştirme sırasında - hangi tür istisnaların ortaya çıkabileceğini bilmek yine de ilginç olabilir.
hek2mgl

11

Bu sorunu çözmek için doğru araç birim testleridir. Birim testlerinin yükseltmediği gerçek kodla ortaya çıkan istisnalar yaşıyorsanız, daha fazla birim testine ihtiyacınız vardır.

Bunu düşün

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

ördek herhangi bir nesne olabilir

Açıktır ki, AttributeErroreğer ördeğin şarabı TypeErroryoksa, ördeğin şarabı varsa ama çağrılabilir değildir. Neyin duck.quack()ortaya çıkacağı hakkında hiçbir fikriniz yok , hatta belki bir DuckErrorveya başka bir şey

Şimdi bunun gibi bir kodunuz olduğunu varsayalım

arr[i] = get_something_from_database()

Bir yükseltirse IndexError, arr [i] 'den mi yoksa veritabanı işlevinin derinliklerinden mi geldiğini bilemezsiniz. genellikle istisnanın nerede meydana geldiği çok önemli değildir, bunun yerine bir şeylerin ters gitmesi ve olmasını istediğiniz şeyin gerçekleşmemesi.

Kullanışlı bir teknik, bunun gibi istisnayı yakalamak ve belki yeniden yükseltmektir.

except Exception as e
    #inspect e, decide what to do
    raise

Onu "yeniden büyüteceksen" neden hiç yakalayasın ki?
Tarnay Kálmán

Sen yok olması comment belirtmek gerekiyordu ne olduğunu, bunu tekrar yükseltme için.
John La Rooy

2
İstisnayı bir yere kaydetmeyi ve ardından yeniden artırmayı da seçebilirsiniz
John La Rooy

3
Birim testleri yazmanın cevap olduğunu sanmıyorum. Soru, "hangi istisnaları bekleyeceğimi nasıl öğrenebilirim" ve birim testleri yazmak bunu bulmanıza yardımcı olmayacaktır. Aslında, birim testi yazmak için, doğru bir birim testi yazmak için hangi istisnaları bekleyeceğinizi zaten bilmeniz gerekir. Asıl soruyu da yanıtlamanız gerekir.
Bruno Ranschaert

6

Şimdiye kadar kimse neden tam,% 100 doğru bir istisnalar listesine sahip olamayacağınızı açıklamadı, bu yüzden yorum yapmaya değer olduğunu düşündüm. Sebeplerden biri birinci sınıf bir işlevdir. Diyelim ki böyle bir işleve sahipsin:

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

Şimdi apl, yükselten herhangi bir istisnayı fgündeme getirebilir . Çekirdek kitaplıkta bunun gibi pek çok işlev olmasa da, özel filtrelerle liste anlama, eşleme, küçültme vb. Kullanan her şey etkilenir.

Dokümantasyon ve kaynak analizörleri buradaki tek "ciddi" bilgi kaynaklarıdır. Ne yapamayacaklarını aklınızda tutun.


5

Soketi kullanırken bununla karşılaştım, karşılaşacağım tüm hata koşullarını öğrenmek istedim (bu nedenle hata oluşturmaya ve hangi soketin ne olduğunu anlamaya çalışmak yerine sadece kısa bir liste istedim). Sonunda "yükseltme" için "/usr/lib64/python2.4/test/test_socket.py" "grep'ing" yaptım:

$ 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,

Oldukça kısa bir hata listesi. Elbette bu, yalnızca duruma göre çalışır ve testlerin doğruluğuna bağlıdır (ki genellikle bunlar). Aksi takdirde, hemen hemen tüm istisnaları yakalamanız, günlüğe kaydetmeniz ve bunları incelemeniz ve bunları nasıl ele alacağınızı bulmanız gerekir (ki bu, birim testi ile zor olmazdı).


4
Bu benim argümanımı güçlendiriyor, Python'da istisna işlemenin çok sorunlu olduğu, çok basit bir şeyle başa çıkmak için grep veya kaynak çözümleyicileri kullanmamız gerekiyorsa (örneğin java'da ilk günden beri var. Bazen ayrıntı iyi bir şeydir. Java ayrıntılıdır. ama en azından kötü sürprizler yok)
GabiMe

@GabiMe, Bu yetenek (veya genel olarak statik yazım), tüm hataları önlemek için bir sihirli değnek değildir. Java, kötü sürprizlerle dolu . Bu yüzden tutulma düzenli olarak çöküyor.
John La Rooy

2

Bilgilendirici bulduğum iki yol var. İlki, istisna türünü görüntüleyecek olan iPython'da kodu çalıştırın.

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

İkinci olarak, çok fazla yakalamaya razı oluruz ve zamanla geliştiririz. Kodunuza bir tryifade ekleyin ve yakalayın except Exception as err. Hangi istisnanın atıldığını bilmek için yeterli veri yazdırın. İstisnalar atıldıkça, daha kesin bir exceptcümle ekleyerek kodunuzu geliştirin . İlgili tüm istisnaları yakaladığınızı hissettiğinizde, her şey dahil olanı kaldırın. Yine de yapılacak iyi bir şey çünkü programlama hatalarını yutuyor.

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

1

normalde, yalnızca birkaç satır kod civarında istisna yakalamanız gerekir. Tüm mainişlevinizi try exceptcümleye koymak istemezsiniz . her birkaç satır için her zaman şimdi (veya kolayca kontrol edebilmelisiniz) ne tür bir istisna ortaya çıkabileceğini.

belgelerde yerleşik istisnaların kapsamlı bir listesi vardır . Beklemediğiniz istisnalar dışında denemeyin, bunlar arama kodunda ele alınabilir / beklenebilir.

düzenleme : neyin fırlatılabileceği, açıkça ne yaptığınıza bağlıdır! bir dizinin rastgele elemanına erişim:IndexError bir diktenin rastgele öğesi: KeyErrorvb.

Sadece bu birkaç satırı IDLE'de çalıştırmayı deneyin ve bir istisnaya neden olun. Ancak birim testi doğal olarak daha iyi bir çözüm olacaktır.


1
Bu benim basit soruma cevap vermiyor. İstisna halini nasıl tasarlayacağımı ya da ne zaman ve nasıl yakalayacağımı sormuyorum. Neyin fırlatılabileceğini nasıl bulacağımı soruyorum
GabiMe

1
@ bugspy.net: İstediğinizi yapmak imkansız ve bu tamamen geçerli bir çözüm.
Daniel Pryden
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.