if <expression> as <variable>

Hi! I’ve been having this idea for a few years and I thought I finally see if what others think of it. I have no experience in language design and I don’t know if this is something I’ve picked up in some other language. I also do not know what the ramifications of implementing this idea would be. I just keep thinking about it :) I quite often write code like the following in python: result = computation() if result: do_something_with_computation(result) More often than not this snippet evolves from something like this: if computation(): … That is, I use the truthiness of the value at first. As the code grows I refactor to actually do something with the result. What I would love to see is the following syntax instead, which to me is much cleaner: if computation() as result: do_something_with_result(result) Basically the result variable would be the result of the if condition’s expression and it would be available the same way it would be if we used my initial snippet (much the same way that the result of with expressions also stays around outside the with-block). Any feedback is appreciated :) Cheers, Denis

On 7 September 2017 at 11:43, Denis Krienbühl <denis@href.ch> wrote:
Hi - thanks for your suggestion! This has actually come up quite a lot in the past. Here's a couple of links to threads you might want to read (it's not surprising if you missed these, it's not that easy to come up with a good search term for this topic). https://mail.python.org/pipermail/python-ideas/2012-January/013461.html https://mail.python.org/pipermail/python-ideas/2009-March/003423.html (This thread includes a note by Guido that he intentionally left out this functionality) In summary, it's a reasonably commonly suggested idea, but there's not enough benefit to warrant adding it to the language. Paul

I also often wonder why we are left doing an assignment and test. You have two options: 1. assign to a variable then test and use 2. repeat the function call I would offer that 'with' [sh|c]ould be used: with test() as x: handle_truthy(x) else: handle_falsey() # do we provide x here too? Because None vs False?

On Thu, Sep 07, 2017 at 04:36:40PM +0200, Jason H wrote:
Personally, I don't see what's wrong with the "assign then test" idiom. x = something() if x: do_stuff()
This would cause confusing errors and mysterious behaviour, depending on whether the test() object was a context manager or not. Which should take priority? If you see: with spam() as x: do_stuff is that a context manager with block (like "with open(...) as f") or your boolean if test in disguise? Having "with" sometimes be a disguised "if" and sometimes a regular "with" will make it really, really hard to reason about code. -- Steve

Sadly it’s hard to create a context manager that skips its body like this: with unpack(computation()) as result: do_something_with_result(result) You can do it with some hackery like described here: https://stackoverflow.com/a/12594789/247482 class unpack: def __init__(self, pred): self.pred = pred def __enter__(self): if self.pred: return self.pred # else skip the with block’s body sys.settrace(lambda *args, **kw: None) frame = inspect.currentframe(1) frame.f_trace = self.trace def trace(self, frame, event, arg): raise def __exit__(self, type, value, traceback): return True # suppress the exception Steven D'Aprano <steve@pearwood.info> schrieb am Do., 7. Sep. 2017 um 18:26 Uhr:

sorry, it’s a bit more difficult. this works: https://gist.github.com/flying-sheep/86dfcc1bdd71a33fa3483b83e254084c Philipp A. <flying-sheep@web.de> schrieb am Do., 7. Sep. 2017 um 21:18 Uhr:

On Fri, Sep 8, 2017 at 5:40 AM, Philipp A. <flying-sheep@web.de> wrote:
sorry, it’s a bit more difficult. this works: https://gist.github.com/flying-sheep/86dfcc1bdd71a33fa3483b83e254084c
If this can be made to work - even hackily - can it be added into contextlib.contextmanager (or technically its helper class)? Currently, its __enter__ looks like this: def __enter__(self): try: return next(self.gen) except StopIteration: raise RuntimeError("generator didn't yield") from None If that raise were replaced with the hackiness of skipping the body, we could wrap everything up nicely: @contextlib.contextmanager def iff(thing): if thing: yield thing # otherwise don't yield for i in range(1, 11): with iff(random.randrange(3)) as val: print(i, val) # won't print any zeroes ChrisA

On 7 September 2017 at 11:43, Denis Krienbühl <denis@href.ch> wrote:
Hi - thanks for your suggestion! This has actually come up quite a lot in the past. Here's a couple of links to threads you might want to read (it's not surprising if you missed these, it's not that easy to come up with a good search term for this topic). https://mail.python.org/pipermail/python-ideas/2012-January/013461.html https://mail.python.org/pipermail/python-ideas/2009-March/003423.html (This thread includes a note by Guido that he intentionally left out this functionality) In summary, it's a reasonably commonly suggested idea, but there's not enough benefit to warrant adding it to the language. Paul

I also often wonder why we are left doing an assignment and test. You have two options: 1. assign to a variable then test and use 2. repeat the function call I would offer that 'with' [sh|c]ould be used: with test() as x: handle_truthy(x) else: handle_falsey() # do we provide x here too? Because None vs False?

On Thu, Sep 07, 2017 at 04:36:40PM +0200, Jason H wrote:
Personally, I don't see what's wrong with the "assign then test" idiom. x = something() if x: do_stuff()
This would cause confusing errors and mysterious behaviour, depending on whether the test() object was a context manager or not. Which should take priority? If you see: with spam() as x: do_stuff is that a context manager with block (like "with open(...) as f") or your boolean if test in disguise? Having "with" sometimes be a disguised "if" and sometimes a regular "with" will make it really, really hard to reason about code. -- Steve

Sadly it’s hard to create a context manager that skips its body like this: with unpack(computation()) as result: do_something_with_result(result) You can do it with some hackery like described here: https://stackoverflow.com/a/12594789/247482 class unpack: def __init__(self, pred): self.pred = pred def __enter__(self): if self.pred: return self.pred # else skip the with block’s body sys.settrace(lambda *args, **kw: None) frame = inspect.currentframe(1) frame.f_trace = self.trace def trace(self, frame, event, arg): raise def __exit__(self, type, value, traceback): return True # suppress the exception Steven D'Aprano <steve@pearwood.info> schrieb am Do., 7. Sep. 2017 um 18:26 Uhr:

sorry, it’s a bit more difficult. this works: https://gist.github.com/flying-sheep/86dfcc1bdd71a33fa3483b83e254084c Philipp A. <flying-sheep@web.de> schrieb am Do., 7. Sep. 2017 um 21:18 Uhr:

On Fri, Sep 8, 2017 at 5:40 AM, Philipp A. <flying-sheep@web.de> wrote:
sorry, it’s a bit more difficult. this works: https://gist.github.com/flying-sheep/86dfcc1bdd71a33fa3483b83e254084c
If this can be made to work - even hackily - can it be added into contextlib.contextmanager (or technically its helper class)? Currently, its __enter__ looks like this: def __enter__(self): try: return next(self.gen) except StopIteration: raise RuntimeError("generator didn't yield") from None If that raise were replaced with the hackiness of skipping the body, we could wrap everything up nicely: @contextlib.contextmanager def iff(thing): if thing: yield thing # otherwise don't yield for i in range(1, 11): with iff(random.randrange(3)) as val: print(i, val) # won't print any zeroes ChrisA
participants (6)
-
Chris Angelico
-
Denis Krienbühl
-
Jason H
-
Paul Moore
-
Philipp A.
-
Steven D'Aprano