PythonTA Checks

This page describes in greater detail the errors that PythonTA checks for. If anything is unclear, incorrect, or missing, please don’t hesitate to send an email to [david at cs dot toronto dot edu].

Improper Python usage

These errors generally indicate a misuse of variables, control flow, or other Python features in our code.

Used before assignment (E0601)

This error occurs when we are using a variable before it has been assigned a value.

print(a)  # Error on this line
a = 1

Undefined variable (E0602)

This error occurs when we are using a variable that has not been defined.

var1 = 1

print(var1)
print(var2)  # Error on this line

Undefined loop variable (W0631)

This error occurs when a loop variable is used outside the for loop where it was defined.

for i in range(0, 2):
    print(i)

print(i)  # i is undefined outside the loop

Python, unlike many other languages (e.g. C, C++, Java), allows loop variables to be accessed outside the loop in which they were defined. However, this practice is discouraged, as it can lead to obscure and hard-to-detect bugs.

See also:

Not in loop (E0103)

This error occurs when the break or continue keyword is used outside of a loop. The keyword break is used to exit a loop early and the keyword continue is used to skip an iteration in a loop. Hence, both keywords only belong inside loops.

from typing import List

def add(lst: List[int]) -> int:
    """Calculate the sum of the elements in the given list."""
    temp = 0
    for item in lst:
        temp += item
    break  # Error on this line
    return temp

A common source of this error is when the break or continue is not indented properly (it must be indented to be considered part of the loop body).

Return outside function (E0104)

This error occurs when a return statement is found outside a function or method.

from typing import List

def add(lst: List[int]) -> None:
    """Calculate the sum of the elements in the given list."""
    temp = 0
    for item in lst:
        temp += item

return False  # Error on this line

A common source of this error is when the return is not indented properly (it must be indented to be considered part of the loop body).

Potential index error (E0643)

This error occurs when trying to access the index of an iterable (such as list, str, or tuple) that’s beyond its length.

coffees = ['americano', 'latte', 'macchiato', 'mocha']
print(coffees[4])  # Error on this line, invalid index

Corrected Version:

coffees = ['americano', 'latte', 'macchiato', 'mocha']
print(coffees[3])

Unreachable (W0101)

This error occurs when there is some code after a return or raise statement. This code will never be run, so either it should be removed, or the function is returning too early.

from typing import List


def add(lst: List[int]) -> int:
    """Return the sum of the elements in the given list."""
    temp = 0
    for item in lst:
        temp += item
    return temp
    temp += 1  # Error on this line

Duplicate key (W0109)

This error occurs when a dictionary literal sets the same key multiple times.

ex = {
    'runner1': '5km',
    'runner1': '7km'
}
print(ex)  # Prints {'runner1': '7km'}

Dictionaries map unique keys to values. When different values are assigned to the same key, the last assignment takes precedence. This is rarely what the user wants when they are constructing a dictionary.

Duplicate Value (W0130)

This error occurs when a set literal contains the same value two or more times.

incorrect_set = {'value 1', 2, 3, 'value 1'}  # Error on this line
print(incorrect_set)  # Prints {2, 3, 'value 1'}

Corrected version:

correct_set = {'value 1', 2, 3}

Sets are unordered and duplicate elements are not allowed.

Unexpected keyword arg (E1123)

This error occurs when a function call passes a keyword argument which does not match the signature of the function being called.

def print_greeting(name: str) -> None:
    """Print a greeting to the person with the given name."""
    print("Hello {}!".format(name))

print_greeting(first_name="Arthur")  # Error on this line

Corrected version:

print_greeting(name="Arthur")

Type errors

These errors are some of the most common errors we encounter in Python. They generally have to do with using a value of one type where another type is required.

No member (E1101)

This error occurs when we use dot notation (my_var.x) to access an attribute or to call a method which does not exist for the given object. This can happen both for built-in types like str and for classes that we define ourselves. This error often results in an AttributeError when we run the code.

x = 'hello world'
print(x.prop)    # Error: strings don't have a 'prop' attribute
print(x.meth())  # Error: strings don't have a 'meth' method

Not callable (E1102)

This error occurs when we try to call a value which is not a function, method, or callable object. In the following example, we should not call x() because x refers to an integer, and calling an integer has no meaning.

x = 10
print(x())  # Error on this line

Assignment from no return (E1111)

This error occurs when we assign a variable to the return value of a function call, but the function never returns anything. In the following example, add_fruit mutates fruit_basket instead of returning a new list. As a result, new_fruit_basket always gets the value None.

from typing import List

def add_fruit(fruit_basket: List[str], fruit: str) -> None:
    """Add fruit to fruit_basket."""
    fruit_basket.append(fruit)

basket = ['apple', 'apple', 'orange']
new_basket = add_fruit(basket, 'banana')  # Error on this line
print(new_basket)  # Prints `None`

We should either modify add_fruit to return a new list, or call add_fruit without assigning the return value to a variable.

Assignment from None (E1128)

This error occurs when we assign a variable the return value of a function call, but the function always returns None. In the following example, add_fruit always returns None. As a result, new_fruit_basket will always get the value None.

from typing import List

def add_fruit(fruit_basket: List[str], fruit: str) -> None:
    """Add fruit to fruit_basket."""
    fruit_basket.append(fruit)
    return None

basket = ['apple', 'apple', 'orange']
new_basket = add_fruit(basket, 'banana')  # Error on this line
print(new_basket)  # Prints `None`

No value for parameter (E1120)

A function must be called with one argument value per parameter in its header. This error indicates that we called a function with too few arguments. In the following example, there should be three values passed to the function instead of two.

def get_sum(x: int, y: int, z: int) -> int:
    """Return the sum of x, y and z."""
    return x + y + z

get_sum(1, 2)  # Error on this line

Corrected version:

get_sum(1, 2, 3)

Too many function args (E1121)

A function must be called with one argument value per parameter in its header. This error indicates that we called a function with too many arguments. In the following example, there should be two values passed to the function instead of three.

def get_sum(x: int, y: int) -> int:
    """Return the sum of x and y."""
    return x + y

get_sum(1, 2, 3)  # Error on this line

Corrected version:

get_sum(1, 2)

Invalid sequence index (E1126)

This error occurs when a list or tuple is indexed using the square bracket notation my_list[...], but the value of the index is not an integer.

Remember that the index indicates the position of the item in the list/tuple.

a = ['p', 'y', 'T', 'A']
print(a['p'])  # Error on this line

Corrected version:

a = ['p', 'y', 'T', 'A']
print(a[0])

Invalid slice index (E1127)

This error occurs when a list or tuple is sliced using the square bracket notation my_list[... : ...], but the two values on the left and right of the colon are not integers.

Remember that the slice numbers indicate the start and stop positions for the slice in the list/tuple.

a = ['p', 'y', 'T', 'A']
print(a['p': 'A'])   # Error on this line

Corrected version:

a = ['p', 'y', 'T', 'A']
print(a[0:3])

Invalid unary operand type (E1130)

This error occurs when we use a unary operator (+, - , ~) on an object which does not support this operator. For example, a list does not support negation.

print(-[1, 2, 3])  # Error on this line

Unsupported binary operation (E1131)

This error occurs when we use a binary arithmetic operator like + or *, but the left and right sides are not compatible types. For example, a dictionary cannot be added to a list.

a = [1, 2]
b = {'p': 1}
c = a + b  # Error on this line

Unsupported membership test (E1135)

This error occurs when we use the membership test a in b, but the type of b does not support membership tests.

The standard Python types which support membership tests are strings, lists, tuples, and dictionaries.

lst = 1132424
if 'a' in lst:  # Error on this line
    print('unsupported membership test')

Unsubscriptable object (E1136)

This error occurs when we try to index a value using square brackets (a[...]), but the type of a does not support indexing (or “subscripting”).

The standard Python types which support indexing are strings, lists, tuples, and dictionaries.

a = [[1, 2], 5]
print(a[1][0])  # Error on this line

Unsupported assignment operation (E1137)

This error occurs when we assign something to an object which does not support assignment (i.e. an object which does not define the __setitem__ method).

my_number = 1.345
my_number[0] = 2  # Error on this line

my_string = "Hello World!"
my_string[6:] = "Universe!"  # Error on this line

Unsupported delete operation (E1138)

This error occurs when the del keyword is used to delete an item from an object which does not support item deletion (i.e. an object that does not define the __delitem__ special method).

from typing import List

class NamedList:
    """A contaner class for storing a list of named integers."""

    def __init__(self, names: List[str], values: List[int]) -> None:
        self._names = names
        self._values = values

    def __getitem__(self, name: str) -> int:
        idx = self._names.index(name)
        return self._values[idx]

    def __contains__(self, name: str) -> bool:
        return name in self._names


named_list = NamedList(['a', 'b', 'c'], [1, 2, 3])
print('c' in named_list)  # Prints True
del named_list['c']  # Error on this line
print('c' in named_list)

Corrected version:

class NamedList:

    ...  # Same as in the code above

    def __delitem__(self, name: str) -> None:
        idx = self._names.index(name)
        del self._names[idx]
        del self._values[idx]


named_list = NamedList(['a', 'b', 'c'], [1, 2, 3])
print('c' in named_list)  # Prints True
del named_list['c']
print('c' in named_list)  # Prints False

Invalid Slice Step (E1144)

This error occurs when a slice step is 0.

greeting = 'HELLO!'
print(greeting[::0])  # Error on this line

Unbalanced tuple unpacking (E0632)

This error occurs when we are trying to assign to multiple variables at once, but the right side has too few or too many values in the sequence.

from typing import Tuple

def set_values() -> Tuple[int, int]:
    """Return a tuple of two integers."""
    var1 = 1
    var2 = 2
    return var1, var2

# Error on the following line. Cannot unpack 2 items into 3 variables.
one, two, three = set_values()

Unbalanced dict unpacking (W0644)

This error occurs when we are trying to assign dictionary keys or values to multiple variables at once, but the right side has too few or too many values.

"""Examples for W0644 unbalanced-dict-unpacking"""

SCORES = {
    "bob": (1, 1, 3, 2),
    "joe": (4, 3, 1, 2),
    "billy": (2, 2, 2, 2),
}

a, b = SCORES  # Error on this line

for d, e in SCORES.values():  # Error on this line
    print(d)

Corrected version:

SCORES = {
    "bob": (1, 1, 3, 2),
    "joe": (4, 3, 1, 2),
    "billy": (2, 2, 2, 2),
}

a, b, c = SCORES  # unpacking the dictionary keys

for d, e, f in SCORES.values():  # unpacking the dictionary values
    print(d)

