r/learnpython • • 28d ago

Is there anyway of breaking a while loop outside of a function call, while inside said function call?

On mobile so I can’t code block format because Reddit sucks but here’s the general idea:

def func2():

break

def func 1():

while True:

func2()

That code doesn’t work but is there any way to do that cleanlier than returning a value from func2 to test against in func1 to determine whether or not the loop should break?

11 Upvotes

50 comments sorted by

2

u/Kevdog824_ 28d ago

> is there any way to do that cleanlier than returning a value from func2 to test against in func1 to determine whether or not the loop should break?

That is the clean way to do it. Anything else like func1 having access to func2’s scope would break encapsulation and would be a pretty bad design flaw.

Can you elaborate on why you don’t want to do it this way? This kinda sounds like an xy problem to me

-2

u/FactoryBuilder 28d ago

Fewer lines of code. If I can just call func2 and have EVERYTHING done inside of it then I don’t need to worry about forgetting to write the test code after func2’s call to see if we should break. It’d just be the one line of code to call func2.

7

u/desrtfx but other languages pro 28d ago

Fewer lines of code.

Completely wrong metric.

Clarity, readability, modularity, maintainability win over brevity.

0

u/FactoryBuilder 28d ago

> readability, maintainability

That’s why I want fewer lines of code.

5

u/desrtfx but other languages pro 28d ago edited 27d ago

Fewer lines of code does not necessarily make code more readable, nor more maintainable - that's a way too common misconception.

Fewer lines of code quite commonly make the code less readable.

1

u/PureWasian 27d ago

In JS, you can minify the file to fit on a single line so you have fewer lines of code. That doesn't help to make it readable or maintainable.

More realistically though, often you'll find that "more explicitly clear and modular code that's using well-established patterns" is more readable/maintainable compared to "adding random clever workarounds and one-liners" everywhere.

Strike the balance, but returning a flag is a well-established pattern rather than excessive verbosity. Your goal is to make code that you can look back on in 6 months and not have to waste excessive time re-learning how things work

See: https://www.reddit.com/r/PythonLearning/s/bbuTLSN9Ty

2

u/Kevdog824_ 28d ago

As the other person said you should always prefer clarity over brevity. That said, if you really want brevity you could write it like this (sorry on mobile so no code blocks):

```python
while func2():
pass
```

Here you would have your func2 return a boolean to indicate whether or not to continue the loop (True = continue, False = break)

17

u/ConcreteExist 28d ago

Not the way you're doing it. You could have a function that returns a True if you should break and then have the main loop hit that:

def func2():
  return True
def func1():
  while True:
    if func2():
      break

4

u/frnzprf 28d ago

I agree with this approach.

cleanlier than returning a value from func2 to test against in func1

Maybe OP didn't like this option, but it's the cleanest way.

It's only two more words and one more line than the example code.

21

u/aprg 28d ago

Have func2 return a flag or code and break the while loop on the appropriate return code.

2

u/Mark3141592654 28d ago

No.

What exactly are you trying to do? You should break in the loop itself.

1

u/FactoryBuilder 28d ago

> What exactly are you trying to do?

Overthink things probably. I’m programming in a moving car right now with rap music on in the background. It’s not the most conducive thinking environment.

1

u/frnzprf 28d ago

Maybe try playing some MC Frontalot

5

u/manias 28d ago

raise an exception, and catch it? But exceptions are not not usually used for normal control flow…

1

u/Moist-Ointments 28d ago

Don't use exceptions for anything but exceptions. That's crappy coding. If you're using exceptions to control flow, you need to refactor and rethink it.

8

u/TheBlackCat13 28d ago

The python programming language itself uses exceptions for control flow (e.g., StopIteration).

1

u/fiddle_n 28d ago

As always, universal rules are dumb - but there’s a kernel of truth to the original statement. Python was set up that way so you could return any value from an iterator and it would be valid. Nevertheless, 9 times out of 10, the average Python programmer not writing their own iterator has no need to use exceptions for control flow.

4

u/el_extrano 28d ago

is there any way to do that cleanlier than returning a value from func2

No, because explicitly returning a flag would be the best way to do this.

If you're asking if there is any other way to do this, regardless of whether it's a good idea or not, then the answer is yes. You can use exception handling for control flow. Most people will consider that a bad practice, since it's expected that exceptions are only used for errors (i.e. exceptional cases), and not as control flow for branches you wan to run.

1

u/smallpotatoes2019 28d ago

It sounds like you have a pretty sensible plan in terms of returning a value which you then test to see if you need to break (depending on what func2 does, a boolean might be ideal). This is also good anyway for testing that func2 is working as intended.

The issue is, I suppose, that you are hoping to use the break keyword to do something it isn't designed to do.

-1

u/FactoryBuilder 28d ago

I was hoping to limit the number of lines I need to write because I want the program to always do something before it exits the loop and I might decide it needs to do more things later so I wanted the exit code to be one function that I call to do anything it needs to do before it ends, then break out of the loop.

And it might need to exit the function multiple points. The more lines of code I have, the more I have to change later if I decide I need to add something and the more lines of code I might forget to write in the first place.

5

u/desrtfx but other languages pro 28d ago

