Python'da "nihayet" her zaman mı çalışır?


126

Python'daki herhangi bir olası deneme bloğu için, finallybloğun her zaman yürütüleceği garanti ediliyor mu?

Örneğin, bir bloktayken döndüğümü exceptvarsayalım:

try:
    1/0
except ZeroDivisionError:
    return
finally:
    print("Does this code run?")

Ya da belki bir Exception:

try:
    1/0
except ZeroDivisionError:
    raise
finally:
    print("What about this code?")

Test, finallyyukarıdaki örnekler için bunun yürütüldüğünü gösteriyor , ancak aklıma gelmeyen başka senaryolar olduğunu düşünüyorum.

finallyPython'da bir bloğun çalıştırılamayacağı herhangi bir senaryo var mı?


18
Hayal edebileceğim tek durum finally, sonsuz bir döngü sys.exitveya zorunlu bir kesinti sırasında gerçekleştirilemiyor veya "amacını bozuyor". Dokümantasyon devletler finallyher zaman çalıştırılır, bu yüzden bu konuda giderdim.
Xay

1
Biraz yanal düşünme ve ne sorduğunuzdan emin değilim, ancak Görev Yöneticisi'ni açıp işlemi sonlandırırsanız çalışmayacağından oldukça eminim finally. Ya da bilgisayar daha önce çökerse aynı şey: D
Alejandro

145
finallygüç kablosu duvardan koparsa çalışmaz.
user253751

3
C # ile ilgili aynı soruya verilen bu cevap ilginizi çekebilir: stackoverflow.com/a/10260233/88656
Eric Lippert

1
Boş bir semaforda engelleyin. Asla sinyal verme. Bitti.
Martin James

Yanıtlar:


207

"Garantili", herhangi bir uygulamanın finallyhak ettiğinden çok daha güçlü bir kelimedir . Ne garantilidir yürütme bütün dışarı akar eğer olmasıdır try- finallyyapı, bu geçecek finallybunu. Garanti edilmeyen şey, uygulamanın try- finally.

  • Bir oluşturucuda finallyveya zaman uyumsuz coroutine'de , nesne hiçbir zaman sonuca varmazsa, hiçbir zaman çalışmayabilir . Olabilecek birçok yol var; Işte bir tane:

    def gen(text):
        try:
            for line in text:
                try:
                    yield int(line)
                except:
                    # Ignore blank lines - but catch too much!
                    pass
        finally:
            print('Doing important cleanup')
    
    text = ['1', '', '2', '', '3']
    
    if any(n > 1 for n in gen(text)):
        print('Found a number')
    
    print('Oops, no cleanup.')
    

    Bu örneğin biraz yanıltıcı olduğunu unutmayın: Oluşturucu çöp toplandığında, Python finallybir GeneratorExitistisna atarak bloğu çalıştırmayı dener , ancak burada bu istisnayı yakalarız ve ardından yieldPython bir uyarı yazdırır ("jeneratör, GeneratorExit ") ve pes ediyor. Ayrıntılar için bkz. PEP 342 (Geliştirilmiş Üreteçler aracılığıyla Coroutines) .

    Bir oluşturucu veya koroutinin sonuca varmak için yürütemeyeceği diğer yollar arasında, nesnenin hiçbir zaman GC'lenmemiş olması (evet, bu, CPython'da bile mümkündür) veya bir async with awaits'nin olması __aexit__veya nesnenin bir bloktaki awaits veya yields olması yer alır finally. Bu listenin kapsamlı olması amaçlanmamıştır.

  • Bir finallyarka plan programı iş parçacığındaki bir , tüm arka plan programı olmayan evreler önce çıkarsa hiçbir zaman yürütülemeyebilir .

  • os._exitfinallyblokları çalıştırmadan süreci hemen durdurur .

  • os.forkfinallyblokların iki kez yürütülmesine neden olabilir . Yalnızca iki kez meydana gelen olaylardan bekleyeceğiniz normal sorunların yanı sıra, paylaşılan kaynaklara erişim doğru şekilde senkronize edilmezse bu, eşzamanlı erişim çatışmalarına (çökmeler, takılmalar, ...) neden olabilir .

    Yana multiprocessingkullandığı çatal-olmadan-exec alt işlemleri oluşturmak için kullanılırken çatal başlatma yöntemini sonra (Unix varsayılan) ve çağrıları os._exitçalışanın iş bittikten sonra işçinin içinde, finallyve multiprocessingetkileşim sorunlu (olabilmektedir örnek ).

  • C düzeyinde bir bölümleme hatası, finallyblokların çalışmasını önleyecektir .
  • kill -SIGKILLfinallyblokların çalışmasını engelleyecektir . SIGTERMve kapatmayı kendiniz kontrol edecek bir işleyici kurmadığınız sürece blokların çalışmasını SIGHUPda engeller finally; varsayılan olarak Python, SIGTERMveya SIGHUP.
  • İçindeki bir istisna, finallytemizlemenin tamamlanmasını engelleyebilir. Kullanıcı isabet kontrol-C ise Özellikle kayda değer bir durumdur sadece biz yürütmeye başlıyoruz olarak finallyblok. Python KeyboardInterrupt, finallybloğun içeriğinin her satırını yükseltir ve atlar . ( KeyboardInterrupt-güvenli kod yazmak çok zordur).
  • Bilgisayar güç kaybederse veya hazırda bekletip uyanmazsa, finallybloklar çalışmaz.

