Apakah 'akhirnya' selalu dijalankan dengan Python?

126

Untuk kemungkinan blok coba-akhirnya dengan Python, apakah dijamin finallyblok tersebut akan selalu dieksekusi?

Misalnya, saya kembali saat berada di dalam exceptblok:

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

Atau mungkin saya meminta kembali Exception:

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

Pengujian menunjukkan yang finallydieksekusi untuk contoh di atas, tetapi saya membayangkan ada skenario lain yang belum saya pikirkan.

Apakah ada skenario di mana finallyblok bisa gagal dieksekusi dengan Python?

Stevoisiak
sumber
18
Satu-satunya kasus yang dapat saya bayangkan finallygagal untuk mengeksekusi atau "mengalahkan tujuannya" adalah selama loop tak terbatas, sys.exitatau interupsi paksa. Status dokumentasi yang finallyselalu dieksekusi, jadi saya akan melakukannya.
Xay
1
Sedikit berpikir lateral dan yakin bukan apa yang Anda tanyakan, tapi saya cukup yakin bahwa jika Anda membuka Task Manager dan menghentikan proses, finallytidak akan berjalan. Atau sama jika komputer macet sebelumnya: D
Alejandro
145
finallytidak akan berfungsi jika kabel daya dicabut dari dinding.
pengguna253751
3
Anda mungkin tertarik dengan jawaban ini untuk pertanyaan yang sama tentang C #: stackoverflow.com/a/10260233/88656
Eric Lippert
1
Blokir di semafor kosong. Jangan pernah memberi isyarat. Selesai.
Martin James

Jawaban:

206

"Dijamin" adalah kata yang jauh lebih kuat daripada implementasi apa pun yang finallylayak. Apa yang dijamin adalah bahwa jika eksekusi mengalir keluar dari keseluruhan try- finallykonstruksi, itu akan melewati finallyuntuk melakukannya. Apa yang tidak dijamin adalah bahwa eksekusi akan keluar dari try- finally.

  • A finallydi generator atau coroutine async mungkin tidak akan pernah berjalan , jika objek tidak pernah dieksekusi hingga kesimpulan. Ada banyak cara yang bisa terjadi; ini dia:

    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.')

    Perhatikan bahwa contoh ini agak rumit: ketika generator dikumpulkan sampah, Python mencoba menjalankan finallyblok dengan memasukkan GeneratorExitpengecualian, tetapi di sini kita menangkap pengecualian itu dan sekali yieldlagi, di mana Python mencetak peringatan ("generator mengabaikan GeneratorExit ") dan menyerah. Lihat PEP 342 (Coroutines via Enhanced Generators) untuk detailnya.

    Cara lain generator atau coroutine mungkin tidak mengeksekusi kesimpulan termasuk jika objek tidak pernah GC'ed (ya, itu mungkin, bahkan dalam CPython), atau jika async with awaitdalam __aexit__, atau jika objek awaits atau yields dalam finallyblok. Daftar ini tidak dimaksudkan untuk menjadi lengkap.

  • Sebuah finallyutas daemon mungkin tidak akan pernah dijalankan jika semua utas non-daemon keluar lebih dulu.

  • os._exitakan segera menghentikan proses tanpa mengeksekusi finallyblok.

  • os.forkdapat menyebabkan finallyblok dieksekusi dua kali . Selain masalah normal yang Anda harapkan dari hal-hal yang terjadi dua kali, hal ini dapat menyebabkan konflik akses bersamaan (mogok, terhenti, ...) jika akses ke sumber daya bersama tidak disinkronkan dengan benar .

    Karena multiprocessingmenggunakan fork-without-exec untuk membuat proses pekerja saat menggunakan metode start fork (default pada Unix), lalu memanggil os._exitpekerja setelah pekerjaan pekerja selesai, finallydan multiprocessinginteraksi bisa menjadi masalah ( contoh ).

  • Kesalahan segmentasi tingkat C akan mencegah finallyblok berjalan.
  • kill -SIGKILLakan mencegah finallyblokir berjalan. SIGTERMdan SIGHUPjuga akan mencegah finallyblok berjalan kecuali Anda memasang penangan untuk mengontrol sendiri pematian itu; secara default, Python tidak menangani SIGTERMatau SIGHUP.
  • Pengecualian dalam finallydapat mencegah pembersihan agar tidak selesai. Satu kasus yang sangat penting adalah jika pengguna menekan control-C tepat saat kita mulai mengeksekusi finallypemblokiran. Python akan menaikkan a KeyboardInterruptdan melewati setiap baris konten finallyblok. ( KeyboardInterrupt-kode aman sangat sulit untuk ditulis).
  • Jika komputer kehilangan daya, atau jika komputer hibernate dan tidak bangun, finallypemblokiran tidak akan berjalan.

