Python'da try vs if kullanma


145

Değişkenin bir değere sahip olup olmadığını test ederken hangisinin tryveya ifyapısının kullanılacağına karar vermenin bir mantığı var mı?

Örneğin, bir liste döndüren veya bir değer döndürmeyen bir işlev vardır. İşleme koymadan önce sonucu kontrol etmek istiyorum. Aşağıdakilerden hangisi daha çok tercih edilir ve neden?

result = function();
if (result):
    for r in result:
        #process items

veya

result = function();
try:
    for r in result:
        #process items
except TypeError:
    pass;

İlgili tartışma:

Python'da üye varlığını kontrol etme


#Process items stanza'nız bir TypeError atmak zorundaysa, başka bir dene: block: hariç. Bu özel örnek için, yalnızca bir if:
richo

Yanıtlar:


237

Python'un LBYL stili üzerinde EAFP stilini (" izin vermekten daha affetmek daha kolay") teşvik ettiğini duyuyorsunuz (" atlamadan önce bak"). Bana göre bu bir verimlilik ve okunabilirlik meselesi.

Örneğinizde (bir liste veya boş bir dize döndürmek yerine, işlevin bir liste döndürmesi Nonegerektiğini söyleyin veya ), zamanın% 99'unun resultgerçekten yinelenebilir bir şey içereceğini düşünüyorsanız, try/exceptyaklaşımı kullanacağım . İstisnalar gerçekten istisnai ise daha hızlı olacaktır. Eğer resultbir Noneo kullanarak, zamanın% 50'den fazla if, muhtemelen daha iyidir.

Bunu birkaç ölçümle desteklemek için:

