Embercode
✕
References and MemoryLesson 6 of 15

40 min read

None bugs and stale references in Python

Why this matters

Many crashes in Python come from one mistake: the program uses a value that is None, which means "no object". A function returns None by accident, a dictionary lookup finds nothing, or a variable is never set. The program then reads an attribute or does arithmetic on the empty value, and Python stops with an error.

The bugs in this lesson are easy to write and hard to find. The line that crashes is often not the line that caused the bug. A value was set to None three steps earlier, or a function forgot its return long before the crash.

By the end of this lesson you will be able to read the error that None causes and find the value that was None. You will know two or three checks that stop each bug before it causes a crash. You will also be able to explain why Python does not have the dangling pointer bug that causes trouble in C and C++, and what the nearest real bugs are.

The second lesson of this module covers what None is and how is differs from ==. The garbage collection lesson covers how Python frees memory. This lesson uses both ideas and does not repeat them.

The Python case: the None value

A name in Python points at an object. The name can also point at None, which means it points at nothing. Picture a label on an empty box. The box exists, so you can read the label and move it. What you cannot do is ask the box for help, because there is no object inside.

Example 1: using a method on None

Here is a small program. Save it as main.py and run it with python3 main.py.

name = None
print("Before:", name)
print(name.upper())
print("After")

The output is:

Before: None
Traceback (most recent call last):
  File "/home/you/main.py", line 3, in <module>
    print(name.upper())
          ^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'upper'

The line Before: None shows that print turns None into the word None without any error. Passing None around is allowed. The program fails only when it asks the value for something.

The line name.upper() is the failure. The dot means "find the method upper on the object in name". There is no object, so Python stops the program and prints a traceback. A traceback is the list of lines that were running when the error happened. Its last line gives the error type and the message. Here the type is AttributeError, and the message names the value and the missing attribute. The word After is never printed, because the program stopped.

The file path and line numbers depend on your machine. The message is the part to read. 'NoneType' object says the value was None, and has no attribute 'upper' says which operation failed. You now know to look at name, and to find where name was last set.

Example 2: a None inside a chain of attributes

Most real code does not use a plain name. It follows a chain, such as head.next.value. Each dot moves one step, and any step can land on None. Here is a small linked structure with one node.

Python · editable

Type nothing into the Input box for this program.

1
AttributeError: 'NoneType' object has no attribute 'value'

Without the try block, the same line stops the program with a traceback. The last line of that traceback is the same AttributeError message.

The object n exists. Its attribute next is None, because the constructor sets it to None. The message names the type NoneType and the attribute value. The part of the chain that was empty is n.next. So when you read a message like this, look at the link in the chain just before the attribute that failed.

Starting values matter here. A new object has only the attributes that its constructor sets. If the constructor sets self.next = None, every new node starts with an empty link, and the code that builds the chain must set it later.

Example 3: a missing key in a dictionary

A common source of None is dict.get. It returns None when the key is not in the dictionary, and it does not raise an error. The trouble starts when the result is used in arithmetic. Python then cannot add a number to None.

Python · editable

Type nothing into the Input box for this program.

None
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

The message says the left side was NoneType and the right side was int. The fix is at the call to get, not at the print line. Look at how count was made, and decide whether a missing key is normal. If it is, give get a default value.

Example 4: looping over None

A for loop needs a value it can step through. If the value is None, the loop cannot start, and Python raises a TypeError. This often happens when a function that should return a list returns None instead.

Python · editable

Type nothing into the Input box for this program.

TypeError: 'NoneType' object is not iterable

The loop never runs its body, because the error happens before the first pass. The message names 'NoneType' object is not iterable, which means the value cannot be stepped through. The fix is to give the variable an empty list instead of None, or to check it before the loop.

Checking before use in Python

Python gives you several ways to stop a None before it crashes the program. Each one answers a slightly different question.

A plain is None check

The simplest check is if x is None. It is the right tool when a missing value is a normal case and the program should do something else. Here is a function that returns a bonus count for an item, and returns 0 when the item is missing.

Python · editable

Type nothing into the Input box for this program.

5
0

The variable count can hold None, which is why the check is needed. The line return count + 1 runs only after the check passes, so the addition is safe.

Use is None, not == None or a plain if count:. The next section explains why.

A default value for dict.get

When a missing key should give a fixed answer, pass the default as the second argument to get. The function dict.get(key, default) returns the default when the key is missing, so the value is never None.

