Python'daki yuvalanmış try / hariç bloklar iyi bir programlama uygulaması mıdır?


201

Öznitelik çağrıları ile içeride bir sözlüğe erişmesi gereken kendi kapsayıcımı yazıyorum. Kabın tipik kullanımı şöyle olacaktır:

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

Bunun gibi bir şey yazmanın aptalca olabileceğini biliyorum, ama sağlamam gereken işlevsellik bu. Bunu şu şekilde uygulamayı düşünüyordum:

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"

Yuvalanmış try / hariç blokların iyi bir uygulama olup olmadığından emin değilim, bu yüzden başka bir yol kullanmak hasattr()ve 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")

Veya bunlardan birini kullanmak ve biri böyle yakalamak için blok deneyin:

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")

Hangi seçenek en pitonik ve zariftir?


Python tanrıları sağlayan takdir ediyorum if 'foo' in dict_container:. Amin.
gseattle

Yanıtlar:


181

İlk örneğiniz gayet iyi. Resmi Python belgeleri bile EAFP olarak bilinen bu stili tavsiye ediyor .

Şahsen, gerekli olmadığında iç içe geçmekten kaçınmayı tercih ederim:

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()Python 2'de uzun zamandır kullanımdan kaldırıldı item in self.dict. Bunun yerine kullanın .


2
return object.__getattribute__(item)yanlıştır ve bir TypeErroryanlış üretilir çünkü yanlış sayıda argüman iletilir. Bunun yerine olması gerekir return object.__getattribute__(self, item).
martineau

13
PEP 20: Düz yuvalanmışdan daha iyidir.
Ioannis Filippidis

7
from NoneSon satırdaki anlamı ne ?
niklas

2
@niklas Esasen istisna bağlamını bastırır ("bu istisnayı ele alırken başka bir istisna oluştu" -esque mesajlar). Buraya
Kade

Python belgelerinin iç içe geçmiş denemeleri tavsiye etmesi biraz çılgınca. Açıkçası korkunç bir tarz. İşlerin böyle başarısız olabileceği bir operasyon zincirini işlemenin doğru yolu, Python'un desteklemediği bir tür monadik yapı kullanmak olacaktır.
Henry Henrinson

19

Java'da akış denetimi için İstisnalar kullanmak kötü bir uygulamadır (esas olarak istisnalar jvm'yi kaynakları toplamaya zorlar ( daha fazla burada )), Python'da 2 önemli prensibiniz vardır: Ördek Yazma ve EAFP . Bu temel olarak, bir nesneyi işe yarayacağını düşündüğünüz şekilde kullanmaya ve işlerin böyle olmadığında işlem yapmaya teşvik edeceğiniz anlamına gelir.

Özetle, tek sorun kodunuzun çok fazla girintili olması olacaktır. Eğer böyle hissediyorsanız, lqc'nin önerdiği yuvaları basitleştirmeye çalışın


10

Özel örneğiniz için, onları iç içe yerleştirmeniz gerekmez. tryBloktaki ifade başarılı olursa, işlev geri döner, bu nedenle tüm try / hariç blokundan sonraki tüm kodlar yalnızca ilk deneme başarısız olursa çalıştırılır. Böylece şunları yapabilirsiniz:

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"

Onları yuvalamak kötü değil, ama onu düz bırakmak gibi hissediyorum, yapıyı daha net hale getiriyor: sırayla bir dizi şeyi deniyorsunuz ve işe yarayan ilkini geri veriyorsunuz.

Bu arada, burada kullanmak __getattribute__yerine gerçekten kullanmak isteyip istemediğinizi düşünmek isteyebilirsiniz __getattr__. Kullanmak __getattr__işleri basitleştirecektir, çünkü normal özellik arama işleminin zaten başarısız olduğunu bilirsiniz.


10

Sadece dikkatli olun - bu durumda önce finallydokunulur AMA da atlanır.

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

Vay canına aklıma geliyor ... Beni bu davranışı açıklayan bir dokümantasyon parçasına götürebilir misiniz?
Michal

1
@Michal: fyi: her iki finallyblok da yürütülür a(0), ancak yalnızca üst öğe finally-returndöndürülür.
Sławomir Lenart

7

Benim düşünceme göre bu, bununla başa çıkmanın en Pythonic yolu olurdu, ancak sorunuzu tartışıyor. Bunun, __getattr__()bunun yerine __getattribute__()yalnızca dahili sözlükte tutulan "özel" niteliklerle uğraşması gerektiği anlamına geldiğini unutmayın.

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")

2
Bir exceptblokta istisna oluşturmanın Python 3'te kafa karıştırıcı çıktı verebileceğini unutmayın. Bunun nedeni (PEP 3134 uyarınca) Python 3'ün ilk istisnayı (the KeyError) ikinci istisnanın (the AttributeError) "bağlamı" olarak izlemesi ve en üst düzeyde, her iki istisnayı da içeren bir izleme yazdırır. Bu, ikinci bir istisna beklenmediğinde yararlı olabilir, ancak kasıtlı olarak ikinci istisnayı artırıyorsanız, bu istenmeyen bir durumdur. Python 3.3 için PEP 415, içeriği kullanarak bastırmak için yeteneği ekledi raise AttributeError("whatever") from None.
Blckknght

