Python Gotchas: They should put that on the box!

June 3, 2024 (6mo ago)

subscribe

This post talks about some common pitfalls and gotchas I've ran into over the years that are tucked away in the Python programming language. In hopes that after reading this article, you won't find yourself saying:

Ross saying to Rachel that they should put that on the box

Hash

In python 3, hash() returns the same results within one process, but different results across invocations. It is advised to use hashlib instead for repeated consistent results.

An example of hash returning different results from separate invocations

Mutable objects as arguments

If a mutable object is used as default argument for a function, it retains its value across multiple function calls. This is because Python's default arguments are evaluated once when the function is defined, not each time the function is called.

An example of list retaining its value across multiple function calls

Class variables and inheritance

Class variables are handled as dictionaries internally and follow Method Resolution Order. This can cause class variables to behave in unexpected ways if you don't understand what is happening under the hood.

Consider the following example:

>>> class A(object):
...     x = 1
...
>>> class B(A):
...     pass
...
>>> class C(A):
...     pass
...
>>> print A.x, B.x, C.x
1 1 1

If I update the class variable x of class B:

>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1

This works as expected. However, if I update the value of x for class A:

>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3

We see that the value of x for class C also changed. Since class C does not have its own variable x, when called, Python would look for it in its parent (class A in this example). Thus, references to C.x are in fact references to A.x.

Scope and variable assignment

In Python, variables that are only referenced inside a function are implicitly global. However, if a variable is assigned a value anywhere within the function’s body, it's considered local unless explicitly declared as global.

You can read more about Python scoping and namespaces here.

An example of UnboundedError caused by scope change

assert statement

(Note: This one is less of a gotcha, more like a necessary piece of information of which not everyone is aware.)

Python’s assert statement allows you to write sanity checks in your code. These checks are known as assertions, and you can use them to test if certain assumptions remain true while you’re developing your code. Assertions are a convenient tool for documenting, debugging, and testing code during development. And you should only use them in this capacity. You should not and can not use them in production code acting as guard clauses. This is because Python allows you to disable assert statements for performance optimization. You can do this by:

  • Running Python with the -O or -OO options.
  • Or Setting the PYTHONOPTIMIZE environment variable to an appropriate value.