Search code examples
pythonwith-statementtqdm

How to access `with`-context from other scope in Python?


I have a with-statement like this:

from tqdm import tqdm

with tqdm(documents) as progress_bar:
    for document in documents:
        if [...]:
            process(document)
            progress_bar.update()

process() is another function, in which I would like to condition the logging on whether there is a tqdm instance in the context (called from the above code) or not (called from somewhere else). In pseudo-code e.g.:

def process(document):
     if <tqdm in context>:
       progress_bar.write("processing")
     else:
       logging.info("processing")

Can I dynamically find and access a with-context (provided by tqdm in this example) from a parent scope? How?

The contextlib documentation does not offer a (straight-forward) way for accessing a with-context.

The workaround I have found so far was to pass the progress_bar object as an optional parameter to process(), and use it if available. However, it seems potentially redundant to change the function just for this purpose.

Does Python in general, or tqdm specifically, provide a pattern for handling this?

Update for background:

The use case here is that in the real-world code here, I have a concatenation of function calls. process() is in fact more complex and calls various other functions. These might log output, which should go to progress_bar.write() if available.

If the leaf function in the call stack cannot access the with-context from the root function, I need to pass the progress_bar object down through all levels in the call tree.


Solution

  • To summarize the comments:

    • There is no way to implicitly access a with-context in another function (and there should not be).

    • The best and cleanest solution is to explicitly pass the object to use for writing. For the given use case, you can/should use a default value like this:

    def process(document, writer: Callable[[str], Any] = logging.info):
        writer("processing")    
        [...]
    

    In order to write to a tqdm instance instead of a logger, you can call process() like this:

    from tqdm import tqdm
    
    with tqdm(documents) as progress_bar:
        for document in documents:
            if [...]:
                process(document, writer=progress_bar.write)
                progress_bar.update()