İstisnaları ne zaman ve nasıl kullanmalıyım?


20

Ayar

İstisnaların ne zaman ve nasıl kullanılacağını belirlemekte sık sık sorun yaşıyorum. Basit bir örnek düşünelim: Abe Vigoda'nın hala hayatta olup olmadığını belirlemek için bir web sayfasını kazıyordum, " http://www.abevigoda.com/ " deyin . Bunu yapmak için tek yapmamız gereken sayfayı indirmek ve "Abe Vigoda" ifadesinin göründüğü zamanları aramak. Abe'nin durumunu da içerdiğinden, ilk görünümü geri getiriyoruz. Kavramsal olarak şöyle görünecektir:

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    # he's either alive or dead
    return status == "alive"

Nerede parse_abe_status(s)"Abe Vigoda bir şeydir " şeklinde bir dize alır ve "bir şey " bölümünü döndürür .

Bu sayfayı Abe'nin durumu için kazımanın çok daha iyi ve daha sağlam yolları olduğunu iddia etmeden önce, bunun içinde bulunduğum ortak bir durumu vurgulamak için kullanılan basit ve anlaşılır bir örnek olduğunu unutmayın.

Şimdi, bu kod nerede sorunla karşılaşabilir? Diğer hataların yanı sıra, bazı "beklenen" olanlar:

  • download_pagesayfayı indiremeyebilir ve atar IOError.
  • URL doğru sayfayı işaret etmeyebilir veya sayfa yanlış indirilmiş olabilir ve bu nedenle isabet yoktur. hitsboş liste o zaman.
  • Web sayfası değiştirildi, muhtemelen sayfa hakkındaki varsayımlarımız yanlış. Belki Abe Vigoda'dan 4 bahsedeceğiz, ama şimdi 5 buluyoruz.
  • Bazı nedenlerden dolayı, hits[0]"Abe Vigoda bir şeydir " biçiminde bir dize olmayabilir ve bu nedenle doğru şekilde ayrıştırılamaz.

İlk durum benim için gerçekten bir sorun değil: a IOErroratıldı ve fonksiyonumun arayanı tarafından ele alınabilir. Öyleyse diğer davaları ve bunları nasıl ele alabileceğimi ele alalım. Ama önce, parse_abe_statusmümkün olan en aptal şekilde uyguladığımızı varsayalım :

def parse_abe_status(s):
    return s[13:]

Yani, herhangi bir hata kontrolü yapmaz. Şimdi, seçeneklere devam edin:

Seçenek 1: Dönüş None

Arayana geri dönerek bir şeylerin yanlış gittiğini söyleyebilirim None:

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    if not hits:
        return None

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    # he's either alive or dead
    return status == "alive"

Arayan Nonebenim işlevimi alırsa , Abe Vigoda'dan bahsedilmediğini varsaymalıdır ve bu nedenle bir şeyler ters gitti. Ama bu oldukça belirsiz, değil mi? Ve düşündüğümüz gibi olmayan duruma yardımcı hits[0]olmaz.

Öte yandan, bazı istisnalar koyabiliriz:

2. Seçenek: İstisnaları Kullanma

Eğer hitsboş bir IndexErrorbiz çalıştığınızda atılacaktır hits[0]. Ama arayanın IndexErrorbenim fonksiyonum tarafından atıldığı beklenmemelidir , çünkü bunun nereden IndexErrorgeldiğine dair bir fikri yoktur ; find_all_mentionstüm bildiği için fırlatılabilirdi . Bu nedenle, bununla başa çıkmak için özel bir istisna sınıfı oluşturacağız:

class NotFoundError(Exception):
    """Throw this when something can't be found on a page."""

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    try:
        hits[0]
    except IndexError:
        raise NotFoundError("No mentions found.")

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    # he's either alive or dead
    return status == "alive"

