Python'un '__enter__' ve '__exit__'lerini açıklama


365

Bunu birinin kodunda gördüm. Bunun anlamı ne?

    def __enter__(self):
        return self

    def __exit__(self, type, value, tb):
        self.stream.close()

from __future__ import with_statement#for python2.5 

class a(object):
    def __enter__(self):
        print 'sss'
        return 'sss111'
    def __exit__(self ,type, value, traceback):
        print 'ok'
        return False

with a() as s:
    print s


print s

19
Burada iyi bir açıklama: effbot.org/zone/python-with-statement.htm
Manur

7
@StevenVascellaro Bir sorunun kodunu düzenlemek, özellikle de kodda hata olduğunda genellikle kötü bir fikirdir . Bu soru Py2 göz önünde bulundurularak sorulmuştur ve soruyu Py3 olarak güncellemek için bir neden yoktur.
jpaugh

Yanıtlar:


421

Bu sihirli yöntemleri ( __enter__, __exit__) kullanmak , withifadeyle kolayca kullanılabilecek nesneleri uygulamanızı sağlar .

Fikir, yürütülen bazı 'cleandown' kodunu gerektiren bir kod oluşturmayı kolaylaştırıyor (bir try-finallyblok olarak düşünün ). Burada biraz daha açıklama .

Yararlı bir örnek, bir veritabanı bağlantı nesnesi olabilir (daha sonra karşılık gelen 'with' ifadesi kapsam dışına çıktığında bağlantıyı otomatik olarak kapatır):

class DatabaseConnection(object):

    def __enter__(self):
        # make a database connection and return it
        ...
        return self.dbconn

    def __exit__(self, exc_type, exc_val, exc_tb):
        # make sure the dbconnection gets closed
        self.dbconn.close()
        ...

Yukarıda açıklandığı gibi, bu nesneyi withifadeyle birlikte kullanın ( from __future__ import with_statementPython 2.5 üzerindeyseniz dosyanın üstünde yapmanız gerekebilir ).

with DatabaseConnection() as mydbconn:
    # do stuff

PEP343 - 'with' ifadesi de güzel bir yazıya sahip.


20
Muhtemelen, her zaman olduğu gibi __enter__dönmelidir selfbağlamda sadece sınıfın diğer yöntemleri çağrılabilir.
ViFI

3
@ViFI def __enter__(self)PEP 343'te 4 örnek vardır ve kimse yapmaz return self: python.org/dev/peps/pep-0343 . Neden böyle düşünüyorsun?
Keder

4
@Grief: 2 nedenden dolayı, bence, 1) selfburada açıklandığı gibi nesne üzerindeki diğer yöntemleri çağıramayacağım : stackoverflow.com/questions/38281853/… 2) self.XYZ sadece kendi nesnesinin bir parçası ve sadece bakım kolu açısından bana uygun görünmüyor. Daha ziyade tam nesneye tanıtıcı döndürmek ve daha sonra sadece gibi bileşenler selfiçin kullanıcı API'leri göstermek istiyorum: with open(abc.txt, 'r') as fin: content = fin.read()
ViFI

4
Dosya nesneleri döndürmek selfdan __enter__gelip siz dosyayı işlemek nasıl olduğu, fwith open(...) as f
holdenweb

2
Anlamak zorunda olduğum bir incelik: nesnenin başlatılması için parametreler gerekiyorsa , bunlar init üzerinde olmalı , kendi kendine değil .
dfrankow

71

Bağlam yöneticilerinin ne olduğunu biliyorsanız , anlamak __enter__ve __exit__büyü yöntemleri için başka bir şeye ihtiyacınız yoktur . Çok basit bir örnek görelim.

Bu örnekte, myfile.txt dosyasını open işlevi yardımıyla açıyorum . Try / finally beklenmedik bir durum meydana gelse bile garanti engellemek myfile.txt kapatılacaktır.

fp=open(r"C:\Users\SharpEl\Desktop\myfile.txt")
try:
    for line in fp:
        print(line)
finally:
    fp.close()

Şimdi birlikte aynı dosyayı açıyorum ile açıklamaya:

with open(r"C:\Users\SharpEl\Desktop\myfile.txt") as fp:
    for line in fp:
        print(line) 

Eğer koda bakarsanız, dosyayı kapatmadım ve hiçbir try / nihayet bloğu yok. Çünkü ile ifadesi otomatik olarak kapanır myfile.txt . Hatta print(fp.closed)döndüren özniteliği çağırarak da kontrol edebilirsiniz True.