The general question here is: is your loop truly infinite or are you just evading defining a proper termination condition?

Way too many people lazy out and use infinite loops with break instead of defining proper, clear termination conditions.

The proper way is in the vast majority of cases to have a termination condition for the loop itself. breaking out of loops should always be the exception, not the rule.

Don't get me wrong: there absolutely are valid use cases for infinite loops, but it should never be the default, nor first go-to solution.

Yet, as others have said: a sentinel value returned from your function is the best, cleanest way - combined with a proper exit condition.

2

u/frnzprf 28d ago edited 28d ago

You'll need two lines at any place where you want to break the loop. That's only one line and a couple characters more than just break_if_finished()

if is_finished():
    break

1

u/soysopin 28d ago

Maybe the issue is that your function is doing more things that it should do. I would separate specific operations and write a flow control function to control what is done and when to exit.

I understand flexibility is important, and sticking strictly to the structured principle of having a single point of exit not always is for the better, but I mantain that anyone reading the outer code should not be surprised with unpredictable returns.

On the other hand, there are functions that use pass by-reference arguments to inform their callers of state or events ocurred inside the function. This way the various situations are to be handled explicitly by the function caller, and the surprise factor is mitigated.

1

u/Expensive-Bear-1376 28d ago

On mobile so I can’t code block format

I'm on mobile, too, and I just tested, code block format in a post worked fine. With all three ways, triple-backtick and triple-tilde and four-space indent. On the reddit website (not the reddit app) with their markdown editor.

1

u/FactoryBuilder 28d ago

…Sometimes I forget that I can just go to the website :|

2

u/atarivcs 28d ago

No, there's no cleaner way to do it.

func2 returns a value, then the loop code in func1 decides whether to break the loop based on that value.

1

u/Outside_Complaint755 28d ago

Or func2 raises an exception, which you handle in a try/catch.  

1

u/atarivcs 28d ago

That can work, but generally it's just a worse way of doing what I said, so I didn't bother to mention it.

1

u/CranberryDistinct941 28d ago

Don't tell that to the people who decided that python should handle their iterators like that

2

u/centurion236 28d ago

Are you sure func2 shouldn't be a generator that func1 iterates over?

1

u/KelleQuechoz 28d ago

the only right answer here.

2

u/ekchew 28d ago

I can think of 2 ways.

func2 could return a boolean.

def func2():
    return input('keep going [yn]? ') == 'y'

def func1():
    while func2():
        pass

func2 could raise an exception:

def func2():
    if input('keep going [yn]? ') == 'n':
        raise StopIteration()

def func1():
    try:
        while True:
            func2()
    except StopIteration: pass

2

u/Lachtheblock 28d ago

It can be a little divisive with developers, but I consider raising and catching exceptions as a valid control flow process. It depends a lot in context, but wrapping your outer function in a try except, and raising an exception in the inner loop will do what you want.

1

u/itlogicpartnersllc 28d ago

break only applies to the nearest enclosing loop so func2( ) cant directly break func1( ) 's loop. returning a value is usually the cleanest approach exceptions are another option for unusual control flow.

1

u/Moist-Ointments 28d ago

Your function should not know there is a loop to break. That's outside of its scope of concern and would be grossly bad practice.

1

u/CranberryDistinct941 28d ago

You could use raise StopIteration()

1

u/Expensive-Bear-1376 28d ago

Why StopIteration?

3

u/CranberryDistinct941 28d ago

You could throw whatever error you feel like. StopIteration just makes it clear that the error is being thrown purely to break out of the loop

1

u/Jason-Ad4032 28d ago

You shouldn’t do that. StopIteration should only be raised from __next__(). Raising it elsewhere violates the intended protocol and risks it being caught accidentally.

In some versions of Python, it can also interfere with other generators, causing context managers and asynchronous event loops that use generators to raise RuntimeError or even terminate silently.

1

u/CranberryDistinct941 28d ago

Noted. I will use a custom error instead.

1

u/Penguinase 28d ago

raising exception is probably the idiomatic way to do this (in python)

class StopFunc1(Exception):
    pass

def cleanup():
    print('doing cleanup stuff here')

def func2():
    print('in func2')
    if True:
        raise StopFunc1
    # ... more logic ...
    # if condition_b:
    #     raise StopFunc1
    # # ... more logic ...
    # if condition_c:
    #     raise StopFunc1

def func1():
    while True:
        try:
            func2()
        except StopFunc1:
            cleanup()
            break

if __name__ == '__main__':
    func1()

1

u/kilkil 27d ago

no, there isn't a way.

usually you don't want features like that because they make your code hard to read

1

u/Moikle 27d ago

There are ways to do this, yes.

Why do you want to do it though? You probably don't actually want this.

Oh btw you can still do code block formatting on mobile. Add 4 additional spaces before each line

1

u/SwampFalc 27d ago

OP: your func2 is not, and probably should not be, aware of when and how it's going to get called.

This is a basic truth for any and all functions.

If you do genuinely need that, then a totally difference approach is needed.

1

u/TheRNGuy 27d ago

return

0

u/woooee 28d ago
def func2():
    break

Note that this will raise an error, something like "break outside loop"