Movatterモバイル変換


[0]ホーム

URL:


— FREE Email Series —

🐍 Python Tricks 💌

Python Tricks Dictionary Merge

🔒 No spam. Unsubscribe any time.

Browse TopicsGuided Learning Paths
Basics Intermediate Advanced
apibest-practicescareercommunitydatabasesdata-sciencedata-structuresdata-vizdevopsdjangodockereditorsflaskfront-endgamedevguimachine-learningnumpyprojectspythontestingtoolsweb-devweb-scraping

Table of Contents

Recommended Video Course
Context Managers and Using Python's with Statement

Context Managers and Python's with Statement

Context Managers and Python's with Statement

byLeodanis Pozo Ramosintermediatepython

Table of Contents

Remove ads

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding:Context Managers and Using Python's with Statement

Thewith statement in Python is a quite useful tool for properly managing external resources in your programs. It allows you to take advantage of existingcontext managers to automatically handle the setup and teardown phases whenever you’re dealing with external resources or with operations that require those phases.

Besides, thecontext management protocol allows you to create your own context managers so you can customize the way you deal with system resources. So, what’s thewith statement good for?

In this tutorial, you’ll learn:

  • What thePythonwith statement is for and how to use it
  • What thecontext management protocol is
  • How to implement your owncontext managers

With this knowledge, you’ll write more expressive code and avoidresource leaks in your programs. Thewith statement helps you implement some common resource management patterns by abstracting their functionality and allowing them to be factored out and reused.

Free Download:Get a sample chapter from Python Tricks: The Book that shows you Python’s best practices with simple examples you can apply instantly to write more beautiful + Pythonic code.

Take the Quiz: Test your knowledge with our interactive “Context Managers and Python's with Statement” quiz. You’ll receive a score upon completion to help you track your learning progress:


Context Managers and Python's with Statement

Interactive Quiz

Context Managers and Python's with Statement

In this quiz, you'll assess your understanding of the Python with statement and context managers. By mastering these concepts, you'll be able to write more expressive code and manage system resources more effectively, avoiding resource leaks and ensuring proper setup and teardown of external resources.

Managing Resources in Python

One common problem you’ll face in programming is how to properly manageexternal resources, such asfiles,locks, and network connections. Sometimes, a program will retain those resources forever, even if you no longer need them. This kind of issue is called amemory leak because the available memory gets reduced every time you create and open a new instance of a given resource without closing an existing one.

Managing resources properly is often a tricky problem. It requires both asetup phase and ateardown phase. The latter phase requires you to perform some cleanup actions, such asclosing a file, releasing a lock, or closing a network connection. If you forget to perform these cleanup actions, then your application keeps the resource alive. This might compromise valuable system resources, such as memory and network bandwidth.

For example, a common problem that can arise when developers are working with databases is when a program keeps creating new connections without releasing or reusing them. In that case, the databaseback end can stop accepting new connections. This might require an admin to log in and manually kill those stale connections to make the database usable again.

Another frequent issue shows up when developers are working with files.Writing text to files is usually a buffered operation. This means that calling.write() on a file won’t immediately result in writing text to the physical file but to a temporary buffer. Sometimes, when the buffer isn’t full and developers forget to call.close(), part of the data can be lost forever.

Another possibility is that your application runs into errors orexceptions that cause the control flow to bypass the code responsible for releasing the resource at hand. Here’s an example in which you useopen() to write some text to a file:

Python
file=open("hello.txt","w")file.write("Hello, World!")file.close()

This implementation doesn’t guarantee the file will be closed if an exception occurs during the.write() call. In this case, the code will never call.close(), and therefore your program might leak a file descriptor.

In Python, you can use two general approaches to deal with resource management. You can wrap your code in:

  1. Atryfinally construct
  2. Awith construct

The first approach is quite general and allows you to provide setup and teardown code to manage any kind of resource. However, it’s a little bit verbose. Also, what if you forget any cleanup actions?

The second approach provides a straightforward way to provide and reuse setup and teardown code. In this case, you’ll have the limitation that thewith statement only works withcontext managers. In the next two sections, you’ll learn how to use both approaches in your code.

Thetryfinally Approach

Working with files is probably the most common example of resource management in programming. In Python, you can use atryfinally statement to handle opening and closing files properly:

Python
# Safely open the filefile=open("hello.txt","w")try:file.write("Hello, World!")finally:# Make sure to close the file after using itfile.close()

In this example, you need to safely open the filehello.txt, which you can do by wrapping the call toopen() in atryexcept statement. Later, when you try to write tofile, thefinally clause will guarantee thatfile is properly closed, even if an exception occurs during the call to.write() in thetry clause. You can use this pattern to handle setup and teardown logic when you’re managing external resources in Python.

Thetry block in the above example can potentially raise exceptions, such asAttributeError orNameError. You can handle those exceptions in anexcept clause like this:

Python
# Safely open the filefile=open("hello.txt","w")try:file.write("Hello, World!")exceptExceptionase:print(f"An error occurred while writing to the file:{e}")finally:# Make sure to close the file after using itfile.close()