>>> import timeit
>>> timeit.timeit(setup="a=1;b=1", stmt="a/b") # no error checking
0.06379691968322732
>>> timeit.timeit(setup="a=1;b=1", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.0829463709378615
>>> timeit.timeit(setup="a=1;b=0", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.5070195056614466
>>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:\n a/b")
0.11940114974277094
>>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:\n a/b")
0.051202772912802175

Yani, bir ififade her zaman size mal olurken , bir try/exceptblok oluşturmak neredeyse ücretsizdir . Ancak Exceptiongerçekte meydana geldiğinde, maliyet çok daha yüksektir.

Ahlaki:

  • try/exceptAkış kontrolü için kullanmak tamam (ve "pitonik") ,
  • ancak Exceptions gerçekten istisnai olduğunda en mantıklıdır .

Python belgelerinden:

EAFP

Affetmek, izin vermekten daha kolaydır. Bu yaygın Python kodlama stili, geçerli anahtarların veya niteliklerin varlığını varsayar ve varsayımın yanlış olduğunu kanıtlarsa istisnaları yakalar. Bu temiz ve hızlı stil, birçok tryve exceptifadenin varlığı ile karakterizedir . Teknik , C gibi diğer birçok dilde ortak olan LBYL stiliyle zıttır .


1
Teşekkür ederim. Bu durumda mantığın sonucun beklentisi olabileceğini görüyorum.
artdanil

6
.... ve bu Python için gerçek bir optimizasyon JIT yapmak çok zor nedenlerinden biridir. LuaJIT 2 kanıtlıyor geçenlerde beta, dinamik diller gibi olabilir gerçekten hızlı olacak; ancak büyük ölçüde başlangıçtaki dil tasarımına ve teşvik ettiği stile bağlıdır. (ilgili bir notta, dil tasarımı en iyi JavaScirpt JIT'lerin bile LuaJIT 1 ile karşılaştırılamaz, çok daha az 2)
Javier

1
@ 2rs2ts: Ben de benzer zamanlamaları kendim yaptım. Python 3'te, anahtarın sözlükte bulunduğu durumlardan try/except% 25 daha hızlıydı if key in d:. Anahtar sözlükte olmadığında beklendiği gibi çok daha yavaştı ve bu cevapla tutarlıydı.
Tim Pietzcker

5
Bu eski bir cevap olduğunu biliyorum, ancak: 1/1timeit gibi ifadeleri kullanmak mükemmel bir seçim değildir, çünkü onlar optimize edilecektir (bakın dis.dis('1/1')ve hiçbir bölünme gerçekleşmez unutmayın).
Andrea Corbellini

1
Ayrıca yapmak istediğiniz işleme de bağlıdır. Tek bir bölüm hızlıdır, bir hatayı yakalamak biraz pahalıdır. Ancak, istisna işlemenin maliyetinin kabul edilebilir olduğu durumlarda bile, bilgisayardan ne yapmasını istediğinizi dikkatlice düşünün. İşlemin sonuçları ne olursa olsun çok pahalıysa ve başarının mümkün olup olmadığını anlamanın ucuz bir yolu varsa: Kullanın. Örnek: Yeterli alan olmadığını anlamak için dosyanın yarısını kopyalamayın.
Bachsau

14

İşleviniz karışık türler (liste veya boş dize) döndürmemelidir. Değerler listesi veya yalnızca boş bir liste döndürmelidir. O zaman hiçbir şey test etmeniz gerekmez, yani kodunuz daraltılır:

for r in function():
    # process items

2
Bu konuda size tamamen katılıyorum; ancak, işlev benim değil ve ben sadece kullanıyorum.
artdanil

2
@artdanil: Böylece bu işlevi, Brandon Corfman'ın düşündüğü gibi çalışan bir işle yazabilirsiniz.
quamrana

24
hangi soruya yalvarır: sarıcı, yinelenemeyen durumu işlemek için bir if veya denemesi kullanmalı mı?
jcdyer

@quamrana tam da yapmaya çalıştığım şey. @Jcd'nin işaret ettiği gibi, soru, sarmalayıcı işlevindeki durumu nasıl ele almam gerektiğidir.
artdanil

12

Sağladığım kod ilk bakışta belli değilse ve kod örneğinden sonra açıklamayı okumak zorundaysanız lütfen çözümümü görmezden gelin.

"Dönen değer yok" ifadesinin dönüş değerinin Yok olduğunu varsayabilir miyim? Cevabınız evet ise veya "no value" False boolean-wise ise, aşağıdakileri yapabilirsiniz, çünkü kodunuz temel olarak "no value" ı "yineleme" olarak ele alır:

for r in function() or ():
    # process items

Eğer function()Doğru değil döner bir şey, sen yinelerler boş başlığın üzerinde, yani herhangi bir yinelemeleri çalışmaz. Bu aslında LBYL'dir.


4

İkinci örneğiniz bozuldu - hem dizelerde hem de listelerde yineleme yapabildiğiniz için kod hiçbir zaman bir TypeError istisnası atmaz. Boş bir dize veya liste üzerinden yineleme de geçerlidir - döngü gövdesini sıfır kez yürütür.


4

Aşağıdakilerden hangisi daha çok tercih edilir ve neden?

Bu durumda Sıçramadan Önce Bak tercih edilir. İstisna yaklaşımı ile, bir TypeError döngü gövgenizin herhangi bir yerinde meydana gelebilir ve yakalanıp atılır, bu da istediğiniz şey değildir ve hata ayıklamayı zorlaştıracaktır.

(Brandon Corfman ile aynı fikirdeyim: boş bir liste yerine 'hiçbir öğe' için Hiçbiri döndürülmüyor. Python'da veya Java'da görülmemesi gereken Java kodlayıcılarının hoş olmayan bir alışkanlığı.)


4

Genel olarak, aldığım izlenim, istisnaların istisnai durumlar için ayrılması gerektiğidir. Eğer resultbeklenen boş olması asla (ama örneğin bir disk vb çöktü, eğer olabilir), ikinci yaklaşım mantıklı. Öte yandan, boş resultkoşullar normal koşullar altında mükemmel bir şekilde makul ise, bir ififade ile test edilmesi daha mantıklıdır.

Aklımda (daha yaygın) senaryo vardı:

# keep access counts for different files
file_counts={}
...
# got a filename somehow
if filename not in file_counts:
    file_counts[filename]=0
file_counts[filename]+=1

eşdeğeri yerine:

...
try:
    file_counts[filename]+=1
except KeyError:
    file_counts[filename]=1

Bu Tim Pietzcker'ın bahsettiği yaklaşımlar arasındaki farkın bir örneğidir: Birincisi LBYL, ikincisi EAFP
Managu

++Python'da çalışmayan hızlı bir not += 1yerine kullanın.
tgray

3

bobince akıllıca ikinci kasayı sarmanın döngüde TypeErrors yakalayabildiğine dikkat çeker, ki bu da istediğiniz şey değildir. Gerçekten bir deneme kullanmak istiyorsanız, döngüden önce yinelenebilir olup olmadığını test edebilirsiniz

result = function();
try:
    it = iter(result)
except TypeError:
    pass
else:
    for r in it:
        #process items

Gördüğünüz gibi, oldukça çirkin. Bunu önermiyorum, ancak bütünlükten bahsedilmelidir.


Gözlerimde, kasıtlı olarak yazım hatalarına girmek her zaman kötü kodlama tarzıdır. Bir geliştirici, ne beklediğini bilmeli ve istisnasız olarak bu değerleri ele almaya hazır olmalıdır. Bir işlem ne yapması gerektiğini (programcının bakış açısından) yaptığında ve beklenen sonuçlar bir liste olduğunda veya Nonesonucu daima is Noneveya ile kontrol edin is not None. Diğer taraftan, meşru sonuçlar için İstisnalar oluşturmak kötü bir stildir. İstisnalar beklenmedik şeyler içindir. Örnek: str.find()hiçbir şey bulunmazsa -1 döndürür, çünkü aramanın kendisi hatasız tamamlanır.
Bachsau

1

Performans söz konusu olduğunda, normalde istisnalar oluşturmayan kod için try bloğunu kullanmak if ifadesini her seferinde kullanmaktan daha hızlıdır. Bu nedenle, karar, excetional davaların olasılığına bağlıdır.


-5

Genel bir kural olarak, akışı kontrol etmek için asla try / catch veya istisna işleme şeylerini kullanmamalısınız. Her ne kadar perde arkası StopIterationistisnalar artırılarak kontrol ediliyor olsa da, ilk kod snippet'inizi ikinciye tercih etmelisiniz.


7
Bu Java için geçerlidir. Python, EAFP'yi oldukça yoğun bir şekilde kucaklıyor.
fengb

3
Hala garip bir uygulama. Çoğu programcının beyni, deneyimime göre EAFP'yi atlamak için kablolu. Sonunda neden belirli bir yolun seçildiğini merak ediyorlar. Bununla birlikte, her kuralı yıkmak için bir zaman ve yer var.
ilowe
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.