Python · editable

Type nothing into the Input box for this program.

0
8

Comparing with == is safe

Calling a method on a value that might be None is a crash. Comparing with == is different. The expression "hello" == text is safe when text is None, because == does not call a method on text. It returns False. The expression text == "hello" is also safe for the same reason, but text.lower() == "hello" is not. The rule is simple: call methods only on values you know are present, and compare with == when you are not sure.

Python · editable

Type nothing into the Input box for this program.

False
False
AttributeError: 'NoneType' object has no attribute 'lower'

Both comparisons are safe, and only the method call fails. Check for None before you call a method, and both values are safe to compare.

Fail early with a clear message

Sometimes None should never happen. A function that needs a name cannot do anything useful without one. In that case, check at the start of the function and raise an error with a message you chose. The error then points at the function that received the bad value, and the message says what was wrong.

Python · editable

Type nothing into the Input box for this program.

caught: name must not be None

Without the check, the crash would happen later, inside the string join or in some function that the label was passed to, and the message would be harder to read.

Type hints for a value that may be missing

A type hint tells a reader, and a type checker, that a value may be missing. The notation str | None means "a string or None". Python does not enforce hints when the program runs, so the check is still needed. The | form needs Python 3.10 or newer. The hint tells the next reader where to look.

Python · editable

Type nothing into the Input box for this program.

0 5

The function returns a number for both inputs, because the check runs first. The hint str | None documents that None is a possible input.

Short-circuit checks

The and operator stops as soon as one side is false. So text is not None and len(text) > 3 never calls len when text is None. The check and the use sit in one expression, and the order matters. Writing len(text) > 3 and text is not None would crash first.

Python · editable

Type nothing into the Input box for this program.

short or missing

Common Python mistakes

Mistake 1: reading the error but not the line

The traceback gives a line number. That line is where Python tried to do the work. The None may have come from an earlier line. A value might have been returned from a function, stored in an attribute or read from a dictionary long before the crash.

When a crash happens, do these steps in order. Read the message and find the type and the value it names. Find that value in the code. Find where it was last set. Then decide whether the None is expected there and needs a check, or whether the code that set it is wrong.

Mistake 2: forgetting the return

A function that does not reach a return gives back None. Many built-in methods work the same way. The list.sort method sorts the list in place and returns None. Assigning its result to a name gives you None, and the next use of that name fails.

Python · editable

Type nothing into the Input box for this program.

[1, 2, 3]
None
TypeError: 'NoneType' object is not subscriptable

The list is sorted, but result holds None. To keep a sorted copy and leave the original alone, use sorted(items), which returns a new list. The same mistake happens with any function that forgets its return line:

Python · editable

Type nothing into the Input box for this program.

TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

The second call to find_price finds no key, so the function ends without a return and gives None. The addition then fails. The message names the two types in the order they appear in the expression, so the NoneType on the right tells you which call returned nothing.

Mistake 3: adding to a value that is still None

A counter that starts as None cannot be added to. The line total += 1 reads the old value first, and None has no addition. Start every counter with a real value, such as 0.

Python · editable

Type nothing into the Input box for this program.

TypeError: unsupported operand type(s) for +=: 'NoneType' and 'int'
1

Mistake 4: treating zero and empty values as missing

A plain if x: test is false for None, and also false for 0, "" and []. A count of 0 is a real answer, but a plain test treats it as missing. Use if x is None when None is the only value that means "missing".

Python · editable

Type nothing into the Input box for this program.

falsy: the plain test cannot tell 0 from None
present, and the count is 0

Python has no dangling references

In C and C++, a pointer holds an address. When delete frees the memory, the pointer keeps the same address, and reading through it is a bug. That is called a dangling pointer. The same bug happens when a vector moves its data and a pointer still holds the old address, and when a function returns the address of one of its local variables.

Python does not have this bug. A name holds a reference to an object, and the reference keeps the object alive. The object is freed only when no name or other reference points at it. A name can never point at memory that was already freed, because the freeing waits until the last reference is gone.

Here is a function that returns a local variable. The local name disappears when the function returns, but the object it pointed at survives, because the return value still refers to it.

Python · editable

Type nothing into the Input box for this program.

[7] 7

The list is safe, because saved refers to it. Python returns objects, not addresses, so the C++ mistake of returning the address of a local variable has no Python form.