The finallyblok bukanlah sistem transaksi; itu tidak memberikan jaminan atomicity atau semacamnya. Beberapa dari contoh ini mungkin tampak jelas, tetapi mudah untuk melupakan hal-hal seperti itu dapat terjadi dan finallyterlalu diandalkan .

user2357112 mendukung Monica
sumber
14
Saya percaya hanya poin pertama dari daftar Anda yang benar-benar relevan, dan ada cara mudah untuk menghindarinya: 1) jangan pernah menggunakan telanjang except, dan jangan pernah masuk ke GeneratorExitdalam generator. Poin-poin tentang utas / mematikan proses / segfaulting / power off diharapkan, python tidak bisa melakukan sihir. Juga: pengecualian dalam finallyjelas masalah tetapi ini tidak mengubah fakta bahwa aliran kontrol itu dipindahkan ke finallyblok. Terkait Ctrl+C, Anda dapat menambahkan penangan sinyal yang mengabaikannya, atau hanya "menjadwalkan" pematian bersih setelah operasi saat ini selesai.
Giacomo Alzetta
8
Penyebutan kill -9 secara teknis benar, tapi agak tidak adil. Tidak ada program yang ditulis dalam bahasa apa pun yang menjalankan kode apa pun setelah menerima kill -9. Faktanya, tidak ada program yang pernah menerima kill -9 sama sekali, jadi meskipun diinginkan, program tidak dapat mengeksekusi apa pun. Itulah inti dari pembunuhan -9.
Tom
10
@ Tom: Intinya tentang kill -9tidak menentukan bahasa. Dan sejujurnya, itu perlu diulang, karena berada di titik buta. Terlalu banyak orang yang lupa, atau tidak menyadari, bahwa program mereka dapat terhenti di jalurnya bahkan tanpa diizinkan untuk dibersihkan.
cHao
5
@GiacomoAlzetta: Ada orang di luar sana yang mengandalkan finallyblok seolah-olah mereka memberikan jaminan transaksional. Mungkin terlihat jelas bahwa mereka tidak melakukannya, tetapi itu bukanlah sesuatu yang disadari semua orang. Adapun kasus genset, ada banyak cara generator mungkin tidak GC'ed sama sekali, dan banyak cara generator atau coroutine mungkin sengaja menghasilkan setelah GeneratorExitbahkan jika itu tidak menangkap GeneratorExit, misalnya jika async withmenunda coroutine di __exit__.
user2357112 mendukung Monica
2
@ user2357112 yeah - Saya telah mencoba selama beberapa dekade untuk mendapatkan devs untuk membersihkan file temp, dll pada startup aplikasi, bukan keluar. Mengandalkan apa yang disebut 'pembersihan dan penghentian yang anggun', meminta kekecewaan dan air mata :)
Martin James
68

Iya. Akhirnya selalu menang.

Satu-satunya cara untuk mengalahkannya adalah dengan menghentikan eksekusi sebelum finally:mendapat kesempatan untuk mengeksekusinya (misalnya, crash interpreter, matikan komputer Anda, tangguhkan generator selamanya).

Saya membayangkan ada skenario lain yang belum saya pikirkan.

Berikut ini beberapa lagi yang mungkin belum Anda pikirkan:

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'

Bergantung pada cara Anda keluar dari penerjemah, terkadang Anda akhirnya dapat "membatalkan", tetapi tidak seperti ini:

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

Menggunakan precarious os._exit(ini termasuk dalam "crash the interpreter" menurut saya):

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

Saat ini saya menjalankan kode ini, untuk menguji apakah akhirnya masih akan mengeksekusi setelah kematian panas alam semesta:

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

Namun, saya masih menunggu hasilnya, jadi periksa lagi nanti.

wim
sumber
5
atau memiliki loop terbatas i di coba catch
sapy
8
finallydi generator atau coroutine dapat dengan mudah gagal dijalankan , tanpa mendekati kondisi "crash the interpreter".
user2357112 mendukung Monica
27
Setelah kematian panas alam semesta, waktu tidak ada lagi, jadi sleep(1)pasti akan menghasilkan perilaku yang tidak terdefinisi. :-D
David Foerster
Anda mungkin ingin menyebutkan _os.exit langsung setelah “satu-satunya cara untuk mengalahkannya adalah dengan merusak kompiler”. Sekarang ini dicampur dalam beberapa contoh di mana akhirnya menang.
Stevoisiak
2
@StevenVascellaro Saya rasa itu tidak perlu - os._exitadalah, untuk semua tujuan praktis, sama dengan menyebabkan crash (keluar yang tidak bersih). Cara keluar yang benar adalah sys.exit.
wim
9