Note that when using unpacking with a dictionary on the right-hand side of an =, the variables on the left-hand side gets assigned the keys of the dictionary. For example,

test = {
  "hi": 0,
  "bye": 1,
}

# `a` gets assigned "hi", `b` gets assigned "bye"
a, b = test

Unpacking non-sequence (E0633)

This error occurs when we are trying to assign to multiple variables at once, but the right side is not a sequence, and so can’t be unpacked.

one, two = 15   # Cannot unpack one thing into two things

Not an iterable (E1133)

This error occurs when a non-iterable value is used in a place where an iterable is expected. An iterable is an object capable of returning its members one at a time. Examples of iterables include sequence types such as list, str, and tuple, some non-sequence types such as dict, and instances of other classes which define the __iter__ or __getitem__ special methods.

for number in 123:  # Error on this line
    print(number)

Corrected version:

for number in [1, 2, 3]:
    print(number)

Not a mapping (E1134)

This error occurs when a non-mapping value is used in a place where mapping is expected. This is a result of unpacking a non-dict with ** in a function call meaning that the parameters are unfilled.

** can only be used on a dict to unpack the values.

def func(a: int, b: float) -> None:
    pass


def call_func() -> None:
    a = 1
    func(**{'a': 10, 'b': 15.2})  # This works
    func(**a)  # Error on this line: non-mapping value 'a' used in a mapping context

Code complexity

Unnecessary negation (C0117)

This error occurs when a boolean expression contains an nnecessary negation. If we are getting this error, the expression can be simplified to not use a negation.

number = 5
if not number >= 0:  # Error on this line
    number_category = 'negative'
else:
    number_category = 'non-negative'

The above can be modified to:

number = 5
if number < 0:
    number_category = 'negative'
else:
    number_category = 'non-negative'

Consider using f-string (C0209)

This error occurs when a string is formatted with % or format(). The preferred way to include Python values in strings is with f-strings.

name = "Bob"
print("Hi! My name is %s!" % name)  # Error on this line
print("{0} is my name!".format(name))  # Error on this line

The above can be changed to:

name = "Bob"
print(f"Hi! My name is {name}!")
print(f"{name} is my name!")

Simplifiable condition (R1726)

This error occurs when a boolean test condition can be simplified. Test conditions are expressions evaluated inside of statements such as if, while, or assert.

if True and a:  # Error on this line
    pass

The above can be modified to:

if a:  # Error was on this line
  pass

Condition evals to constant (R1727)

This error occurs when a boolean test condition always evaluates to a constant. Test conditions are expressions evaluated inside of statements such as if, while, or assert.

if a or True:  # Error on this line
    pass

The above can be modified to:

if True:  # Error was on this line
  pass

Singleton comparison (C0121)

This error occurs when an expression is compared to a singleton value like True, False or None .

Here is an example involving a comparison to None:

from typing import Optional

def square(number: Optional[float]) -> Optional[float]:
    """Return the square of the number."""
    if number == None:  # Error on this line
        return None
    else:
        return number**2

The above can be modified to:

def square(number: Optional[float]) -> Optional[float]:
    """Return the square of the number."""
    if number is None:
        return None
    else:
        return number ** 2

On the other hand, if you are comparing a boolean value to True or False, you can actually omit the comparison entirely:

# Bad
def square_if_even(number: int) -> int:
    if (number % 2 == 0) == True:
        return number ** 2
    else:
        return number


# Good
def square_if_even(number: int) -> int:
    if number % 2 == 0:
        return number ** 2
    else:
        return number

See also:

Using constant test (W0125)

This error occurs when a conditional statement (like an if statement) uses a constant value for its test. In such a case, a conditional statement is not necessary, as it will always result in the same path of execution.

def square(number: float) -> float:
    """Return the square of the number."""
    if True:
        return number**2
    return number**3  # This line will never be executed

Redeclared Assigned Name (W0128)

This error occurs when a variable is redeclared on the same line it was assigned.

x, x = 1, 2   # Error on this line

Too many branches (R0912)

The function or method has too many branches, making it hard to follow. This is a sign that the function/method is too complex, and should be split up.

Note: The checker limit is 12 branches.

def lots_of_branches(arg: bool) -> None:
    """Example to demonstrate max branching."""
    if arg == 1:
        pass
    elif arg == 2:
        pass
    elif arg == 3:
        pass
    elif arg == 4:
        pass
    elif arg == 5:
        pass
    elif arg == 6:
        pass
    elif arg == 7:
        pass
    elif arg == 8:
        pass
    elif arg == 9:
        pass
    elif arg == 10:
        pass
    elif arg == 11:
        pass
    elif arg == 12:
        pass
    elif arg == 13:
        pass

Too many nested blocks (R1702)

This error occurs when we have more than three levels of nested blocks in our code. Deep nesting is a sign that our function or method is too complex, and should be broken down using helper functions or rewritten as a list comprehension.

Note: This checker does not count function, method, or class definitions as blocks, so the example below is considered to have six nested blocks, not seven.

"""Example for too many nested blocks"""
from typing import List, Tuple, Optional

def cross_join(x_list: List[Optional[int]], y_list: List[Optional[int]],
               z_list: List[Optional[int]]) -> List[Tuple[int, int, int]]:
    """Perform an all-by-all join of all elements in the input lists.

    Note: This function skips elements which are None.
    """
    cross_join_list = []
    for x in x_list:  # Error on this line: "Too many nested blocks"
        if x is not None:
            for y in y_list:
                if y is not None:
                    for z in z_list:
                        if z is not None:
                            cross_join_list.append((x, y, z))
    return cross_join_list

The code above can be fixed using a helper function:

def drop_none(lst: List[Optional[int]]) -> List[int]:
    """Return a copy of `lst` with all `None` elements removed."""
    new_lst = []
    for element in lst:
        if element is not None:
            new_lst.append(element)
    return new_lst


def cross_join(x_list: List[Optional[int]], y_list: List[Optional[int]],
               z_list: List[Optional[int]]) -> List[Tuple[int, int, int]]:
    """Perform an all-by-all join of all elements in the input lists."""
    cross_join_list = []
    for x in drop_none(x_list):
        for y in drop_none(y_list):
            for z in drop_none(z_list):
                cross_join_list.append((x, y, z))
    return cross_join_list

or using list comprehension:

def cross_join(x_list: List[Optional[int]], y_list: List[Optional[int]],
               z_list: List[Optional[int]]) -> List[Tuple[int, int, int]]:
    """Perform an all-by-all join of all elements in the input lists."""
    cross_join_list = [
        (x, y, z)
        for x in x_list
        if x is not None
        for y in y_list
        if y is not None
        for z in z_list
        if z is not None
    ]
    return cross_join_list

Too many lines (C0302)

This error occurs when the file has too many lines. The limit for too many lines is specified through the max-module-lines configuration option.

Note: The default value is 1000.

Too many arguments (R0913)

The function or method is defined with too many arguments. This is a sign that the function/method is too complex, and should be split up, or that some of the arguments are related, and should be combined and passed as a single object.

Note: The checker limit is 5 arguments.

def foo_bar(arg1: int, arg2: int, arg3: int, arg4: int, arg5: int,
            arg6: int) -> None:
    """I have too many arguments."""
    pass

Too many locals (R0914)

The function or method has too many local variables.

Note: The checker limit is 15 local variables.

def too_many_locals() -> None:
    """Example function that has to many local variables."""
    local_variable_1 = 1
    local_variable_2 = 2
    local_variable_3 = 3
    local_variable_4 = 4
    local_variable_5 = 5
    local_variable_6 = 6
    local_variable_7 = 7
    local_variable_8 = 8
    local_variable_9 = 9
    local_variable_10 = 10
    local_variable_11 = 11
    local_variable_12 = 12
    local_variable_13 = 13
    local_variable_14 = 14
    local_variable_15 = 15
    local_variable_16 = 16

Too many statements (R0915)

The function or method has too many statements. We should split it into smaller functions/methods.

Note:

  • The checker limit is 50 statements.

  • Comments do not count as statements.

from typing import Any

def statement(arg: Any) -> None:
    """Dummy function to demonstrate an example within `too_many_statements`."""
    pass


def too_many_statements(arg: bool) -> None:
    """There are too many statements in this function."""
    statement_1 = 1
    statement_2 = 2
    statement_3 = 3
    statement_4 = 4
    statement_5 = 5
    statement_6 = 6
    statement_7 = 7
    statement_8 = 8
    statement_9 = 9
    statement_10 = 10
    statement_11 = 11
    statement_12 = 12
    statement_13 = 13
    statement_14 = 14
    statement_15 = 15
    statement_16 = 16
    statement_17 = 17
    statement_18 = 18
    statement_19 = 19
    statement_20 = 20
    statement_21 = 21
    statement_22 = 22
    statement_23 = 23
    statement_24 = 24
    statement_25 = 25
    statement_26 = 26
    statement_27 = 27
    statement_28 = 28
    statement_29 = 29
    statement_30 = 30
    statement_31 = 31
    statement_32 = 32
    statement_33 = 33
    statement_34 = 34
    statement_35 = 35
    statement_36 = 36
    statement_37 = 37
    statement_38 = 38
    statement_39 = 39
    statement_40 = 40

    statement('function calls are statements too')
    statement('function calls are statements too')
    statement('function calls are statements too')
    statement('function calls are statements too')
    statement('function calls are statements too')
    statement('function calls are statements too')
    statement('function calls are statements too')
    statement('function calls are statements too')

    if arg:
        statments = 'this block (including condition) counts as 2 statements.'

Unused variable (W0612)

This error occurs when we have a defined variable that is never used.

def square(number: float) -> float:
    """Return the square of the number."""
    exponent = 2  # Unused variable 'exponent'
    return number ** 2

Unused argument (W0613)

This error occurs when a function argument is never used in the function.

def add(x: float, y: float, z: float) -> float:  # Unused argument 'z'
    """Return the sum of <x> and <y>."""
    return x + y

Pointless statement (W0104)

This error occurs when a statement does not have any effect. This means that the statement could be removed without changing the behaviour of the program.

from typing import List

def add(lst: List[int]) -> int:
    """Calculate the sum of the elements in the given list."""
    temp = 0
    for item in lst:
        temp += item
    temp  # Error on this line

Pointless string statement (W0105)

This error occurs when a string statement does not have any effect. Very similar to error W0104, but for strings.

def say_hi() -> None:
    """Prints 'hi' to the console."""
    message = "hi"
    "say hi"  # Error on this line
    print(message)

Unnecessary dict index lookup (R1733)

This error occurs when values for a dictionary are accessed using an index lookup (that is, through dictionary_name[key]) instead of directly while iterating over key-value pairs of the dictionary.

