-
Notifications
You must be signed in to change notification settings - Fork 0
3. Common Pitfalls
- Iterating twice
>>> from statistics import mean, median >>> cubes = (n**3 for n in range(10)) >>> mean(nums) 202.5 >>> median(nums)
StatisticsError: no median for empty data
- Containment checking
>>> cubes = (n**3 for n in range(10)) >>> 125 in cubes True >>> 125 in cubes False
This might look unexpected behaviour 💭. Containment internally iterates over the iterable, hence the second case here is committing the same treason as the first one, i.e., iterating over an iterator twice. Iterating once exhausts an iterator and makes it empty and hence the weird behaviour.
But why does an iterator behave so?
a = b = [1,2]
a.append(3)
What is b
? It's [1,2,3]
.
a = [1,2]
b = a.copy() # (shallow) copy
a.append(3)
Now, b
is still [1,2]
.
def append5(data=[]):
data.append(5)
return data
Using this append5
method results in an astonishing behaviour
>>> append5()
[5]
>>> append5()
[5, 5]
>>> append5()
[5, 5, 5]
Lesson: Don't use mutable values as default parameter values 🚫
Let's say we want a running average and we implement it using a class
class Averager:
def __init__(self):
self.series = []
def __call__(self, num):
self.series.append(num)
return sum(self.series)/len(self.series)
This can be used as
>>> avg = Averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
Same thing can be achieved using a closure
def make_averager():
series = []
def average(num):
series.append(num)
return sum(series)/len(series)
return average
It too achieves the same purpose, after all "Closures and Objects are equivalent". On SO
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
Now, let's say we rewrite our make_averager
as
def make_averager():
count = 0
total = 0
def average(num):
count += 1
total += num
return total/count
return average
Trying to do the same thing leads us to UnboundLocalError
avg(10)
UnboundLocalError: local variable 'count' referenced before assignment
You would need to use nonlocal
keyword here
📝Note: This pitfall is not just for Python but programming languages in general.
Floats are tricky! 1.1 + 2.2 == 3.3
gives False
😕
See Python FAQ
Take a moment to look at the function f
below. Before you try running it, write on paper what the output would be of x1 = f(1/10)
. Now, (still on paper) plug that back into f
and calculate x2 = f(x1)
. Keep going for 10 iterations.
def f(x):
if x <= 1/2:
return 2 * x
if x > 1/2:
return 2*x - 1
Only after you've written down what you think the answer should be, run the code below:
x = 1/10
for i in range(80):
print(x)
x = f(x)
What went wrong?