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 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)