Deleting a name does not free anything that another name still points at. It only removes that one name. If you read the name after the del, Python raises a NameError, which is a safe error and not undefined behaviour:

Python · editable

Type nothing into the Input box for this program.

NameError: name 'p' is not defined

The name p is gone, so the error says so. The list it pointed at is freed only if no other name still reaches it. Python never gives you a name that points at freed memory.

Stale references and saved positions

Python does avoid dangling references, but it does not avoid stale data. A stale reference is a name that still points at an old object, after the program has replaced the object in its container. The old object is not freed, because the name still reaches it. So the name keeps working, but it shows an old value.

Python · editable

Type nothing into the Input box for this program.

[1, 2] [9, 9]
{'debug': False} {'debug': True}

The name row still points at the old list, because the program replaced the item in grid but did not touch row. The name config keeps the old dictionary for the same reason. Neither one crashes. Both show data that is no longer current. The fix is to read the value again from its container after a change, or to keep one name for the object and change the object in place.

The closest Python bug to the C++ vector example is a saved position. If you save an index into a list and then change the list, the index may now point at a different item.

Python · editable

Type nothing into the Input box for this program.

c
0 b

The program saved the index 1 for "b". Then pop(0) removed the first item, so every later item moved one place to the left. The index 1 now points at "c". The program printed c, which is wrong, and it did not crash. The fix is to look up the position again after the list changes, as the last line does. Any saved index is valid only until the next change to the list.

Practice

Exercise 1

What does this Python program print?

name = None
print("empty" if name is None else name.strip())

Show the answer

It prints empty. The check name is None is true, so the expression takes the first branch, and name.strip() is never called. Without the check, the program would fail with an AttributeError.

Exercise 2

The program below has one line that fails. Predict the exact message.

class Box:
    def __init__(self):
        self.label = None
 
box = Box()
print(box.label.upper())

Show the answer

The message is AttributeError: 'NoneType' object has no attribute 'upper'. The traceback points at the last line of the program. The label attribute is None because the constructor sets it to None, and the program never gives it a value.

Exercise 3

The function below fails when item is not in stock. Change it so a missing item counts as 0, and the result is double the count.

def doubled(stock, item):
    return stock.get(item) * 2

Show the answer

def doubled(stock, item):
    value = stock.get(item)
    if value is None:
        return 0
    return value * 2

The check handles the missing key. With apple at 4 in the dictionary, the function returns 8, and for pear it returns 0. A shorter form is return stock.get(item, 0) * 2, which uses the default argument of get.

Exercise 4

Why does this code print the wrong item, and how do you fix it?

letters = ["x", "y", "z"]
pos = letters.index("y")
letters.insert(0, "w")
print(letters[pos])

Show the answer

The saved position pos is 1, which pointed at "y". The call insert(0, "w") shifts every later item one place to the right, so position 1 now holds "x". The program prints x. The fix is to look up the position again after the change: print(letters[letters.index("y")]) prints y. Any saved index is valid only until the list changes.

Exercise 5

The function below returns the length of a title, or 0 when the title is missing. Explain why the plain test if title: gives the wrong answer for an empty title, and rewrite the function so that only a missing title counts as missing.

def title_length(title):
    if not title:
        return 0
    return len(title)

Show the answer

The plain test not title is true for None and also for the empty string "". The empty string is a real title with length 0, and the function returns 0 for it anyway. For this function the answer is the same, but the reason matters when a zero or an empty value has a different meaning. The version that checks only for None is:

def title_length(title):
    if title is None:
        return 0
    return len(title)

With this version, an empty title returns 0 because its length is 0, and a missing title returns 0 because the function was told to. The two cases are now separate, and a reader can see that.

Summary

  • A None value points to nothing. Reading an attribute or calling a method on it raises AttributeError, and using it in arithmetic or a loop raises TypeError.
  • Read the last line of the traceback. It names the type and the operation, and NoneType tells you the value was None.
  • Check with is None before you use a value that might be missing, pass a default to dict.get, or raise your own error with a clear message.
  • A function without a return line gives None. Check that each function returns what its callers use.
  • Python has no dangling references, because a reference keeps its object alive. The nearest bugs are stale references, which show an old value, and saved indexes, which point at a different item after a change.
Check yourself: None and stale references (6 questions) →Practice this topic: coding problems for References and Memory →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Module assignment: Python catalog with safe copies