Python basics: with mode and __enter__ and __exit__

1. Description

       Some tasks may need to be set up in advance and cleaned up afterwards. The with method is a very cool statement in python, which is safe, reliable and convenient. How can our own class have the ability to with? It must have __enter__()a method, another__exit__(),因此,这里介绍这些能力。

        A good example for this scenario is file handling, where you need to get a file handle, read data from the file, and then close the file handle.

2. Python’s with statement

2.1 The difference between with statement and without

        A good example is file handling, where you need to get a file handle, read data from the file, and then close the file handle. If the with statement is not used, the code is as follows:

file = open("/tmp/foo.txt")
data = file.read()
file.close()

        There are two questions here:

  •         1) You may forget to close the file handle;
  •         2) An exception occurred when reading data from the file, and no processing was performed.

        Here is an enhanced version that handles exceptions:

file = open("/tmp/foo.txt")
try:
    data = file.read()
finally:
    file.close()

        Although this code works well, it is too verbose. This is the time for with to show off his skills. In addition to having more elegant syntax, with can also handle exceptions generated by the context very well. Here is the code for the with version:

with open("/tmp /foo.txt") as file:
    data = file.read()

2.2 How does with work?

        This seems full of magic, but it's not just magic, Python's handling of with is also very clever. The basic idea is that the object evaluated by with must have a __enter__()method, a __exit__()method.

        After the statement immediately following with is evaluated, __enter__()the method that returns the object is called, and the return value of this method will be assigned to the variable following as. When all the code blocks following with are executed, __exit__()the method that returned the object previously will be called.

        The following example illustrates how with works:

#!/usr/bin/env python
# with_example01.py

class Sample:
    def __enter__(self):
        print( "In __enter__()" )
        return "Foo"

    def __exit__(self, type, value, trace):
        print( "In __exit__()")


def get_sample():
    return Sample()


with get_sample() as sample:
    print ("sample:", sample)

        The output is as follows

bash-3.2$ ./with_example01.py
In __enter__()
sample: Foo
In __exit__()

        As you can see,

   __enter__()
   __enter__()The value returned by the method when the method is executed - in this case "Foo", assigns the value to the variable 'sample'
        and executes the code block, printing the value of the variable "sample" as "Foo"
  __exit__()The method is called

        The real power of with is that it can handle exceptions. You may have noticed that the Sample class __exit__method has three parameters val, type and  trace. These parameters are quite useful in exception handling. Let's change the code and see how it works.

#!/usr/bin/env python
# with_example02.py


class Sample:
    def __enter__(self):
        return self

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

    def do_something(self):
        bar = 1/0
        return bar + 10

with Sample() as sample:
    sample.do_something()

        In this example, get_sample() after with becomes Sample(). This doesn't matter, as long as the object returned by the statement immediately following with has  __enter__()and __exit__()methods. In this example, the Sample() __enter__()method returns the newly created Sample object and assigns it to the variable sample.

After the code is executed:

bash-3.2$ ./with_example02.py
type: <type 'exceptions.ZeroDivisionError'>
value: integer division or modulo by zero
trace: <traceback object at 0x1004a8128>
Traceback (most recent call last):
  File "./with_example02.py", line 19, in <module>
    sample.do_somet hing()
  File "./with_example02.py", line 15, in do_something
    bar = 1/0
ZeroDivisionError: integer division or modulo by zero

__exit__()Actually, the method is executed         when any exception is thrown in the code block after with . As shown in the example, when an exception is thrown, the associated type, value and stack trace are passed to __exit__()the method, so the ZeroDivisionError exception thrown is printed. When developing a library, operations such as cleaning up resources, closing files, etc. can be placed __exit__in methods.

        Therefore, Python's with statement provides an effective mechanism to make the code more concise, and at the same time, the cleanup work is easier when an exception occurs.

3. Example: Context management protocol (__enter__,__exit)

3.1. Context management protocol

       As revealed above: in order for an object to be compatible with the with statement, the __enter__ and __exit__ methods must be declared in the object's class

  1. __enter__() will be executed when the with statement appears (instantiated object)
  2. __exit__() will not be executed until the code block of the with statement is executed.

        First we will imitate python's open file method: 