Bunun nedeni, open işlevi tarafından döndürülen dosya nesnelerinin (örneğimdeki fp) iki yerleşik yöntemi __enter__ve olmasıdır __exit__. İçerik yöneticisi olarak da bilinir. __enter__yöntemi blok ile başında, __exit__ yöntemi ise sonunda çağırır. Not: with deyimi yalnızca bağlam değiştirme protokolünü destekleyen nesnelerle çalışır, yani sahip oldukları __enter__ve __exit__yöntemleri. Her iki yöntemi de uygulayan bir sınıf, bağlam yöneticisi sınıfı olarak bilinir.

Şimdi kendi bağlam yöneticisi sınıfımızı tanımlayalım .

 class Log:
    def __init__(self,filename):
        self.filename=filename
        self.fp=None    
    def logging(self,text):
        self.fp.write(text+'\n')
    def __enter__(self):
        print("__enter__")
        self.fp=open(self.filename,"a+")
        return self    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("__exit__")
        self.fp.close()

with Log(r"C:\Users\SharpEl\Desktop\myfile.txt") as logfile:
    print("Main")
    logfile.logging("Test1")
    logfile.logging("Test2")

İkinizin de temel bir anlayış var artık umut __enter__ve __exit__sihirli yöntemlerle.


53

Googling'in python belgelerini __enter__ve __exit__yöntemlerini bulmak garip bir şekilde zor buldum , bu yüzden burada başkalarına yardımcı olmak için bağlantı:

https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
(ayrıntı her iki sürüm için de aynıdır)

object.__enter__(self)
Bu nesneyle ilgili çalışma zamanı bağlamını girin. withİfadesi bilançosunu fıkrada belirtilen hedef (s) bu yöntemin dönüş değeri varsa bağlayacaktır.

object.__exit__(self, exc_type, exc_value, traceback)
Bu nesneyle ilgili çalışma zamanı bağlamından çıkın. Parametreler, bağlamdan çıkılmasına neden olan istisnayı tanımlar. Bağlam istisnasız olarak çıkarıldıysa, üç bağımsız değişken de olacaktır None.

Bir istisna sağlanmışsa ve yöntem istisnayı bastırmak istiyorsa (yani, yayılmasını engelliyorsa), gerçek bir değer döndürmelidir. Aksi takdirde, istisna bu yöntemden çıkıldığında normal olarak işlenir.

__exit__()Yöntemlerin aktarılan istisnayı yeniden yükseltmemesi gerektiğini unutmayın ; bu arayanın sorumluluğundadır.

__exit__Yöntem argümanlarının net bir açıklamasını umuyordum . Bu eksik ama onları çıkarabiliriz ...

Muhtemelen exc_typeistisna sınıfıdır.

Aktarılan istisnayı yeniden yükseltmemeniz gerektiğini söylüyor. Bu bize argümanlardan birinin gerçek bir İstisna örneği olabileceğini düşündürüyor ... ya da belki de onu tür ve değerden kendiniz başlatmanız mı gerekiyor?

Bu makaleye bakarak cevap verebiliriz:
http://effbot.org/zone/python-with-statement.htm

Örneğin, aşağıdaki __exit__yöntem herhangi bir TypeError yutar, ancak diğer tüm istisnaların aşağıdakileri gerçekleştirmesine izin verir:

def __exit__(self, type, value, traceback):
    return isinstance(value, TypeError)

... açıkça valuebir İstisna örneğidir.

Ve muhtemelen tracebackbir Python geri izleme nesnesidir.


2
Katılıyorum. Bu url'yi bulmak çok zor.
Shihao Xu

PEP referansında bu gömülü bitin arg kullanımını not etmek önemli olabilir: python.org/dev/peps/pep-0343/#generator-decorator
Tcll

43

Çağrı sırasını örneklemek için yukarıdaki cevaplara ek olarak, basit bir çalışma örneği

class myclass:
    def __init__(self):
        print("__init__")

    def __enter__(self): 
        print("__enter__")

    def __exit__(self, type, value, traceback):
        print("__exit__")

    def __del__(self):
        print("__del__")

with myclass(): 
    print("body")

Çıktı üretir:

__init__
__enter__
body
__exit__
__del__

Bir hatırlatma: sözdizimi kullanılırken with myclass() as mcmc değişkeni __enter__()yukarıdaki durumda döndürülen değeri alır None! Böyle bir kullanım için, dönüş değerini tanımlamanız gerekir, örneğin:

def __enter__(self): 
    print('__enter__')
    return self

3
Ve tanımların sırası değiştirilse bile, yürütme sırası aynı kalır!
Sean

1
Bu çok yardımcı oldu. Teşekkür ederim.
Reez0

5

cevaplarımı eklemeyi deneyin (öğrenme düşüncem):

__enter__ve [__exit__]her ikisi de " with ifadesi " ( PEP 343 ) gövdesine giriş ve çıkıştan çağrılan ve her ikisinin uygulanmasına bağlam yöneticisi adı verilen yöntemlerdir.