In this example, you catch any potential exceptions that can occur while writing to the file. In real-life situations, you should use a specificexception type instead of the generalException to prevent unknown errors from passing silently.

Thewith Statement Approach

The Pythonwith statement creates aruntime context that allows you to run a group of statements under the control of acontext manager.PEP 343 added thewith statement to make it possible to factor out standard use cases of thetryfinally statement.

Compared to traditionaltryfinally constructs, thewith statement can make your code clearer, safer, and reusable. Many classes in thestandard library support thewith statement. A classic example of this isopen(), which allows you to work withfile objects usingwith.

To write awith statement, you need to use the following general syntax:

Python
withexpressionastarget_var:do_something(target_var)

The context manager object results from evaluating theexpression afterwith. In other words,expression must return an object that implements thecontext management protocol. This protocol consists of twospecial methods:

  1. .__enter__() is called by thewith statement to enter the runtime context.
  2. .__exit__() is called when the execution leaves thewith code block.

Theas specifier is optional. If you provide atarget_var withas, then thereturn value of calling.__enter__() on the context manager object is bound to that variable.

Note: Some context managers returnNone from.__enter__() because they have no useful object to give back to the caller. In these cases, specifying atarget_var makes no sense.

Here’s how thewith statement proceeds when Python runs into it:

  1. Callexpression to obtain a context manager.
  2. Store the context manager’s.__enter__() and.__exit__() methods for later use.
  3. Call.__enter__() on the context manager and bind its return value totarget_var if provided.
  4. Execute thewith code block.
  5. Call.__exit__() on the context manager when thewith code block finishes.

In this case,.__enter__(), typically provides the setup code. Thewith statement is acompound statement that starts a code block, like aconditional statement or afor loop. Inside this code block, you can run several statements. Typically, you use thewith code block to manipulatetarget_var if applicable.

Once thewith code block finishes,.__exit__() gets called. This method typically provides the teardown logic or cleanup code, such as calling.close() on an open file object. That’s why thewith statement is so useful. It makes properly acquiring and releasing resources a breeze.

Here’s how to open yourhello.txt file for writing using thewith statement:

Python
withopen("hello.txt",mode="w")asfile:file.write("Hello, World!")

When you run thiswith statement,open() returns anio.TextIOBase object. This object is also a context manager, so thewith statement calls.__enter__() and assigns its return value tofile. Then you can manipulate the file inside thewith code block. When the block ends,.__exit__() automatically gets called and closes the file for you, even if an exception is raised inside thewith block.

Thiswith construct is shorter than itstryfinally alternative, but it’s also less general, as you already saw. You can only use thewith statement with objects that support the context management protocol, whereastryfinally allows you to perform cleanup actions for arbitrary objects without the need for supporting the context management protocol.

In Python 3.1 and later, thewith statementsupports multiple context managers. You can supply any number of context managers separated by commas:

Python
withA()asa,B()asb:pass

This works like nestedwith statements but without nesting. This might be useful when you need to open two files at a time, the first for reading and the second for writing:

Python
withopen("input.txt")asin_file,open("output.txt","w")asout_file:# Read content from input.txt# Transform the content# Write the transformed content to output.txtpass

In this example, you can add code for reading and transforming the content ofinput.txt. Then you write the final result tooutput.txt in the same code block.

Using multiple context managers in a singlewith has a drawback, though. If you use this feature, then you’ll probably break your line length limit. To work around this, you need to use backslashes (\) for line continuation, so you might end up with an ugly final result.

Thewith statement can make the code that deals with system resources more readable, reusable, and concise, not to mention safer. It helps avoid bugs and leaks by making it almost impossible to forget cleaning up, closing, and releasing a resource after you’re done with it.

Usingwith allows you to abstract away most of the resource handling logic. Instead of having to write an explicittryfinally statement with setup and teardown code each time,with takes care of that for you and avoids repetition.

Using the Pythonwith Statement

As long as Python developers have incorporated thewith statement into their coding practice, the tool has been shown to have several valuable use cases. More and more objects in the Python standard library now provide support for the context management protocol so you can use them in awith statement.

In this section, you’ll code some examples that show how to use thewith statement with several classes both in the standard library and in third-party libraries.

Working With Files

So far, you’ve usedopen() to provide a context manager and manipulate files in awith construct. Opening files using thewith statement is generally recommended because it ensures that openfile descriptors are automatically closed after the flow of execution leaves thewith code block.

As you saw before, the most common way to open a file usingwith is through the built-inopen():

Python
withopen("hello.txt",mode="w")asfile:file.write("Hello, World!")

In this case, since the context manager closes the file after leaving thewith code block, a common mistake might be the following:

Python
>>>file=open("hello.txt",mode="w")>>>withfile:...file.write("Hello, World!")...13>>>withfile:...file.write("Welcome to Real Python!")...Traceback (most recent call last):  File"<stdin>", line1, in<module>ValueError:I/O operation on closed file.

