-
10 common beginner mistakes in Python
Python is an easy language to learn. And there are many self-taught programmers who don't really go with the best practices from the start. Very often during the development process and when viewing the solutions of our users in CheckiO we are faced with a lot of these mistakes. For this reason in this article I highlighted the most common beginner mistakes, so nobody repeat them again.
Incorrect indentation, tabs and spaces
Never use tabs, only spaces. Use 4 spaces for a tab. And follow a consistent indentation pattern, because many Python features rely on indentation. If your code is executing a task when it shouldn't then review the indentation you’re using.
Using a Mutable Value as a Default Value
def foo(numbers=[]): numbers.append(9) return numbers
We take a list, add 9.
>>> foo() [9] >>> foo(numbers=[1,2]) [1, 2, 9] >>> foo(numbers=[1,2,3]) [1, 2, 3, 9]
And here what happens when calling foo
without numbers.
>>> foo() # first time, like before [9] >>> foo() # second time [9, 9] >>> foo() # third time... [9, 9, 9] >>> foo() # WHAT IS THIS BLACK MAGIC?! [9, 9, 9, 9]
In Python default values or functions are instantiated not when the function is called, but when it's defined.
Solution
def foo(numbers=None): if numbers is None: numbers = [] numbers.append(9) return numbers
Well, some other default values do work as expected.
def foo(count=0): count += 1 return count
>>> foo() 1 >>> foo() 1 >>> foo(2) 3 >>> foo(3) 4 >>> foo() 1
The reason for this is not in the default value assignment, but in the value itself. An integer is an immutable type. Doing count += 1
the original value of count isn’t changing.
Try to call a function as the default value:
def get_now(now=time.time()): return now
As you can see, while the value of time.time()
is immutable, it returns the same time.
>>> get_now() 1373121487.91 >>> get_now() 1373121487.91 >>> get_now() 1373121487.91
Handle specific exceptions
If you know what exception the code is going to throw, just except
that specific exception.
Don't write like this:
try: # do something here except Exception as e: # handle exception
When on a Django's model you're calling get()
method and the object is not present, Django throws ObjectDoesNotExists
exception. You need to catch this exception, not Exeption
.
Write a lot of comments and docstrings
As we've already talked about in one of our articles, if you can't make the code easier and there is something not so obvious, you need to write a comment.
Writing docstrings to functions/methods is also a good habit.
def create_user(name, height=None, weight=None): '''Create a user entry in database. Returns database object created for user.''' # Logic to create entry into db and return db object
Scoping
Python understands global variables if we access them within a function:
bar = 42 def foo(): return bar
Here we're using a global variable called bar
inside foo
and it works as it should:
>>> foo() 42
It also works if we use some function on a global
bar = [42] def foo(): bar.append(0) >>> bar [42, 0]
What if we change bar
?
>>> bar = 42 ... def foo(): ... bar = 0 ... foo() ... return bar 42
Here the line bar = 0
, instead of changing bar
, created a new, local variable also called bar
and set its value to 0
.
Let's look at a less common version of this mistake to understand when and how Python decided to treat variables as global or local. We'll add an assignment to bar after we print it:
bar = 42 def foo(): print(bar) bar = 0
This shouldn't break our code. But it does.
>>> foo() Traceback (most recent call last): File "", line 1, in foo() File " ", line 3, in foo print bar UnboundLocalError: local variable ''bar'' referenced before assignment
There are two parts to this misunderstanding:
1) Python is being executed statement-by-statement, and NOT line-by-line,
2) Python statically gathers informations about the local scope of the function when the def statement is executed. Reaching bar=0
it adds bar to the list of local variable for foo
.
bar = 42 def foo(baz): if baz > 0: return bar bar = 0
Python can't know that the local bar
was assigned to.
bar = 42 def foo(): return bar if False: bar = 0
Running foo
we get:
Traceback (most recent call last): File "", line 1, in foo() File " ", line 3, in foo return bar UnboundLocalError: local variable 'bar' referenced before assignment
Python still declares bar
as statically local.
Solutions:
1. Using the global
keyword, but it is very often a bad idea, if you can avoid using global it is better to do so.
>>> bar = 42 ... def foo(): ... global bar ... print(bar) ... bar = 0 ... ... foo() 42 >>> bar 0
2. Don't use a global that isn't constant. If you want to keep a value that is used throughout your code, define it as a class attribute for a new class.
>>> class Baz(object): ... bar = 42 ... ... def foo(): ... print(Baz.bar) # global ... bar = 0 # local ... Baz.bar = 8 # global ... print(bar_ ... ... foo() ... print(Baz.bar) 42 0 8
"How To Use Variables in Python 3" - a very detailed explanation by Lisa Tagliaferri
Edge cases first
Pseudo code 1:
if request is post: if parameter 'param1' is specified: if the user can perform this action: # Execute core business logic here else: return saying 'user cannot perform this action' else: return saying 'parameter param1 is mandatory' else: return saying 'any other method apart from POST method is disallowed'
Pseudo code 2:
if request is not post: return saying 'any other method apart from POST method is disallowed' if parameter 'param1' not is specified: return saying 'parameter param1 is mandatory' if the user cannot perform this action: return saying 'user cannot perform this action' # Execute core business logic here
These pseudo codes are for handling a request, and the second one is much easier to read, it doesn't have a lot of nesting, which avoids common problems.
Copying
>>> a = [2, 4, 8] >>> b = a >>> a[1] = 10 >>> b [2, 10, 8]
As you can see a and b are pointing at the same object, and thus by changing a - b will also change.
You can google this trick.
>>> b = a[:] >>> a[1] = 10 >>> b [2, 4, 8] >>> a [2, 10, 8]
Or the copy method.
>>> b = a.copy() >>> a[1] = 10 >>> b [2, 4, 8] >>> a [2, 10, 8]
In this case we can see that the b away is an a list copy, but by changing a we don't change b.
Although here you can also step on a rake with the nested lists.
>>> a = [[1,2], [8,9]] >>> b = a.copy() >>> a[0][0] = 5 >>> a [[5, 2], [8, 9]] >>> b [[5, 2], [8, 9]] >>>
Or, for example, the multiplication of list.
>>> a = [[1,2]] * 3 >>> a [[1, 2], [1, 2], [1, 2]] >>> a[0][0] = 8 >>> a [[8, 2], [8, 2], [8, 2]]
To avoid it once and for all use deepcopy.
>>> from copy import deepcopy >>> c = deepcopy(a) >>> a[0][0] = 10 >>> a [[10, 2], [8, 9]] >>> b [[10, 2], [8, 9]] >>> c [[8, 2], [8, 9]]
Creating count-by-one errors on loops
Remember that a loop doesn't count the last number you specify in a range. So if you specify the range (1, 11), you actually get output for values between 1 and 10.
>>> a = list(range(1, 11)) >>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> a[0] 1 >>> a[0:5] [1, 2, 3, 4, 5]
Wrong capitalization
If you can't access a value you expected, you have to check capitalization. Python is case sensitive, so MyVar is different from myvar and MYVAR.
>>> MyVar = 1 >>> MYVAR Traceback (most recent call last): File "< stdin >", line 1, in < module > NameError: name 'MYVAR' is not defined
Using class variables incorrectly
>> class A(object): ... x = 1 ... >>> class B(A): ... pass ... >>> class C(A): ... pass ... >>> print A.x, B.x, C.x 1 1 1
Example makes sense.
>> B.x = 2 >>> print A.x, B.x, C.x 1 2 1
And again.
>>> A.x = 3 >>> print A.x, B.x, C.x 3 2 3
You ask why did C.x change if we've only changed A.x.? Well, class variables in Python are internally handled as dictionaries and follow the Method Resolution Order (MRO). That's why the attribute x will be looked up in its base classes (only A in the above example, although Python supports multiple inheritance) since it's not found in class C. So, C doesn't have its own x property, independent of A. And like that references to C.x are in fact references to A.x. This causes a Python problem unless it's handled properly.
Conclusion
As you can see, there are a lot of things that could be done wrong, especially if you're new to Python. But by keeping in mind this mistakes you can pretty easily avoid them. You just need a little practice and it'll become a habit. Nevertheless, it's not nearly the whole list and there might be the things you'd want to add. So, what kind of mistakes did you encounter besides those mentioned above?
Related articles
- /r/learnpython specific FAQ
- "Python Binding a Name to an Object" by John Philip Jones
Welcome to CheckiO - games for coders where you can improve your codings skills.
The main idea behind these games is to give you the opportunity to learn by exchanging experience with the rest of the community. Every day we are trying to find interesting solutions for you to help you become a better coder.
Join the Game