sample_dict = {"key_one": "value_one", "key_two": "value_two"}

for key, value in sample_dict.items():
    print(key, sample_dict[key])  # Error on this line

The code above can be fixed by accessing the dictionary values directly:

sample_dict = {"key_one": "value_one", "key_two": "value_two"}

for key, value in sample_dict.items():
    print(key, value)  # Direct access instead of an index lookup

Unnecessary list index lookup (R1736)

This error occurs when iterating through a list while keeping track of both index and value using enumerate() but still using the index to access the value (which can be accessed directly).

colours = ['red', 'blue', 'yellow', 'green']

for index, colour in enumerate(colours):
    print(index)
    print(colours[index])  # Error on this line

Corrected version:

colours = ['red', 'blue', 'yellow', 'green']

for index, colour in enumerate(colours):
    print(index)
    print(colour)

Unnecessary pass (W0107)

This error occurs when a pass statement is used that can be avoided (or has no effect). pass statements should only be used to fill what would otherwise be an empty code block, since code blocks cannot be empty in Python.

from typing import List

def add(lst: List[int]) -> int:
    """Calculate the sum of the elements in the given list."""
    temp = 0
    for item in lst:
        temp += item
        pass  # Error on this line
    return temp

In the above example, the pass statement is “unnecessary” as the program’s effect is not changed if pass is removed.

See also:

Unnecessary ellipsis (W2301)

This error occurs when a docstring is the preceding line of an ellipsis or if there is a statement in the same scope as an ellipsis. An ellipsis should only be used as a “placeholder” to fill in a block of code that requires at least one statement.

def my_func() -> None:
    """Test Doctest"""
    ...  # Error on this line
    if True:
        ...  # Error on this line
        return None

Corrected version:

def my_func() -> None:
    """Test Doctest"""
    if True:
        ...

Inconsistent return statements (R1710)

This error occurs when you have a function that sometimes returns a non-None value and sometimes _ implicitly_ returns None. This is an issue because in Python, we prefer making code explicit rather than implicit.

import math
from typing import List, Optional


def add_sqrts(x: float, y: float) -> Optional[float]:
    """Return the sum of the square roots of x and y, or None if
    either number is negative."""
    if x >= 0 and y >= 0:
        return math.sqrt(x) + math.sqrt(y)
    else:
        return  # Error: this should be `return None` instead.


def index_of(numbers: List[int], n: int) -> Optional[int]:
    """Return the index of the first occurrence of n in numbers,
    or None if n doesn't appear in the list.
    """
    i = 0
    for number in numbers:
        if number == n:
            return i
        i += 1


def day_name_to_number(day: str) -> int:
    """Return a number between 0-6 representing the given day of the week."""
    if day == 'Monday':
        return 0
    elif day == 'Tuesday':
        return 1
    elif day == 'Wednesday':
        return 2
    elif day == 'Thursday':
        return 3
    elif day == 'Friday':
        return 4
    elif day == 'Saturday':
        return 5
    elif day == 'Sunday':
        return 6

In add_sqrts, we should change return into return None to make better contrast the return value with the other branch. In the other two functions, it’s possible that none of the return statements will execute, and so the end of the function body will be reached, causing a None to be returned implicitly. (Forgetting about this behaviour actually is a common source of bugs in student code!) In both cases, you can fix the problem by adding an explicit return None to the end of the function body.

In CSC148, you may sometimes choose resolve this error by instead raising an error rather than returning None.

Consider using with (R1732)

This error occurs when a resource allocating operation such as opening a file can be replaced by a with block. By using with, the file is closed automatically which saves resources.

file = open('my_file.txt', 'r')  # Error on this line: need to manually close file
file.close()

Corrected version:

with open('my_file.txt', 'r') as file:
    ... # No need to manually close the file

Use list literal (R1734)

This error occurs when list() is used instead of [] to create an empty list.

lst = [1, 2, 3, 4]
even_lst = list()  # Error on this line.

for x in lst:
    if x % 2 == 0:
        even_lst.append(x)

The above can be modified to:

lst = [1, 2, 3, 4]
even_lst = []  # This is a fixed version.

for x in lst:
    if x % 2 == 0:
        even_lst.append(x)

Use dict literal (R1735)

This error occurs when dict() is used instead of {} to create an empty dictionary.

students_info = [[1002123, "Alex H", "CS"], [1001115, "Jack K", "PSY"]]

cs_student_dict = dict()  # Error on this line.

for student in students_info:
    if student[2] == "CS":
        cs_student_dict[student[0]] = student[1]

Corrected version:

students_info = [[1002123, "Alex H", "CS"], [1001115, "Jack K", "PSY"]]

cs_student_dict = {}  # This is a fixed version.

for student in students_info:
    if student[2] == "CS":
        cs_student_dict[student[0]] = student[1]

Nested min-max (W3301)

This error occurs when there are nested calls of min or max instead of using a single min/max call.

Note that using a single min/max call is perfectly fine since you can pass in an arbitrary number of arguments.

"""Examples for W3301 nested-min-max"""

smallest = min(12, min(1, 2))

largest = max(12, max(1, 2))

Corrected version:

smallest = min(12, 1, 2)

largest = max(12, 1, 2)

Documentation and naming

Good documentation and identifiers are essential for writing software. PyTA helps check to make sure we haven’t forgotten to document anything, as well as a basic check on the formatting of our identifiers.

Empty Docstring (C0112)

This error occurs when a module, function, class or method has an empty docstring.

def is_false(obj: bool) -> bool:
    """
    """
    return obj is False

Invalid name (C0103)

This error occurs when a name does not follow the Python Naming Convention associated with its role (constant, variable, etc.).

  • Names of variables, attributes, methods, and arguments should be in lowercase_with_underscores.

  • Names of constants should be in ALL_CAPS_WITH_UNDERSCORES.

  • Names of classes should be in CamelCase.

A special character accepted in all types of names is _. Numbers are allowed in all names, but names must not begin with a number.

def is_positive(number: int) -> bool:
    """Check if number is positive."""
    Result = number > 0  # Error on this line: Invalid name 'Result'
    return Result

Disallowed name (C0104)

This error occurs when a variable name is chosen to be a typical generic name, rather than a meaningful one. Here are some of the disallowed names to avoid:

  • foo

  • bar

  • baz

  • toto

  • tutu

  • tata

def is_positive(number: int) -> bool:
    """Check if number is positive."""
    foo = number > 0  # Error on this line: Blacklisted name 'foo'
    return foo

Function redefined (E0102)

This error occurs when a function, class or method is redefined. If we are getting this error, we should make sure all the functions, methods and classes that we define have different names.

def is_positive(number: int) -> bool:
    """Check if number is positive."""
    return number > 0


def is_positive(number: int) -> bool:  # Error on this line: Function redefined
    """Check if number is positive."""
    return number >= 0

Duplicate argument name (E0108)

This error occurs if there are duplicate parameter names in function definitions. All parameters must have distinct names, so that we can refer to each one separately in the function body.

from typing import List

def add(lst: List[int], lst: List[int]) -> int:  # Error on this line
    """Calculate the sum of the elements in the given list."""
    temp = 0
    for item in lst:
        temp += item
    return temp

Redefined argument from local (R1704)

This error occurs when a local name is redefining the name of a parameter.

def greet_person(name, friends) -> None:
    """Print the name of a person and all their friends."""
    print("My name is {}".format(name))
    for name in friends:  # Error on this line
        print("I am friends with {}".format(name))

Corrected version:

def greet_person(name, friends) -> None:
    """Print the name of a person and all their friends."""
    print("My name is {}".format(name))
    for friend in friends:
        print("I am friends with {}".format(friend))

See also: Redefined outer name (W0621)

Redefined outer name (W0621)

This error occurs when we are redefining a variable name that has already been defined in the outer scope.

For example, this error will occur when we have a local name identical to a global name. The local name takes precedence, but it hides the global name, making it no longer accessible. Note that the global name is not accessible anywhere in the function where it is redefined, even before the redefinition.

file_data = None  # 'file_data' defined here in the outer scope

def read_file(filename) -> str:
    """Read the contents of a file."""
    with open(filename) as fh:
        file_data = fh.read()  # Redefining name 'file_data' that has already been
    return file_data           # defined in the outer scope.

Redefined builtin (W0622)

This error occurs when we are redefining a built-in function, constant, class, or exception.

id = 100  # Error on this line: Redefining built-in 'id'

def sum(a: float, b: float) -> float:  # Error on this line: Redefining built-in 'sum'
    return a - b  # D'oh

The following is a list of builtin functions in Python 3.6.

abs                 all                 any                 ascii               bin
bool                bytearray           bytes               callable            chr
classmethod         compile             complex             copyright           credits
delattr             dict                dir                 divmod              dreload
enumerate           eval                exec                filter              float
format              frozenset           get_ipython         getattr             globals
hasattr             hash                help                hex                 id
input               int                 isinstance          issubclass          iter
len                 license             list                locals              map
max                 memoryview          min                 next                object
oct                 open                ord                 pow                 print
property            range               repr                reversed            round
set                 setattr             slice               sorted              staticmethod
str                 sum                 super               tuple               type
vars                zip

Naming convention violation (C9103)

This error aims to provide a more detailed and beginner-friendly error message about how a variable name violated Python naming conventions.

Again, refer to the Python Naming Conventions for further details but as a quick reminder:

  • Avoid using the single character variable names l, O, or I as they are indistinguishable from the numbers zero and one.

  • Names of variables, attributes, methods, and arguments should be in snake_case.

  • Names of constants should be in UPPER_CASE_WITH_UNDERSCORES.

  • Names of classes, exceptions, type variables and type aliases should be in PascalCase.

  • Numbers are allowed in names but names cannot start with a number.

  • Names should not exceed 30 characters, with the exception of type variables and type aliases which have a limit of 20.

And a quick reminder on the previously mentioned types of naming conventions:

  • snake_case: only lowercase words with each word separated by an underscore “_”.

  • UPPER_CASE_WITH_UNDERSCORES only uppercase words with each word separated by an underscore “_”.

  • PascalCase: capitalize the first letter of each word with no separation between each word.

"""Examples for C9103 naming-convention-violation."""

not_upper_for_const = "CONSTANT"  # naming-convention-violation


def NotSnakeCase():  # naming-convention-violation
    pass


class not_pascal_case:  # naming-convention-violation
    pass

Note: this checker is intended to replace C0103 (invalid-name).

Module name violation (C9104)

This error occurs when the name of a module violates Python naming conventions. The names of modules should be in snake_case and should not exceed 30 characters.

Imports

There are standards governing how we should organize our imports, or even possibly which modules we may import at all.