The firstwith successfully writes"Hello, World!" intohello.txt. Note that.write() returns the number of bytes written into the file,13. When you try to run a secondwith, however, you get aValueError because yourfile is already closed.

Another way to use thewith statement to open and manage files is by usingpathlib.Path.open():

Python
>>>importpathlib>>>file_path=pathlib.Path("hello.txt")>>>withfile_path.open("w")asfile:...file.write("Hello, World!")...13

Path is a class that represents concrete paths to physical files in your computer. Calling.open() on aPath object that points to a physical file opens it just likeopen() would do. So,Path.open() works similarly toopen(), but the file path is automatically provided by thePath object you call the method on.

Sincepathlib provides an elegant, straightforward, andPythonic way to manipulate file system paths, you should consider usingPath.open() in yourwith statements as a best practice in Python.

Finally, whenever you load an external file, your program should check for possible issues, such as a missing file, writing and reading access, and so on. Here’s a general pattern that you should consider using when you’re working with files:

Python
importpathlibimportloggingfile_path=pathlib.Path("hello.txt")try:withfile_path.open(mode="w")asfile:file.write("Hello, World!")exceptOSErroraserror:logging.error("Writing to file%s failed due to:%s",file_path,error)

In this example, you wrap thewith statement in atryexcept statement. If anOSError occurs during the execution ofwith, then you uselogging to log the error with a user-friendly and descriptive message.

Traversing Directories

Theos module provides a function calledscandir(), which returns an iterator overos.DirEntry objects corresponding to the entries in a given directory. This function is specially designed to provide optimal performance when you’re traversing a directory structure.

A call toscandir() with the path to a given directory as an argument returns an iterator that supports the context management protocol:

Python
>>>importos>>>withos.scandir(".")asentries:...forentryinentries:...print(entry.name,"->",entry.stat().st_size,"bytes")...Documents -> 4096 bytesVideos -> 12288 bytesDesktop -> 4096 bytesDevSpace -> 4096 bytes.profile -> 807 bytesTemplates -> 4096 bytesPictures -> 12288 bytesPublic -> 4096 bytesDownloads -> 4096 bytes

In this example, you write awith statement withos.scandir() as the context manager supplier. Then you iterate over the entries in the selected directory (".") andprint their name and size on the screen. In this case,.__exit__() callsscandir.close() to close the iterator and release the acquired resources. Note that if you run this on your machine, you’ll get a different output depending on the content of your current directory.

Performing High-Precision Calculations

Unlike built-infloating-point numbers, thedecimal module provides a way to adjust the precision to use in a given calculation that involvesDecimal numbers. The precision defaults to28 places, but you can change it to meet your problem requirements. A quick way to perform calculations with a custom precision is usinglocalcontext() fromdecimal:

Python
>>>fromdecimalimportDecimal,localcontext>>>withlocalcontext()asctx:...ctx.prec=42...Decimal("1")/Decimal("42")...Decimal('0.0238095238095238095238095238095238095238095')>>>Decimal("1")/Decimal("42")Decimal('0.02380952380952380952380952381')

Here,localcontext() provides a context manager that creates a local decimal context and allows you to perform calculations using a custom precision. In thewith code block, you need to set.prec to the new precision you want to use, which is42 places in the example above. When thewith code block finishes, the precision is reset back to its default value,28 places.

Handling Locks in Multithreaded Programs

Another good example of using thewith statement effectively in the Python standard library isthreading.Lock. This class provides a primitive lock to prevent multiple threads from modifying a shared resource at the same time in amultithreaded application.

You can use aLock object as the context manager in awith statement to automatically acquire and release a given lock. For example, say you need to protect the balance of a bank account:

Python
importthreadingbalance_lock=threading.Lock()# Use the try ... finally patternbalance_lock.acquire()try:# Update the account balance here ...finally:balance_lock.release()# Use the with patternwithbalance_lock:# Update the account balance here ...

Thewith statement in the second example automatically acquires and releases a lock when the flow of execution enters and leaves the statement. This way, you can focus on what really matters in your code and forget about those repetitive operations.

In this example, the lock in thewith statement creates a protected region known as thecritical section, which prevents concurrent access to the account balance.

Testing for Exceptions With pytest

So far, you’ve coded a few examples using context managers that are available in the Python standard library. However, several third-party libraries include objects that support the context management protocol.

Say you’retesting your code withpytest. Some of your functions and code blocks raise exceptions under certain situations, and you want to test those cases. To do that, you can usepytest.raises(). This function allows you to assert that a code block or a function call raises a given exception.

Sincepytest.raises() provides a context manager, you can use it in awith statement like this:

Python
>>>importpytest>>>1/0Traceback (most recent call last):  File"<stdin>", line1, in<module>ZeroDivisionError:division by zero>>>withpytest.raises(ZeroDivisionError):...1/0...>>>favorites={"fruit":"apple","pet":"dog"}>>>favorites["car"]Traceback (most recent call last):  File"<stdin>", line1, in<module>KeyError:'car'>>>withpytest.raises(KeyError):...favorites["car"]...