with ifadesi, try nihayet maddesinin akış kontrolünü gizlemeyi ve kodu anlaşılmaz yapmayı amaçlamaktadır.

with ifadesinin sözdizimi:

with EXPR as VAR:
    BLOCK

(PEP 343'te belirtildiği gibi):

mgr = (EXPR)
exit = type(mgr).__exit__  # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
    try:
        VAR = value  # Only if "as VAR" is present
        BLOCK
    except:
        # The exceptional case is handled here
        exc = False
        if not exit(mgr, *sys.exc_info()):
            raise
        # The exception is swallowed if exit() returns true
finally:
    # The normal and non-local-goto cases are handled here
    if exc:
        exit(mgr, None, None, None)

bazı kodları deneyin:

>>> import logging
>>> import socket
>>> import sys

#server socket on another terminal / python interpreter
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> s.listen(5)
>>> s.bind((socket.gethostname(), 999))
>>> while True:
>>>    (clientsocket, addr) = s.accept()
>>>    print('get connection from %r' % addr[0])
>>>    msg = clientsocket.recv(1024)
>>>    print('received %r' % msg)
>>>    clientsocket.send(b'connected')
>>>    continue

#the client side
>>> class MyConnectionManager:
>>>     def __init__(self, sock, addrs):
>>>         logging.basicConfig(level=logging.DEBUG, format='%(asctime)s \
>>>         : %(levelname)s --> %(message)s')
>>>         logging.info('Initiating My connection')
>>>         self.sock = sock
>>>         self.addrs = addrs
>>>     def __enter__(self):
>>>         try:
>>>             self.sock.connect(addrs)
>>>             logging.info('connection success')
>>>             return self.sock
>>>         except:
>>>             logging.warning('Connection refused')
>>>             raise
>>>     def __exit__(self, type, value, tb):
>>>             logging.info('CM suppress exception')
>>>             return False
>>> addrs = (socket.gethostname())
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> with MyConnectionManager(s, addrs) as CM:
>>>     try:
>>>         CM.send(b'establishing connection')
>>>         msg = CM.recv(1024)
>>>         print(msg)
>>>     except:
>>>         raise
#will result (client side) :
2018-12-18 14:44:05,863         : INFO --> Initiating My connection
2018-12-18 14:44:05,863         : INFO --> connection success
b'connected'
2018-12-18 14:44:05,864         : INFO --> CM suppress exception

#result of server side
get connection from '127.0.0.1'
received b'establishing connection'

ve şimdi manuel olarak deneyin (çeviri sözdizimini izleyerek):

>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #make new socket object
>>> mgr = MyConnection(s, addrs)
2018-12-18 14:53:19,331         : INFO --> Initiating My connection
>>> ext = mgr.__exit__
>>> value = mgr.__enter__()
2018-12-18 14:55:55,491         : INFO --> connection success
>>> exc = True
>>> try:
>>>     try:
>>>         VAR = value
>>>         VAR.send(b'establishing connection')
>>>         msg = VAR.recv(1024)
>>>         print(msg)
>>>     except:
>>>         exc = False
>>>         if not ext(*sys.exc_info()):
>>>             raise
>>> finally:
>>>     if exc:
>>>         ext(None, None, None)
#the result:
b'connected'
2018-12-18 15:01:54,208         : INFO --> CM suppress exception

sunucu tarafının sonucu eskisi gibi

kötü ingilizcem ve belirsiz açıklamalarım için üzgünüm, teşekkür ederim ....


1

Buna bağlam yöneticisi denir ve sadece diğer programlama dilleri için benzer yaklaşımların mevcut olduğunu eklemek istiyorum. Bunları karşılaştırmak, python'daki bağlam yöneticisini anlamada yardımcı olabilir. Temel olarak, başlatılması gereken bazı kaynaklarla (dosya, ağ, veritabanı) uğraşırken bir bağlam yöneticisi kullanılır ve bir noktada yırtılma (imha etme). In Java 7 ve üzeri biz halini alır otomatik kaynak yönetimini vardır:

//Java code
try (Session session = new Session())
{
  // do stuff
}

Oturumun AutoClosableya da (birçok) alt arabiriminden birini uygulaması gerektiğini unutmayın .

In C # , biz biçimini alır kaynaklarını yönetmek için ifadeleri kullanıyor adres:

//C# code
using(Session session = new Session())
{
  ... do stuff.
}

Hangi Sessionuygulamak gerekir IDisposable.

In piton , kullandığımız o sınıf uygulamalıdır __enter__ve __exit__. Yani şu şekilde olur:

#Python code
with Session() as session:
    #do stuff

Diğerlerinin de belirttiği gibi, aynı mekanizmayı uygulamak için her zaman tüm dillerde try / finally ifadesini kullanabilirsiniz. Bu sadece sözdizimsel şekerdir.

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.