Şimdi sayfa değiştiyse ve beklenmedik sayıda isabet olursa ne olur? Kod hala işe yarayabileceğinden bu felaket değildir, ancak bir arayan ekstra dikkatli olmak isteyebilir veya bir uyarı kaydetmek isteyebilir. Bu yüzden bir uyarı vereceğim:

class NotFoundError(Exception):
    """Throw this when something can't be found on a page."""

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    try:
        hits[0]
    except IndexError:
        raise NotFoundError("No mentions found.")

    # say we expect four hits...
    if len(hits) != 4:
        raise Warning("An unexpected number of hits.")
        logger.warning("An unexpected number of hits.")

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    # he's either alive or dead
    return status == "alive"

Son olarak, bunun statuscanlı ya da ölü olmadığını görebiliriz. Belki de garip bir nedenden ötürü, bugün ortaya çıktı comatose. O zaman dönmek istemiyorum False, çünkü Abe öldü. Burada ne yapmalıyım? Muhtemelen bir istisna atın. Ama ne tür? Özel bir istisna sınıfı oluşturmalı mıyım?

class NotFoundError(Exception):
    """Throw this when something can't be found on a page."""

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    try:
        hits[0]
    except IndexError:
        raise NotFoundError("No mentions found.")

    # say we expect four hits...
    if len(hits) != 4:
        raise Warning("An unexpected number of hits.")
        logger.warning("An unexpected number of hits.")

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    if status not in ['alive', 'dead']:
        raise SomeTypeOfError("Status is an unexpected value.")

    # he's either alive or dead
    return status == "alive"

Seçenek 3: Aralarında bir yer

İstisnalar hariç, ikinci yöntemin tercih edilebilir olduğunu düşünüyorum, ancak istisnaları doğru şekilde kullanıp kullanmadığımı bilmiyorum. Daha deneyimli programcıların bunu nasıl başaracağını merak ediyorum.

Yanıtlar:


17

Python'daki öneri, başarısızlığı belirtmek için istisnalar kullanmaktır. Düzenli olarak hata bekleseniz bile bu doğrudur.

Kodunuzu arayan kişinin bakış açısından bakın:

my_status = get_abe_status(my_url)

Yok'u döndürürsek ne olur? Arayan, get_abe_status'un başarısız olduğu durumu özel olarak ele almazsa, my_stats Yok olarak devam etmeye çalışır. Bu, daha sonra teşhis edilmesi zor bir hataya neden olabilir. Yok'u kontrol etseniz bile, bu kodun get_abe_status () işlevinin neden başarısız olduğuna dair hiçbir fikri yoktur.

Ama ya bir istisna getirirsek? Arayan özel olarak durumu ele almıyorsa, istisna eninde sonunda varsayılan istisna işleyiciye çarparak yukarı doğru yayılır. Bu istediğiniz şey olmayabilir, ancak programın başka bir yerinde ince bir hata getirmekten daha iyidir. Buna ek olarak, istisna ilk sürümde kaybolanlar hakkında bilgi verir.

Arayanın bakış açısından, bir dönüş değerinden bir istisna almak daha kolaydır. Ve bu, hata koşullarını değer döndürmemek için istisnalar kullanmak için python stili.

Bazıları farklı bir bakış açısı alacak ve sadece gerçekten gerçekleşmesini beklemediğiniz durumlar için istisnalar kullanmanız gerektiğini savunacak. Normalde koşmanın herhangi bir istisna getiremeyeceğini savunuyorlar. Bunun için verilen nedenlerden biri, istisnaların oldukça verimsiz olmasıdır, ancak bu aslında Python için doğru değildir.

Kodunuzda birkaç nokta:

try:
    hits[0]
except IndexError:
    raise NotFoundError("No mentions found.")

Bu, boş bir listeyi kontrol etmenin gerçekten kafa karıştırıcı bir yoludur. Sadece bir şeyi kontrol etmek için bir istisna yaratmayın. Bir if kullanın.

