Consider the case of creating a nested list structure by multiplying:
li = [[]] * 3
print(li)
# Out: [[], [], []]
At first glance we would think we have a list of containing 3 different nested lists. Let's try to append 1
to the first one:
li[0].append(1)
print(li)
# Out: [[1], [1], [1]]
1
got appended to all of the lists in li
.
The reason is that [[]] * 3
doesn't create a list
of 3 different list
s. Rather, it creates a list
holding 3 references to the same list
object. As such, when we append to li[0]
the change is visible in all sub-elements of li
. This is equivalent of:
li = []
element = [[]]
li = element + element + element
print(li)
# Out: [[], [], []]
element.append(1)
print(li)
# Out: [[1], [1], [1]]
This can be further corroborated if we print the memory addresses of the contained list
by using id
:
li = [[]] * 3
print([id(inner_list) for inner_list in li])
# Out: [6830760, 6830760, 6830760]
The solution is to create the inner lists with a loop:
li = [[] for _ in range(3)]
Instead of creating a single list
and then making 3 references to it, we now create 3 different distinct lists. This, again, can be verified by using the id
function:
print([id(inner_list) for inner_list in li])
# Out: [6331048, 6331528, 6331488]
You can also do this. It causes a new empty list to be created in each
append
call.
>>> li = []
>>> li.append([])
>>> li.append([])
>>> li.append([])
>>> for k in li: print(id(k))
...
4315469256
4315564552
4315564808
Don't use index to loop over a sequence.
Don't:
for i in range(len(tab)):
print(tab[i])
Do:
for elem in tab:
print(elem)
for
will automate most iteration operations for you.
Use enumerate if you really need both the index and the element.
for i, elem in enumerate(tab):
print((i, elem))
Be careful when using "==" to check against True or False
if (var == True):
# this will execute if var is True or 1, 1.0, 1L
if (var != True):
# this will execute if var is neither True nor 1
if (var == False):
# this will execute if var is False or 0 (or 0.0, 0L, 0j)
if (var == None):
# only execute if var is None
if var:
# execute if var is a non-empty string/list/dictionary/tuple, non-0, etc
if not var:
# execute if var is "", {}, [], (), 0, None, etc.
if var is True:
# only execute if var is boolean True, not 1
if var is False:
# only execute if var is boolean False, not 0
if var is None:
# same as var == None
Do not check if you can, just do it and handle the error
Pythonistas usually say "It's easier to ask for forgiveness than permission".
Don't:
if os.path.isfile(file_path):
file = open(file_path)
else:
# do something
Do:
try:
file = open(file_path)
except OSError as e:
# do something
Or even better with Python 2.6+
:
with open(file_path) as file:
It is much better because it is much more generic. You can apply try/except
to almost anything. You don't need to care about what to do to prevent it, just care about the error you are risking.
Do not check against type
Python is dynamically typed, therefore checking for type makes you lose flexibility. Instead, use duck typing by checking behavior. If you expect a string in a function, then use str()
to convert any object to a string. If you expect a list, use list()
to convert any iterable to a list.
Don't:
def foo(name):
if isinstance(name, str):
print(name.lower())
def bar(listing):
if isinstance(listing, list):
listing.extend((1, 2, 3))
return ", ".join(listing)
Do:
def foo(name) :
print(str(name).lower())
def bar(listing) :
l = list(listing)
l.extend((1, 2, 3))
return ", ".join(l)
Using the last way, foo
will accept any object. bar
will accept strings, tuples, sets, lists and much more. Cheap DRY.
Don't mix spaces and tabs
Use object as first parent
This is tricky, but it will bite you as your program grows. There are old and new classes in Python 2.x
. The old ones are, well, old. They lack some features, and can have awkward behavior with inheritance. To be usable, any of your class must be of the "new style". To do so, make it inherit from object
.
Don't:
class Father:
pass
class Child(Father):
pass
Do:
class Father(object):
pass
class Child(Father):
pass
In Python 3.x
all classes are new style so you don't need to do that.
Don't initialize class attributes outside the init method
People coming from other languages find it tempting because that is what you do in Java or PHP. You write the class name, then list your attributes and give them a default value. It seems to work in Python, however, this doesn't work the way you think. Doing that will setup class attributes (static attributes), then when you will try to get the object attribute, it will gives you its value unless it's empty. In that case it will return the class attributes. It implies two big hazards:
If the class attribute is changed, then the initial value is changed.
If you set a mutable object as a default value, you'll get the same object shared across instances.
Don't (unless you want static):
class Car(object):
color = "red"
wheels = [Wheel(), Wheel(), Wheel(), Wheel()]
Do :
class Car(object):
def __init__(self):
self.color = "red"
self.wheels = [Wheel(), Wheel(), Wheel(), Wheel()]