You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
5.8 KiB
Markdown
218 lines
5.8 KiB
Markdown
|
|
Goto in Python
|
|
==============
|
|
|
|
I really know that Python was meant to be used without a goto, but sometimes it
|
|
really makes sense to use it.
|
|
|
|
## Goto usage patterns:
|
|
|
|
Let's first show a few common and in my opinion sensible (at least sometimes)
|
|
uses of goto. Also, I'll discuss possible goto-free alternatives of the code
|
|
and why they feel worse.
|
|
|
|
### Error handling
|
|
|
|
The most common one, used all over the Linux kernel for example:
|
|
|
|
```c
|
|
// ...
|
|
|
|
if(! something_we_dont_want_to_fail()):
|
|
goto err;
|
|
// We know it hasn't failed, so we can use it
|
|
|
|
// ...
|
|
|
|
return result; // OK
|
|
|
|
err:
|
|
clean_up()
|
|
return 0; // This did not work.
|
|
```
|
|
|
|
Often, there are more goto's for one label, and fall through is utilized, e.g.:
|
|
|
|
```c
|
|
// Open the file
|
|
if (file_empty(f))
|
|
goto clean_files;
|
|
// Read the file
|
|
if (bad_data(f))
|
|
goto clean_files;
|
|
// Load data into memory
|
|
if (could_not_process(data))
|
|
goto clean_memory;
|
|
return processed_data;
|
|
|
|
clean_memory:
|
|
free(data);
|
|
clean_files:
|
|
close(f);
|
|
|
|
return 0;
|
|
|
|
```
|
|
|
|
The goto-free alternative is to put everything into functions and check all the
|
|
functions. This might lead to creating functions with no semantics, and that
|
|
does not generally make the code more readable.
|
|
|
|
The lucky part is, this is the case that is simple to solve with exceptions in
|
|
much the same way.
|
|
|
|
There is still a case to be made with the multiple cleanup blocks and
|
|
fall-through, but one of the ways of solving that is to have the exception know
|
|
what to clean up. That might be a bit ugly, but the goto is way worse.
|
|
|
|
|
|
### Alternative methods of evaluation
|
|
|
|
Sometimes, one would like to try several different paths to get a result, in
|
|
some order, i.e. with pseudocode like this:
|
|
|
|
```c
|
|
int result = 0;
|
|
|
|
// Method 1 (the most likely to work)
|
|
result = method_1();
|
|
if(result)
|
|
goto done;
|
|
|
|
// Method 1 did not work, try method 2:
|
|
result = method_2();
|
|
if(result)
|
|
goto done;
|
|
|
|
// Try method 3 maybe?
|
|
result = method_3();
|
|
if (! result)
|
|
return ERROR;
|
|
|
|
done:
|
|
// We now have result, so process that
|
|
// ...
|
|
```
|
|
|
|
Possible options to fixing this:
|
|
|
|
- Create a function that gets the result. In that function, the goto's can be
|
|
replaced by return's, so that is nice, but the function might not have
|
|
sensible name and need an unreasonable number of arguments, if the methods
|
|
are complicated enough.
|
|
- Reorder the methods to try from the least preffered to most. That obscures
|
|
the algorithm, and might not be an option, if the methods have side effects.
|
|
|
|
There exists a solution with exceptions: Raise an exception when one method
|
|
fails, and try other methods in the exception handling block. That leads to
|
|
complicated catch[^I will call it "catch" block even though in Python this
|
|
technically is an "except" block, because it seems more obvious what I mean.]
|
|
blocks, which is not a good practice. And what is worse, for the third method
|
|
the blocks would be nested!
|
|
|
|
### Retries
|
|
|
|
Sometimes, you need to try something several times, or skip something. In some
|
|
code (not originally in C), I had something along the lines of:
|
|
|
|
```c
|
|
struct line_with_number get_next_reasonable_line():
|
|
// Imagine here a static FILE object, from which I read the lines.
|
|
int linelen = 255;
|
|
char *line;
|
|
static int line_number
|
|
|
|
next_line:
|
|
getline(line, linelen, file);
|
|
if (line == NULL)
|
|
// Just return some kind of error
|
|
return {.line = NULL, .number = line_number};
|
|
|
|
line = strip_comments(line);
|
|
if (strlen(line) == 0)
|
|
goto next_line;
|
|
// Look at the line more closely
|
|
if (we_do_not_like(line))
|
|
goto next_line;
|
|
|
|
return {.line = line, .number = line_number};
|
|
```
|
|
|
|
The "obvious" solution to this is to use `while` and `continue`. But that is
|
|
not necessarily better -- the code is quite likely to pass only once, so there
|
|
is no semantics of a cycle. Also, what would be the condition here? We have no
|
|
data, so maybe `while(true)`. Or in the better case, `do ... while(strlen(line)
|
|
== 0 || we_do_not_like(line))`. When the condition is not so simple, using
|
|
infinite loop is the way to go. But such code is not prettier, since it uses
|
|
unreasonable flow control and loses the jump's meaning.
|
|
|
|
|
|
There may be a few more places where a goto is a sensible option, but I don't
|
|
recall any of them, so they might not be as important.
|
|
|
|
## What to do in Python?
|
|
|
|
There is no simple way around the second use-case for a goto. In some code, I
|
|
ended up with following code (and to make it worse, that was inside a function,
|
|
all of it):
|
|
|
|
```python
|
|
# NOTE: This is really ugly goto, simulated using exceptions
|
|
class EverythingIsOK(Exception): pass
|
|
|
|
try:
|
|
# Method 1:
|
|
result = method_1()
|
|
if result: raise EverythingIsOK() # Goto
|
|
|
|
# Method 1 did not work, try method 2:
|
|
result = method_2()
|
|
if result: raise EverythingIsOK()
|
|
|
|
# Try method 3 maybe?
|
|
result = method_3()
|
|
if not result:
|
|
raise OperationalError() # Not OK, Not goto.
|
|
except EverythingIsOK: # Label
|
|
pass
|
|
|
|
# Process result
|
|
```
|
|
|
|
This is ugly enough, but can only jump downwards. Jumping upwards probably
|
|
needs the trick with `continue`, and unfortunately, there is no `do-while` in
|
|
Python.
|
|
|
|
```python
|
|
while True: # Label
|
|
# code
|
|
continue # Goto
|
|
# code
|
|
break # Boilerplate
|
|
```
|
|
|
|
If you do not like that the goto's look different, you can use this mess:
|
|
|
|
```python
|
|
class MyGoto(Exception): pass
|
|
while True: # Label
|
|
try:
|
|
# code
|
|
raise MyGoto() # Goto
|
|
# code
|
|
break
|
|
except MyGoto: # Even more boilerplate.
|
|
continue
|
|
```
|
|
|
|
I have no solution for using a single label for jumping from above _and_ below.
|
|
But I hope I will never see code that needs that.
|
|
|
|
This post is not meant to encourage use of goto. It is considered harmful, and
|
|
in most cases, rightfully so. But still, there are some reasonable use cases,
|
|
so it is worth knowing when to use it and what can you do when the language
|
|
does not support goto.
|
|
|
|
And probably needless to say: I would still rather have native goto support,
|
|
than need to use awful hacks to simulate the flow which I have in mind.
|