CodeGym /Courses /Python SELF EN /Standard Errors

Standard Errors

Python SELF EN
Level 20 , Lesson 1
Available

7.1 Misusing expressions as default values for function arguments

You've learned a lot already, so let's go over some common rookie mistakes – it's better to learn from others' errors than our own, right?

Python allows you to specify that functions can have optional arguments by setting them with default values. This is a pretty handy feature, but it can lead to some tricky situations if the default you're using is a mutable type. For instance, check out this function definition:


def foo(bar=[]):  # bar is an optional argument 
# and by default, it's an empty list.
    bar.append("baz")  # this line might cause trouble...
    return bar

A common mistake here is thinking that the default value for the optional argument is reset every time the function is called without a specific value for that argument.

In the code above, you might expect that each time you call the function foo() (without providing a value for bar), it would return ["baz"], because you'd think every time foo() is called (without setting bar), bar gets set to [] (a new empty list).

But let's see what actually happens:


result = foo()
print(result)  # ["baz"]
            
result = foo()
print(result)  # ["baz", "baz"]
            
result = foo()
print(result)  # ["baz", "baz", "baz"]

Why does the function keep adding baz to the existing list each time foo() is called, instead of creating a new list every time?

The answer lies in a deeper understanding of what's happening "under the hood" in Python. Specifically: the default value for a function is initialized only once, at the time the function is defined.

So, the bar argument is initialized to its default (i.e., an empty list) only once when foo() is first defined, but subsequent calls to foo() (without bar) continue to use that same list created during the function's first definition.

As a workaround for this common mistake, you can use the following definition:


def foo(bar=None):             
    if bar is None:
        bar = []                                          
    bar.append("baz")   
    return bar
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]

7.2 Misusing class variables

Let's look at the following example:


class A(object):
    x = 1
       
class B(A):
    pass
       
class C(A):
    pass
       
print(A.x, B.x, C.x) 
# 1 1 1

Everything seems alright.

Now let's assign a value to class B's x field:


B.x = 2
print(A.x, B.x, C.x)
# 1 2 1

Everything as expected.

Now let's change class A's variable:


A.x = 3
print(A.x, B.x, C.x)
# 3 2 3

Strange, we only changed A.x. Why did C.x change too?

In Python, class variables are handled like dictionaries and follow what is commonly referred to as the Method Resolution Order (MRO). So, in the code above, since attribute x is not found in class C, it will be found in its base classes (just A in this example, although Python does support multiple inheritance).

In other words, C doesn't have its own x property independent of A. Thus, references to C.x actually point to A.x. This can be problematic if not considered carefully. When learning Python, pay special attention to class attributes and how they work.

7.3 Incorrect exception parameter specification

Suppose you have the following code snippet:


try:
    l = ["a", "b"]
    int(l[2])
except ValueError, IndexError:  # To catch both exceptions, right?
    pass
        
Traceback (most recent call last):
    File "<stdin>", line 3, in <module>
IndexError: list index out of range

The problem here is that the except statement doesn't take a list of exceptions specified that way. As a result, in the code above, the IndexError exception isn't caught by the except statement; instead, the exception ends up binding to a parameter named IndexError.

Important! You might come across this kind of code in examples online because it's how it was done in Python 2.x.

The correct way to catch multiple exceptions with an except statement is to specify the first parameter as a tuple containing all the exceptions you want to catch. Additionally, for better readability, you can use the keyword as, like this:


try:
    l = ["a", "b"]
    int(l[2])
except (ValueError, IndexError) as e:  
    pass
2
Task
Python SELF EN, level 20, lesson 1
Locked
Default parameters.
Default parameters.
2
Task
Python SELF EN, level 20, lesson 1
Locked
Incorrect parameter specification
Incorrect parameter specification
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION