Why isn't this a thing in Python?
Note
This basically implements the idea behind PEP 3136 – Labeled break and continue.
- Break from multiple loops at once in Python
- Index
- Getting Started
- Keyword
- Linter and Type Checker Configuration
- Command-Line Interface
- Deployment
- Contributing
- Authors
- Disclaimer
- License
You will need Python +3.10 to run this module
# vermin output
Minimum required versions: 3.10
Incompatible versions: 2Note
The heavy use of ast.unparse makes this incompatible with Python version lower than 3.10
pip install --upgrade breakallYou can use breakall by decorating your function with @enable_breakall:
>>> from breakall import enable_breakall
>>>
>>> @enable_breakall
... def main():
... for i in range(10):
... for j in range(10):
... for k in range(10):
... if k == 5:
... breakall
... print(i, j, k)Alternatively, you can enable breakall for all functions in your module by calling enable_breakall() without arguments:
from breakall import enable_breakall
def main():
for i in range(10):
for j in range(10):
for k in range(10):
if k == 5:
breakall
print(i, j, k)
# This will enable breakall for all functions in the current scope
enable_breakall()
main()Or you can use the command-line interface:
python -m breakall script.py
# or
breakall script.pyThis will automatically enable breakall for all functions in the file.
You can use breakall to break from multiple loops at once.
There is different syntax to determine how many loops to break from.
The breakall keyword will break from all loops in the current scope.
>>> from breakall import enable_breakall
>>>
>>> @enable_breakall
... def test():
... for i in range(3):
... for j in range(3):
... if j == 1:
... breakall
... print(f" ({i}, {j})")
... print(f" End of i={i}")
... print("Done")
>>>
>>> test()
(0, 0)
DoneAs you can see, the loops are completely exited without printing the rest of the iterations or "End of i={i}" message.
The breakall: n keyword will break from n loops in the current scope.
>>> from breakall import enable_breakall
>>>
>>> @enable_breakall
... def test():
... for i in range(3):
... for j in range(3):
... for k in range(3):
... if k == 1:
... breakall: 2
... print(f" ({i}, {j}, {k})")
... print(f" End of j={j}")
... print(f"End of i={i}")
... print("Done")
>>>
>>> test()
(0, 0, 0)
End of i=0
(1, 0, 0)
End of i=1
(2, 0, 0)
End of i=2
DoneIn this example, breakall: 2 breaks from the innermost two loops (the j and k loops), but the outermost i loop continues.
The breakall @ n keyword will break from all loops up to and including the n-th loop from the inside.
>>> from breakall import enable_breakall
>>>
>>> @enable_breakall
... def test():
... for i in range(3): # Loop 1
... for j in range(3): # Loop 2
... for k in range(3): # Loop 3
... if k == 1:
... breakall @ 2
... print(f" ({i}, {j}, {k})")
... print(f" End of k loop")
... print(f"End of j loop")
... print("Done")
>>>
>>> test()
(0, 0, 0)
(1, 0, 0)
(2, 0, 0)
DoneIn this example, breakall @ 2 targets loop 2 (the j loop), so it breaks from both loop 3 (k) and loop 2 (j), but loop 1 (i) continues.
Note
The breakall @ n keyword is 1-indexed, where 1 is the innermost loop
You can also use a variable to determine how many loops to break from:
>>> from breakall import enable_breakall
>>>
>>> @enable_breakall
... def test():
... n = 2
... for i in range(3):
... for j in range(3):
... for k in range(3):
... breakall @ n # breakall: n also works
... print("Shouldn't print")
... print("Shouldn't print")
... print("Should print")
... print("Done")
>>>
>>> test()
Should print
Should print
Should print
DoneYou can even perform calculations in the loop breaking statement:
>>> from breakall import enable_breakall
>>>
>>>
>>> @enable_breakall
>>> def test():
... for i in range(3):
... for j in range(3):
... for k in range(3):
... breakall: 1 + 2
... print("Shouldn't print")
... print("Shouldn't print")
... print("Shouldn't print")
... print("Done")
>>>
>>>
>>> test()
DoneAll errors originating from an invalid use of breakall all inherit BreakAllError.
There are three types of errors:
BreakAllEnvironmentError: When theenable_breakallcouldn't properly transform the function (keep in mind thatenable_breakallneeds to have access to the function's source code to transform it)BreakAllSyntaxError: When thebreakallstatement is used with invalid syntax, such as an invalid loop number or break countBreakAllRuntimeError: When thebreakallstatement is used with a valid syntax but results in an invalid loop number or break count at runtime, such as when using a variable that is out of range or a calculation that results in an invalid loop number or break count
It even gives you proper error messages
# breakall @ 4
BreakAllSyntaxError: Invalid loop number
File ".../test.py", line 7, in test
breakall @ 4
^
There are only 3 loops to break up until this point. Note that it is impossible to break to a loop defined later. (test.py)
# breakall: 4
BreakAllSyntaxError: Invalid break count
File ".../test.py", line 7, in test
breakall: 4
^
There are only 3 loops to break. (test.py)# When `n` is too small
Traceback (most recent call last):
File ".../test.py", line 17, in <module>
test()
File ".../test.py", line 7, in test
for i in range(3):
File ".../breakall/runtime.py", line 170, in destination_from_loop_number
raise BreakAllRuntimeError(
breakall.exceptions.BreakAllRuntimeError: Invalid loop number
File ".../test.py", line 11, in test
breakall @ n
^
The loop number must be greater than 0# When using a calculation that results in an invalid loop number or break count
Traceback (most recent call last):
File ".../test.py", line 17, in <module>
test()
File ".../test.py", line 7, in test
for i in range(3):
File ".../breakall/runtime.py", line 88, in destination_from_break_count
raise BreakAllRuntimeError(
breakall.exceptions.BreakAllRuntimeError: Invalid break count
File ".../test.py", line 11, in test
breakall: 1 + 3
^^^^^
There are only 3 loops to break.When using breakall, certain linting and type-checking tools may report false positives since they don't understand the breakall statement transformation.
You can import the breakall statement to make your type checker happy, but it is not necessary for the code to work:
from breakall import breakall
def hello():
for i in range(n):
for j in range(m):
if j == 1:
breakall
print(i, j)Add to your module using breakall:
# ruff: noqa: B018, F842Where:
B018- Useless expression (for barebreakallstatements)F842- Local variable name is assigned to but never used (forbreakall: nstatements where the loop counter variable is not used)
Add to your module using breakall:
# pyright: reportUnusedExpression=false, reportUnusedVariable=false, reportGeneralTypeIssues=falseWhere:
reportUnusedExpression=false- Allow unused expressions (forbreakallstatements)reportUnusedVariable=false- Allow unused variables (forbreakall: nstatements where the loop counter variable is not used)reportGeneralTypeIssues=false- Suppress general type issues that may when usingbreakall: nstatements (nis an integer, not a type)
You can use the breakall module directly from the command line without decorating functions:
python -m breakall script.pyThe CLI automatically applies enable_breakall to all functions in the target file:
# my_script.py
def nested_loops():
for i in range(3):
for j in range(3):
if j == 1:
breakall
print(f" ({i}, {j})")
print(f" End of i={i}")
print("Done")
nested_loops()$ python -m breakall my_script.py
(0, 0)
DoneBy default, the CLI only applies enable_breakall to functions in the main file. If you're using breakall in imported modules, use the --trace flag:
python -m breakall script.py --traceIf your imported module contains breakall, it will fail without --trace:
# helper.py
def helper_function():
for i in range(3):
for j in range(3):
if j == 1:
breakall
print(f" ({i}, {j})")
print(f" End of i={i}")
print("Done")# main.py
from helper import helper_function
helper_function()$ python -m breakall main.py
(0, 0)
Traceback (most recent call last):
File "main.py", line 3, in <module>
helper_function()
File "helper.py", line 6, in helper_function
breakall
NameError: name 'breakall' is not definedUsing --trace enables import hooking, so all imported modules support breakall:
$ python -m breakall main.py --trace
(0, 0)
DoneThe --trace flag automatically applies the enable_breakall decorator to all functions in imported modules, allowing them to use the breakall statement.
You can save the transformed Python code to a file using the --output option:
python -m breakall script.py --output transformed.pyThis generates a new file with all breakall statements converted to proper loop-breaking logic.
This module is currently in development and might contain bugs.
Feel free to use it in production if you feel like it is suitable for your production even if you may encounter issues.
Pull requests are welcome. For major changes, please open an discussion first to discuss what you would like to change.
Please make sure to update the tests as appropriate.
- Anime no Sekai - Initial work - Animenosekai
This project is not affiliated with the Python Software Foundation.
This project is licensed under the MIT License - see the LICENSE file for details
