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 __future__ import annotations
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 __future__ import annotations
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 __future__ import annotations
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 __future__ import annotations
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 __future__ import annotations
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)
Keyword argument superseded by positional argument (W1117)
This error occurs when a function is called with keyword arguments with the same name as positional-only parameters and the function contains a keyword variadic parameter dict.
In the example below, greeting
is a positional-only parameter of the function print_greeting
.
However, when the function is called, "Hi"
is passed as a keyword argument. As a result, it will be collected in a dictionary by the keyword variadic parameter kwds
,
and the function will print out the default value "Hello"
, which is an unintended behavior of the function call.
"""Examples for W1117: kwarg-superseded-by-positional-arg"""
def print_greeting(greeting="Hello", /, **kwds) -> None:
"""Prints out the greeting provided as a positional argument,
or "Hello" otherwise.
"""
print(greeting)
print_greeting(greeting="Hi") # Error on this line
Corrected version:
print_greeting("Hi")
This will print out "Hi"
as intended.
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 __future__ import annotations
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 __future__ import annotations
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 unnecessary 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 __future__ import annotations
from typing import 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 __future__ import annotations
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 __future__ import annotations
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 returns (R9710)
This error occurs when you have a function that sometimes has return statements with
non-None
values and sometimes has only return
. This is an issue because in Python, we prefer making code explicit
rather than implicit. This error replaces pylint’s R1710.
import math
from typing import 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 str_to_int(s: str) -> Optional[int]:
"""Return the integer representation of the string s, or None if it cannot be converted."""
try:
return int(s)
except ValueError:
return # Error: this should be `return None` instead.
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
.
Missing return statement (R9711)
This error occurs when a function is missing return statements in at least one branch, when the function’s return type annotation is not “None”.
from __future__ import annotations
from typing import Optional
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
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
Unmentioned Parameter (C9960)
This error occurs when a function parameter is not explicitly mentioned in the docstring. Parameters should be clearly documented in the function’s docstring, not just within doctests or other parts of the function. By default, this checker is disabled.
"""Example for C9960 unmentioned-parameter"""
def multiply(a: int, b: int) -> int: # C9960: 'a' and 'b' are not mentioned
"""Multiply two numbers."""
return a * b
def divide(numerator: int, denominator: int) -> float: # C9960: 'denominator' is not mentioned
"""Divide the numerator as specified.
Parameters:
numerator: The number to be divided.
"""
return numerator / denominator
def generate_list(n: int) -> list: # C9960: 'n' is not mentioned in the main docstring (only in doctests)
"""Generate a list of numbers
>>> n = 3
>>> generate_list(n)
[0, 1, 2]
"""
return [i for i in range(n)]
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 __future__ import annotations
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
, orI
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
In addition, you can specify if you want to allow for local imports through allow-local-imports
option:
import python_ta
python_ta.check_all(..., config={'allow-local-imports': True})
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
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:
Invalid field call (E3701)
The dataclasses.field
function is used to specify the behaviour of instance attributes when defining a dataclass.
This function returns a Field
object that contains the arguments that were set in the function. This function should
only be used as the value of an assignment in a dataclass definition or in the make_dataclass()
function. Any other
use will be considered invalid.
from dataclasses import field
money = field(default=5) # Error on this line
Corrected version:
from dataclasses import dataclass, field
@dataclass
class Wallet:
"""A custom class for storing information about a wallet."""
money: int = field(default=5)
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
Return in finally (W0134)
This error occurs when a return
statement is used inside a finally
block. Doing so overwrites any previous return
values therefore should be avoided.
For example, in the code example below, if an IndexError
occurs we want
error_codes[1]
to be returned, however since there is a return statement in the finally
block error_codes[2]
will be
returned instead. Moving that return statement outside the finally
block would resolve the issue.
def error_code(error_codes):
try:
print(error_codes[0])
except IndexError:
return error_codes[1]
finally:
return error_codes[2] # Error on this line
Corrected Version:
def error_code(error_codes):
try:
print(error_codes[0])
except IndexError:
return error_codes[1]
return error_codes[2]
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 range
s 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 __future__ import annotations
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 __future__ import annotations
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"]
})
Redundant Condition (R9900)
This error occurs when an if
or while
condition is guaranteed to be true.
Example:
def return_large_number(x: int) -> int:
"""Return number x only if it's greater than 1000
Preconditions:
- x > 1000
"""
# the if condition is already checked by function precondition
if x > 1000: # Error on this line
return x
def nested_condition(x: int) -> int:
if x > 10:
# the condition `x > 5` is already guaranteed by `x > 10`
if x > 5: # Error on this line
return x
return 0
def redundant_condition(x: bool) -> None:
# the if condition is always true
if x or not x: # Error on this line
print("redundant")
This error will only be checked if the z3-solver
library is installed and z3
option of PythonTA
is enabled:
import python_ta
python_ta.check_all(config={
"z3": True
})
Impossible Condition (R9901)
This error occurs when an if
or while
condition is guaranteed to be false.
Example:
def print_none_negative_number(x: int) -> None:
"""Print number x only if it's greater or equal to 0
Preconditions:
- x >= 0
"""
# the if condition is impossible given the function precondition
if x < 0: # Error on this line
raise Exception("x is smaller than zero")
print(x)
def impossible_condition(x: bool) -> None:
# the if condition is always false
if x and not x: # Error on this line
print("impossible")
def display_number(x: int) -> str:
"""Display numbers 0 to 2 in word"""
if x == 0:
"zero"
elif x == 1:
return "one"
elif x == 2:
return "two"
elif x == 2: # Error on this line
return "two again"
This error will only be checked if the z3-solver
library is installed and z3
option of PythonTA
is enabled:
import python_ta
python_ta.check_all(config={
"z3": True
})
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)
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 __future__ import annotations
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 __future__ import annotations
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 __future__ import annotations
from typing import 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 __future__ import annotations
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
Mypy-based checks
The following errors are identified by the StaticTypeChecker, which uses Mypy to detect issues related to type annotations in Python code. Further information on Mypy can be found in its official documentation.
Incompatible Argument Type (E9951)
This error occurs when a function is called with an argument that does not match the expected type for that parameter. See the Mypy documentation.
def calculate_area(radius: float) -> float:
"""Calculate the area of a circle with the given radius"""
return 3.14159 * radius * radius
area = calculate_area("five") # Error: Function argument should be float, but got str
def convert_to_upper(text: str) -> str:
"""Convert the given text to uppercase"""
return text.upper()
result = convert_to_upper(5) # Error: Function argument should be str, but got int
Incompatible Assignment (E9952)
This error occurs when an expression is assigned to a variable, but the types are incompatible. See the Mypy documentation.
age: int = 25
age = "twenty-five" # Error: Incompatible types in assignment (expression has type "str", variable has type "int")
count: int = "ten" # Error: Incompatible types in assignment (expression has type "str", variable has type "int")
List Item Type Mismatch (E9953)
This error occurs when a list item has a type that does not match the expected type for that position in the list. See the Mypy documentation.
names: list[str] = ["Alice", "Bob", 3] # Error: List item 2 has incompatible type "int"; expected "str"
numbers: list[int] = [1, 2, "three"] # Error: List item 2 has incompatible type "str"; expected "int"
mixed: list[float] = [1.1, 2.2, "3.3"] # Error: List item 2 has incompatible type "str"; expected "float"
Unsupported Operand Types (E9954)
This error occurs when an operation is attempted between incompatible types, such as adding a string to an integer. See the Mypy documentation.
result = "hello" - 5 # Error: Unsupported operand types for - ("str" and "int")
total = 10 + "20" # Error: Unsupported operand types for + ("int" and "str")
value = 5.5 * "3" # Error: Unsupported operand types for * ("float" and "str")
Union Attribute Error (E9955)
This error occurs when attempting to access an attribute on a Union
type that may not exist on all possible types in the union. See the Mypy documentation.
from typing import Union
def get_status(status: Union[str, int, float]) -> str:
return (status
.upper()) # Error: Item "int" of "str | int | float" has no attribute "upper"
# Error: Item "float" of "str | int | float" has no attribute "upper"
def get_keys(data: Union[dict, list]) -> list:
return data.keys() # Error: Item "list" of "dict | list" has no attribute "keys"
Dictionary Item Type Mismatch (E9956)
This error occurs when a dictionary entry contains a key or value type that does not match the expected type. See the Mypy documentation.
data: dict[int, str] = {1: "one", 2: "two", "three": 3} # Error: Dict entry 2 has incompatible type "str": "int"; expected "int": "str"
info: dict[str, float] = {"pi": 3.14, "e": 2.71, 42: "1.618"} # Error: Dict entry 2 has incompatible type "int": "str"; expected "str": "float"
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:
Value comparisons which compares values
Membership tests which checks whether a value is present in some other object
Identity comparisons which checks whether two variables or values represent the same object
"""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)
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
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 identifiersa, b = 10, 20
, a dictionary elementfoo['a'] = 10
, a class attributefoo.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
SyntaxError: invalid syntax
Some of the common causes of this error include:
Missing colon at the end of an
if
,elif
,else
,for
,while
,class
, ordef
statement.if spam == 42 # Error on this line: missing colon print('Hello!')
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!')
Missing quotation mark at the beginning or the end of a string literal.
print('Hello!) # Error on this line: missing closing quote (')
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
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
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.
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
, orclass
.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.