70

I'd just like to exit out of a with statement under certain conditions:

with open(path) as f:
    print 'before condition'
    if <condition>: break #syntax error!
    print 'after condition'

Of course, the above doesn't work. Is there a way to do this? (I know that I can invert the condition: if not <condition>: print 'after condition' -- any way that is like above?)

Risadinha
  • 16,058
  • 2
  • 88
  • 91
jmilloy
  • 7,875
  • 11
  • 53
  • 86
  • 3
    I'm curious why `if not ` is undesirable in your situation. – senderle Jun 25 '12 at 18:40
  • 6
    @senderle I think having to indent the rest of the block can reduce readability and obscure the flow (especially in cases like this one where breaking out feels like the exception). Imagine three more conditions, with code blocks after each one... – jmilloy Jun 25 '12 at 18:48
  • 1
    Forget it... none of the answers here are worth the trouble LOL – Yan King Yin Oct 18 '22 at 17:44

13 Answers13

94

with giving you trouble? Throw more with-able objects at the problem!

class fragile(object):
    class Break(Exception):
      """Break out of the with statement"""

    def __init__(self, value):
        self.value = value

    def __enter__(self):
        return self.value.__enter__()

    def __exit__(self, etype, value, traceback):
        error = self.value.__exit__(etype, value, traceback)
        if etype == self.Break:
            return True
        return error

Just wrap the expression you're going to with with fragile, and raise fragile.Break to break out at any point!

with fragile(open(path)) as f:
    print 'before condition'
    if condition:
        raise fragile.Break
    print 'after condition'

Benefits of this setup

  • Uses with and just the with; doesn't wrap your function in a semantically misleading one-run 'loop' or a narrowly specialized function, and doesn't force you to do any extra error handling after the with.
  • Keeps your local variables available, instead of having to pass them to a wrapping function.
  • Nestable!

    with fragile(open(path1)) as f:
        with fragile(open(path2)) as g:
            print f.read()
            print g.read()
            raise fragile.Break
            print "This wont happen"
        print "This will though!"
    

    This way, you don't have to create a new function to wrap the outer with if you want both to break.

  • Doesn't require restructuring at all: just wrap what you already have with fragile and you're good to go!

Downsides of this setup

  • Doesn't actually use a 'break' statement. Can't win em all ;)
Orez
  • 1,089
  • 8
  • 10
  • 1
    I think this is a better setup than most others. Doesn't require refactoring of the code, and `fragile()` is reusable. – Mikhail Dec 09 '14 at 05:41
  • 1
    Just came back by this, and I do like this. – jmilloy May 24 '16 at 13:59
  • 1
    I think a larger downside is that it makes it a bit clumsy to call the methods of the context manager that you have wrapped. I like the general solution but would instead implement it as a static function which monkey patches the __enter__ and __exit__ by wrapping the currently existing __enter__ and __exit__. There is probably some fancy way to do that with a decorator aswell.... – phil_20686 Jul 21 '16 at 15:22
  • 2
    It's clever and uses the decorator pattern (my favorite), but it uses exception handling for control flow. I'm on the fence. – Jacob Zimmerman Sep 28 '16 at 22:47
  • This is a clever idea which I'm adapting to allow me to still use Context Managers while conditionally skipping cleanup of a `with`'d resource (a temporary file) if an exception occurred during processing, permitting debugging. – scubbo Jul 06 '20 at 15:09
  • If a `Break1` subclass is added to `fragile` and the `fragile.__exit__` method is modified to raise a `Break` exception if called with `Break1`, then breaking out of both of two nested `with`s is implemented. – NameOfTheRose Mar 12 '21 at 10:42
  • Is a 'pass' statement missing inside class Break or maybe I'm mistaken? – madtyn Feb 07 '22 at 10:13
  • 1
    @madtyn the docstring counts as a statement, so we're able to omit the `pass`. Keeping or omitting the `pass` in this case is a matter of preference. – Orez Feb 08 '22 at 15:59
  • Thanks, I didn't know this detail and I would never have immagined this. – madtyn Feb 09 '22 at 09:30
57

The best way would be to encapsulate it in a function and use return:

def do_it():
    with open(path) as f:
        print 'before condition'
        if <condition>:
            return
        print 'after condition'
Trevor
  • 9,518
  • 2
  • 25
  • 26
  • 7
    This returns but doesn't quite "break out" of a with statement. Users must be aware that the `__exit__` is executed regardless with this approach. – Asclepius May 19 '20 at 04:12
18

This is an ancient question, but this is an application for the handy "breakable scope" idiom. Just imbed your with statement inside:

for _ in (True,):
    with open(path) as f:
        print 'before condition'
        if <condition>: break
        print 'after condition'

This idiom creates a "loop", always executed exactly once, for the sole purpose of enclosing a block of code inside a scope that can be broken out of conditionally. In OP's case, it was a context manager invocation to be enclosed, but it could be any bounded sequence of statements that may require conditional escape.

The accepted answer is fine, but this technique does the same thing without needing to create a function, which is not always convenient or desired.

user2137858
  • 229
  • 2
  • 3
  • I like that this clearly only executes once (rather than the `while True` variants), and that you included a name for the idiom. – jmilloy Oct 15 '20 at 19:50
11

I think you should just restructure the logic:

with open(path) as f:
    print 'before condition checked'
    if not <condition>:
        print 'after condition checked'
