% PyTA Help Documentation
Welcome to the PyTA documentation website, which describes in greater detail the errors that PyTA 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].
These errors generally indicate a misuse of variables, control flow, or other Python features in our code.
This error occurs when we are using a variable before it has been assigned a value.
This error occurs when we are using a variable that has not been defined.
This error occurs when a loop variable is used outside the for
loop where it was defined.
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:
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.
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).
This error occurs when a return
statement is found outside a function or method.
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).
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.
This error occurs when a dictionary literal sets the same key multiple times.
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.
This error occurs when a function call passes a keyword argument which does not match the signature of the function being called.
Corrected version:
print_greeting(name="Arthur")
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.
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.
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.
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
.
We should either modify add_fruit
to return a new list, or call add_fruit
without assigning the return value to a variable.
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
.
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.
Corrected version:
get_sum(1, 2, 3)
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.
Corrected version:
get_sum(1, 2)
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.
Corrected version:
a = ['p', 'y', 'T', 'A']
print(a[0])
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.
Corrected version:
a = ['p', 'y', 'T', 'A']
print(a[0:3])
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.
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.
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.
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.
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).
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).
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
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.
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.
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.
Corrected version:
for number in [1, 2, 3]:
print(number)
This error occurs when a boolean expression contains an unneeded negation. If we are getting this error, the expression can be simplified to not use a negation.
The above can be modified to:
number = 5
if number < 0:
number_category = 'negative'
else:
number_category = 'non-negative'
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
:
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:
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.
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.
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.
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
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.
The function or method has too many local variables.
Note: The checker limit is 15 local variables.
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.
This error occurs when we have a defined variable that is never used.
This error occurs when a function argument is never used in the function.
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.
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.
In the above example, the pass
statement is "unnecessary" as the program's effect is not changed if pass
is removed.
See also:
This error occurs when you have a function that sometimes returns a non-None
value and sometimes implicitly returns None
.
This is an issue because in Python, we prefer making code explicit rather than implicit.
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
.
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.
This error occurs when a module, function, class or method has an empty docstring.
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 blacklisted names to avoid:
foo
bar
baz
toto
tutu
tata
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.
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.
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.
This error occurs when a local name is redefining the name of a parameter.
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: 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.
This error occurs when we are redefining a built-in function, constant, class, or exception.
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
There are standards governing how we should organize our imports, or even possibly which modules we may import at all.
In CSC108/CSC148, we should only use the Python language features we have covered in lectures, or ones that we have explicitly mentioned for an exercise/lab/assignment. No other external libraries may be used.
The module is unable to be imported. Check the spelling of the module name, or whether the module is in the correct directory.
There are other forms of import statements that may cause this error. For example:
import missing_module as foo # This module does not exist
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.
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.
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()
A module should not be imported more than once.
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.
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.
Different modules should not be imported on a single line.
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
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.
Imports should be grouped by package.
Corrected version:
from sys import byteorder, stdin # Same packages should be grouped
from math import floor
Imports should be placed at the top of the module, above any other code, but below the module docstring.
This error occurs when we import a module which is not used anywhere in our code.
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.
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
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.
Corrected version:
class Cat(Animal):
"""A worthy companion."""
def make_sound(self) -> str:
return 'Miew...'
This error occurs when a method takes a different number of arguments than the interface that it implements or the method that it overrides.
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!!")
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.
Corrected version:
class PremiumBankAccount(StandardBankAccount):
...
def withdraw(self, amount: float = 200) -> float: # Note the default argument
...
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
.
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.
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.
When using inheritance, we should call the __init__
method of the parent class and not of some unrelated class.
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 considered super!
- Python's super considered harmful
- StackOverflow: What does 'super' do in Python?
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.
We should do this instead:
class SomeNumbers:
"""A class to store some numbers."""
def __init__(self) -> None:
self.num = 1
self.other_num = None
def set_other_num(self, other_num: int) -> None:
self.other_num = other_num
Method hidden (E0202) {#E0202}
If we accidentally hide a method with an attribute, it can cause other code to attempt to invoke what it believes to be a method, which will fail since it has become an attribute instead. This will cause the program to raise an error.
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.
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.
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)
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.
Corrected version:
class FancyFloat(float):
"""A fancy floating point number."""
pass
A class should not inherit from a different class multiple times.
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.
Corrected version:
class Saxophone:
"""A jazzy musical instrument."""
def __init__(self) -> None:
self._sound = "Saxamaphone...."
def make_sound(self) -> None:
print(self._sound)
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.
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
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.
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:
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.
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:
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
.
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.
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.
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.
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!
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')
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.
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
The Python raise
statement expects an object that is derived from the BaseException
class. We cannot call raise
on integers or strings.
See also: 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.
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.
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.
Input / output functions (input
, open
and print
) should not be used in this course unless explicitly required. If print
statements are used to debug the code, they should be removed prior to submission.
This error occurs when the loop will only ever iterate once.
This usually occurs when every possible execution path through the loop body ends in a return
or break
statement.
def all_even(nums: List[int]) -> bool:
"""Return whether nums contains only even numbers."""
for num in nums:
if num % 2 == 0:
return True
else:
return False
The iteration variable in a for loop was used unnecessarily.
Corrected version:
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
Note: Only for Python 3: If the iteration variable of a for loop is shadowed by the iteration variable inside a list comprehension, this checker may not work properly and report a false error.
Example:
def f(lst):
s = 0
for i in range(len(lst)): # Checker will detect an error on this line even though there is none.
lst = [i for i in range(i)]
for x in lst:
s += x
return s
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.
Corrected version:
name = "Amy"
age = "17"
country = "England"
s = "{} who is {} lives in {}".format(name, age, country)
See also: E1121
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.
Corrected version:
s = "{} and {}".format("first", "second")
See also: E1120
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
.
Corrected version:
s = '{last_name}, {fist_name} - {age}'.format(last_name='bond', first_name='james', age=37)
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.
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.
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.
Corrected version:
s = "{} and {}".format("a", "b")
or:
s = "{0} and {1}".format("a", "b")
This error occurs when a string literal contains a backslash that is not part of an escape sequence.
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')
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.
This error occurs when type
is used instead of isinstance
to perform a type check. Use isinstance(x, Y)
instead of type(x) == Y
.
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
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.
Many new users of Python would expect the output of the code above to be:
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]
However, the actual output is:
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
If we want to prevent this surprising behavior, we should use None
as the default argument, and then check for None
inside the function body. For example, the following code prints the expected output:
from typing import List, Optional
def make_list(n: int, lst: Optional[List[int]]=None) -> List[int]:
if lst is None:
lst = []
for i in range(n):
lst.append(i)
return lst
print(make_list(5))
print(make_list(5))
See also:
It is more pythonic to iterate through a dictionary directly, without calling the .keys
method.
Corrected version:
for item in menu:
print("My store sells {}.".format(item))
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.
Corrected version:
if 'anchovies' in pizza_toppings:
print("Awesome!")
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.
Corrected version:
my_lucky_number = 7
print(my_lucky_number) # Prints 7
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.
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
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.
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:
- Literally Literals and Other Number Oddities In Python
- StackOverflow: About the changing id of an immutable string
- StackOverflow: When does Python allocate new memory for identical strings?
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.
Corrected version:
lst = [1, 2, 3]
lst.append(4)
print("Appended 4 to my list!")
This error occurs when the __len__
special method returns something other than a non-negative integer.
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)
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.
Corrected version:
def func(temp: int) -> bool:
"""Return whether <temp> is greater than 0."""
return temp > 0
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.
Corrected version:
def print_greeting(name: str) -> None:
"""Print a greeting to the person with the given name."""
print('Hello {}!'.format(name))
This error occurs when the code is indented with a mix of tabs and spaces. Please note that spaces are the preferred indentation method.
Corrected version:
def hello_world() -> None:
"""Greet the universe with a friendly 'Hello World!'."""
print("Hello World!")
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.
Corrected version:
def is_positive(number: int) -> str:
"""Return whether the number is 'positive' or 'negative'."""
if number > 0:
return 'positive'
else:
return 'negative'
This error occurs when we end a Python statement with a semicolon. There is no good reason to ever use a semicolon in Python.
Corrected version:
print("Hello World!")
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:
while the corrected file which contains a trailing newline character would not:
print("Hello World!") # Trailing newline is present: ¬
This error occurs when a file ends with more than one newline character (i.e. when a file contains trailing blank lines). For example:
Corrected version:
print("Hello World!") # This file ends with a single newline character! :)
This error occurs when we use an inconsistent number of spaces to indent arguments or parameters in function and method calls or definitions.
Corrected version:
def print_address(recipient_name: str,
street_number_and_name: str,
city: str,
province: str,
country: str) -> None:
"""Print the provided address in a standardized format."""
address_string = (
"{recipient_name}\n"
"{street_number_and_name}\n"
"{city}, {province}\n"
"{country}"
.format(
recipient_name=recipient_name,
street_number_and_name=street_number_and_name,
city=city,
province=province,
country=country))
print(address_string)
This error occurs when a line is longer than a predefined number of characters. Our default limit for all lines is 80 characters.
-
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. -
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. -
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. -
Assignment operator
=
used inside a condition expression (likely in place of the equality operator==
). -
Missing quote at the beginning or the end of a string literal.
-
Assignment to a Python keyword.
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.
-
-
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.
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
.
This error occurs when we attempt to use C-style "pre-increment" or "pre-decrement" operators ++
and --
, which do not exist in Python.
Corrected version:
spam = 0
spam += 1
spam -= 1