PEP 8 — Style Guide for Python Code

Mohammad Shahil
8 min readMar 2, 2024

Read my article or go with details documentation in PEP

One of Guido’s key insights is that code is read much more often than it is written. The guidelines provided here are intended to improve the readability of code and make it consistent across the wide spectrum of Python code.

A style guide is about consistency. Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is the most important.

Code Lay-out

Indentation

Use 4 spaces per indentation level.

# Correct:

# Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
var_three, var_four)

# Add 4 spaces (an extra level of indentation) to
# distinguish arguments from the rest.
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)

# Hanging indents should add a level.
foo = long_function_name(
var_one, var_two,
var_three, var_four)

The 4-space rule is optional for continuation lines.

Optional:

# Hanging indents *may* be indented to other than 4 spaces.
foo = long_function_name(
var_one, var_two,
var_three, var_four)

When the conditional part of an if-statement is long enough to require that it be written across multiple lines,

# No extra indentation.
if (this_is_one_thing and
that_is_another_thing):
do_something()

# Add a comment, which will provide some distinction in editors
# supporting syntax highlighting.
if (this_is_one_thing and
that_is_another_thing):
# Since both conditions are true, we can frobnicate.
do_something()

# Add some extra indentation on the conditional continuation line.
if (this_is_one_thing
and that_is_another_thing):
do_something()

The closing brace/bracket/parenthesis on multiline constructs may either line up under the first non-whitespace character of the last line of list, as in:

my_list = [
1, 2, 3,
4, 5, 6,
]
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)

Maximum Line Length

Limit all lines to a maximum of 79 characters.

For flowing long blocks of text with fewer structural restrictions (docstrings or comments), the line length should be limited to 72 characters.

Backslashes may still be appropriate at times. For example, long, multiple with-statements could not use implicit continuation before Python 3.10, so backslashes were acceptable for that case:

with open('/path/to/some/file/you/want/to/read') as file_1, \
open('/path/to/some/file/being/written', 'w') as file_2:
file_2.write(file_1.read())

Should a Line Break Before or After a Binary Operator?

# Correct:
# easy to match operators with operands
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)

Blank Lines

Surround top-level function and class definitions with two blank lines.

Method definitions inside a class are surrounded by a single blank line.

Extra blank lines may be used (sparingly) to separate groups of related functions.

Imports

  • Imports should usually be on separate lines:
# Correct: 
import os import sys

# Wrong:
import sys, os

# Its okay to say this though
# Correct:
from subprocess import Popen,PIPE

Module Level Dunder Names

Module level “dunders” (i.e. names with two leading and two trailing underscores) such as __all__, __author__, __version__, etc. should be placed after the module docstring but before any import statements except from __future__ imports. Python mandates that future-imports must appear in the module before any other code except docstrings:

"""This is the example module.
This module does stuff.
"""
from __future__ import barry_as_FLUFL__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'
import os
import sys

String Quotes

In Python, single-quoted strings and double-quoted strings are the same.

Whitespace in Expressions and Statements

Pet Peeves

Avoid extraneous whitespace in the following situations:

  • Immediately inside parentheses, brackets or braces:
# Correct: 
spam(ham[1], {eggs: 2})

# Wrong:
spam( ham[ 1 ], { eggs: 2 } )

Between a trailing comma and a following close parenthesis.
# Correct:
foo = (0,)

# Wrong:
bar = (0, )

Immediately before a comma, semicolon, or colon:
# Correct:
if x == 4: print(x, y); x, y = y, x

# Wrong:
if x == 4 : print(x , y) ; x , y = y , x

However, in a slice the colon acts like a binary operator, and should have equal amounts on either side

# Correct:
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower::step]
ham[lower+offset : upper+offset]
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
ham[lower + offset : upper + offset]
# Wrong:
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : step]
ham[ : upper]
  • Immediately before the open parenthesis that starts the argument list of a function call:
# Correct: 
spam(1)

# Wrong:
spam (1)

Immediately before the open parenthesis that starts an indexing or slicing:
# Correct:
dct['key'] = lst[index]

# Wrong:
dct ['key'] = lst [index]

More than one space around an assignment (or other) operator to align it with another:

# Correct:
x = 1
y = 2
long_variable = 3

# Wrong:
x = 1
y = 2
long_variable = 3
# Correct:
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)
# Wrong:
i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)

Function annotations should use the normal rules for colons and always have spaces around the -> arrow if present.

# Correct:
def munge(input: AnyStr): ...
def munge() -> PosInt: ...
# Wrong:
def munge(input:AnyStr): ...
def munge()->PosInt: ...

Don’t use spaces around the = sign when used to indicate a keyword argument

# Correct:
def complex(real, imag=0.0):
return magic(r=real, i=imag)
# Wrong:
def complex(real, imag = 0.0):
return magic(r = real, i = imag)

When combining an argument annotation with a default value, however, do use spaces around the = sign:

# Correct:
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
# Wrong:
def munge(input: AnyStr=None): ...
def munge(input: AnyStr, limit = 1000): ...

Compound statements (multiple statements on the same line) are generally discouraged:

# Correct:
if foo == 'blah':
do_blah_thing()
do_one()
do_two()
do_three()

Rather not:

# Wrong:
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()

When to Use Trailing Commas

Trailing commas are usually optional, except they are mandatory when making a tuple of one element. For clarity, it is recommended to surround the latter in (technically redundant) parentheses:

# Correct:
FILES = ('setup.cfg',)
# Wrong:
FILES = 'setup.cfg',