Forbidden imports (E9999)

This error occurs when your code imports a module which is not allowed (usually for the purpose of an assignment/exercise).

import copy   # Error on this line
from sys import path  # Error on this line
import python_ta  # No error

PythonTA allows you to specify all the modules that you wish to allow for a particular file using the allowed-import-modules configuration option:

import python_ta
python_ta.check_all(..., config={'allowed-import-modules': ["random"]})

You can specify any additional modules you want to allow for import using the extra-imports configuration option:

import python_ta
python_ta.check_all(..., config={'extra-imports': ["math", "tkinter"]})

You can also use a configuration file to specify both the allowed-import-modules and extra-imports.

[FORBIDDEN IMPORT]
allowed-import-modules = random
extra-imports = math, tkinter

Import error (E0401)

The module is unable to be imported. Check the spelling of the module name, or whether the module is in the correct directory.

import missing_module  # This module does not exist

There are other forms of import statements that may cause this error. For example:

import missing_module as foo  # This module does not exist

No name in module (E0611)

This error occurs when we are trying to access a variable from an imported module, but that variable name could not be found in that referenced module.

from math import does_not_exist

Wildcard import (W0401)

We should only import what we need. Wildcard imports (shown below) are generally discouraged, as they add all objects from the imported module into the global namespace. This makes it difficult to tell in which module a particular class, function or constant is defined, and may cause problems, for example, when multiple modules have objects with identical names.

from valid_module import *

Rather than importing everything with wildcard *, we should specify the names of the objects which we would like to import:

from module_name import SOME_CONSTANT, SomeClass, some_function

Or, if we need to import many objects from a particular module, we can import the module itself, and use it as a namespace for the required objects:

import module_name

c = module_name.SomeClass()

Reimported (W0404)

A module should not be imported more than once.

import math
import math  # Importing a module twice

Import self (W0406)

A module should not import itself. For example, if we have a module named W0406_import_self, it should not import a module with the same name.

import w0406_import_self  # Importing a module from within a module with
                          # the same name

This error can occur when the name of our Python file conflicts with the name of a module which we would like to import. For example, if we have a Python file named math.py, calling import math from within that file (or from within any Python file in the same directory) will import _ our_ math.py file, and not the math module from the standard library.

Shadowed import (W0416)

This error occurs when a module is imported with an aliased name that has already been used by a previous import. This prevents the original module from ever being used later in your code.

import math
import tkinter as math  # Error on this line

Cyclic import (R0401)

A module should not import a file which results in an import of the original module.

Example File 1

from cyclic_import_helper import Chicken


class Egg:
    """ Which came first? """
    offspring: Chicken

Example File 2

from r0401_cyclic_import import Egg


class Chicken:
    """ Which came first? """
    eggs: list[Egg]

Consider using from import (R0402)

Some imports are long and go through multiple layers of packages or modules. It’s common to want to rename these imports as the last imported module or package using the as keyword. Consider using the from import syntax instead.

import python_ta.contracts as contracts  # Error on this line

Corrected version:

from python_ta import contracts

Multiple imports (C0410)

Different modules should not be imported on a single line.

import sys, math

Rather, each module should be imported on a separate line.

import sys
import math

Note, however, that we can import multiple functions, classes, or constants on one line, as long as they are from the same module.

from shutil import copy, SameFileError

Wrong import order (C0411)

This error occurs when the PEP8 import order is not respected. We should do standard library imports first, then third-party libraries, then local imports.

from assignment_1 import solution  # Your own modules should be imported last
import sys  # "standard modules" should be imported first

Ungrouped imports (C0412)

Imports should be grouped by package.

from sys import byteorder  # Same packages should be grouped
from math import floor
from sys import stdin  # Same packages should be grouped

Corrected version:

from sys import byteorder, stdin  # Same packages should be grouped
from math import floor

Wrong import position (C0413)

Imports should be placed at the top of the module, above any other code, but below the module docstring.

my_list = ['a', 'b']
import math  # Imports should be at the top (below the docstring)

Import outside toplevel (C0415)

Imports should be placed at the top-level of the module, not inside function or class bodies.

def import_module():
    import something  # Error on this line

Unused import (W0611)

This error occurs when we import a module which is not used anywhere in our code.

import re  # Module imported, but not used

Classes and objects

Too many instance attributes (R0902)

The class has too many instance attributes, which suggests that it is too complicated and tries to do too many things.

Note: The checker limit is 7 instance attributes.

class MyClass(object):
    """Class with too many instance attributes."""

    def __init__(self) -> None:
        self.animal = 'Dog'
        self.bread = 'Sourdough'
        self.liquid = 'Water'
        self.colour = 'Black'
        self.shape = 'Circle'
        self.direction = 'Up'
        self.clothing = 'Shirt'
        self.number = 3

One solution is to logically decompose the class into multiple classes, each with fewer instance attributes. We can then use composition to access those attributes in a different class.

class Edible(object):
    """Class with a few instance attributes."""

    def __init__(self) -> None:
        self.bread = "Sourdough"
        self.liquid = "Water"


class Ownership(object):
    """Class with a few instance attributes."""

    def __init__(self) -> None:
        self.animal = "Dog"
        self.clothing = "Shirt"


class Description(object):
    """Class with a few instance attributes."""

    def __init__(self) -> None:
        self.colour = "Black"
        self.shape = "Circle"
        self.direction = "Up"
        self.number = 3


class Composition(object):
    """Class using composition to leverage other classes."""

    def __init__(self) -> None:
        self.edible = Edible()
        self.ownership = Ownership()
        self.description = Description()

See also: R0914

Abstract method (W0223)

This error occurs when an abstract method (i.e. a method with a raise NotImplementedError statement) is not overridden inside a subclass of the abstract class.

class Animal:
    """Abstract class to be implemented by all animals."""
    name: str

    def __init__(self, name: str) -> None:
        self.name = name

    def make_sound(self) -> str:
        raise NotImplementedError


class Cat(Animal):  # Error: Method 'make_sound' is not overridden
    """A worthy companion."""
    pass

Corrected version:

class Cat(Animal):
    """A worthy companion."""

    def make_sound(self) -> str:
        return 'Miew...'

Arguments differ (W0221)

This error occurs when a method takes a different number of arguments than the interface that it implements or the method that it overrides.

class Animal:
    """Abstract class to be implemented by all animals."""
    _name: str

    def __init__(self, name: str) -> None:
        self._name = name

    def make_sound(self, mood: str) -> None:
        """Print a sound that the animal would make in a given mood."""
        raise NotImplementedError


class Dog(Animal):
    """A man's best friend."""

    def make_sound(self, mood: str, state: str) -> None:  # Error: Parameter differs
        if mood == 'happy':
            print("Woof Woof!")
        elif state == 'angry':
            print("Grrrrrrr!!")

Corrected version:

class Dog(Animal):
    """A man's best friend."""

    def make_sound(self, mood: str) -> None:
        if mood == 'happy':
            print("Woof Woof!")
        elif mood == 'angry':
            print("Grrrrrrr!!")

Different method signature (W0222)

When a child class overrides a method of the parent class, the new method should have the same signature as the method which it is overriding. In other words, the names and the order of the parameters should be the same in the two methods. Furthermore, if a parameter in the parent method has a default argument, it must also have a default argument in the child method.

class StandardBankAccount:
    """A standard bank account."""

    def __init__(self, balance: float) -> None:
        self._balance = balance

    def withdraw(self, amount: float = 20) -> float:
        """Withdraw money from the bank account."""
        if amount <= self._balance:
            self._balance -= amount
            return amount
        else:
            return 0


class PremiumBankAccount(StandardBankAccount):
    """A premium bank account.

    This bank account has more features than the standard bank account,
    but it also costs more.
    """

    def withdraw(self, amount: float) -> float:  # Error on this line
        """Withdraw money from the bank account."""
        if amount <= self._balance - 2:
            # Charge a $2 transaction fee
            self._balance -= 2
            self._balance -= amount
            return amount
        else:
            return 0

Corrected version:

class PremiumBankAccount(StandardBankAccount):
    ...

    def withdraw(self, amount: float = 200) -> float:  # Note the default argument
        ...

Return in __init__ (E0101)

This error occurs when the __init__ method contains a return statement.

The purpose of the __init__ method is to initialize the attributes of an object. __init__ is called by the special method __new__ when a new object is being instantiated, and __new__ will raise a TypeError if __init__ returns anything other than None.

class Animal:
    """A carbon-based life form that eats and moves around."""
    _name: str

    def __init__(self, name: str) -> None:
        self._name = name
        return True  # Error on this line

Protected member access (W0212)

Attributes and methods whose name starts with an underscore should be considered “private” and should not be accessed outside of the class in which they are defined.

class Animal:
    """A carbon-based life form that eats and moves around."""
    _name: str

    def __init__(self, name: str) -> None:
        self._name = name


dog = Animal('Charly')
print(dog._name)  # Error on this line: Access of protected member `dog._name`

Private attributes and methods can be modified, added, or removed by the maintainer of the class at any time, which makes external code which uses those attributes or methods fragile. Furthermore, modifying a private attribute or calling a private method may lead to undefined behavior from the class.

Bad parent init (W0233)

When using inheritance, we should call the __init__ method of the parent class and not of some unrelated class.

class ClassA:
    """An unrelated class."""

    def __init__(self) -> None:
        pass

class Parent:
    """A parent class."""

    def __init__(self) -> None:
        pass

class Child(Parent):
    """A child class."""

    def __init__(self) -> None:
        ClassA.__init__(self)  # `ClassA` is not a parent of `Child`

To fix this, call the __init__ method of the parent class.

class Child(Parent):
    """A child class."""

    def __init__(self) -> None:
        Parent.__init__(self)

Another option is to use super().

class Child(Parent):
    """A child class."""

    def __init__(self) -> None:
        super().__init__()

See also:

Super without brackets (W0245)

When making a call to a parent class using super(), we must always include the brackets since it is a type of function call. Without the brackets, Python may interpret it as the super function itself rather than calling the function to access the superclass.

class Animal:
    """A class that represents an animal"""
    def __init__(self) -> None:
        print('This is an animal')


class Cat(Animal):
    """A class that represents a cat"""
    def __init__(self) -> None:
        super.__init__() # Error on this line, no brackets following super call
        print('This is a cat')

Corrected version:

class Animal:
    """A class that represents an animal"""
    def __init__(self) -> None:
        print('This is an animal')


class Cat(Animal):
    """A class that represents a cat"""
    def __init__(self) -> None:
        super().__init__()
        print('This is a cat')

Super with arguments (R1725)

This error occurs when calling super() with the class and instance as these can be ommited from Python 3.