K. Brafford
  • 3,755
  • 2
  • 26
  • 30
  • 6
    @jmilloy, I think this is at least as good a solution as encapsulating the `with` statement in a function and using `return`, or raising an exception. Frankly, under many circumstances, I think this would be the most elegant and readable solution. – senderle Jun 25 '12 at 18:58
  • 5
    @senderle Agreed 100%. However, it's simply not the question. – jmilloy Jun 25 '12 at 19:01
  • 4
    Well, I guess if we're discussing hypothetical poor constructs that Python doesn't allow, we could bemoan the lack of a decent goto statement :-) http://entrian.com/goto/ – K. Brafford Jun 25 '12 at 19:20
8

Since break can only occur inside a loop your options are somewhat limited inside the with to:

  • return (put "with" + associated statements inside function)
  • exit (bail from program - probably not ideal)
  • exception (generate exception inside "with", catch below)

Having a function and using return is probably the cleanest and easiest solution here if you can isolate the with and the associated statements (and nothing else) inside a function.

Otherwise generate an exception inside the with when needed, catch immediately below/outside the with to continue the rest of the code.

Update: As OP suggests in the comments below (perhaps tonuge in cheek?) one could also wrap the with statement inside a loop to get the break to work - though that would be semantically misleading. So while a working solution, probably not something that would be recommend).

Levon
  • 138,105
  • 33
  • 200
  • 191
  • Yes I know; I'd like to know if there's something that *does* work. – jmilloy Jun 25 '12 at 18:33
  • @Matthias Yeah I thought of that -- seems like wrapping it in a function is a bit cleaner. – jmilloy Jun 25 '12 at 18:36
  • @Levon I actually tried with a `return` to check, but you can only return from a function block, not a with block. I'm sure you know this, but the way you wrote it makes it sound like returning only stops execution of the rest of the block in the `with`, whereas in reality it also prevents any code after the `with` block but in the same function. – jmilloy Jun 25 '12 at 18:39
  • 1
    @Matthias In fact, I often find myself wishing that `with` blocks were like `try` blocks and could be cleanly followed by exception cases. That's what I'd do if I could. – jmilloy Jun 25 '12 at 18:53
  • @Levon I guess we could add an option to your list: wrap the with inside a silly for loop like `for i in [0]` so that you can break. – jmilloy Jun 25 '12 at 19:04
  • @jmilloy I guess that's true .. in which case I would use `while True:` as the construct. It would be misleading semantically though for others looking at the code later. I think the `return` inside the function (if the with+statements work that way), followed by the exception are the best options. – Levon Jun 25 '12 at 19:06
5

This question was asked before Python 3.4 existed but with 3.4 you can use contextlib.supress, suppressing your own personal exception.

See that this (runnable as is) code

from contextlib import suppress

class InterruptWithBlock(UserWarning):
    """To be used to interrupt the march of a with"""

condition = True
with suppress(InterruptWithBlock):
    print('before condition')
    if condition: raise InterruptWithBlock()
    print('after condition')

# Will not print 'after condition` if condition is True.

So with the code in the question, you'd do:

with suppress(InterruptWithBlock) as _, open(path) as f:
    print('before condition')
    if <condition>: raise InterruptWithBlock()
    print('after condition')

Note: If you're (still) before 3.4, you can still make your own suppress context manager easily.

thorwhalen
  • 1,920
  • 14
  • 26
3
f = open("somefile","r")
for line in f.readlines():
       if somecondition: break;
f.close()

I dont think you can break out of the with... you need to use a loop...

[edit] or just do the function method others mentioned

Joran Beasley
  • 110,522
  • 12
  • 160
  • 179
2

as shorthand snippet:

class a:
    def __enter__(self):
        print 'enter'
    def __exit__(self ,type, value, traceback):
        print 'exit'

for i in [1]:
    with a():
        print("before")
        break
        print("after")

...

enter
before
exit
ARtnaMed
  • 21
  • 1
  • 1
    This is reminiscent of how Python C API puts the contents of macros in a dowhile loop sometimes, except that is to get the semicolon at the end. – Mad Physicist Feb 22 '16 at 16:36
  • Is it more efficient to actually use a tuple (as in https://stackoverflow.com/a/43795918 above) or a list (as here)? – Marcel Waldvogel May 31 '19 at 08:23
1

There exists a function __exit__() for this purpose. Syntax is as follows:

with VAR = EXPR:
  try:
    BLOCK
  finally:
    VAR.__exit__()
Syscall
  • 19,327
  • 10
  • 37
  • 52
Rahul Nimbal
  • 525
  • 7
  • 11
1

Use while True:

while True:
    with open(path) as f:
        print 'before condition'
        if <condition>: 
            break 
        print 'after condition n'
    break
upe
  • 1,862
  • 1
  • 19
  • 33
0

You could put everything inside a function, and when the condition is true call a return.

YuriAlbuquerque
  • 2,178
  • 1
  • 11
  • 19
0

Here is another way of doing it using try and except, even though inverting the condition is probably more convenient.

class BreakOut(Exception): pass

try:
    with open(path) as f:
        print('before condition')
        if <condition>: 
            raise BreakOut #syntax error!
        print('after condition')
except BreakOut:
    pass
upe
  • 1,862
  • 1
  • 19
  • 33
-1

Change from "break" to "f.close"

with open(path) as f:
    print('before condition')
    if <condition>: f.close()
    print('after condition')
  • For one, it doesn't generalize. The question is about with statements in general, the file context manager is just an example. – jmilloy Oct 15 '20 at 19:47