When trailing commas are redundant, they are often helpful when a version control system is used, when a list of values, arguments or imported items is expected to be extended over time.

# Correct:
FILES = [
'setup.cfg',
'tox.ini',
]
initialize(FILES,
error=True,
)
# Wrong:
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)

Comments

Comments that contradict the code are worse than no comments. Always make a priority of keeping the comments up-to-date when the code changes!

Comments should be complete sentences. The first word should be capitalized, unless it is an identifier that begins with a lower case letter

Block Comments use #

Inline Comments

Inline comments are unnecessary and in fact distracting if they state the obvious. Don’t do this:

x = x + 1                 # Increment x

But sometimes, this is useful:

x = x + 1                 # Compensate for border

Documentation Strings

Conventions for writing good documentation strings (a.k.a. “docstrings”) are immortalized in PEP 257.

Write docstrings for all public modules, functions, classes, and methods. Docstrings are not necessary for non-public methods, but you should have a comment that describes what the method does.

"""Return a foobang

Optional plotz says to frobnicate the bizbaz first.
"""

Naming Conventions

Descriptive: Naming Styles

The following naming styles are commonly distinguished:

  • b (single lowercase letter)
  • B (single uppercase letter)
  • lowercase
  • lower_case_with_underscores
  • UPPERCASE
  • UPPER_CASE_WITH_UNDERSCORES
  • CapitalizedWords (or CapWords, or CamelCase – so named because of the bumpy look of its letters [4]). This is also sometimes known as StudlyCaps.
  • Note: When using acronyms in CapWords, capitalize all the letters of the acronym. Thus HTTPServerError is better than HttpServerError.
  • mixedCase (differs from CapitalizedWords by initial lowercase character!)
  • Capitalized_Words_With_Underscores (ugly!)

Names to Avoid

Never use the characters ‘l’ (lowercase letter el), ‘O’ (uppercase letter oh), or ‘I’ (uppercase letter eye) as single character variable names.

Function and Variable Names

Function names should be lowercase, with words separated by underscores as necessary to improve readability.

Variable names follow the same convention as function names.

Function and Method Arguments

Always use self for the first argument to instance methods.

Always use cls for the first argument to class methods.

Thus class_ is better than clss

Constants

Constants are usually defined on a module level and written in all capital letters with underscores separating words. Examples include MAX_OVERFLOW and TOTAL..

Programming Recommendations

# Correct:
if foo is not None:
# Wrong:
if not foo is None:
  • Always use a def statement instead of an assignment statement that binds a lambda expression directly to an identifier:
# Correct: 
def f(x): return 2*x

# Wrong:
f = lambda x: 2*x
  • The first form means that the name of the resulting function object is specifically ‘f’ instead of the generic ‘<lambda>’. This is more useful for tracebacks and string representations in general. The use of the assignment statement eliminates the sole benefit a lambda expression can offer over an explicit def statement (i.e. that it can be embedded inside a larger expression)

Additionally, for all try/except clauses, limit the try clause to the absolute minimum amount of code necessary. Again, this avoids masking bugs:

# Correct:
try:
value = collection[key]
except KeyError:
return key_not_found(key)
else:
return handle_value(value)
# Wrong:
try:
# Too broad!
return handle_value(collection[key])
except KeyError:
# Will also catch KeyError raised by handle_value()
return key_not_found(key)

Be consistent in return statements.

# Correct:

def foo(x):
if x >= 0:
return math.sqrt(x)
else:
return None

def bar(x):
if x < 0:
return None
return math.sqrt(x)
# Wrong:

def foo(x):
if x >= 0:
return math.sqrt(x)

def bar(x):
if x < 0:
return
return math.sqrt(x)
  • Use ''.startswith() and ''.endswith() instead of string slicing to check for prefixes or suffixes.

startswith() and endswith() are cleaner and less error prone:

# Correct:
if foo.startswith('bar'):
# Wrong:
if foo[:3] == 'bar':
  • Object type comparisons should always use isinstance() instead of comparing types directly:
# Correct:
if isinstance(obj, int):
# Wrong:
if type(obj) is type(1):
  • For sequences, (strings, lists, tuples), use the fact that empty sequences are false:
# Correct:
if not seq:
if seq:
# Wrong:
if len(seq):
if not len(seq):
  • Don’t write string literals that rely on significant trailing whitespace. Such trailing whitespace is visually indistinguishable and some editors (or more recently, reindent.py) will trim them.
  • Don’t compare boolean values to
# Correct:
if greeting
# Wrong:
if greeting == True:
# Wrong:
if greeting is True:
  • Use of the flow conttatements return/break/continue within the finally suite of a try...finally, where the flow control statement would jump outside the finally suite, is discouraged. This is because such statements will implicitly cancel any active exception that is propagating through the finally suite:
# Wrong: 
def foo():
try:
1 / 0
finally:
return 42

Variable Annotations

  • Annotations for module level variables, class and instance variables, and local variables should have a single space after the colon.
# Correct:

code: int

class Point:
coords: Tuple[int, int]
label: str = '<unknown>'
# Wrong:

code:int # No space after colon
code : int # Space before colon

class Test:
result: int=0 # No spaces around equality sign

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Mohammad Shahil
Mohammad Shahil

Written by Mohammad Shahil

Passionate about the realms of ML and AI. mission to explore, learn, and innovate in these dynamic field. transform data into insights & drive meaningful impact

No responses yet

Write a response