finallyBlok, bir işlem sistemi değildir; atomiklik garantileri veya benzeri herhangi bir şey sağlamaz. Bu örneklerden bazıları açık görünebilir, ancak bu tür şeylerin olabileceğini unutmak ve finallyçok fazla güvenmek kolaydır .


14
Listenizin yalnızca ilk noktasının gerçekten alakalı olduğuna inanıyorum ve bundan kaçınmanın kolay bir yolu var: 1) asla çıplak kullanmayın exceptve asla GeneratorExitbir jeneratörün içine girmeyin. İş parçacıkları / işlemi sonlandırma / segfaulting / kapatma ile ilgili noktalar beklenir, python sihir yapamaz. Ayrıca: in istisnalar finallyaçıkça bir sorun vardır ama bu denetim akışı olduğu gerçeğini değiştirmez edildi taşındı finallybloğun. Ctrl+CBununla ilgili olarak , onu yok sayan bir sinyal işleyici ekleyebilir veya mevcut işlem tamamlandıktan sonra basit bir şekilde temiz bir kapatma "planlayabilirsiniz".
Giacomo Alzetta

8
Kill -9'dan bahsetmek teknik olarak doğru, ancak biraz haksız. Herhangi bir dilde yazılmış hiçbir program kill -9 aldığında herhangi bir kod çalıştırmaz. Aslında, hiçbir program hiçbir zaman bir kill -9 almaz , bu yüzden istese bile hiçbir şeyi yürütemez. Bütün öldürme noktası bu -9.
Tom

10
@Tom: Konu kill -9bir dil belirtmedi. Ve açıkçası, tekrar edilmesi gerekiyor çünkü kör bir noktada duruyor. Çok fazla insan, programlarının temizlenmesine bile izin verilmeksizin durdurulabileceğini unutur veya fark etmez.
cHao

5
@GiacomoAlzetta: finallySanki işlem garantileri veriyormuş gibi bloklara güvenen insanlar var . Yapmadıkları açık görünebilir, ancak bu herkesin fark ettiği bir şey değildir. Jeneratör kasasına gelince, bir jeneratörün hiç GC'lenememesinin birçok yolu vardır ve bir jeneratör veya coroutine , GeneratorExityakalanmasa bile GeneratorExit, örneğin bir async withaskıya alınırsa, yanlışlıkla üretilebilecek birçok yol vardır. bir coroutine in __exit__.
user2357112 Monica'yı

2
@ user2357112 evet - Onlarca yıldır geliştiricilerin uygulama başlangıcında geçici dosyaları vb. temizlemesini sağlamaya çalışıyorum, çıkış değil. Sözde 'temizle ve nazikçe sonlandırmaya' güvenmek, hayal kırıklığı ve gözyaşı istiyor :)
Martin James

68

Evet. Sonunda her zaman kazanır.

Onu yenmenin tek yolu, yürütme finally:şansı bulamadan yürütmeyi durdurmaktır (örneğin, tercümanı çökertmek, bilgisayarınızı kapatmak, bir jeneratörü sonsuza kadar askıya almak).

Aklıma gelmeyen başka senaryolar olduğunu hayal ediyorum.

İşte düşünmemiş olabileceğiniz birkaç tane daha:

def foo():
    # finally always wins
    try:
        return 1
    finally:
        return 2

def bar():
    # even if he has to eat an unhandled exception, finally wins
    try:
        raise Exception('boom')
    finally:
        return 'no boom'

Tercümandan nasıl ayrıldığınıza bağlı olarak, bazen sonunda "iptal" edebilirsiniz, ancak şu şekilde değil:

>>> import sys
>>> try:
...     sys.exit()
... finally:
...     print('finally wins!')
... 
finally wins!
$

Güvencesizliği kullanmak os._exit(bence bu "tercümanı çökertmek" kategorisine girer):

>>> import os
>>> try:
...     os._exit(1)
... finally:
...     print('finally!')
... 
$

Şu anda bu kodu çalıştırıyorum, nihayet evrenin ısı ölümünden sonra hala çalışıp çalışmayacağını test etmek için:

try:
    while True:
       sleep(1)
finally:
    print('done')

Yine de sonucu bekliyorum, bu yüzden daha sonra tekrar kontrol edin.


5
ya da try
catch'te

8
finallybir üreteçte veya korutinde, bir "yorumlayıcıyı çökertme" durumuna yakın bir yere gitmeden, yürütmek oldukça kolay bir şekilde başarısız olabilir .
user2357112

