1

For example, here is the code not inline:

with x:
    do(stuff)

I can do that in a single line like so:

with x: do(stuff)

However I would like to know if there is a way to do something like this (inline):

a = [(lambda x: with x: do(stuff))(x) for x in someList]

Edit:

To elaborate, say I have a list of objects that each have their own context. This means that do(stuff) would behave differently when used with x and the above statement would go through each object in that list, enter their context, run do(stuff), then continue to the next.

Since doing this as I have tried it doesn't work, I would like to know if there is any way to do this, and how.

The extended version of above code would look like this (and it works):

a = []
for x in someList:
    with x: a.append( do(stuff) )

2 Answers2

5

Well since you're ok with a workaround then more-itertools.with_iter may be your best bet.

Note: It is a 3rd party package so you'll need to install it if you haven't

from more_itertools import with_iter

a = [do(stuff) for _ in with_iter(x)]
#your example lacks what do and x really are/do so this comprehension is inferred

If you don't want to use a 3rd party package then all with_iter does is:

def with_iter(context_manager):
    with context_manager as iterable:
        yield from iterable

So this is easy to implement yourself.

Jab
  • 26,853
  • 21
  • 75
  • 114
3

With a lambda expression in particular, this would be impossible (part of the deal with lambdas is that you can't assign variables inside of them). A workaround for that would be to construct a proper function in advance and then have a lambda call that proper function:

def _f(x):
    with x as y: 
       return do(stuff)

a = [_f(x) for x in someList]

I couldn't get with: to work inside a lambda, and unfortunately you can't do the open/close routine manually either - I suspect for the same reason (you can't assign variables inside a lambda).


However, using lambdas and the assignment operator introduced in python 3.8, then you can do this:

>>> a = [(lambda x:(f := open(x), f.read(), f.close())[1])(i) 
         for i in ('hello.txt', 'dunno.txt', 'goodbye.txt')]
>>> print(a)
['Hello', 'I dunno', 'Goodbye']

which uses a tuple as an analogue for putting multiple statements inside a lambda. In a nutshell, it does the following things in order:

  1. In the first element of the tuple, use an assignment operator to create a named variable
  2. In the second element of the tuple, perform an operation on the newly-created named variable that returns a value. Leave this value stored in the second element of the tuple.
  3. In the third element of the tuple, close the resource we opened initially in the first part of the tuple, again by using the named variable. In this case, file.close() returns None, we don't care because it doesn't actually raise an error.
  4. Tell the lambda to return the second element of the tuple (the one in which we did the important operation on the opened resource).

This takes advantage of both the newly-added Assignment Operator in python 3.8, as well as the fact that tuples are evaluated in order from front to back. Theoretically, you could use this paradigm to implement entire functions inside tuples, though that code would get hellishly complicated. This is unpythonic as-is.

Disclaimers: this method does require opening and closing the file manually (or, in your case, I guess, calling __enter__() and __exit__() manually), and as a result it doesn't fully encapsulate the functionality of with. I have no idea how you would do error handling on this.

I tested this using the python 3.8 beta, and 3.8 isn't going to be properly released until at least October 21 2019. This will not work in any currently-existing version of python before 3.8. I dunno if this qualifies as intended functionality or if it's a bug, so maybe be careful if you choose to try using this for whatever reason.

Green Cloak Guy
  • 23,793
  • 4
  • 33
  • 53