In the first example, you usepytest.raises() to capture theZeroDivisionError that the expression1 / 0 raises. The second example uses the function to capture theKeyError that is raised when you access a key that doesn’t exist in a given dictionary.

If your function or code block doesn’t raise the expected exception, thenpytest.raises() raises a failure exception:

Python
>>>importpytest>>>withpytest.raises(ZeroDivisionError):...4/2...2.0Traceback (most recent call last):...Failed:DID NOT RAISE <class 'ZeroDivisionError'>

Another cool feature ofpytest.raises() is that you can specify a target variable to inspect the raised exception. For example, if you want to verify the error message, then you can do something like this:

Python
>>>withpytest.raises(ZeroDivisionError)asexc:...1/0...>>>assertstr(exc.value)=="division by zero"

You can use all thesepytest.raises() features to capture the exceptions you raise from yourfunctions and code block. This is a cool and useful tool that you can incorporate into your current testing strategy.

Summarizing thewith Statement’s Advantages

To summarize what you’ve learned so far, here’s an inexhaustive list of the general benefits of using the Pythonwith statement in your code:

  • Makesresource management safer than its equivalenttryfinally statements
  • Encapsulates standard uses oftryfinally statements incontext managers
  • Allows reusing the code that automatically manages thesetup andteardown phases of a given operation
  • Helps avoidresource leaks

Using thewith statement consistently can improve the general quality of your code and make it safer by preventing resource leak problems.

Using theasync with Statement

Thewith statement also has an asynchronous version,async with. You can use it to write context managers that depend on asynchronous code. It’s quite common to seeasync with in that kind of code, as manyIO operations involve setup and teardown phases.

For example, say you need to code an asynchronous function to check if a given site is online. To do that, you can useaiohttp,asyncio, andasync with like this:

Python
 1# site_checker_v0.py 2 3importaiohttp 4importasyncio 5 6asyncdefcheck(url): 7asyncwithaiohttp.ClientSession()assession: 8asyncwithsession.get(url)asresponse: 9print(f"{url}: status ->{response.status}")10html=awaitresponse.text()11print(f"{url}: type ->{html[:17].strip()}")1213asyncdefmain():14awaitasyncio.gather(15check("https://realpython.com"),16check("https://pycoders.com"),17)1819asyncio.run(main())

Here’s what this script does:

  • Line 3importsaiohttp, which provides an asynchronous HTTP client and server forasyncio and Python. Note thataiohttp is a third-party package that you can install by runningpython -m pip install aiohttp on your command line.
  • Line 4 importsasyncio, which allows you to writeconcurrent code using theasync andawait syntax.
  • Line 6 definescheck() as an asynchronous function using theasynckeyword.

Insidecheck(), you define two nestedasync with statements:

  • Line 7 defines an outerasync with that instantiatesaiohttp.ClientSession() to get a context manager. It stores the returned object insession.
  • Line 8 defines an innerasync with statement that calls.get() onsession usingurl as an argument. This creates a second context manager and returns aresponse.
  • Line 9 prints the responsestatus code for theurl at hand.
  • Line 10 runs an awaitable call to.text() onresponse and stores the result inhtml.
  • Line 11 prints the siteurl and its document type,doctype.
  • Line 13 defines the script’smain() function, which is also acoroutine.
  • Line 14 callsgather() fromasyncio. This function runsawaitable objects in a sequence concurrently. In this example,gather() runs two instances ofcheck() with a differentURL for each.
  • Line 19 runsmain() usingasyncio.run(). This function creates a newasyncioevent loop and closes it at the end of the operation.

If yourun this script from your command line, then you get an output similar to the following:

Shell
$pythonsite_checker_v0.pyhttps://realpython.com: status -> 200https://pycoders.com: status -> 200https://pycoders.com: type -> <!doctype html>https://realpython.com: type -> <!doctype html>

Cool! Your script works and you confirm that both sites are currently available. You also retrieve the information regarding document type from each site’s home page.

Note: Your output can look slightly different due to the nondeterministic nature of concurrent task scheduling and network latency. In particular, the individual lines can come out in a different order.

Theasync with statement works similar to the regularwith statement, but it requires anasynchronous context manager. In other words, it needs a context manager that is able to suspend execution in its enter and exit methods. Asynchronous context managers implement the special methods.__aenter__() and.__aexit__(), which correspond to.__enter__() and.__exit__() in a regular context manager.

Theasync with ctx_mgr construct implicitly usesawait ctx_mgr.__aenter__() when entering the context andawait ctx_mgr.__aexit__() when exiting it. This achievesasync context manager behavior seamlessly.

Creating Custom Context Managers

You’ve already worked with context managers from the standard library and third-party libraries. There’s nothing special or magical aboutopen(),threading.Lock,decimal.localcontext(), or the others. They just return objects that implement the context management protocol.

