Python 'with' ifadesi kullanırken bir istisna yakalama


293

Utanç için, python 'with' deyiminin istisnalarını nasıl ele alacağımı anlayamıyorum. Bir kod varsa:

with open("a.txt") as f:
    print f.readlines()

Gerçekten bir şey yapmak için 'dosya bulunamadı istisna' işlemek istiyorum. Ama yazamıyorum

with open("a.txt") as f:
    print f.readlines()
except:
    print 'oops'

ve yazamaz

with open("a.txt") as f:
    print f.readlines()
else:
    print 'oops'

try / hariç deyimine 'with' eklemek başka bir işe yaramaz: istisna oluşmaz. 'With' ifadesinin içindeki hatayı Pythonic bir şekilde işlemek için ne yapabilirim?


Ne demek bir try / hariç deyiminde "ile" çevrelemek "başka işe yaramıyor: istisna yükseltilmedi" ? Bir withifade, çevredeki bir try...exceptifadeyi sihirli bir şekilde bozmaz .
Aran-Fey

4
İlginçtir, Java'nın deneyin-with-kaynaklar deyimi yapar tam olarak istediğiniz bu kullanım örneğini destekler. docs.oracle.com/javase/tutorial/essential/exceptions/…
Nayuki

Yanıtlar:


256
from __future__ import with_statement

try:
    with open( "a.txt" ) as f :
        print f.readlines()
except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available
    print 'oops'

Açık çağrıdan çalışma koduna kıyasla hatalar için farklı bir işlem yapmak istiyorsanız, şunları yapabilirsiniz:

try:
    f = open('foo.txt')
except IOError:
    print('error')
else:
    with f:
        print f.readlines()

3
Stackoverflow.com/questions/5205811/… 'da belirtildiği gibi , buradaki try bloğu gerçekten çok geniştir. Bağlam yöneticisini oluştururken istisnalar ile with ifadesinin gövdesindekiler arasında bir ayrım yapılmadığından, tüm kullanım durumları için geçerli bir çözüm olmayabilir.
ncoghlan

@ncoghlan Ancak , ilgisi olmayan bir istisnanın kaynağına daha yakın olmak için try...exceptiçeride fazladan bloklar ekleyebilirsiniz . withopen()
rbaleksandar

1
@rbaleksandar Doğru hatırlıyorsam, yorumum kesinlikle yanıttaki ilk örneğe atıfta bulunuyordu, burada ifadeyle birlikte tüm ifadenin try / hariç bloğunun içinde olduğu (yani iç try / wait bloklarınız olsa bile, kaçmasına izin verdikleri istisnalar hala dış olana çarptı). Douglas daha sonra bu ayrımın önemli olduğu durumları ele almak için ikinci örneği ekledi.
ncoghlan

3
Bu örnekte dosya kapanacak mı? Soruyorum çünkü "ile" kapsamı dışında açıldı.
Mike Collins

6
@MikeCollins 'İle' çıkıldığında, dosya 'ile' öncesinde açılsa bile açık dosyayı kapatır.
user7938784

75

Bunu yapmanın en iyi "Pythonic" yolu, withifadeden yararlanma, ifadenin arka planını veren PEP 343'te Örnek # 6 olarak listelenmiştir .

@contextmanager
def opened_w_error(filename, mode="r"):
    try:
        f = open(filename, mode)
    except IOError, err:
        yield None, err
    else:
        try:
            yield f, None
        finally:
            f.close()

Aşağıdaki gibi kullanılır:

with opened_w_error("/etc/passwd", "a") as (f, err):
    if err:
        print "IOError:", err
    else:
        f.write("guido::0:0::/:/bin/sh\n")

38
Sevdim ama biraz fazla kara büyü gibi geliyor. Okuyucu için tamamen açık değil
Paul Seeb

5
@PaulSeeb Neden tanımlamanız ve her ihtiyacınız olduğunda bunu yapmaktan kurtulmuyorsunuz? Uygulama düzeyinde tanımlanır ve diğer bağlam yöneticileri kadar büyülüdür. Ben with deyimini kullanan birisinin bunu açıkça anlayacağını düşünüyorum (eğer beğenmezseniz işlevin adı da daha etkileyici olabilir). "With" deyiminin kendisi bu şekilde çalışacak, "güvenli" bir kod bloğu tanımlayacak ve denetim işlevlerini bağlam yöneticilerine devredecek şekilde tasarlanmıştır (kodu daha açık hale getirmek için).

9
Tüm bu sorun sadece kullanıcı kodunda nihayet bloğu yazmamak için. Hepimiz with ifadesinde uzun bir hype semptomu çektiğimizi düşünmeye başlıyorum.
jgomo3

1
Python'da istisnaları işlemenin en iyi yolu, onları yakalayan ve döndüren bir işlev yazmaktır? Ciddi anlamda? İstisnaları işlemenin pitonik yolu bir try...exceptifade kullanmaktır .
Aran-Fey

58

Python 'with' ifadesi kullanırken bir istisna yakalama

With ifadesi Python 2.6'dan bu yana__future__ içe aktarmadan kullanılabilir . Python 2.5 kadar erken alabilirsiniz (ancak bu noktada yükseltme zamanı!):

from __future__ import with_statement

İşte düzeltmek için en yakın şey. Neredeyse oradasınız, ancak withbir excepthükmünüz yok:

with open("a.txt") as f: 
    print(f.readlines())
except:                    # <- with doesn't have an except clause.
    print('oops')

Bağlam yöneticisinin __exit__yöntemi, döndürürse False, tamamlandığında hatayı yeniden oluşturur. Eğer geri gelirse True, bastırır. openBuiltin var __exit__dönmez Truesadece bir try yuva o gerek böylece blok dışında,:

try:
    with open("a.txt") as f:
        print(f.readlines())
except Exception as error: 
    print('oops')

Ve standart kazan plakası: except:yakalayan çıplak BaseExceptionve olası her türlü istisna ve uyarıyı kullanmayın. En az bu kadar spesifik olun Exceptionve bu hata için belki de yakalayın IOError. Yalnızca işlemeye hazır olduğunuz hataları yakalayın.

Yani bu durumda şunları yaparsınız:

>>> try:
...     with open("a.txt") as f:
...         print(f.readlines())
... except IOError as error: 
...     print('oops')
... 
oops

2

Bileşik bir withifadeden kaynaklanan istisnaların olası kökenleri arasında ayrım yapma

Bir withifadede ortaya çıkan istisnalar arasında ayrım yapmak zordur, çünkü farklı yerlerde ortaya çıkabilirler. İstisnalar aşağıdaki yerlerden birinden (veya burada çağrılan işlevlerden) kaynaklanabilir:

  • ContextManager.__init__
  • ContextManager.__enter__
  • gövdesi with
  • ContextManager.__exit__

Daha fazla ayrıntı için Context Manager Türleri ile ilgili belgelere bakın .

Biz sadece sarma, bu farklı durumu ayırt etmek istiyorsanız withbir içine try .. exceptyeterli değildir. Aşağıdaki örneği düşünün (örnek ValueErrorolarak kullanmak , ancak tabii ki başka bir istisna türüyle değiştirilebilir):

try:
    with ContextManager():
        BLOCK
except ValueError as err:
    print(err)

Burada exceptdört farklı yerin hepsinden kaynaklanan istisnaları yakalayacak ve böylece aralarında ayrım yapmaya izin vermeyecektir. Dışarı bağlam yöneticisi nesne örneğinin taşırsanız with, biz ayırt edebilir __init__ve BLOCK / __enter__ / __exit__:

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        with mgr:
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        # At this point we still cannot distinguish between exceptions raised from
        # __enter__, BLOCK, __exit__ (also BLOCK since we didn't catch ValueError in the body)
        pass

Etkili bir şekilde bu sadece __init__parçaya yardımcı oldu, ancak withyürütmeye başlayan gövdenin (yani __enter__ve diğerleri arasında ayrım yapma ) olup olmadığını kontrol etmek için ekstra bir sentinel değişkeni ekleyebiliriz :

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        entered_body = False
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        else:
            # At this point we know the exception came either from BLOCK or from __exit__
            pass

Zor kısım, ortaya çıkan istisnalar arasında ayrım yapmaktır BLOCKve __exit__çünkü nasıl ele alınacağına karar verebilecek withiradenin gövdesinden kaçan bir istisna geçirilir __exit__( dokümanlara bakın ). Ancak __exit__kendini yükseltirse, orijinal istisna yenisiyle değiştirilecektir. Bu davalarla başa çıkmak için, fark edilmeden kaçabilecek herhangi bir potansiyel istisnayı depolamak ve daha sonra en dışta yakalananla karşılaştırmak exceptiçin - vücudun genel bir cümlesini ekleyebiliriz - eğer aynıysa, menşe ya da aksi halde ( en dıştaki gerçek değeri döndürerek istisnayı bastırması durumunda)withexceptBLOCK__exit____exit__except basitçe yürütülmez).

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    entered_body = exc_escaped_from_body = False
    try:
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
            except Exception as err:  # this exception would normally escape without notice
                # we store this exception to check in the outer `except` clause
                # whether it is the same (otherwise it comes from __exit__)
                exc_escaped_from_body = err
                raise  # re-raise since we didn't intend to handle it, just needed to store it
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        elif err is exc_escaped_from_body:
            print('BLOCK raised:', err)
        else:
            print('__exit__ raised:', err)

PEP 343'te belirtilen eşdeğer formu kullanarak alternatif yaklaşım

PEP 343 - "with" Deyimi , withdeyimin eşdeğer bir "birlikte olmayan" sürümünü belirtir . Burada çeşitli parçaları kolayca try ... exceptfarklı potansiyel hata kaynaklarıyla sarabilir ve böylece birbirinden ayırabiliriz :

import sys

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        value = type(mgr).__enter__(mgr)
    except ValueError as err:
        print('__enter__ raised:', err)
    else:
        exit = type(mgr).__exit__
        exc = True
        try:
            try:
                BLOCK
            except TypeError:
                pass
            except:
                exc = False
                try:
                    exit_val = exit(mgr, *sys.exc_info())
                except ValueError as err:
                    print('__exit__ raised:', err)
                else:
                    if not exit_val:
                        raise
        except ValueError as err:
            print('BLOCK raised:', err)
        finally:
            if exc:
                try:
                    exit(mgr, None, None, None)
                except ValueError as err:
                    print('__exit__ raised:', err)

Genellikle daha basit bir yaklaşım iyi olur

Bu tür özel istisna muamelesine duyulan ihtiyaç oldukça nadir olmalı ve normalde withbir try ... exceptbloğa normal olarak sarılması yeterli olacaktır. Özellikle çeşitli hata kaynakları farklı (özel) istisna türleriyle (içerik yöneticilerinin buna göre tasarlanması gerekir) belirtilirse, bunlar arasında kolayca ayrım yapabiliriz. Örneğin:

try:
    with ContextManager():
        BLOCK
except InitError:  # raised from __init__
    ...
except AcquireResourceError:  # raised from __enter__
    ...
except ValueError:  # raised from BLOCK
    ...
except ReleaseResourceError:  # raised from __exit__
    ...
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.