# say we expect four hits...
if len(hits) != 4:
    raise Warning("An unexpected number of hits.")
    logger.warning("An unexpected number of hits.")

Logger.warning satırının asla düzgün çalışmayacağını biliyor musunuz?


1
Yanıtınız için teşekkürler. Yayınlanan koda bakmanın yanı sıra, bir istisnayı ne zaman ve nasıl atacağım konusundaki duygularımı geliştirdi.
jme

4

Kabul edilen cevap kabul edilmeyi hak ediyor ve soruyu cevaplıyor, bunu sadece biraz ekstra arka plan sağlamak için yazıyorum.

Python'un inançlarından biri şudur: af dilemek izin almaktan daha kolaydır. Bu, genellikle yalnızca bir şeyler yaptığınız anlamına gelir ve istisnalar bekliyorsanız, bunları ele alırsınız. Bir istisna alamayacağınızdan emin olmak için elden önce kontrol yaparsa yapmak yerine.

Size C ++ / Java'dan zihniyetteki farkın ne kadar dramatik olduğunu göstermek için bir örnek sunmak istiyorum. C ++ 'da bir for döngüsü genellikle şöyle görünür:

for(int i = 0; i != myvector.size(); ++i) ...

Bunu düşünmenin bir yolu: myvector[k]k> = myvector.size () işlevine erişmek bir istisnaya neden olur. Prensip olarak bunu (çok garip bir şekilde) bir try-catch olarak yazabilirsiniz.

    for(int i = 0; ; ++i)  {
        try {
           ...
        } catch (& std::out_of_range)
             break

Veya benzeri. Şimdi, bir python for loop'ta neler olduğunu düşünün:

for i in range(1):
    ...

Bu nasıl çalışıyor? For döngüsü, (1) aralığının sonucunu alır ve üzerinde iter () çağırır ve ona bir yineleyici yakalar.

b = range(1).__iter__()

Sonra her döngü yinelemesinde bir sonraki çağırır, şu kadar ...

>>> next(b)
0
>>> next(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Başka bir deyişle, python'daki bir for döngüsü aslında kılık değiştirmekten başka bir denemedir.

Somut soruya gelince, istisnaların normal işlev yürütmeyi durdurduğunu ve ayrıca ele alınması gerektiğini unutmayın. Python'da, fonksiyonunuzdaki kodun geri kalanını yürütmenin bir anlamı olmadığında ve / veya iadelerin hiçbiri fonksiyonda ne olduğunu doğru bir şekilde yansıtmadığında bunları serbestçe atmalısınız. Bir fonksiyondan erken dönmenin farklı olduğuna dikkat edin: erken dönmeniz, cevabı zaten çözdüğünüz anlamına gelir ve cevabı bulmak için kodun geri kalanına ihtiyacınız yoktur. Yanıt bilinmediğinde istisnalar atılması gerektiğini ve yanıtı belirlemek için kodun geri kalanı makul çalıştırılamaz diyorum. Şimdi, hangi istisnaları atmayı seçtiğiniz gibi, kendisini "doğru şekilde yansıtın" hepsi bir dokümantasyon meselesidir.

Özel kodunuzda, isabetlerin boş bir liste olmasına neden olan herhangi bir durumun atması gerektiğini söyleyebilirim. Neden? İşlevin kurulum şekli, isabetleri ayrıştırmadan cevabı belirlemenin bir yolu yoktur. Dolayısıyla, isabetler URL'nin bozuk olması veya isabetlerin boş olması nedeniyle ayrıştırılamazsa, işlev soruyu cevaplayamaz ve aslında gerçekten denemeye bile çalışamaz.

Bu özel durumda, ayrıştırmayı başarsanız ve makul bir cevap almasanız bile (canlı veya ölü), yine de atmanız gerektiğini savunacağım. Niye ya? Çünkü, işlev bir boole döndürür. Hiçbirini Geri Göndermek müşteriniz için çok tehlikelidir. None üzerinde bir if kontrolü yaparlarsa, başarısızlık olmaz, sessizce False olarak kabul edilir. Yani, müşterinizin temelde her zaman bir if yapması gerekir. Hiçbiri sessiz arızaları istemiyorsa zaten kontrol edin ... bu yüzden muhtemelen atmalısınız.


2

İstisnai bir şey olduğunda istisnalar kullanmalısınız . Yani, uygulamanın uygun kullanımı göz önüne alındığında oluşmaması gereken bir şey. Metodunuzun tüketicisinin bulunamayacak bir şey aramasına izin veriliyorsa ve bekleniyorsa, "bulunamadı" ifadesi istisnai bir durum değildir. Bu durumda, null veya "None" veya {} veya boş bir dönüş kümesini gösteren bir şey döndürmelisiniz.

Öte yandan, gerçekten yönteminizin tüketicilerinin her zaman (bir şekilde berbat olmadıkları sürece) aranan şeyi bulmasını beklerseniz, o zaman bunu bulmak bir istisna olacaktır ve bununla birlikte gitmelisiniz.

Anahtar, istisna işlemenin pahalı olabileceğidir - istisnaların, kişilerin neden ortaya çıktıklarını deşifre etmelerine yardımcı olmak için, yığın izlemesi gibi uygulamanızın durumu hakkında bilgi toplaması gerekir. Yapmaya çalıştığın şey bu değil.


1
Bir değer bulmaya izin verilmemesine karar verirseniz, ne olduğunu göstermek için ne kullandığınıza dikkat edin. Yönteminizin bir döndürmesi gerekiyorsa Stringve göstergeniz olarak "Hiçbiri" seçeneğini belirlerseniz, "Hiçbiri" nin asla geçerli bir değer olmayacağına dikkat etmeniz gerekir. Ayrıca, verilere bakmak ve bir değer bulmak ile verileri alamamak arasında bir fark olduğunu unutmayın, bu nedenle verileri bulamayız. Bu iki durum için aynı sonuca sahip olmak, bir tane olmasını beklediğinizde hiçbir değer elde etmediğinizde görünürlüğünüz olmadığı anlamına gelir.
hohossler

Satır içi kod blokları ters tırnaklarla (`) işaretlenmiştir, belki de" Yok "ile yapmak istediğiniz şey budur?
Izkata

3
Korkarım bu Python'da kesinlikle yanlış. C ++ / Java stili akıl yürütmeyi başka bir dile uyguluyorsunuz. Python, for döngüsünün sonunu belirtmek için istisnalar kullanır; bu oldukça sıra dışı.
Nir Friedman

2

Eğer bir fonksiyon yazıyorsam

 def abe_is_alive():

Ben bunu yazardım return Trueya Falsebirinde ya da diğer kesinlikle eminim durumlarda ve raisebaşka bir durumda (örneğin bir hata raise ValueError("Status neither 'dead' nor 'alive'")). Çünkü benimkini çağıran fonksiyon bir boolean beklemektedir ve bunu kesin olarak sağlayamazsam düzenli program akışı devam etmemelidir.

Beklenenden farklı sayıda "isabet" alma örneğiniz gibi bir şey, muhtemelen görmezden gelirim; isabetlerden biri hala "Abe Vigoda {ölü | canlı}" kalıbımla eşleştiği sürece, bu iyi. Bu, sayfanın yeniden düzenlenmesini sağlar, ancak yine de uygun bilgileri alır.

Ziyade

try:
    hits[0] 
except IndexError:
    raise NotFoundError

Açıkça kontrol ediyorum:

if not hits:
    raise NotFoundError

çünkü bu "daha ucuz" olma eğilimindedir try.

Size katılıyorum IOError; Ayrıca, web sitesine bağlanırken hata işlemeyi denemem - eğer bir sebepten dolayı, bu bizim için uygun bir yer değil (soruyu cevaplamamıza yardımcı olmaz) ve geçmelidir çağrı işlevine.

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.