You can provide the same functionality by implementing both the.__enter__() and the.__exit__() special methods in yourclass-based context managers. You can also create customfunction-based context managers using thecontextlib.contextmanager decorator from the standard library and an appropriately codedgenerator function.

In general, context managers and thewith statement aren’t limited to resource management. They allow you to provide and reuse common setup and teardown code. In other words, with context managers, you can perform any pair of operations that needs to be donebefore andafter another operation or procedure, such as:

  • Open and close
  • Lock and release
  • Change and reset
  • Create and delete
  • Enter and exit
  • Start and stop
  • Setup and teardown

You can provide code to safely manage any of these pairs of operations in a context manager. Then you can reuse that context manager inwith statements throughout your code. This prevents errors and reduces repetitive boilerplate code. It also makes yourAPIs safer, cleaner, and more user-friendly.

In the next two sections, you’ll learn the basics of creating class-based and function-based context managers.

Coding Class-Based Context Managers

To implement the context management protocol and createclass-based context managers, you need to add both the.__enter__() and the__exit__() special methods to your classes. The table below summarizes how these methods work, the arguments they take, and the logic you can put in them:

MethodDescription
.__enter__(self)This method handles the setup logic and is called when entering a newwith context. Its return value is bound to thewith target variable.
.__exit__(self, exc_type, exc_value, exc_tb)This method handles the teardown logic and is called when the flow of execution leaves thewith context. If an exception occurs, thenexc_type,exc_value, andexc_tb hold the exception type, value, and traceback information, respectively.

When thewith statement executes, it calls.__enter__() on the context manager object to signal that you’re entering into a new runtime context. If you provide a target variable with theas specifier, then the return value of.__enter__() is assigned to that variable.

When the flow of execution leaves the context,.__exit__() is called. If no exception occurs in thewith code block, then the three last arguments to.__exit__() are set toNone. Otherwise, they hold the type, value, andtraceback associated with the exception at hand.

If the.__exit__() method returnsTrue, then any exception that occurs in thewith block is swallowed and the execution continues at the next statement afterwith. If.__exit__() returnsFalse, then exceptions are propagated out of the context. This is also the default behavior when the method doesn’t return anything explicitly. You can take advantage of this feature to encapsulate exception handling inside the context manager.

Writing a Sample Class-Based Context Manager

Here’s a sample class-based context manager that implements both methods,.__enter__() and.__exit__(). It also shows how Python calls them in awith construct:

Python
>>>classHelloContextManager:...def__enter__(self):...print("Entering the context...")...return"Hello, World!"...def__exit__(self,exc_type,exc_value,exc_tb):...print("Leaving the context...")...print(exc_type,exc_value,exc_tb,sep="\n")...>>>withHelloContextManager()ashello:...print(hello)...Entering the context...Hello, World!Leaving the context...NoneNoneNone

HelloContextManager implements both.__enter__() and.__exit__(). In.__enter__(), you first print a message to signal that the flow of execution is entering a new context. Then you return the"Hello, World!" string. In.__exit__(), you print a message to signal that the flow of execution is leaving the context. You also print the content of its three arguments.

When thewith statement runs, Python creates a new instance ofHelloContextManager and calls its.__enter__() method. You know this because you getEntering the context... printed on the screen.

Note: A common mistake when you’re using context managers is forgetting to call the object passed to thewith statement.

In this case, the statement can’t get the required context manager, and you get anAttributeError like this:

Python
>>>withHelloContextManagerashello:...print(hello)...Traceback (most recent call last):  File"<stdin>", line1, in<module>AttributeError:__enter__

The exception message doesn’t say too much, and you might feel confused in this kind of situation. So, make sure to call the object in thewith statement to provide the corresponding context manager.

Then Python runs thewith code block, which printshello to the screen. Note thathello holds the return value of.__enter__().

When the flow of execution exits thewith code block, Python calls.__exit__(). You know that because you getLeaving the context... printed on your screen. The final line in the output confirms that the three arguments to.__exit__() are set toNone.

Note: A common trick when you don’t remember the exact signature of.__exit__() and don’t need to access its arguments is to use*args and**kwargs like indef __exit__(self, *args, **kwargs):.

Now, what happens if an exception occurs during the execution of thewith block? Go ahead and write the followingwith statement:

Python
>>>withHelloContextManager()ashello:...print(hello)...hello[100]...Entering the context...Hello, World!Leaving the context...<class 'IndexError'>string index out of range<traceback object at 0x7f0cebcdd080>Traceback (most recent call last):  File"<stdin>", line3, in<module>IndexError:string index out of range

In this case, you try to retrieve the value at index100 in thestring"Hello, World!". This raises anIndexError, and the arguments to.__exit__() are set to the following:

  • exc_type is the exception class,IndexError.
  • exc_value is the exception instance.
  • exc_tb is the traceback object.

This behavior is quite useful when you want to encapsulate the exception handling in your context managers.

Handling Exceptions in a Context Manager

