Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to open an arbitrary number of items using `with` in python? [duplicate]

I've got a situation where I have several items I'd like to open using a with block. In my case, these are external hardware devices which require some clean-up when closed -- but that doesn't really matter for the point at hand.

Assuming a class something like:

class Controller(object):

    def __init__(self, name):
        self._name = name

    def __enter__(self):
        # Do some work on entry
        print("Entering", self._name)
        return self

    def __exit__(self, type, value, traceback):
        # Clean up (restoring external state, turning off hardware, etc)
        print("Exiting", self._name)
        return False

    def work(self):
        print("Working on", self._name)

I would (given a fixed number of Controllers), do something like

with Controller("thing1") as c1:
    with Controller("thing2") as c2:
        c1.do_work()
        c2.do_work()

However, I've run across a situation where I have a flexible number of things I need to manage in this manner. That is, I have a situation similar to:

things = ["thing1", "thing2", "thing3"] # flexible in size
for thing in things:
    with Controller(thing) as c:
        c.do_work()

However, the above doesn't quite do what I need -- which is to have Controllers for all things in scope at one time.

I've built a toy example which works through recursion:

def with_all(controllers, f, opened=None):
    if opened is None:
        opened = []

    if controllers:
        with controllers[0] as t:
            opened.append(t)
            controllers = controllers[1:]

            with_all(controllers, f, opened)
    else:
        f(opened)

def do_work_on_all(controllers):
    for c in controllers:
        c.work()

names = ["thing1", "thing2", "thing3"]
controllers = [Controller(n) for n in names]

with_all(controllers, do_work_on_all)

but I don't like the recursion or the abstraction of the actual function call. I'm interested in ideas for doing this in a more "pythonic" way.

like image 823
Austin Glaser Avatar asked Jan 16 '18 19:01

Austin Glaser


1 Answers

Yes there is a more pythonic way to do that, using the standard library contextlib, which has a class ExitStack which does excatly what you want:

with ExitStack() as stack:
    controllers = [stack.enter_context(Controller(n)) for n in names]

This should do what you want.

like image 105
MegaIng Avatar answered Nov 15 '22 08:11

MegaIng