class Open:
   def __init__(self,name):
          self.name = name
  
   def __enter__(self):                 #在实例化打开文件时即触发,在with时触发
         print('执行__enter__')
         return self                    #return的self会赋值给f,相当于通过Open类实例化处对象f

   def __exit__(self,exc_type,exc_val,exc_tb):    #在with中的代码块执行完毕才会触发
         print('执行__exit__')
 

with Open('a.txt') as f:                #会触发enter  '执行__enter__',相当于--》f=Open('a.txt').__enter__()
  print(f)                              #<__main__.Open object at 0x01477270>
  print(f.name)                         #'a.txt'
print('*'*10)                           #先---'执行__exit__'
                                        #后---'*********'   


Output:
---------------------------------------------
执行__enter__
<__main__.Open object at 0x000000000210B208>
a.txt
执行__exit__
**********
---------------------------------------------

3.2 Set error detection in __exit__

        There are three parameters in __exit__() representing the exception type, exception value and traceback information respectively. Executing __exit__ means that the with statement has been executed.

        1. If the return value of __exit__ is not True, then:

        a. If there is no exception in the with statement, the program will execute normally.

         b. If an exception occurs in the with statement, the program will execute the error statement in the with statement and execute __exit__, and then the program will terminate and 'throw out' the exception.

class Open:
    def __init__(self,name):
        self.name = name
  
    def __enter__(self):                        
        print('执行__enter__')
        return self                    
  
    def __exit__(self,exc_type,exc_val,exc_tb):    
        print('执行__exit__')
        print(exc_type)                #<class 'AttributeError'>
        print(exc_val)                #'Open' object has no attribute 'age'
        print(exc_tb)                #<traceback object at 0x0178F738>
 
with Open('a.txt') as f:
     print(f)
     print(f.age)       
     #因为f对象没有age属性,则出现异常,程序执行到该句时将异常传递给__exit__的三个参数,并结束程序执行,报错
     print(f.name)        #该行语句后面的语句都不会执行,包括with语句的以外的语句也不会执行
print('*'*10) 


Output:
---------------------------------------------------------
执行__enter__
    print(f.age)
AttributeError: 'Open' object has no attribute 'age'
<__main__.Open object at 0x000000000257E4A8>
执行__exit__
<class 'AttributeError'>
'Open' object has no attribute 'age'
<traceback object at 0x0000000002583288>
---------------------------------------------------------

 

        2. If the return value of __exit__ is True, then:  a. If there is no exception in the with statement, the program will execute normally. b. If an exception occurs in the with statement, the program will execute the error statement in the with statement and execute __exit__. 'Swallow' exception. Then the remaining statements in the with statement will not be executed, but statements other than the with statement will continue to be executed.

class Open:
    def __init__(self,name):
        self.name = name
  
    def __enter__(self):                        
        print('执行__enter__')
        return self                    
  
    def __exit__(self,exc_type,exc_val,exc_tb):    
        print('执行__exit__')
        print(exc_type)                #<class 'AttributeError'>
        print(exc_val)                #'Open' object has no attribute 'age'
        print(exc_tb)                #<traceback object at 0x0178F738>
        return True
with Open('a.txt') as f:
     print(f)
     print(f.age)        #因为f对象没有age属性,则出现异常,程序执行到该句时将异常传递给__exit__的三个参数,并结束程序执行,'吞掉异常'不会报错
     print(f.name)       #该行语句后面的with中的语句都不会执行,但是with语句的以外的语句会继续执行
print('*'*10)            #'*********'

 

4. Conclusion

  1. The purpose of using the with statement is to put the code block into with for execution. After with ends, the cleanup work is automatically completed without manual intervention. 2. In a programming environment that needs to manage some resources such as files, network connections and locks, you can Customize the mechanism to automatically release resources in __exit__, so you don't have to worry about this issue anymore, which will be very useful.

Guess you like

Origin blog.csdn.net/gongdiwudu/article/details/118145478