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.
2 Answers 2
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:
-
\$\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– Stephen2020年02月27日 16:55:00 +00:00Commented Feb 27, 2020 at 16:55
-
2\$\begingroup\$
alias
will still be equal tothing
after thewith
statement exits. It doesn't remove the variable, so a strong reference will still exist! \$\endgroup\$AJNeufeld– AJNeufeld2020年02月27日 17:11:27 +00:00Commented 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– Stephen2020年02月27日 18:06:47 +00:00Commented 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– Samwise2020年02月27日 18:41:06 +00:00Commented Feb 27, 2020 at 18:41
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.
-
\$\begingroup\$ Would it be helpful to set
self._tgt = None
in the__exit__
method? This would ensure the reference was removed quickly. Or would that happen because the whole thing disappears when leaving thewith
statement? \$\endgroup\$paxdiablo– paxdiablo2024年04月03日 02:10:12 +00:00Commented Apr 3, 2024 at 2:10 -
\$\begingroup\$ Not quite, because the value tied to
self._tgt
is still referenced by the returned value after__enter__
. For example,with ContextWrap('myval') as x:
points the namex
toself._tgt
, so even if you setself._tgt
toNone
, it would just remove it from the contextmanager's scope (inside the class), but wouldn't get rid of it entirely \$\endgroup\$C.Nivs– C.Nivs2024年04月03日 02:23:59 +00:00Commented Apr 3, 2024 at 2:23 -
1\$\begingroup\$ Furthermore, the context manager itself is removed when the scope exits (after
__exit__
), so theself._tgt = None
is just busy work \$\endgroup\$C.Nivs– C.Nivs2024年04月03日 02:26:25 +00:00Commented Apr 3, 2024 at 2:26
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\$Context Manager Wrapper
and there was a paragraph explaining what the code does. \$\endgroup\$d = {'a':{'b':{'c':{'d':'e'}}}}
where the object is complex/deeply nested. \$\endgroup\$