| python |
I've been using Python as scripting language on a non-regular basis. It means I can (I possibly do) write non-idiomatic Python code. I've decided to collect in one place popular Python anti-patterns and bad practices to avoid them.
- References
- 1 - Collections
- 1.1 - Iterate over a list
- 1.2 - Iterate over a list in reverse order
- 1.3 - Access the last element in a list
- 1.4 - Use sequence unpacking
- 1.5 - Use lists comprehensions
- 1.6 - Use enumerate function
- 1.7 - Use keys to sort list
- 1.8 - Use all/any functions
- 1.9 - Dictionaries: avoid using keys() function
- 1.10 - Dictionaries: Iterate over keys and values
- 1.11 - Use dictionaries comprehension
- 1.12 - Use
namedtuple
instead of simple class - 1.13 - Use
defaultdict
and/orCounter
- 1.14 - Modifying a list while iterating over it
- 2 - Functions
- 3 - Variables
- 4 - Classes
- 5 - Exceptions
- Python Patterns (see src)
- The Little Book of Python Anti-Patterns (see src)
- Python Data Structures Idioms by Ivan Mushketyk
- Buggy Python Code: The 10 Most Common Mistakes That Python Developers Make by Martin Chikilian
- Youtube: 5 Common Python Mistakes and How to Fix Them by Corey Schafer
- Python Worst Practices by Daniel Greenfeld
- What the f*ck Python! 😱
all
any
range
enumerate
sorted
zip
collections.namedtuple
collections.defaultdict
collections.Counter
Ref: [3]
Very Bad
l = [1, 2, 3, 4, 5]
i = 0
while i < len(l):
print(l[i])
i += 1
Bad
for i in range(len(l)):
print(l[i])
Good
for v in l:
print(v)
Ref: [3]
Bad
for i in range(len(l) - 1, -1, -1):
print(l[i])
Good
for i in reversed(l):
print(i)
Ref: [3]
Bad
l = [1, 2, 3, 4, 5]
>>> l[len(l) - 1]
5
Good
>>> l[-1]
5
>>> l[-2]
4
>>> l[-3]
3
Ref: [3]
Bad
l1 = l[0]
l2 = l[1]
l3 = l[2]
Good
l1, l2, l3 = [1, 2, 3]
>>> l1
1
>>> l2
2
>>> l3
3
Ref: [3]
Bad
under_18_grades = []
for grade in grades:
if grade.age <= 18:
under_18_grades.append(grade)
Good
under_18_grades = [grade for grade in grades if grade.age <= 18]
Ref: [3, 6]
Bad
for i in range(len(menu_items)):
menu_items = menu_items[i]
print("{}. {}".format(i, menu_items))
Good
for i, menu_items in enumerate(menu_items):
print("{}. {}".format(i, menu_items))
Bad
foo = [1, 2, 3]
for i, item in zip(range(len(foo)), foo):
print i, item
Good
foo = [1, 2, 3]
for i, item in enumerate(foo):
print i, item
Ref: [3]
people = [Person('John', 30), Person('Peter', 28), Person('Joe', 42)]
Bad
def compare_people(p1, p2):
if p1.age < p2.age:
return -1
if p1.age > p2.age:
return 1
return 0
sorted(people, cmp=compare_people)
[Person(name='Peter', age=28), Person(name='John', age=30), Person(name='Joe', age=42)]
Good
sorted(people, key=lambda p: p.age)
[Person(name='Peter', age=28), Person(name='John', age=30), Person(name='Joe', age=42)]
Ref: [3]
Bad
def all_true(lst):
for v in lst:
if not v:
return False
return True
Good
all([True, False])
>> False
any([True, False])
>> True
all([person.age > 18 for person in people])
# or generator
all(person.age > 18 for person in people)
Ref: [3]
Bad
for k in d.keys():
print(k)
Good
for k in d:
print(k)
Ref: [3]
Bad
for k in d:
v = d[k]
print(k, v)
Good
for k, v in d.items():
print(k, v)
Ref: [3]
Bad
d = {}
for person in people:
d[person.name] = person
Good
d = {person.name: person for person in people}
Ref: [3]
Bad
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
Good
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
In addition namedtuple
implements __str__
, __repr__
, and __eq__
methods:
>>> Point(1, 2)
Point(x=1, y=2)
>>> Point(1, 2) == Point(1, 2)
True
Ref: [3]
We need to count a number of times an element is encountered in a collection
Bad
d = {}
for v in lst:
if v not in d:
d[v] = 1
else:
d[v] += 1
Good
>>> d = defaultdict(lambda: 42)
>>> d['key']
42
from collections import defaultdict
d = defaultdict(int)
for v in lst:
d[v] += 1
Good
from collections import Counter
>>> counter = Counter(lst)
>>> counter
Counter({4: 3, 1: 2, 2: 1, 3: 1, 5: 1})
Ref: [4]
Bad
>>> odd = lambda x : bool(x % 2)
>>> numbers = [n for n in range(10)]
>>> for i in range(len(numbers)):
... if odd(numbers[i]):
... del numbers[i] # BAD: Deleting item from a list while iterating over it
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
IndexError: list index out of range
Good
>>> odd = lambda x : bool(x % 2)
>>> numbers = [n for n in range(10)]
>>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all
>>> numbers
[0, 2, 4, 6, 8]
Ref: [4, 5]
Bad
def foo(bar=[]): # bar is optional and defaults to [] if not specified
bar.append("baz")
return bar
>>> foo()
["baz"]
>>> foo()
["baz", "baz"]
>>> foo()
["baz", "baz", "baz"]
The default value for a function argument is only evaluated once, at the time that the function is defined. Thus, the bar argument is initialized to its default (i.e., an empty list) only when foo() is first defined, but then calls to foo() (i.e., without a bar argument specified) will continue to use the same list to which bar was originally initialized.
Good
def foo(bar=None):
if bar is None: # or if not bar:
bar = []
bar.append("baz")
return bar
>>> foo()
["baz"]
>>> foo()
["baz"]
>>> foo()
["baz"]
Ref: [6]
Bad
- Too many characters on one line
- Lambdas by design does not have docstrings
- Does not necessarily mean less characters
- I can’t get this sample to work!
swap = lambda a, x, y:
lambda f = a.__setitem__:
(f(x, (a[x], a[y])),
f(y, a[x][0]), f(x, a[x][1]))()
Good
- Doc strings that show up nicely in help/Sphinx
- Easier to read
- In Python, functions are first class objects
- Whenever possible avoid using lambdas
def swap(a, x, y):
""" Swap two position values in a list """
a[x],a[y] = a[y],a[x]
Ref:
- [4]
- Python scoping: understanding LEGB
- Python Frequently Asked Questions: Why am I getting an UnboundLocalError when the variable has a value?
Bad
def func1(param=None):
def func2():
if not param:
param = 'default'
print param
# Just return func2.
return func2
if __name__ == '__main__':
func1('test')()
$ python test.py
Traceback (most recent call last):
File "test.py", line 11, in
func1('test')()
File "test.py", line 3, in func2
if not param:
UnboundLocalError: local variable 'param' referenced before assignment
Good
def func1(param=None):
def func2(param2=param):
if not param2:
param2 = 'default'
print param2
# Just return func2.
return func2
Bad
>>> x = 10
>>> def foo():
... x += 1
... print x
...
>>> foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment
>>> lst = [1, 2, 3]
>>> def foo1():
... lst.append(5) # This works ok...
...
>>> foo1()
>>> lst
[1, 2, 3, 5]
>>> lst = [1, 2, 3]
>>> def foo2():
... lst += [5] # ... but this bombs!
...
>>> foo2()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'lst' referenced before assignment
Ref: [4]
Bad
>>> def create_multipliers():
... return [lambda x : i * x for i in range(5)]
>>> for multiplier in create_multipliers():
... print multiplier(2)
...
Expected
0
2
4
6
8
Actual
8
8
8
8
8
Good
>>> def create_multipliers():
... return [lambda x, i=i : i * x for i in range(5)]
...
>>> for multiplier in create_multipliers():
... print multiplier(2)
...
0
2
4
6
8
Ref: [6]
Bad
object = MyObject()
map = Map()
zip = 90213 # common US developer mistake
id = 34 # I still fight this one
Good
obj = MyObject() # necessary abbreviation
object_ = MyObject() # Underscore so we don't overwrite
map_obj = Map() # combine name w/necessary abbreviation
map_ = Map()
zip_code = 90213 # Explicit name with US focus
postal_code = 90213 # i18n explicit name
zip_ = 90213
pk = 34 # pk is often synonymous with id
id_ = 34
Ref: [6]
Bad
c = "green"
a = False
p = 20
t = "04/20/2011"
clr = "green"
ctv = False
pythnYrs = 20
pthnFrstSd = "04/20/2011"
strColor = "green"
boolActive = False
intPythonYears = 20
dtPythonFirstUsed = "04/20/2011"
Good
color = "green"
active = False
python_years = 20
python_first_used = "04/20/2011"
Ref: [6]
Bad
import logging
log = logging.getLogger()
class JavaStyle:
""" Quiz: what else am I doing wrong here? """
def __init__(self):
self.name = ""
def get_name(self):
return self.name
def set_name(self, name):
log.debug("Setting the name to %s" % name)
if isinstance(name, str):
self.name = name
else:
raise TypeError()
if __name__ == "__main__":
j = JavaStyle()
j.set_name("pydanny did this back in 2006!")
print(j.get_name())
Good
import logging
log = logging.getLogger()
class PythonStyle(object):
def __init__(self):
self._name = ""
@property
def name(self):
return self._name
@name.setter
def name(self, value):
""" Because name is probably a string we'll assume that we can
infer the type from the variable name"""
log.debug("Setting the name to %s" % value)
self._name = value
if __name__ == "__main__":
p = PythonStyle()
p.name = "pydanny doing it the right way"
print(p.name)
Ref: [6]
Bad
class WebService:
@property
def connect(self):
self.proxy = xmlrpc.Server("http://service.xml")
if __name__ == '__main__':
ws = WebService()
ws.connect
Good
class WebService:
def connect(self):
self.proxy = xmlrpc.Server("http://service.xml")
if __name__ == '__main__':
ws = WebService()
ws.connect()
Ref: [6]
Bad
try:
do_akshun(value)
except:
pass
Good
Use specific exceptions and/or logging
class AkshunDoesNotDo(Exception):
""" Custom exceptions makes for maintainable code """
pass
try:
do_akshun(value)
except AttributeError as e:
log.info("Can I get attribution for these slides?")
do_bakup_akshun(vlue)
except Exception as e:
log.debug(str(e))
raise AkshunDoesNotDo(e)