1
\$\begingroup\$

I wanted to ask this here because what I wanted to originally do felt like a really Pythonic method. I want to be able to use the syntax:

d = {'apple':{'cranberry':{'banana':{'chocolate':[1,2,3,4,5,6]}}},'b':2}
with d['apple']['cranberry']['banana']['chocolate'] as item:
    for i in item:
        print(i)

    item.append('k')

but found that Python doesn't allow for using lists, dicts, etc. as context managers.

So I implemented my own:

def context_wrap(target):
    class ContextWrap:
        def __init__(self, tgt):
            self._tgt = tgt

        def __enter__(self):
            return self._tgt

        def __exit__(self, type, value, traceback):
            pass

    return ContextWrap(target)

if __name__ == '__main__':
    with context_wrap({'a':1}) as item:
        print(item['a'])

    with context_wrap([1,2,3,4,5]) as item:
        print(item[1])

    with context_wrap(3) as item:
        print (item)

In the above code, you can take any random object and wrap it inside an object that acts as a context manager controlling the underlying object. This means that inside any with clause, you can simply use the object with its alias. I feel like it looks a lot cleaner and clearer than something like:

alias = d['apple']['cranberry']['banana']['chocolate']
for i in alias:
    print(i)

alias.append('k')

I wanted to know if there was a more "Pythonic" way to do it. So the improvement that I'm looking for is in terms of better reliance on the standard Python library and/or in terms of my syntax.

\$\endgroup\$
5
  • 5
    \$\begingroup\$ What's the advantage of this over just saying item = d['a']? Usually the purpose of a context manager is to do something in the __exit__ (close a file, free a resource, etc) but your use case doesn't require anything like that. \$\endgroup\$
    – Samwise
    Feb 26, 2020 at 23:59
  • \$\begingroup\$ Welcome to code review, the question might be better received if the title was something like Context Manager Wrapper and there was a paragraph explaining what the code does. \$\endgroup\$
    – pacmaninbw
    Feb 27, 2020 at 0:23
  • 1
    \$\begingroup\$ @pacmaninbw Let me amend my post. \$\endgroup\$
    – Stephen
    Feb 27, 2020 at 0:25
  • \$\begingroup\$ @SamStafford This is going to be used for more complicated objects where you wouldn't want to just endlessly list the same parameters over and over. Something like d = {'a':{'b':{'c':{'d':'e'}}}} where the object is complex/deeply nested. \$\endgroup\$
    – Stephen
    Feb 27, 2020 at 0:25
  • \$\begingroup\$ You might want to read the guidelines for good questions at codereview.stackexchange.com/help/how-to-ask. \$\endgroup\$
    – pacmaninbw
    Feb 27, 2020 at 0:30

2 Answers 2

7
\$\begingroup\$

I think what you are doing is crazy, but yes, you can use Python’s library functions to clean it up.

from contextlib import contextmanager

@contextmanager
def context_wrap(target):
    yield target

Again, this is busy work.

alias = thing

is clearer, shorter and faster than

with context_wrap(thing) as alias:
\$\endgroup\$
4
  • \$\begingroup\$ Wow, this is extremely much cleaner than my solution. And I can see a few uses for it! Using a context manager doesn't leave a strong reference around in case something needs to be cleaned up by the garbage collector! I think it's especially useful for classes. \$\endgroup\$
    – Stephen
    Feb 27, 2020 at 16:55
  • 1
    \$\begingroup\$ alias will still be equal to thing after the with statement exits. It doesn't remove the variable, so a strong reference will still exist! \$\endgroup\$
    – AJNeufeld
    Feb 27, 2020 at 17:11
  • \$\begingroup\$ I think I have some more work to do then. But thanks for the help anyway! \$\endgroup\$
    – Stephen
    Feb 27, 2020 at 18:06
  • 2
    \$\begingroup\$ Trying to trick the Python garbage collector into cleaning things up at specific times is not going to be a fun game to play. Write it in Rust if object lifetimes are that important! :) \$\endgroup\$
    – Samwise
    Feb 27, 2020 at 18:41
3
\$\begingroup\$

AJNeufeld provides the proper way to create small context managers using contextlib.contextmanager. While this is, for sure, the best way to accomplish your goal, I wanted to comment on the general use case of functions like the following:

def context_wrap(target):
    class ContextWrap:
        def __init__(self, tgt):
            self._tgt = tgt

        def __enter__(self):
            return self._tgt

        def __exit__(self, type, value, traceback):
            pass

    return ContextWrap(target)

With this sort of function, you are creating the class, instantiating it, and then throwing away the class definition on each function call. All this needs to be is the class definition, and then use that directly:

class ContextWrap:
    def __init__(self, tgt):
        self._tgt = tgt

    def __enter__(self):
        return self._tgt

    def __exit__(self, type, value, traceback):
        pass


with ContextWrap(target) as cw:
    # do something

Which is much lighter since you aren't re-creating resources all the time.

Any time you find yourself using that pattern, go simpler.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.