Three bugs that hide in functions
Functions that go through a list have a few classic bugs. None of them gives an error message: the function runs and returns a wrong answer, which makes them hard to spot. It pays to know them on sight.
Bug 1: using a variable from outside the function
A function can see the variables of the main program, and using one of them by accident gives a function that only seems to work:
def print_items(items: list):
for item in shopping:
print(item)
shopping = ["milk", "eggs"]
print_items(shopping)
print_items(["tea", "rice"])
milk
eggs
milk
eggsThe loop goes through shopping, not the parameter items, so the function prints the same list whatever you give it. It gets worse: in your programs, the lines that try out a function go under if __name__ == "__main__":, and the checks on this site call your function without running that block. Then shopping doesn't exist at all, and the function stops with a NameError. Inside a function, use only its parameters and the variables it creates itself.
Bug 2: overwriting a parameter
This function should count the scores that are at least the given limit:
def count_at_least(scores: list, score: int) -> int:
count = 0
for score in scores:
if score >= score:
count += 1
return count
print(count_at_least([3, 8, 5, 10], 6))
4It should print 2 (for 8 and 10). The loop variable has the same name as the second parameter, so the first round of the loop replaces the limit 6 with the score 3. From then on, score >= score compares a number with itself, which is always true. Give every variable a name of its own:
def count_at_least(scores: list, limit: int) -> int:
count = 0
for score in scores:
if score >= limit:
count += 1
return count
The same happens when a helper list inside the function gets the parameter's name, as in scores = []: the list the function was given is gone.
Bug 3: returning too early
return ends the function at once, even in the middle of a loop. This function should tell whether a list has a negative number in it:
def has_negative(numbers: list) -> bool:
for number in numbers:
if number < 0:
return True
else:
return False
print(has_negative([4, -2, 7]))
FalseThe first number, 4, isn't negative, so the else branch returns False before the loop ever reaches -2. One negative number is enough to answer yes, but to answer no, the function has to check every number. So return False belongs after the loop:
def has_negative(numbers: list) -> bool:
for number in numbers:
if number < 0:
return True
return False
print(has_negative([4, -2, 7]))
print(has_negative([4, 2, 7]))
True
False