3
@Blckknght: Her iki istisnayı da içeren bir geri izleme yazdırmak bu durumda iyi olur. Diğer bir deyişle, battaniye açıklamanızın her zaman istenmeyen olduğunu doğru bulmuyorum. Buradaki kullanımda, a'yı KeyErrordönüştürüyor AttributeErrorve bir geri izlemede olanların yararlı ve uygun olacağını gösteriyor.
martineau

Daha karmaşık durumlar için haklı olabilirsiniz, ancak istisna türleri arasında dönüşüm yaparken, genellikle ilk istisnanın ayrıntılarının dış kullanıcı için önemli olmadığını bilirsiniz. Yani, __getattr__bir istisna ortaya çıkarırsa , hata muhtemelen öznitelik erişiminde bir yazım hatasıdır, geçerli sınıfın kodundaki bir uygulama hatası değildir. Önceki istisnayı içerik olarak göstermek bunu karıştırabilir. Ve bağlamı bastırsanız bile, raise Whatever from Nonegerekirse önceki istisnayı elde edebilirsiniz ex.__context__.
Blckknght

1
Cevabınızı kabul etmek istedim, ancak soruda, iç içe geçme / yakalama bloğunu kullanmanın iyi bir uygulama olup olmadığını merak ettim. Öte yandan en zarif çözüm ve kodumda kullanacağım. Çok teşekkürler Martin.
Michal

Michal: Rica ederim. Ayrıca kullanmaktan daha hızlı __getattribute__().
martineau

4

Python'da affetmeyi istemekten izin almak daha kolaydır. İç içe kural dışı durum işleme terlemeyin.

(Ayrıca, has*neredeyse her zaman kapak altında istisnalar kullanır.)


4

Belgelere göre , tuples veya bunun gibi çoklu istisnaları ele almak daha iyidir:

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

2
Bu cevap orijinal soruyu gerçekten ele almaz, ancak okuyan herkes için "çıplak" notu, sonunda NameError & KeyboardInterrupt dahil olmak üzere her şeyi yakalayacağı için korkunç bir fikirdir (genellikle).
kaybetti

Kod, print ifadesinden hemen sonra aynı istisnayı yeniden ortaya koyarsa, bu gerçekten büyük bir anlaşma. Bu durumda, gizlemeden istisna hakkında daha fazla bağlam sağlayabilir. Yeniden yükseltmediyse, tamamen katılırdım, ancak niyet etmediğin bir istisnayı gizleme riski olduğunu düşünmüyorum.
NimbusScale

4

Yuvalanmış try / hariç için iyi ve basit bir örnek aşağıdakiler olabilir:

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

Şimdi çeşitli kombinasyonları deneyin ve doğru sonucu elde edersiniz:

divide(15, 3)
# 5.0

divide(15, 0)
# inf

divide(-15, 0)
# -inf

divide(0, 0)
# nan

[tabii ki numpy'miz var, bu yüzden bu işlevi yaratmamıza gerek yok]


2

Kaçınmaktan hoşlandığım bir şey, eskisini tutarken yeni bir istisna oluşturmaktır. Hata mesajlarının okunmasını kafa karıştırıcı hale getirir.

Örneğin, kodumda, başlangıçta yazdım

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

Ve bu mesajı aldım.

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

İstediğim şuydu:

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

İstisnaların nasıl ele alınacağını etkilemez. Her iki kod bloğunda da bir KeyError yakalanmış olurdu. Bu sadece stil puanı alma meselesidir.


Daha fazla stil puanı from Noneiçin kabul edilen yanıtın zam kullanımı konusuna bakın . :)
Pianosaurus

1

Sonunda try-haricinde nihayet bloğun içinde yuvalanmışsa, "child" sonucu nihayet korunur. Henüz resmi açıklama bulamadım, ancak aşağıdaki kod snippet'i Python 3.6'da bu davranışı gösterir.

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

Bu davranış, nihayet blok dışında iç içe yerleştirildiğinde @ Sławomir Lenart tarafından verilen örnekten farklıdır.
Guanghua Shu

0

Bunun pitonik veya zarif olma meselesi olduğunu sanmıyorum. Bu, istisnaları olabildiğince önleme meselesidir. İstisnalar, denetiminiz olmayan kod veya olaylarda oluşabilecek hataları işlemek içindir. Bu durumda, bir öğenin bir özellik veya sözlükte olup olmadığını kontrol ederken tam kontrole sahip olursunuz, bu nedenle iç içe geçmiş istisnalardan kaçının ve ikinci denemenize sadık kalın.


Dokümanlardan: Çok iş parçacıklı bir ortamda, LBYL ( Sıçramadan Önce Bak) yaklaşımı “görünüş ” ile “sıçramak” arasında bir yarış koşulu getirme riski taşıyabilir. Örneğin, eşlemedeki anahtar: dönüş eşlemesi [anahtar], başka bir iş parçacığı testten sonra ancak aramadan önce anahtarı eşlemeden kaldırırsa kod başarısız olabilir. Bu sorun kilitlerle ya da EAFP ( Affetmeden izin istemekten daha kolay) yaklaşımı
Nuno André
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.