class DummyClass:
    def __init__(self):
        super(DummyClass, self).__init__()  # Error on this line

Corrected Version:

class DummyClass:
    def __init__(self):
        super().__init__()  # Error was on this line

Attribute defined outside init (W0201)

Any attribute we define for a class should be created inside the __init__ method. Defining it outside this method is considered bad practice, as it makes it harder to keep track of what attributes the class actually has.

class SomeNumbers:
    """A class to store some numbers."""
    num: int

    def __init__(self) -> None:
        self.num = 1

    def set_other_num(self, other_num: int) -> None:
        self.other_num = other_num

We should do this instead:

class SomeNumbers:
    """A class to store some numbers."""

    def __init__(self) -> None:
        self.num = 1
        self.other_num = None

    def set_other_num(self, other_num: int) -> None:
        self.other_num = other_num

Method hidden (E0202)

If we accidentally hide a method with an attribute, it can cause other code to attempt to invoke what it believes to be a method, which will fail since it has become an attribute instead. This will cause the program to raise an error.

class Person:
    """Generic person with a name and a hobby."""
    name: str
    hobby: str

    def __init__(self, name: str, hobby: str) -> None:
        self.name = name
        self.hobby = hobby

    def hobby(self) -> str:  # Error on this line
        return "No hobbies; I just work and study!"

Access to member before definition (E0203)

Before trying to use a member of a class, it should have been defined at some point. If we try to use it before assigning to it, an error will occur.

class Animal:
    """A carbon-based life form that eats and moves around."""

    def __init__(self, name: str) -> None:
        print(self._name)  # Haven't defined `self._name` yet, can't use
        self._name = name

Unexpected special method signature (E0302)

This error occurs when a special method (also known as a “dunder method”, because it has double underscores or “ dunders” on both sides) does not have the expected number of parameters. Special methods have an expected signature, and if we create a method with the same name and a different number of parameters, it can break existing code and lead to errors.

class Animal:
    """A carbon-based life form that eats and moves around."""
    _name: str

    def __init__(self, name: str) -> None:
        self._name = name

    def __str__(self, unexpected_argument: str) -> str:  # Error on this line
        return unexpected_argument

Corrected version:

class Animal:
    """A carbon-based life form that eats and moves around."""
    _name: str

    def __init__(self, name: str) -> None:
        self._name = name

    def __str__(self) -> str:
        return '<Animal({})>'.format(self._name)

Inheriting from a non-class (E0239)

A new class can only inherit from a different class (i.e. a Python object which defines the type of an object). It cannot inherit from an instance of a class or from a Python literal such as a string, list, or dictionary literal.

class FancyFloat('float'):  # Error on this line
    """A fancy floating point number."""
    pass

Corrected version:

class FancyFloat(float):
    """A fancy floating point number."""
    pass

Duplicate bases (E0241)

A class should not inherit from a different class multiple times.

class Animal:
    """A carbon-based life form that eats and moves around."""
    pass

class Dog(Animal, Animal):  # Only include Animal once to inherit properly
    """A man's best friend."""
    pass

No method argument (E0211)

Each method in a class needs to have at least one parameter, which by convention we name self. When we create an instance of a class and call an instance method, Python automatically passes the class instance as the first argument to the method. If a method does not expect any arguments, this will result in an error.

class Saxophone:
    """A jazzy musical instrument."""
    _sound: str

    def __init__(self) -> None:
        self._sound = "Saxamaphone...."

    def make_sound() -> None:  # Error on this line
        print("Don't know what sound I can make!")

Corrected version:

class Saxophone:
    """A jazzy musical instrument."""

    def __init__(self) -> None:
        self._sound = "Saxamaphone...."

    def make_sound(self) -> None:
        print(self._sound)

self as the first argument (E0213)

The first parameter of a method should always be called self. While it is possible to name the first parameter something else, using the word self is a convention that is strongly adhered to by the Python community and makes it clear that we did not simply forget to add self or accidentally intended a function as a method.

class SecretKeeper:
    """A class which stores a secret as a private attribute."""
    _secret: str

    def __init__(self, secret: str) -> None:
        self._secret = secret

    def guess_secret(obj, secret) -> bool:  # Error: 'obj' should be 'self'
        """Guess the private secret."""
        return obj._secret == secret

Corrected version:

class SecretKeeper:
    """A class which stores a secret as a private attribute."""

    def __init__(self, secret: str) -> None:
        self._secret = secret

    def guess_secret(self, secret) -> bool:
        """Guess the private secret."""
        return self._secret == secret

No self use (R0201)

If a method does not make use of the first argument self, it means that the task that the method is performing is not linked to the class of which it is a member. In such a case, we should rewrite the method as a function (by removing the first parameter self) and move it outside the class.

In the following example, add_small_coins does not make use of the first parameter self and so can be moved outside the class as a function.

class CashRegister:
    """A cash register for storing money and making change."""
    _current_balance: float

    def __init__(self, balance: float) -> None:
        self._current_balance = balance

    def add_small_coins(self, nickels: int = 0, dimes: int = 0, quarters: int = 0) -> float:
        """Return the dollar value of the small coins."""
        return 0.05 * nickels + 0.10 * dimes + 0.25 * quarters

Corrected version:

class CashRegister:
    """A cash register for storing money and making change."""
    _current_balance: float

    def __init__(self, balance: float) -> None:
        self._current_balance = balance


def add_small_coins(nickels: int = 0, dimes: int = 0, quarters: int = 0) -> float:
    """Return the dollar value of the small coins."""
    return 0.05 * nickels + 0.10 * dimes + 0.25 * quarters

See also:

Bad static method argument (W0211)

This error occurs when a static method has self as the first parameter. Static methods are methods that do not operate on instances. If we feel that the logic of a particular function belongs inside a class, we can move that function into the class and add a @staticmethod decorator to signal that the method is a static method which does not take a class instance as the first argument. If such a static method contains self as the first parameter, it suggests that we are erroneously expecting a class instance as the first argument to the method.

class CashRegister:
    """A cash register for storing money and making change."""

    def __init__(self, balance: float) -> None:
        self._current_balance = balance

    @staticmethod
    # Error on the following line: Static method with 'self' as first argument
    def add_small_coins(self, nickels: int = 0, dimes: int = 0, quarters: int = 0):
        """Return the dollar value of the small coins."""
        return 0.05 * nickels + 0.10 * dimes + 0.25 * quarters

Corrected version:

class CashRegister:
    """A cash register for storing money and making change."""
    _current_balance: float

    def __init__(self, balance: float) -> None:
        self._current_balance = balance

    @staticmethod
    def add_small_coins(nickels: int = 0, dimes: int = 0, quarters: int = 0) -> float:
        """Return the dollar value of the small coins."""
        return 0.05 * nickels + 0.10 * dimes + 0.25 * quarters

See also:

Exceptions

Pointless exception statement (W0133)

This error occurs when an exception is created but never assigned, raised or returned for use anywhere in the code.

"""Pointless Exception Statement Example"""
# The exception below is not raised, assigned, nor returned for use anywhere in this file.
# Note: ValueError is a subclass of Exception (it is a more specific type of exception in Python).


def reciprocal(num: float) -> float:
    """Return 1 / num."""
    if num == 0:
        ValueError('num cannot be 0!')  # Error on this line
    else:
        return 1 / num

This error can be resolved by assigning, raising or returning the exception as demonstrated below:

def reciprocal(num: float) -> float:
    """Return 1 / num."""
    if num == 0:
        raise ValueError('num cannot be 0!')
    else:
        return 1 / num

Bare exception (W0702)

If the except keyword is used without being passed an exception, all exceptions will be caught. This is not good practice, since we may catch exceptions that we do not want to catch. For example, we typically do not want to catch the KeyboardInterrupt exception, which is thrown when a user attempts to exist the program by typing Ctrl-C.

from typing import Optional

def divide(numerator: float, denominator: float) -> Optional[float]:
    """Divide the numerator by the denominator."""
    try:
        return numerator / denominator
    except:
        print("Some exception occurd! Could have been a KeyboardInterrupt!")

Broad exception caught (W0718)

Using except Exception: is only slightly more specific than except: and should also be avoided ( see W0702). Since most builtin exceptions, and all user-defined exceptions, are derived from the Exception class, using except Exception: provides no information regarding which exception actually occurred. Exceptions which we do not expect can go unnoticed, and this may lead to bugs.

"""Broad Exception Caught Example"""
# The caught exception below is too broad and vague.
try:
    1 / 0
except Exception:  # Error on this line
    print('An error occurred')

Broad exception raised (W0719)

This error is emitted when one raises a generic exception. Raising exceptions that are not specific will cause the program to catch generic exceptions. This is bad practice because we may catch exceptions that we don’t want. Catching generic exceptions can also hide bugs and make it harder to debug programs.

"""Broad Exception Raised Example"""
# The raised exception below is too broad and vague.
raise Exception("This is a broad exception.")  # Error on this line

This error can be resolved by raising a more specific exception:

Note: NameError is a subclass of Exception (it is a more specific type of exception in Python).

raise NameError("The variable x doesn't exist!")

Duplicate except blocks (W0705)

This error occurs when we try to catch the same exception multiple times. Only the first except block for a particular exception will be reached.

from typing import Optional

def divide(numerator: float, denominator: float) -> Optional[float]:
    """Divide the numerator by the denominator."""
    try:
        return numerator / denominator
    except ZeroDivisionError:
        print("Can't divide by 0!")
    except ZeroDivisionError:
        print("This duplicate exception block will never be reached!")

Bad exception order (E0701)

Except blocks are analyzed sequentially (from top to bottom) and the first block that meets the criteria for catching the exception will be used. This means that if we have a generic exception type before a specific exception type, the code for the specific exception type will never be reached.

from typing import Optional

def divide(numerator: float, denominator: float) -> Optional[float]:
    """Divide the numerator by the denominator."""
    try:
        return numerator / denominator
    except Exception:
        print("Some exception occurd! But I don't know which one?!")
    except ZeroDivisionError:
        print("This exception block will never be reached!")

Binary op exception (W0711)

The Python except statement can catch multiple exceptions, if those exceptions are passed as a tuple. It is possible (but incorrect!) to pass except an expression containing the exception classes separated by a binary operator such as and or or. In such a case, only one of the exceptions will be caught!

def divide_and_square(numerator: float, denominator: float) -> float:
    """Divide the numerator by the denominator and square the result."""
    try:
        return (numerator / denominator) ** 2
    except ZeroDivisionError or OverflowError:  # Error on this line
        return float('nan')

Corrected version:

def divide_and_square(numerator: float, denominator: float) -> float:
    """Divide the numerator by the denominator and square the result."""
    try:
        return (numerator / denominator) ** 2
    except (ZeroDivisionError, OverflowError):
        return float('nan')

Misplaced bare raise (E0704)

The Python raise statement can be used without an expression only inside an except block. In this case, it will re-raise the exception that was caught by the except block. This may be useful if, for example, we wish to do some cleanup (e.g. close file handles), or print an error message, before passing the exception up the call stack.

def divide(numerator: float, denominator: float) -> float:
    """Divide the numerator by the denominator."""
    try:
        return numerator / denominator
    except ZeroDivisionError:
        print("Can't divide by 0!")
    raise  # Error on this line

Corrected version:

def divide(numerator: float, denominator: float) -> float:
    """Divide the numerator by the denominator."""
    try:
        return numerator / denominator
    except ZeroDivisionError:
        print("Can't divide by 0!")
        raise

Raising bad type (E0702)

The Python raise statement expects an object that is derived from the BaseException class. We cannot call raise on integers or strings.

raise 1  # Error on this line

See also: E0710

Raising non-exception (E0710)

The Python raise statement expects an object that is derived from the BaseException class. All user-defined exceptions should inherit from the Exception class (which will make them indirect descendents of the BaseException class). Attempting to raise any other object will lead to an error.

class NotAnException:
    """This class does not inherit from BaseException."""
    pass

raise NotAnException()

NotImplemented raised (E0711)

NotImplemented should only be used as a return value for binary special methods, such as __eq__, __lt__, __add__, etc., to indicate that the operation is not implemented with respect to the other type. It is not interchangeable with NotImplementedError, which should be used to indicate that the abstract method must be implemented by the derived class.

class Account:
    """Abstract base class describing the API for an account."""
    _balance: float

    def __init__(self, balance: float) -> None:
        self._balance = balance

    def withdraw(self, amount: float) -> float:
        """Withdraw some money from this account."""
        # Error on the following line: Use `NotImplementedError` instead
        raise NotImplemented

Catching non-exception (E0712)

The Python raise statement expects an object that is derived from the BaseException class (see E0710). Accordingly, the Python except statement also expects objects that are derived from the BaseException class. Attempting to call except on any other object will lead to an error.

class NotAnException:
    """This class does not inherit from BaseException."""
    pass

try:
    n = 5 / 0
except NotAnException:  # Error on this line: NotAnException does not inherit
    pass                # from BaseException

Custom errors

Global variables (E9997)

When writing Python programs, your variables should always be defined within functions. (A global variable is a variable that isn’t defined within a function.)

Example:

ex = 1

def add_ex(n: int) -> int:
    """Add ex to n."""
    return ex + n

Global variables should be avoided because they can be changed by other functions, which causes unpredictable behaviour in your program. You can indicate that a global variable shouldn’t be changed by naming it using the ALL_CAPS naming style:

EX = 1


def add_ex(n: int) -> int:
  """Add EX to n."""
  return EX + n

We call variables that are named using this style constants, and expect that they don’t change when we run our code. PythonTA allows global constants, and so would not report the forbidden-global-variables error on our second example.

See also: Global Variables Are Bad

Top Level Code (E9992)

This error occurs when code statements are placed in the top level. The type of statements allowed in the top level are imports, function/class definitions, assignment to constants, and the main block.

Example:

def example_function(name: str) -> str:
    return f'Hello {name}!'


print(example_function('Fred'))  # error on this line

To fix this, you could place the testing code inside the main block. For example:

def example_function(name: str) -> str:
    return f'Hello {name}!'


if __name__ == '__main__':
    print(example_function('Fred'))

Forbidden IO function (E9998)

Input / output functions (input, open and print) should not be used unless explicitly required. If print calls are used to debug the code, they should be removed prior to submission.

Example:

def hello() -> None:
    """Print a message to the user."""
    # You should not use input action in some assignments
    name = input("What is your name?")  # Error on this line

    # You should not use print action in some assignments
    print('hello, ' + name)  # Error on this line


if __name__ == '__main__':
    hello()

By default, there are no input/output functions (input, open and print) allowed. However, users may want to specify the permissible functions for utilizing input/output operations. Use the allowed-io option to specify a list of function names where input/output functions are allowed. For example, suppose the user defined a Python function as follows:

def hello_world() -> None:
    """The first steps in learning Python"""

    print('Hello World')    # Error on this line (print is an I/O function)

Use the following configuration to allow the usage of an input/output function (print in this case):

import python_ta
python_ta.check_all(config={
    'allowed-io': ['hello_world']
})

The exception is calling IO functions inside the main block, which is allowed.

if __name__ == "__main__":
    name = input()

By default, input, open and print are not allowed. However, you can choose which I/O functions specifically to disallow using the forbidden-io-functions option. This takes a list of function names that should not be used. For example, use the following configuration to forbid the use of print but allow input and open:

import python_ta
python_ta.check_all(config={
    "forbidden-io-functions": ["print"]
})

Loop iterates only once (E9996)

This error occurs when a loop will only ever iterate once. This occurs when every possible execution path through the loop body ends in a return statement (or another type of statement that ends the loop, like break).

Example:

def all_even(nums: list[int]) -> bool:
    """Return whether nums contains only even numbers."""
    for num in nums:      # This loop will only ever run for one iteration before returning.
        if num % 2 == 0:
            return True
        else:
            return False

In this example, the return value of all_even is based only on the first number in nums, and none of the other list elements are checked. This version would incorrectly return True on the list [2, 3]. Here is a corrected version of this function:

def all_even(nums: list[int]) -> bool:
    """Return whether nums contains only even numbers."""
    for num in nums:      # This loop will only ever run for one iteration before returning.
        if num % 2 != 0:
            return False

    return True

By moving the return True to outside the loop, we ensure that the only way True is returned is when there are only even numbers in the list.

Invalid Range Index (E9993)

This error occurs when we call the range function but with argument(s) that would cause the range to be empty or only have one element.

Examples:

for i in range(0):
    print(i)

for i in range(10, 0):
    print(i)

for j in range(0, 1, 3):
    print(j)

for m in range(4, 5):
    print(m)

When such ranges are used with a loop, the loop will iterate either zero or one time, which is almost certainly not what we intended! This usually indicates an error with how range is called.

Unnecessary Indexing (E9994)

This error occurs when we use a for loop or comprehension that goes over a range of indexes for a list, but only use those indexes to access elements from the list.

Example (For loop):

def sum_items(lst: List[int]) -> int:
    """Return the sum of a list of numbers."""
    s = 0
    for i in range(len(lst)):  # Error on this line (i is highlighted).
        s += lst[i]

    return s

We can simplify the above code by changing the loop to go over the elements of the list directly:

def sum_items(lst: List[int]) -> int:
    """Return the sum of a list of numbers."""
    s = 0
    for x in lst:
        s += x

    return s

Example (Comprehension):

def list_comp(lst: list) -> list:
    """Return all the items in lst in a new list."""
    return [lst[i] for i in range(len(lst))]  # Error on this line

We can simplify the above code by changing the comprehension to go over the elements of the list directly:

def list_comp(lst: list) -> list:
    """Return all the items in lst in a new list."""
    return [x for x in lst]

In general, we should only loop over indexes (for i in range(len(lst))) if we are using the index for some purpose other than indexing into the list. One common example is if we want to iterate over two lists in parallel:

For loop:

def print_sum(lst1: List[int], lst2: List[int]) -> None:
    """Print the sums of each corresponding pair of items in lst1 and lst2.
    Precondition: lst1 and lst2 have the same length.
    """
    for i in range(len(lst1)):
        print(lst1[i] + lst2[i])

Comprehension:

def parallel_lst(lst1: List[int], lst2: List[int]) -> list:
    """Return a list of the concatenation of the values of lst1 and lst2 at index i.
    Precondition: lst1 and lst2 have the same length."""
    return [lst1[i] + lst2[i] for i in range(len(lst1))]

For Target Subscript (E9984)

This error occurs when an index variable in a for loop or comprehension uses indexing notation, which can occur if you mix up the index variable and the list being iterated over.

Example (For loop):

def example1(lst: List[int]) -> int:
    """For loop target is an element of a list."""
    s = 0
    for lst[0] in lst:  # Error on this line. lst[0] is highlighted.
        s += lst[0]
    return s

Example (Comprehension):

def example7(lst: List[int]) -> List[int]:
    """Comprehension target is an element of a list."""
    return [lst[0] for lst[0] in lst]  # Error on this line. lst[0] is highlighted.

To fix this, always use a brand-new variable name for your index variable. For example:

For loop:

def example1(lst: List[int]) -> int:
    s = 0
    for number in lst:  # Fixed
        s += number
    return s

Comprehension:

def example7(lst: List[int]) -> List[int]:
    return [number for number in lst]  # Fixed

Possibly undefined variable (E9969)

This error occurs when we use a variable that might not be defined prior to its use. The most common cause is when we define a variable in one branch of an if statement, but not another.

Example:

x = 0
if x > 10:
    x = 5
else:
    y = 5
print(x + y)    # y might not be defined

Redundant assignment (E9959)

This error occurs when we have two assignment statements to the same variable, without using that variable in between the assignment statement. In this case, the first statement is redundant, since it gets overridden by the second.

Example:

x = 10  # This assignment statement is redundant
y = 5
x = 1
print(x)

Shadowing in comprehension (E9988)

This error occurs when a variable in a comprehension shadows (i.e., has the same name as) a variable from an outer scope, such as a local variable in the same function. In general you should avoid reusing variable names within the same function, and so you can fix this error by renaming the variable in the comprehension.

Example:

def num_lst(n: int) -> List[int]:
    """Return a list of integers from 0 to <n>, in that order."""
    return [n for n in range(n)]

Missing parameter type (E9970)

This error occurs when we have written a function definition but are missing a type annotation for a parameter.

Example:

def add_one(n) -> int:
    """Return n + 1."""
    return n + 1

Type is assigned (E9995)

This error occurs when a type is not annotated but rather assigned in a function or class definition. In Python, default values for function arguments and class instance variables are assigned using = during their respective definitions. Type annotations, on the other hand, are declared using :. Below is a correct usage of assigning default values and annotating types.

def print_str_argument(str_argument: str = "Some default value."):
    print(str_argument)


class BirthdayCake:
    number_of_candles: int = 1 # 1 is the default value

An incorrect usage of assigning default values and annotating types is shown below. Example:

from typing import List
import datetime


class Person:
    name = "Bob"


def add_two_numbers(
    x=int, # Error on this line
    y=List[float], # Error on this line
    z: type = complex # No error on this line
) -> int:
    return (x + y) * z