27
Evrenin ısı ölümünden sonra zamanın varlığı sona erer, bu yüzden sleep(1)kesinlikle tanımlanmamış davranışla sonuçlanır. :-D
David Foerster

_Os.exit'i “onu yenmenin tek yolu derleyiciyi çökertmektir” den sonra doğrudan belirtmek isteyebilirsiniz. Şu anda, sonunda kazandığı örnekler arasında karışık.
Stevoisiak

2
@StevenVascellaro Bunun gerekli olduğunu düşünmüyorum - os._exittüm pratik amaçlar için, bir çökme (kirli çıkış) tetiklemekle aynıdır. Çıkmanın doğru yolu sys.exit.
wim

9

Göre Python belgelerinde :

Daha önce ne olursa olsun, son blok, kod bloğu tamamlandığında ve ortaya çıkan istisnalar ele alındığında yürütülür. Bir istisna işleyicisinde veya else-bloğunda bir hata olsa ve yeni bir istisna ortaya çıksa bile, son bloktaki kod hala çalıştırılır.

Ayrıca, son bloğunda bir tane de dahil olmak üzere birden fazla dönüş ifadesi varsa, son olarak blok dönüşünün yürütülecek tek şey olduğu da unutulmamalıdır.


8

Hem evet hem hayır.

Garantili olan, Python'un her zaman nihayet bloğunu çalıştırmaya çalışacağıdır. Bloktan döndüğünüzde veya yakalanmamış bir istisna ortaya çıkardığınızda, nihayet blok, istisnayı gerçekten döndürmeden veya yükseltmeden hemen önce çalıştırılır.

(sadece sorunuzdaki kodu çalıştırarak kendinizi kontrol etmiş olabileceğiniz şey)

Nihayet bloğunun nerede çalıştırılmayacağını hayal edebildiğim tek durum, Python yorumlayıcısının kendisinin örneğin C kodu içinde veya elektrik kesintisi nedeniyle çökmesidir.


Ha ha .. ya Try catch bir sonsuz döngü var
sapy

Bence "Evet ve hayır" en doğru olanı. Son olarak her zaman kazanır "nihayet:" hala mevcuttur ve tercüman nihayet çalıştırmayı dener olarak "kazanır" tanımlanır: blok ve başarılı olacaktır "her zaman" anlamına gelir tercüman çalışabiliyor ve kod nerede. Bu "Evet" ve çok koşullu. "Hayır", yorumlayıcının "nihayet" öncesinde durdurabileceği tüm yollar: - elektrik kesintisi, donanım arızası, öldürme - yorumlayıcıya yönelik, yorumlayıcıdaki hatalar veya bağlı olduğu kod, tercümanı asmanın diğer yolları. Ve "nihayet:" in içine takılmanın yolları.
Bill IV

1

Bunu bir jeneratör işlevi kullanmadan buldum:

import multiprocessing
import time

def fun(arg):
  try:
    print("tried " + str(arg))
    time.sleep(arg)
  finally:
    print("finally cleaned up " + str(arg))
  return foo

list = [1, 2, 3]
multiprocessing.Pool().map(fun, list)

Uyku, tutarsız süre boyunca çalışabilecek herhangi bir kod olabilir.

Burada gerçekleşiyor gibi görünen şey, bitirmek için ilk paralel işlemin try bloğunu başarılı bir şekilde bırakması, ancak daha sonra fonksiyondan herhangi bir yerde tanımlanmamış bir değer (foo) döndürmeye çalışarak bir istisnaya neden olmasıdır. Bu istisna, diğer işlemlerin nihai bloklarına ulaşmasına izin vermeden haritayı öldürür.

Ayrıca, hattı bar = bazztry bloğundaki sleep () çağrısından hemen sonra eklerseniz . Sonra bu çizgiye ulaşan ilk süreç bir istisna atar (çünkü bazz tanımlanmamıştır), bu da kendi nihayet bloğunun çalıştırılmasına neden olur, ancak daha sonra haritayı öldürür, diğer deneme bloklarının nihai bloklarına ulaşmadan kaybolmasına neden olur ve ilk süreç de dönüş ifadesine ulaşmıyor.

Bunun Python çoklu işlemesi için anlamı, işlemlerden birinin bile bir istisnası olsa bile tüm süreçlerdeki kaynakları temizlemek için istisna işleme mekanizmasına güvenemeyeceğinizdir. Ek sinyal işleme veya çoklu işlem harita çağrısı dışındaki kaynakları yönetme gerekli olacaktır.


-2

Birkaç örnekle birlikte nasıl çalıştığını görmeye yardımcı olmak için kabul edilen cevaba ek:

  • Bu:

     try:
         1
     except:
         print 'except'
     finally:
         print 'finally'
    

    çıktı verecek

    en sonunda

  •    try:
           1/0
       except:
           print 'except'
       finally:
           print 'finally'
    

    çıktı verecek


    sonunda hariç


2
Bu soruya hiç cevap vermiyor. Sadece ne zaman çalıştığına dair örnekler var .
zixuan
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.