As an example of encapsulating exception handling in a context manager, say you expectIndexError to be the most common exception when you’re working withHelloContextManager. You might want to handle that exception in the context manager so you don’t have to repeat the exception-handling code in everywith code block. In that case, you can do something like this:

Python
# exc_handling.pyclassHelloContextManager:def__enter__(self):print("Entering the context...")return"Hello, World!"def__exit__(self,exc_type,exc_value,exc_tb):print("Leaving the context...")ifisinstance(exc_value,IndexError):# Handle IndexError here...print(f"An exception occurred in your with block:{exc_type}")print(f"Exception message:{exc_value}")returnTruewithHelloContextManager()ashello:print(hello)hello[100]print("Continue normally from here...")

In.__exit__(), you check ifexc_value is an instance ofIndexError. If so, then you print a couple of informative messages and finally return withTrue. Returning atruthy value makes it possible to swallow the exception and continue the normal execution after thewith code block.

In this example, if noIndexError occurs, then the method returnsNone and the exception propagates out. However, if you want to be more explicit, then you can returnFalse from outside theif block.

If you runexc_handling.py from your command line, then you get the following output:

Shell
$pythonexc_handling.pyEntering the context...Hello, World!Leaving the context...An exception occurred in your with block: <class 'IndexError'>Exception message: string index out of rangeContinue normally from here...

HelloContextManager is now able to handleIndexError exceptions that occur in thewith code block. Since you returnTrue when anIndexError occurs, the flow of execution continues in the next line, right after exiting thewith code block.

Opening Files for Writing: First Version

Now that you know how to implement the context management protocol, you can get a sense of what this would look like by coding a practical example. Here’s how you can take advantage ofopen() to create a context manager that opens files for writing:

Python
# writable.pyclassWritableFile:def__init__(self,file_path):self.file_path=file_pathdef__enter__(self):self.file_obj=open(self.file_path,mode="w")returnself.file_objdef__exit__(self,exc_type,exc_val,exc_tb):ifself.file_obj:self.file_obj.close()

WritableFile implements the context management protocol and supports thewith statement, just like the originalopen() does, but it always opens the file for writing using the"w" mode. Here’s how you can use your new context manager:

Python
>>>fromwritableimportWritableFile>>>withWritableFile("hello.txt")asfile:...file.write("Hello, World!")...

After running this code, yourhello.txt file contains the"Hello, World!" string. As an exercise, you can write a complementary context manager that opens files for reading, but usingpathlib functionalities. Go ahead and give it a shot!

Redirecting the Standard Output

A subtle detail to consider when you’re writing your own context managers is that sometimes you don’t have a useful object to return from.__enter__() and therefore to assign to thewith target variable. In those cases, you can returnNone explicitly or you can just rely on Python’simplicit return value, which isNone as well.

For example, say you need to temporarily redirect the standard output,sys.stdout, to a given file on your disk. To do this, you can create a context manager like this:

Python
# redirect.pyimportsysclassRedirectedStdout:def__init__(self,new_output):self.new_output=new_outputdef__enter__(self):self.saved_output=sys.stdoutsys.stdout=self.new_outputdef__exit__(self,exc_type,exc_val,exc_tb):sys.stdout=self.saved_output

This context manager takes a file object through its constructor. In.__enter__(), you reassign the standard output,sys.stdout, to an instance attribute to avoid losing the reference to it. Then you reassign the standard output to point to the file on your disk. In.__exit__(), you just restore the standard output to its original value.

To useRedirectedStdout, you can do something like this:

Python
>>>fromredirectimportRedirectedStdout>>>withopen("hello.txt","w")asfile:...withRedirectedStdout(file):...print("Hello, World!")...print("Back to the standard output...")...Back to the standard output...

The outerwith statement in this example provides the file object that you’re going to use as your new output,hello.txt. The innerwith temporarily redirects the standard output tohello.txt, so the first call toprint() writes directly to that file instead of printing"Hello, World!" on your screen. Note that when you leave the innerwith code block, the standard output goes back to its original value.

RedirectedStdout is a quick example of a context manager that doesn’t have a useful value to return from.__enter__(). However, if you’re only redirecting theprint() output, you can get the same functionality without the need for coding a context manager. You just need to provide afile argument toprint() like this:

Python
>>>withopen("hello.txt","w")asfile:...print("Hello, World!",file=file)...

In this examples,print() takes yourhello.txt file as an argument. This causesprint() to write directly into the physical file on your disk instead of printing"Hello, World!" to your screen.

Measuring Execution Time

Just like every other class, a context manager can encapsulate some internalstate. The following example shows how to create astateful context manager to measure the execution time of a given code block or function:

Python
# timing.pyfromtimeimportperf_counterclassTimer:def__enter__(self):self.start=perf_counter()self.end=0.0returnlambda:self.end-self.startdef__exit__(self,*args):self.end=perf_counter()

When you useTimer in awith statement,.__enter__() gets called. This method usestime.perf_counter() to get the time at the beginning of thewith code block and stores it in.start. It also initializes.end and returns alambda function that computes a time delta. In this case,.start holds the initial state or time measurement.