class MyDataType:
    x = datetime.time # Error on this line
    y = Person # Error on this line
    z: complex = complex # No error on this line

To fix these errors, one may make the following changes.

from typing import List
import datetime


class Person:
    name = "Bob"


def add_two_numbers(
    x: int,
    y: List[float],
    z: type = complex
) -> int:
    return (x + y) * z


class MyDataType:
    x: datetime.time
    y: Person
    z: complex = complex

Missing return type (E9971)

This error occurs when we have written a function definition but are missing a type annotation for the return value. Use None as the type annotation if the function does not return anything.

Example:

def add_one(n: int):
    """Return n + 1."""
    return n + 1

Missing attribute type (E9972)

This error occurs when we have written a class but are missing a type annotation for an instance attribute assigned in the class initializer.

Example:

class ExampleClass:
    """Class docstring."""
    def __init__(self) -> None:
        """Initialize a new instance of this class."""
        self.inst_attr: str = 'hi'  # Instance variable should be annotated in class body
        self.inst_attr2 = True  # Instance variable should be annotated in class body

These type annotations should be written at the top of the class body. For example:

class ExampleClass:
    """Class docstring."""
    inst_attr: str
    inst_attr2: bool

    def __init__(self):  # Missing return type annotation
        """Initialize a new instance of this class."""
        self.inst_attr = 'hi'
        self.inst_attr2 = True

Missing space in doctest (E9973)

This error occurs when a doctest found in the docstring of a function is not followed by a space. In this case the doctest will not actually be parsed.

Example:

def f(x: int) -> int:
    """Return one plus x.

    >>>f(10)  # Error on this line: Won't actually be parsed as a doctest!
    11
    """

This can simply be corrected by adding a space before the code be executed:

def f(x: int) -> int:
    """Return one plus x.

    >>> f(10)  # Adding a space will allow the doctest to be parsed.
    11
    """

Pycodestyle errors (E9989)

These errors are based on the Python code style guidelines (“PEP8”) published by the Python team. These errors do not affect the functionality of your code, but can affect its readability. The error messages display how to fix them (e.g., by adding spaces or adding/removing blank lines).

See also: PEP 8 – Style Guide for Python Code

By default, all styling guidelines checked by pycodestyle are reported. To ignore a specific check, use the pycodestyle-ignore option. This takes in a list of error codes from pycodestyle error codes to ignore. For example, use the following configuration to ignore E302 (expected 2 blank lines, found 0), and E305 (expected 2 blank lines after end of function or class):

import python_ta
python_ta.check_all(config={"pycodestyle-ignore": ["E302", "E305"]})

Comparison of constants (R0133)

This error occurs when two constants are compared with each other. The result of the comparison is always the same. It is better to use the constant directly.

def is_equal_to_one() -> bool:
    return 1 == 1  # Error on this line

Corrected version:

def is_equal_to_one(a: int) -> bool:
    return a == 1

Forbidden Python syntax (E9950)

This error occurs when disallowed Python syntax is used in code. Disallowed syntax refers to any Python syntax that shouldn’t be used because it defeats the purpose of an assessment. Used for teaching purposes.

Example:

"""Examples for E9950 forbidden-python-syntax."""

count = 10
while count > -1:  # forbidden python syntax
    if count == 5:
        continue  # forbidden python syntax

for i in range(1, 10):  # forbidden python syntax
    if i == 5:
        break  # forbidden python syntax

squares = [i ** 2 for i in range(1, 10)]  # forbidden python syntax

By default, all Python syntax is allowed. To forbid a specific type of syntax, use the disallowed-python-syntax option. This takes a list of names of AST nodes from astroid to forbid. For example, use the following configuration to forbid break and continue statements, comprehensions, and for and while loops:

import python_ta
python_ta.check_all(config={
    "disallowed-python-syntax": ["Break", "Continue", "Comprehension", "For", "While"]
})

Miscellaneous

Too many format args (E1305)

This error occurs when we use the format method on a string, but call it with more arguments than the number of {} in the string.

name = 'Amy'
age = '17'
country = 'England'
city = 'London'

# Error on the following line
s = '{} who is {} lives in {}'.format(name, age, country, city)

Corrected version:

name = "Amy"
age = "17"
country = "England"

s = "{} who is {} lives in {}".format(name, age, country)

See also: E1121

Too few format args (E1306)

This error occurs when we use the format method on a string, but call it with fewer arguments than the number of {} in the string.

s = '{} and {}'.format('first')  # Error on this line

Corrected version:

s = "{} and {}".format("first", "second")

See also: E1120

F-string without interpolation (W1309)

This error occurs when there are no interpolation variables present in an f-string. This might indicate that there is either a bug in the code (that is, there should be an interpolation variable in the f-string) or the f-string can be a normal string.

print(f'Hello World!')  # Error on this line

The issue above can be resolved in 2 ways - either the f-string can be converted into a normal string, or the missing interpolation variable can be added to the f-string. The snippet below shows both these solutions.

# Using a normal string instead of an f-string
print('Hello World!')

# Adding an interpolation to the f-string
entity = "World"
print(f'Hello {entity}!')

See also: W1310

Format string without interpolation (W1310)

This error occurs when a format string does not have any interpolation variables. This can be an issue as it can mean that either the string can be a normal string which does not need any formatting, or there is a bug in the code and there should be interpolation variables in the string.

greeting = 'Hello There, '.format(name='person')  # Error on this line

The error above can be resolved as follows:

greeting = 'Hello There, {name}'.format(name='person')

See also: W1309

Missing format argument key (W1303)

This error occurs when a format string that uses named fields does not receive the required keywords. In the following example, we should assign three values for last_name, first_name, and age.

# Error on the following line: missing format argument for 'age'
s = '{last_name}, {fist_name} - {age}'.format(last_name='bond', first_name='james')

Corrected version:

s = '{last_name}, {fist_name} - {age}'.format(last_name='bond', first_name='james', age=37)

See also: E1120, E1306

Bad str strip call (E1310)

This error occurs when we call strip, lstrip, or rstrip, but pass an argument string which contains duplicate characters. The argument string should contain the distinct characters that we want to remove from the end(s) of a string.

filename = 'attachment.data'
basename = filename.strip('data')  # Error on this line
print(basename)  # Prints 'chment.'

It is a common mistake to think that mystring.strip(chars) removes the substring chars from the beginning and end of mystring. It actually removes all characters in chars from the beginning and end of mystring, irrespective of their order! If we pass an argument string with duplicate characters to mystring.strip, we are likely misinterpreting what this method is doing.

Format combined specification (W1305)

This error occurs when a format string contains both automatic field numbering (e.g. {}) and manual field specification (e.g. {0}).

For example, we should not use {} and {index} at the same time.

s = '{} and {0}'.format('a', 'b')  # Error on this line

Corrected version:

s = "{} and {}".format("a", "b")

or:

s = "{0} and {1}".format("a", "b")

Anomalous backslash in string (W1401)

This error occurs when a string literal contains a backslash that is not part of an escape sequence.

print('This is a bad escape: \d')  # Error in this line

The following is a list of recognized escape sequences in Python string literals.

\newline    \a          \r          \xhh
\\          \b          \t          \N{name}
\'          \f          \v          \uxxxx
\"          \n          \ooo        \Uxxxxxxxx

If a backslash character is not used to start one of the escape sequences listed above, we should make this explicit by escaping the backslash with another backslash.

print('This is a tab: \t')
print('This is a newline: \n')
print('This is not an escape sequence: \\d')

Redundant unittest assert (W1503)

The first argument of assertTrue and assertFalse is a “condition”, which should evaluate to True or False. These methods evaluate the condition to check whether the test passes or fails. The conditions should depend on the code that we are testing, and should not be a constant literal like True or 4. Otherwise, the test will always have the same result, regardless of whether our code is correct.

from typing import List
import unittest

def is_sorted(lst: List[float]) -> bool:
    """Check if <lst> is sorted in ascending order."""
    return lst == sorted(lst)


class TestStringMethods(unittest.TestCase):
    """Simple tests for example purposes."""

    def test_isupper(self) -> None:
        """Simple tests for example purposes."""
        # Valid:
        self.assertTrue(is_sorted([1, 2, 3]))
        self.assertFalse(is_sorted([1, 3, 2]))

        # If a constant is passed as parameter, that condition is always true:
        self.assertTrue('YES')
        self.assertTrue(1)
        self.assertTrue(True)
        self.assertTrue(False)

Unidiomatic type check (C0123)

This error occurs when type is used instead of isinstance to perform a type check. Use isinstance(x, Y) instead of type(x) == Y.

from typing import Union

def is_int(obj: Union[int, float, str]) -> bool:
    """Check if the given object is of type 'int'."""
    return type(obj) == int  # Error on this line

The above can be modified to:

def is_int(obj: Union[int, float, str]) -> bool:
    """Check if the given object is of type 'int'."""
    return isinstance(obj, int)

See also: C0121

Dangerous default value (W0102)

This warning occurs when a mutable object, such as a list or dictionary, is provided as a default argument in a function definition. Default arguments are instantiated only once, at the time when the function is defined (i.e. when the interpreter encounters the def ... block). If the default argument is mutated when the function is called, it will remain modified for all subsequent function calls. This leads to a common “gotcha” in Python, where an “empty” list or dictionary, specified as the default argument, starts containing values on calls other than the first call.

from typing import List

def make_list(n: int, lst: List[int]=[]) -> List[int]:
    for i in range(n):
        lst.append(i)
    return lst


print(make_list(5))
print(make_list(5))

Many new users of Python would expect the output of the code above to be:

[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]

However, the actual output is:

[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]

If we want to prevent this surprising behavior, we should use None as the default argument, and then check for None inside the function body. For example, the following code prints the expected output:

from typing import List, Optional

def make_list(n: int, lst: Optional[List[int]]=None) -> List[int]:
    if lst is None:
        lst = []
    for i in range(n):
        lst.append(i)
    return lst


print(make_list(5))
print(make_list(5))

See also:

Consider iterating dictionary (C0201)

It is more pythonic to iterate through a dictionary directly, without calling the .keys method.

menu = {'pizza': 12.50, 'fries': 5.99, 'fizzy drink': 2.00}

for item in menu.keys():  # Error on this line
    print("My store sells {}.".format(item))

Corrected version:

for item in menu:
    print("My store sells {}.".format(item))

Superfluous parens (C0325)

This error occurs when a keyword, such as if or for, is followed by a single item enclosed in parentheses. In such a case, parentheses are not necessary.

pizza_toppings = ['extra cheese', 'pineapple', 'anchovies']

if ('anchovies' in pizza_toppings):  # Error on this line
    print("Awesome!")

Corrected version:

if 'anchovies' in pizza_toppings:
    print("Awesome!")

Trailing comma tuple (R1707)

This error occurs when a Python expression is terminated by a comma. In Python, a tuple is created by the comma symbol, not by parentheses. This makes it easy to create a tuple accidentally, by misplacing a comma, which can lead to obscure bugs. In order to make our intention clear, we should always use parentheses when creating a tuple, and we should never leave a trailing comma in our code.

my_lucky_number = 7,  # Error on this line
print(my_lucky_number)  # Prints (7,)

Corrected version:

my_lucky_number = 7
print(my_lucky_number)  # Prints 7

Assert on tuple (W0199)

This error occurs when an assert statement is called with a tuple as the first argument. assert acting on a tuple passes if and only if the tuple is non-empty. This is likely not what the programmer had intended.

def check(condition1: bool, condition2: bool) -> None:
    assert (condition1, condition2)  # Error on this line

If we would like to assert multiple conditions, we should join those conditions using the and operator, or use individual assert statements for each condition.

def check(condition1: bool, condition2: bool, condition3: bool) -> None:
    # Option 1
    assert (condition1 and condition2 and condition3)
    # Option 2
    assert condition1
    assert condition2
    assert condition3

If we would like assert to show a special error message when the assertion fails, we should provide that message as the second argument.

def check(condition, message):
    assert condition, message  # the message is optional

Literal comparison (R0123)

This error occurs when we use the identity operator is to compare non-boolean Python literals. Whether or not two literals representing the same value (e.g. two identical strings) have the same identity can vary, depending on the way the code is being executed, the code that has ran previously, and the version and implementation of the Python interpreter. For example, each of the following assertions pass if the lines are evaluated together from a Python file, but assert num is 257 and assert chars is 'this string fails' fail if the lines are entered into a Python interpreter one-by-one.

num = 256
assert num is 256

num = 257
assert num is 257  # Assertion fails if typed into a Python interpreter

chars = 'this_string_passes'
assert chars is 'this_string_passes'

chars = 'this string fails'
assert chars is 'this string fails'  # Assertion fails if typed into a Python interpreter

To prevent the confusion, it is advisable to use the equality operator == when comparing objects with Python literals.

num = 256
assert num == 256

num = 257
assert num == 257

chars = 'this_string_passes'
assert chars == 'this_string_passes'

chars = 'this string fails'
assert chars == 'this string fails'

See also:

Expression not assigned (W0106)

This error occurs when an expression that is not a function call is not assigned to a variable. Typically, this indicates that we were intending to do something else.

lst = [1, 2, 3]
lst.append(4), "Appended 4 to my list!"  # Eror on this line

Corrected version:

lst = [1, 2, 3]
lst.append(4)
print("Appended 4 to my list!")

Invalid length returned (E0303)

This error occurs when the __len__ special method returns something other than a non-negative integer.

from typing import List

class Company:
    """A company with some employees."""

    def __init__(self, employees: List[str]) -> None:
        self._employees = employees

    def __len__(self) -> int:
        return -1  # Error on this line

Corrected version:

class Company:
    """A company with some employees."""

    def __init__(self, employees: List[str]) -> None:
        self._employees = employees

    def __len__(self) -> int:
        return len(self._employees)

Forgotten debug statement (W1515)

This warning occurs when debugging breakpoints (such as breakpoint(), sys.breakpointhook(), and pdb.set_trace()) are found. These breakpoints should be removed in production code.

import sys
import pdb
breakpoint()  # Error on this line
sys.breakpointhook()  # Error on this line
pdb.set_trace()  # Error on this line

Modified iterators in for loops

Modified iterating list (W4701)

This error occurs when a list is modified inside a for loop by adding or removing items from the list. Other types of modification are okay, and do not trigger the error. A copy of the list can be used instead.

data = [1, 2, 3, 4]

for x in data:
    data.remove(4) # Error on this line

Modified iterating dict (E4702)

This error occurs when a dictionary is modified inside a for loop by adding or removing items from the dict. Other types of modification (like assigning a new value to an existing key) are actually okay, and do not trigger the error. A copy of the dict can be used instead.

data = {'x': 1, 'y': 2, 'z': 3}

for key, value in data:
    data['w'] = 0 # Error on this line

Modified iterating set (E4703)

This error occurs when a set is modified inside a for loop by adding or removing items from the set. Other types of modification are actually okay, and do not trigger the error. A copy of the set can be used instead.

data = {1, 2, 3}

for x in data:
    data.remove(3) # Error on this line

(#style) =

Style errors

Multiple statements (C0321)

This error occurs when we write more than one statement on a single line. According to PEP8, _ multiple statements on the same line are discouraged_.

def is_positive(number: int) -> str:
    """Return whether the number is 'positive' or 'negative'."""
    if number > 0: return 'positive'  # Error on this line
    else:
        return 'negative'


def is_negative(number: int) -> bool:
    """Return whether the number is negative."""
    b = number < 0; return b  # Error on this line

Corrected version:

def is_positive(number: int) -> str:
    """Return whether the number is 'positive' or 'negative'."""
    if number > 0:
        return 'positive'
    else:
        return 'negative'

Unnecessary semicolon (W0301)

This error occurs when we end a Python statement with a semicolon. There is no good reason to ever use a semicolon in Python.

print("Hello World!");  # Error on this line

Corrected version:

print("Hello World!")

Missing final newline (C0304)

This error occurs when a file is missing a trailing newline character. For example, if we represent a (typically invisible) newline character as ¬, the following file would raise this error:

print("Hello World!")  # Trailing newline is absent:

while the corrected file which contains a trailing newline character would not:

print("Hello World!")  # Trailing newline is present:  ¬

Trailing newlines (C0305)

This error occurs when a file ends with more than one newline character (i.e. when a file contains trailing blank lines). For example:

print("Hello World!")  # Too many newline characters after this line





Corrected version:

print("Hello World!")  # This file ends with a single newline character! :)

Bad file encoding (C2503)

This error occurs when there is an encoding declaration at the top of the Python file or if any identifier uses non-ASCII characters.

# -*- coding: utf-8 -*-  # Error on this line
z̯̯͡a̧͎̺l̡͓̫g̹̲o̡̼̘ = 2  # Error on this line
my_int = 3

Corrected version:

my_int = 3

Line too long (C0301)

This error occurs when a line is longer than a predefined number of characters. Our default limit for all lines is 80 characters.

TEMP = 'This constant is in the file c0301_line_too_long.py. This is a very long line.... in fact it is too long!'

Bad chained comparison (W3601)

This error occurs when a chained comparison uses incompatible comparison operators, i.e. operators that perform a different kind of test. For example, < has a different meaning than is and in.

The different types of comparison operators can be classified in the following categories:

"""Examples for W3601 bad-chained-comparison"""

x = 5

if 0 < x is 5:  # Error on this line
    print("hi")

a = 1
my_list = [1, 2, 3, 4]

if a > 0 not in my_list:  # Error on this line
    print("whee")

if a == x is 1 in my_list:  # Error on this line
    print("yikes")

Syntax errors

Syntax Error (E0001)

  1. SyntaxError: Missing parentheses in call to ‘print’

    In Python 3, print is a builtin function, and should be called like any other function, with arguments inside parentheses. In previous versions of Python, print had been a keyword.

    print "Hello world!"   # Error on this line
    print("Hello world!")  # Correct version
    
  2. SyntaxError: can’t assign to literal

    There must always be a variable on the left-hand side of the equals sign (where the term “ variable” can refer to a single identifier a = 10, multiple identifiers a, b = 10, 20, a dictionary element foo['a'] = 10, a class attribute foo.bar = 10, etc.). We cannot assign to a string or numeric literal.

    a = 12
    12 = a  # Error on this line: can't assign to literal
    'hello' = a  # Error on this line: can't assign to literal
    
  3. SyntaxError: invalid syntax

    Some of the common causes of this error include:

    1. Missing colon at the end of an if, elif, else, for, while, class, or def statement.

      if spam == 42  # Error on this line: missing colon
          print('Hello!')
      
    2. Assignment operator = used inside a condition expression (likely in place of the equality operator ==).

      if spam = 42:  # Error on this line: assignment (`=`) instead of equality (`==`)
          print('Hello!')
      
    3. Missing quotation mark at the beginning or the end of a string literal.

      print('Hello!)  # Error on this line: missing closing quote (')
      
    4. Assignment to a Python keyword.

      class = 'algebra'  # Error on this line: assignment to keyword 'class'
      

      The following is a list of Python keywords which cannot be used as variable names:

      False      class      finally    is         return
      None       continue   for        lambda     try
      True       def        from       nonlocal   while
      and        del        global     not        with
      as         elif       if         or         yield
      assert     else       import     pass
      break      except     in         raise
      
    5. Use of an undefined operator. For example, there are no “increment by one” ++ or “decrement by one” -- operators in Python.

    spam = 0
    spam++  # Error on this line
    spam--  # Error on this line
    
  4. IndentationError: unindent does not match any outer indentation level

    We must use a constant number of whitespace characters for each level of indentation. If we start a code block using four spaces for indentation, we must use four spaces throughout that code block.

    num_even = 0
    num_odd = 0
    for i in range(100):
        if i % 2 == 0:
            num_even += 1
          else:  # Error on this line: six spaces before `else:` instead of four
            num_odd += 1
    

    Note that it is strongly recommended that we always use four spaces per indentation level throughout our code.

  5. IndentationError: unexpected indent

    In Python, the only time we would increase the indentation level of our code is to define a new code block after a compound statement such as for, if, def, or class.

    for i, animal in enumerate(['Monkey', 'Donkey', 'Platypus']):
        print(i)
          print(animal)  # IndentationError: unexpected indent
    

Nonexistent operator (E0107)

This error occurs when we attempt to use C-style “pre-increment” or “pre-decrement” operators ++ and --, which do not exist in Python.

spam = 0
++spam  # Error on this line
--spam  # Error on this line

Corrected version:

spam = 0
spam += 1
spam -= 1

Older errors

The following errors are no longer checked by the latest version of PythonTA.

Bad whitespace (C0326)

This error occurs when we include a wrong number of spaces around an operator, bracket, or block opener. We should aim to follow the PEP8 convention on whitespace in expressions and statements .

Bad indentation (W0311)

This error occurs when an unexpected number of tabs or spaces is used to indent the code. It is recommended that we use four spaces per indentation level throughout our code.

Mixed indentation (W0312)

This error occurs when the code is indented with a mix of tabs and spaces. Please note that spaces are the preferred indentation method.

Bad continuation (C0330)

This error occurs when we use an inconsistent number of spaces to indent arguments or parameters in function and method calls or definitions.