The purpose of my question is to strengthen my knowledge base with Python and get a better picture of it, which includes knowing its faults and surprises. To keep things sp
One of the biggest surprises I ever had with Python is this one:
a = ([42],)
a[0] += [43, 44]
This works as one might expect, except for raising a TypeError after updating the first entry of the tuple! So a
will be ([42, 43, 44],)
after executing the +=
statement, but there will be an exception anyway. If you try this on the other hand
a = ([42],)
b = a[0]
b += [43, 44]
you won't get an error.
def f():
x += 1
x = 42
f()
results in an UnboundLocalError
, because local names are detected statically. A different example would be
def f():
print x
x = 43
x = 42
f()
If you create a list of list this way:
arr = [[2]] * 5
print arr
[[2], [2], [2], [2], [2]]
Then this creates an array with all elements pointing to the same object ! This might create a real confusion. Consider this:
arr[0][0] = 5
then if you print arr
print arr
[[5], [5], [5], [5], [5]]
The proper way of initializing the array is for example with a list comprehension:
arr = [[2] for _ in range(5)]
arr[0][0] = 5
print arr
[[5], [2], [2], [2], [2]]
Expressions in default arguments are calculated when the function is defined, not when it’s called.
Example: consider defaulting an argument to the current time:
>>>import time
>>> def report(when=time.time()):
... print when
...
>>> report()
1210294387.19
>>> time.sleep(5)
>>> report()
1210294387.19
The when
argument doesn't change. It is evaluated when you define the function. It won't change until the application is re-started.
Strategy: you won't trip over this if you default arguments to None
and then do something useful when you see it:
>>> def report(when=None):
... if when is None:
... when = time.time()
... print when
...
>>> report()
1210294762.29
>>> time.sleep(5)
>>> report()
1210294772.23
Exercise: to make sure you've understood: why is this happening?
>>> def spam(eggs=[]):
... eggs.append("spam")
... return eggs
...
>>> spam()
['spam']
>>> spam()
['spam', 'spam']
>>> spam()
['spam', 'spam', 'spam']
>>> spam()
['spam', 'spam', 'spam', 'spam']
Not including an __init__
.py in your packages. That one still gets me sometimes.
Due to 'truthiness' this makes sense:
>>>bool(1)
True
but you might not expect it to go the other way:
>>>float(True)
1.0
This can be a gotcha if you're converting strings to numeric and your data has True/False values.