|
| 1 | +# pycobytes[28] := Breaking Specificity |
| 2 | +<!-- #SQUARK live! |
| 3 | +| dest = issues/(issue)/28 |
| 4 | +| title = Breaking Specificity |
| 5 | +| head = Breaking Specificity |
| 6 | +| index = 28 |
| 7 | +| tags = keywords |
| 8 | +| date = 2025 May 13 |
| 9 | +--> |
| 10 | + |
| 11 | +> *To understand recursion, one must first understand recursion.* |
| 12 | +
|
| 13 | +Hey pips! |
| 14 | + |
| 15 | +If you’ve ever found yourself needing to exit out of a loop early, you can do that with the `break` keyword. |
| 16 | + |
| 17 | +```py |
| 18 | +>>> for i in range(1, 999, 4): |
| 19 | + print(i) |
| 20 | + |
| 21 | + if i > 10: |
| 22 | + break |
| 23 | +1 |
| 24 | +5 |
| 25 | +9 |
| 26 | +13 |
| 27 | +``` |
| 28 | + |
| 29 | +Simple as that. It just terminates the loop there and then. |
| 30 | + |
| 31 | +A common example of where you might see this is in a simple linear search: |
| 32 | + |
| 33 | +```py |
| 34 | +def find_first_active_pair(database: dict): |
| 35 | + out = [] |
| 36 | + |
| 37 | + for profile in database.values(): |
| 38 | + if profile.is_active(): |
| 39 | + out.append(profile) |
| 40 | + |
| 41 | + if len(profile) == 2: |
| 42 | + break # exits the for loop |
| 43 | + |
| 44 | + return out |
| 45 | +``` |
| 46 | + |
| 47 | +This of course also works for (hah) the `while` loop. If you ever see `while True:`, there’s a good chance it’ll have a `break` inside to prevent the program from running indefinitely. |
| 48 | + |
| 49 | +```py |
| 50 | +>>> while True: |
| 51 | + k = random.randint(1, 10) |
| 52 | + if k == 10: |
| 53 | + break |
| 54 | + else: |
| 55 | + print(k) |
| 56 | + |
| 57 | +2 |
| 58 | +7 |
| 59 | +1 |
| 60 | +5 |
| 61 | +1 |
| 62 | +9 |
| 63 | +``` |
| 64 | + |
| 65 | +Now `while` does inherently have a condition after it, but this condition is only checked at the *start* of each iteration. Sometimes, especially with more complex loops, you may want to check many conditions throughout the loop, maybe at different points. |
| 66 | + |
| 67 | +```py |
| 68 | +while game.state = "running": |
| 69 | + if 0 >= player.health: |
| 70 | + # this won’t immediately exit the loop |
| 71 | + game.state = "over" |
| 72 | + |
| 73 | + # so we can force an exit |
| 74 | + break |
| 75 | + |
| 76 | + game.tick() |
| 77 | +``` |
| 78 | + |
| 79 | +Having `break` here gives you flexibility and more control over when you exit the loop. |
| 80 | + |
| 81 | +Did you know, there’s a little-known keyword combo in Python – the **for-else** loop. You heard me right. |
| 82 | + |
| 83 | +```py |
| 84 | +for cell in mind: |
| 85 | + cell.blown() |
| 86 | +else: |
| 87 | + print("whattttt") |
| 88 | +``` |
| 89 | + |
| 90 | +Not quite your average if-else block, eh? |
| 91 | + |
| 92 | +The code inside `else` here only runs if the loop fully finishes – i.e. it did not encounter any `break`, so every iteration was ran. |
| 93 | + |
| 94 | +This one’s quite niche. It’s good for edge cases: |
| 95 | + |
| 96 | +```py |
| 97 | +def find_user(id: int, database: dict) |
| 98 | + found = None |
| 99 | + |
| 100 | + for profile in database.values(): |
| 101 | + if profile.id = id: |
| 102 | + found = profile |
| 103 | + break |
| 104 | + else: |
| 105 | + raise UserNotFoundError(f"Did not find user #{id}") |
| 106 | + |
| 107 | + found.check() |
| 108 | + found.sanitise() |
| 109 | + return found |
| 110 | +``` |
| 111 | + |
| 112 | +You may notice here that we could have totally achieved the same thing by just `return`-ing straight from the loop, and we wouldn’t need the for-else... |
| 113 | + |
| 114 | +```py |
| 115 | +def find_user(id: int, database: dict) |
| 116 | + for profile in database.values(): |
| 117 | + if profile.id = id: |
| 118 | + profile.check() |
| 119 | + profile.sanitise() |
| 120 | + return profile |
| 121 | + |
| 122 | + raise UserNotFoundError(f"Did not find user #{id}") |
| 123 | +``` |
| 124 | + |
| 125 | +These are both examples of short-circuiting – exiting something early to avoid doing unnecessary work. In this case I’d say using `return` is definitely cleaner than the for-else. In fact, leveraging functions and `return` is a really powerful way of handling code that needs to ‘stop’ early. Probably part of the reason why you don’t see for-else at all is because you can achieve the same thing with just a function. |
| 126 | + |
| 127 | +Alright, 1 more keyword for you. If you ever need to end the current iteration but not exit the loop entirely, you can ‘skip’ to the next iteration with `continue`: |
| 128 | + |
| 129 | +```py |
| 130 | +def find_good_pet(database: dict): |
| 131 | + for profile in database.values(): |
| 132 | + if profile.pets is None: |
| 133 | + continue |
| 134 | + |
| 135 | + ranked = sorted( |
| 136 | + profile.pets, |
| 137 | + key = lambda pet: pet.happiness, |
| 138 | + reverse = True |
| 139 | + ) |
| 140 | + |
| 141 | + return ranked[0] |
| 142 | +``` |
| 143 | + |
| 144 | +This skips over the rest of the code in the loop and moves straight on to the next iteration. |
| 145 | + |
| 146 | +`continue` is less obviously named than `break` (`skip` or `next` would probably have been better), but hey. |
| 147 | + |
| 148 | + |
| 149 | +<br> |
| 150 | + |
| 151 | + |
| 152 | +## Challenge |
| 153 | + |
| 154 | +Given a list of numbers, can you find the **first 3** that are odd square numbers with more than 1 digit? |
| 155 | + |
| 156 | +```py |
| 157 | +>>> l = [1, 9, 6, 15, 25, 3, 81, 0, -1, 49, 169, 196] |
| 158 | +>>> (your_code) |
| 159 | +[25, 81, 49] |
| 160 | +``` |
| 161 | + |
| 162 | + |
| 163 | +<br> |
| 164 | + |
| 165 | + |
| 166 | +--- |
| 167 | + |
| 168 | +<div align="center"> |
| 169 | + |
| 170 | +[](http://thecodelesscode.com/case/129) |
| 171 | + |
| 172 | +[*The Codeless Code*, Case 129](http://thecodelesscode.com/case/129) |
| 173 | + |
| 174 | +</div> |
0 commit comments