Note: To take a deeper dive into how to time your code, check outPython Timer Functions: Three Ways to Monitor Your Code.

Once thewith block ends,.__exit__() gets called. The method gets the time at the end of the block and updates the value of.end so that thelambda function can compute the time required to run thewith code block.

Here’s how you can use this context manager in your code:

Python
>>>fromtimeimportsleep>>>fromtimingimportTimer>>>withTimer()astimer:...# Time-consuming code goes here......sleep(0.5)...>>>timer()0.5005456680000862

WithTimer, you can measure the execution time of any piece of code. In this example,timer holds an instance of thelambda function that computes the time delta, so you need to calltimer() to get the final result.

Creating Function-Based Context Managers

Python’sgenerator functions and thecontextlib.contextmanager decorator provide an alternative and convenient way to implement the context management protocol. If you decorate an appropriately coded generator function with@contextmanager, then you get afunction-based context manager that automatically provides both required methods,.__enter__() and.__exit__(). This can make your life more pleasant by saving you some boilerplate code.

The general pattern to create a context manager using@contextmanager along with a generator function goes like this:

Python
>>>fromcontextlibimportcontextmanager>>>@contextmanager...defhello_context_manager():...print("Entering the context...")...yield"Hello, World!"...print("Leaving the context...")...>>>withhello_context_manager()ashello:...print(hello)...Entering the context...Hello, World!Leaving the context...

In this example, you can identify two visible sections inhello_context_manager(). Before theyield statement, you have the setup section. There, you can place the code that acquires the managed resources. Everything before theyield runs when the flow of execution enters the context.

After theyield statement, you have the teardown section, in which you can release the resources and do the cleanup. The code afteryield runs at the end of thewith block. Theyield statement itself provides the object that will be assigned to thewith target variable.

This implementation and the one that uses the context management protocol are practically equivalent. Depending on which one you find more readable, you might prefer one over the other. A downside of the function-based implementation is that it requires an understanding of advanced Python topics, such asdecorators and generators.

The@contextmanager decorator reduces the boilerplate required to create a context manager. Instead of writing a whole class with.__enter__() and.__exit__() methods, you just need to implement a generator function with a singleyield that produces whatever you want.__enter__() to return.

Opening Files for Writing: Second Version

You can use the@contextmanager to reimplement yourWritableFile context manager. Here’s what rewriting it with this technique looks like:

Python
>>>fromcontextlibimportcontextmanager>>>@contextmanager...defwritable_file(file_path):...file=open(file_path,mode="w")...try:...yieldfile...finally:...file.close()...>>>withwritable_file("hello.txt")asfile:...file.write("Hello, World!")...

In this case,writable_file() is a generator function that opensfile for writing. Then it temporarily suspends its own execution andyields the resource sowith can bind it to its target variable. When the flow of execution leaves thewith code block, the function continues to execute and closesfile correctly.

Mocking the Time

As a final example of how to create custom context managers with@contextmanager, say you’re testing a piece of code that works with time measurements. The code usestime.time() to get the current time measurement and do some further computations. Since time measurements vary, you decide to mocktime.time() so you can test your code.

Here’s a function-based context manager that can help you do that:

Python
>>>fromcontextlibimportcontextmanager>>>fromtimeimporttime>>>@contextmanager...defmock_time():...globaltime...saved_time=time...time=lambda:42...yield...time=saved_time...>>>withmock_time():...print(f"Mocked time:{time()}")...Mocked time: 42>>># Back to normal time>>>time()1616075222.4410584

Insidemock_time(), you use aglobal statement to signal that you’re going to modify the global nametime. Then you save the originaltime() function object insaved_time so you can safely restore it later. The next step is tomonkey patchtime() using alambda function that always returns the same value,42.

The bareyield statement specifies that this context manager doesn’t have a useful object to send back to thewith target variable for later use. Afteryield, you reset the globaltime to its original content.

When the execution enters thewith block, any calls totime() return42. Once you leave thewith code block, calls totime() return the expected current time. That’s it! Now you can test your time-related code.

Writing Good APIs With Context Managers

Context managers are quite flexible, and if you use thewith statement creatively, then you can define convenient APIs for your classes,modules, and packages.

For example, what if the resource you wanted to manage is thetext indentation level in some kind of report generator application? In that case, you could write code like this:

Python
withIndenter()asindent:indent.print("hi!")withindent:indent.print("hello")withindent:indent.print("bonjour")indent.print("hey")

This almost reads like adomain-specific language (DSL) for indenting text. Also, notice how this code enters and leaves the same context manager multiple times to switch between different indentation levels. Running this code snippet leads to the following output and prints neatly formatted text:

hi!    hello        bonjourhey

How would you implement a context manager to support this functionality? This could be a great exercise to wrap your head around how context managers work. So, before you check out the implementation below, you might take some time and try to solve this by yourself as a learning exercise.