Menurut dokumentasi Python :

Tidak peduli apa yang terjadi sebelumnya, blok terakhir dijalankan setelah blok kode selesai dan setiap pengecualian yang muncul ditangani. Bahkan jika ada kesalahan dalam penangan pengecualian atau blok lain dan pengecualian baru dimunculkan, kode di blok terakhir masih berjalan.

Perlu juga dicatat bahwa jika ada beberapa pernyataan pengembalian, termasuk satu di blok akhirnya, maka pengembalian blok terakhir adalah satu-satunya yang akan dieksekusi.

jayce
sumber
8

Ya dan tidak.

Yang dijamin adalah Python akan selalu mencoba mengeksekusi blok terakhir. Dalam kasus di mana Anda kembali dari blok atau memunculkan pengecualian yang tidak tertangkap, blok terakhir dijalankan tepat sebelum benar-benar mengembalikan atau meningkatkan pengecualian.

(apa yang dapat Anda kendalikan sendiri hanya dengan menjalankan kode di pertanyaan Anda)

Satu-satunya kasus yang dapat saya bayangkan di mana blok terakhir tidak akan dieksekusi adalah ketika juru bahasa Python itu sendiri crash misalnya di dalam kode C atau karena pemadaman listrik.

Serge Ballesta
sumber
ha ha .. atau ada loop tak terbatas dalam coba catch
sapy
Saya pikir "Ya dan tidak" adalah yang paling benar. Akhirnya: selalu menang di mana "selalu" berarti penerjemah dapat menjalankan dan kode untuk "akhirnya:" masih tersedia, dan "menang" didefinisikan sebagai penerjemah akan mencoba menjalankan blok akhirnya: dan itu akan berhasil. Itulah "Ya" dan itu sangat bersyarat. "Tidak" adalah semua cara yang mungkin dihentikan oleh penerjemah sebelum "akhirnya:" - kegagalan daya, kegagalan perangkat keras, mematikan -9 yang ditujukan ke penerjemah, kesalahan dalam penerjemah atau kode yang bergantung padanya, cara lain untuk menggantung penerjemah. Dan cara untuk bertahan di dalam "akhirnya:".
Bill IV
1

Saya menemukan yang ini tanpa menggunakan fungsi generator:

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)

Tidur dapat berupa kode apa pun yang mungkin berjalan untuk waktu yang tidak konsisten.

Apa yang tampaknya terjadi di sini adalah bahwa proses paralel pertama yang diselesaikan meninggalkan blok percobaan dengan sukses, tetapi kemudian mencoba mengembalikan dari fungsi sebuah nilai (foo) yang belum didefinisikan di mana pun, yang menyebabkan pengecualian. Pengecualian itu membunuh peta tanpa membiarkan proses lain mencapai blok akhirnya.

Juga, jika Anda menambahkan baris bar = bazztepat setelah panggilan sleep () di blok coba. Kemudian proses pertama untuk mencapai garis itu mengeluarkan pengecualian (karena bazz tidak ditentukan), yang menyebabkan blok akhirnya sendiri dijalankan, tetapi kemudian membunuh peta, menyebabkan blok percobaan lainnya menghilang tanpa mencapai blok akhirnya, dan proses pertama untuk tidak mencapai pernyataan pengembaliannya.

Apa artinya ini untuk multiprocessing Python adalah Anda tidak dapat mempercayai mekanisme penanganan pengecualian untuk membersihkan sumber daya di semua proses jika salah satu proses dapat memiliki pengecualian. Penanganan sinyal tambahan atau pengelolaan sumber daya di luar panggilan peta multiprosesing akan diperlukan.

Blair Houghton
sumber
-2

Tambahkan jawaban yang diterima, hanya untuk membantu melihat cara kerjanya, dengan beberapa contoh:

  • Ini:

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

    akan mengeluarkan

    akhirnya

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

    akan mengeluarkan

    kecuali
    akhirnya

Basj
sumber
2
Ini sama sekali tidak menjawab pertanyaan itu. Ini hanya memiliki contoh kapan itu berhasil .
zixuan