A List of Python Tradgedies



Python is a language of all time. Despite its longevity, rich ecosystem, and extensive feature list, the language has some tragic quirks that hurt my brain everytime I encounter them. This is a list of some of my favorites that I will continually update.


List * operator used on lists

This operator is a python classic. It is pretty simple, and I would argue even elegant. Simply take the contents of a list and repeat that nn times.

Consider the following piece of code:


def print_arr(arr):
  for sub_arr in arr:
    print(sub_arr)

nested_arr = [[0] * 4] * 4

print_arr(nested_arr)

As expected our output is a 4x4 matrix of zeros.


[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]

Now lets say we edit this array with nested_arr[0][0] = 42 and call print_arr again. Now we get:


[42, 0, 0, 0]
[42, 0, 0, 0]
[42, 0, 0, 0]
[42, 0, 0, 0]

??????? Witchcraft. It seems that our * operator has repeated the pointer to that list 4 times instead of copying the list. This is gargantuanly unintuitive. Instead we are restricted to the ugly and potentially unpythonic


nested_arr = [[0] * 4 for _ in range(4)]

Function argument defaults

Consider the following:


def func(l=[]):
  l.append(1)
  print(l)

func()
func()

# This prints:
[1]
[1, 1]

?????? l is not being reconstructed when we use the default argument. Again tragic reference wizardry. The only way I know to work around this is the following:


def func(l=None):
  if l is None:
    l = []
  l.append(1)
  print(l)