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()  >>> 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  >>> 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.
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
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
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
foo and it works as it should:
>>> foo() 42
It also works if we use some function on a global
bar =  def foo(): bar.append(0) >>> bar [42, 0]
What if we change
>>> 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
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
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
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.
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
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.
>>> a = [2, 4, 8] >>> b = a >>> a = 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 = 10 >>> b [2, 4, 8] >>> a [2, 10, 8]
Or the copy method.
>>> b = a.copy() >>> a = 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 = 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 = 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 = 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 1 >>> a[0:5] [1, 2, 3, 4, 5]
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
>>> 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.
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?
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