For this case you may use recursion. It feels to be most natural for the case (of course if there's no special treatment in Python yet):
ARCHIVE_PATH = "path/to/archive.tar" INNER_PATHS = [ "nested/within/archive/one.tar", "nested/within/archive/two.tar", # Arbitary number of these ] def list_inner_contents(archive_path, inner_paths): def rec(tf, rest_paths): if not rest_paths: return tf.getnames() with TarFile(fileobj = tf.extractfile(rest_paths[0])) as tf2: return rec(tf2, rest_paths[1: ]) with TarFile(archive_path) as tf: try: return rec(tf, inner_paths) except RuntimeError: # We come here in case the inner_paths list is too long # and we go too deeply in the recursion return None
This function is a decorator that can be used to define a factory function for with statement context managers, without needing to create a class or separate __enter__() and __exit__() methods.,This function is a decorator that can be used to define a factory function for async with statement asynchronous context managers, without needing to create a class or separate __aenter__() and __aexit__() methods. It must be applied to an asynchronous generator function.,ContextDecorator makes it possible to use a context manager in both an ordinary with statement and also as a function decorator.,The function being decorated must return a generator-iterator when called. This iterator must yield exactly one value, which will be bound to the targets in the with statement’s as clause, if any.
from contextlib
import contextmanager
@contextmanager
def managed_resource( * args, ** kwds):
# Code to acquire resource, e.g.:
resource = acquire_resource( * args, ** kwds)
try:
yield resource
finally:
# Code to release resource, e.g.:
release_resource(resource)
>>>
with managed_resource(timeout = 3600) as resource:
...# Resource is released at the end of this block,
...# even
if code in the block raises an exception
from contextlib
import asynccontextmanager
@asynccontextmanager
async def get_connection():
conn = await acquire_db_connection()
try:
yield conn
finally:
await release_db_connection(conn)
async def get_all_users():
async with get_connection() as conn:
return conn.query('SELECT ...')
import time
from contextlib
import asynccontextmanager
@asynccontextmanager
async def timeit():
now = time.monotonic()
try:
yield
finally:
print(f 'it took {time.monotonic() - now}s to run')
@timeit()
async def main():
#...async code...
from contextlib
import contextmanager
@contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close()
from contextlib
import closing
from urllib.request
import urlopen
with closing(urlopen('https://www.python.org')) as page:
for line in page:
print(line)
from contextlib
import asynccontextmanager
@asynccontextmanager
async def aclosing(thing):
try:
yield thing
finally:
await thing.aclose()
While comparing it to the first example we can see that a lot of boilerplate code is eliminated just by using with. The main advantage of using a with statement is that it makes sure our file is closed without paying attention to how the nested block exits.,A common use case of context managers is locking and unlocking resources and closing opened files (as I have already shown you).,This is not the only way to implement Context Managers. There is another way and we will be looking at it in the next section.,In our case the __exit__ method returns None (when no return statement is encountered then the method returns None). Therefore, the with statement raises the exception:
with open('some_file', 'w') as opened_file:
opened_file.write('Hola!')
file = open('some_file', 'w')
try:
file.write('Hola!')
finally:
file.close()
class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, traceback):
self.file_obj.close()
with File('demo.txt', 'w') as opened_file:
opened_file.write('Hola!')
with File('demo.txt', 'w') as opened_file:
opened_file.undefined_function('Hola!')
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: 'file' object has no attribute 'undefined_function'
Mar 26, 2020 • 9 min read
with open("file.txt", "wt") as f:
f.write("contents go here")
try:
f = open("file.txt", "wt")
text = f.write("contents go here")
finally:
f.close()
class CustomFileOpen:
""
"Custom context manager for opening files."
""
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
self.f = open(self.filename, self.mode)
return self.f
def __exit__(self, * args):
self.f.close()
with CustomFileOpen("file.txt", "wt") as f:
f.write("contents go here")
@contextmanager
def some_generator(<arguments>):
<setup>
try:
yield <value>
finally:
<cleanup>
with some_generator(<arguments>) as <variable>:
<body>
Let’s start with a simple example to understand the context manager concept.,Suppose that you have a file called data.txt that contains an integer 100.,However, the data.txt may contain data that cannot be converted to a number. In this case, the code will result in an exception.,When you use ContextManager class with the with statement, Python implicitly creates an instance of the ContextManager class (instance) and automatically call __enter__() method on that instance.
The following program reads the data.txt
file, converts its contents to a number, and shows the result to the standard output:
.wp - block - code { border: 0; padding: 0; } .wp - block - code > div { overflow: auto; } .shcb - language { border: 0; clip: rect(1 px, 1 px, 1 px, 1 px); - webkit - clip - path: inset(50 % ); clip - path: inset(50 % ); height: 1 px; margin: -1 px; overflow: hidden; padding: 0; position: absolute; width: 1 px; word - wrap: normal; word - break: normal; } .hljs { box - sizing: border - box; } .hljs.shcb - code - table { display: table; width: 100 % ; } .hljs.shcb - code - table > .shcb - loc { color: inherit; display: table - row; width: 100 % ; } .hljs.shcb - code - table.shcb - loc > span { display: table - cell; } .wp - block - code code.hljs: not(.shcb - wrap - lines) { white - space: pre; } .wp - block - code code.hljs.shcb - wrap - lines { white - space: pre - wrap; } .hljs.shcb - line - numbers { border - spacing: 0; counter - reset: line; } .hljs.shcb - line - numbers > .shcb - loc { counter - increment: line; } .hljs.shcb - line - numbers.shcb - loc > span { padding - left: 0.75 em; } .hljs.shcb - line - numbers.shcb - loc::before { border - right: 1 px solid #ddd; content: counter(line); display: table - cell; padding: 0 0.75 em; text - align: right; - webkit - user - select: none; - moz - user - select: none; - ms - user - select: none; user - select: none; white - space: nowrap; width: 1 % ; } f = open('data.txt') data = f.readlines() # convert the number to integer and display it print(int(data[0])) f.close() Code language: Python(python)
For example, if the data.txt
contains the string '100'
instead of the number 100, you’ll get the following error:
ValueError: invalid literal
for int() with base 10: "'100'"
Code language: Python(python)
To fix this, you may use the try...except...finally
statement:
try:
f = open('data.txt')
data = f.readlines()
# convert the number to integer and display it
print(int(data[0]))
except ValueError as error:
print(error)
finally:
f.close() Code language: Python(python)
Here is the typical syntax of the with
statement:
with context as ctx:
# use the the object
# context is cleaned upCode language: Python(python)
The following shows how to access the f
variable after the with
statement:
with open('data.txt') as f: data = f.readlines() print(int(data[0])) print(f.closed) # TrueCode language: Python(python)