Ready? Here’s how you might implement this functionality using a context manager class:

Python
classIndenter:def__init__(self):self.level=-1def__enter__(self):self.level+=1returnselfdef__exit__(self,exc_type,exc_value,exc_tb):self.level-=1defprint(self,text):print("    "*self.level+text)

Here,.__enter__() increments.level by1 every time the flow of execution enters the context. The method also returns the current instance,self. In.__exit__(), you decrease.level so the printed text steps back one indentation level every time you exit the context.

The key point in this example is that returningself from.__enter__() allows you to reuse the same context manager across several nestedwith statements. This changes the text indentation level every time you enter and leave a given context.

A good exercise for you at this point would be to write a function-based version of this context manager. Go ahead and give it a try!

Creating an Asynchronous Context Manager

To create an asynchronous context manager, you need to define the.__aenter__() and.__aexit__() methods. The script below is a reimplementation of the original scriptsite_checker_v0.py you saw before, but this time you provide a custom asynchronous context manager to wrap the session creation and closing functionalities:

Python
# site_checker_v1.pyimportaiohttpimportasyncioclassAsyncSession:def__init__(self,url):self._url=urlasyncdef__aenter__(self):self.session=aiohttp.ClientSession()response=awaitself.session.get(self._url)returnresponseasyncdef__aexit__(self,exc_type,exc_value,exc_tb):awaitself.session.close()asyncdefcheck(url):asyncwithAsyncSession(url)asresponse:print(f"{url}: status ->{response.status}")html=awaitresponse.text()print(f"{url}: type ->{html[:17].strip()}")asyncdefmain():awaitasyncio.gather(check("https://realpython.com"),check("https://pycoders.com"),)asyncio.run(main())

This script works similar to its previous version,site_checker_v0.py. The main difference is that, in this example, you extract the logic of the original outerasync with statement and encapsulate it inAsyncSession.

In.__aenter__(), you create anaiohttp.ClientSession(), await the.get() response, and finally return the response itself. In.__aexit__(), you close the session, which corresponds to the teardown logic in this specific case. Note that.__aenter__() and.__aexit__() must return awaitable objects. In other words, you must define them withasync def, which returns a coroutine object that is awaitable by definition.

If you run the script from your command line, then you get an output similar to this:

Shell
$pythonsite_checker_v1.pyhttps://realpython.com: status -> 200https://pycoders.com: status -> 200https://realpython.com: type -> <!doctype html>https://pycoders.com: type -> <!doctype html>

Great! Your script works just like its first version. It sendsGET requests to both sites concurrently and processes the corresponding responses.

Finally, a common practice when you’re writing asynchronous context managers is to implement the four special methods:

  1. .__aenter__()
  2. .__aexit__()
  3. .__enter__()
  4. .__exit__()

This makes your context manager usable with both variations ofwith.

Conclusion

The Pythonwith statement is a powerful tool when it comes to managing external resources in your programs. Its use cases, however, aren’t limited to resource management. You can use thewith statement along with existing and custom context managers to handle the setup and teardown phases of a given process or operation.

The underlyingcontext management protocol allows you to create custom context managers and factor out the setup and teardown logic so you can reuse them in your code.

In this tutorial, you learned:

  • What thePythonwith statement is for and how to use it
  • What thecontext management protocol is
  • How to implement your owncontext managers

With this knowledge, you’ll write safe, concise, and expressive code. You’ll also avoid resource leaks in your programs.

Take the Quiz: Test your knowledge with our interactive “Context Managers and Python's with Statement” quiz. You’ll receive a score upon completion to help you track your learning progress:


Context Managers and Python's with Statement

Interactive Quiz

Context Managers and Python's with Statement

In this quiz, you'll assess your understanding of the Python with statement and context managers. By mastering these concepts, you'll be able to write more expressive code and manage system resources more effectively, avoiding resource leaks and ensuring proper setup and teardown of external resources.

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding:Context Managers and Using Python's with Statement

🐍 Python Tricks 💌

Get a short & sweetPython Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

AboutLeodanis Pozo Ramos

Leodanis is an industrial engineer who loves Python and software development. He's a self-taught Python developer with 6+ years of experience. He's an avid technical writer with a growing number of articles published on Real Python and other sites.

» More about Leodanis

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

MasterReal-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

MasterReal-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students.Get tips for asking good questions andget answers to common questions in our support portal.


Looking for a real-time conversation? Visit theReal Python Community Chat or join the next“Office Hours” Live Q&A Session. Happy Pythoning!

Keep Learning

Related Topics:intermediatepython

Recommended Video Course:Context Managers and Using Python's with Statement

Related Tutorials:

Keep reading Real Python by creating a free account or signing in:

Already have an account?Sign-In

Almost there! Complete this form and click the button below to gain instant access:

Python Tricks: The Book

"Python Tricks: The Book" – Free Sample Chapter (PDF)

🔒 No spam. We take your privacy seriously.


[8]ページ先頭

©2009-2025 Movatter.jp