Python Style Guide
A mostly reasonable approach to Python
This is a modern Python style guide born from numerous open source projects and collaborative discussions. You can use this concise guide on its own or with PEP8. This guide focuses on the cutting edge of Python 3.
A style guide must constantly adapt to newer norms, and its basis is community. If you find this useful, please share. If you have updates to contribute, please submit a PR so that the community can adopt them.
Other Style Guides
1.1 Primitives: When you access a primitive type you work directly on its value.
string
number
boolean
None
foo = 1
bar = foo
bar = 9
print(foo, bar) # => 1, 9
1.2 Complex: When you access a complex type you work on a reference to its value.
dict
list
function
foo = [1, 2]
bar = foo
bar[0] = 9
print(foo[0], bar[0]) # => 9, 9
2.1 Use CONST
for all of your references; avoid using var
. Python does not have constant
type, so you need to observe the convention using UPPERCASE for constants and never modify them.
Why? This ensures that you can’t reassign your references, which can lead to bugs and difficult to comprehend code.
# bad
foo = 1
bar = 2
# good
FOO = 1
BAR = 2
3.1 Use the literal syntax for dictionary creation.
# bad
item = dict()
# good
item = {}
3.2 Use computed key names when creating dictionaries with dynamic key names.
Why? They allow you to define all the key of a dictionary in one place.
def get_key(k):
return f'a key named {k}'
# bad
obj = {
'id': 5,
'name': 'San Francisco',
}
obj[get_key('enabled')] = True
# good
obj = {
'id': 5,
'name': 'San Francisco',
get_key('enabled'): True,
}
3.3 Prefer the dictionary spread operator over copy()
to shallow-copy and extend dictionaries.
# bad
original = {'a': 1, 'b': 2}
clone = original.copy()
clone.update({'c': 3})
# good
original = {'a': 1, 'b': 2}
clone = {**original, 'c': 3}
# good
original = {'a': 1, 'b': 2}
original_2 = {'c': 3, 'd': 4}
long_clone = {**original, **original_2, 'e': 5}
3.4 Use line breaks after open and before close dictionary braces only if a dictionary has multiple lines.
# bad - single item will not exceed one line
single_map = {
'a': 1,
}
# bad - single line
item_map = {
'a': 1, 'b': 2, 'c': 3,
}
# good
single_map = {'a': 1}
item_map = {
'a': 1,
'b': 2,
'c': 3,
}
3.5 Use dict.get(key)
to get properties.
Why? Getting via
dict[key]
will break on missing key, and requires bloated code to guard against.
item_map = {
'a': 1,
'b': 2,
}
# bad - throws error
item_map['c']
# bad - bloated code
try:
item_map['c']
except KeyError:
item_map['c'] = 3
return item_map['c']
# good
item_map.get('c')
# good
item_map['c'] = item_map.get('c') or 3
4.1 Use the literal syntax for list creation.
# bad
items = list()
# good
items = []
4.2 Use list spreads *
to copy and extend lists.
# bad
items = ['a', 'b']
clone = items.copy() + ['c']
# good
items = ['a', 'b']
clone = [*items, 'c']
# good
items = ['a', 'b']
items_2 = ['c', 'd']
clone = [*items, *items2, 'e']
4.3 Use line breaks after open and before close list brackets only if a list has multiple lines.
# bad - single line
items = [
[0, 1], [2, 3], [4, 5],
]
# bad - no line break after bracket
dict_list = [{
'id': 1
}, {
'id': 2
}]
number_list = [
1, 2,
]
# good
items = [[0, 1], [2, 3], [4, 5]]
dict_list = [
{'id': 1},
{'id': 2},
]
number_list = [
1,
2,
]
5.1 Use list destructuring.
items = [1, 2, 3, 4, 5]
# bad
first = items[0]
second = items[1]
# good
first, second, *tail = items
first, second, *rest, last = items
6.1 Use single quotes ''
for strings.
Why? Less escaping for double quote
""
, less bloat, and makes code more searchable.
# bad
name = "Capt. Janeway"
# bad
json_string = "{\"a\": 1}"
# bad - f string should contain interpolation or newlines
name = f'Capt. Janeway'
# good
name = 'Capt. Janeway'
# good
json_string = '{"a": 1}'
6.2 Strings that cause the line to go over 79 characters should not be written across multiple lines using string concatenation.
Why? Broken strings are painful to work with and make code less searchable.
# bad
error_msg = 'This is a super long error that was thrown because \
of Batman. When you stop to think about how Batman had anything to do \
with this, you would get nowhere \
fast.'
# bad
error_msg = 'This is a super long error that was thrown because ' + \
'of Batman. When you stop to think about how Batman had anything to do ' + \
'with this, you would get nowhere fast.'
# good
error_msg = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.'
6.3 When programmatically building up strings, use template strings instead of concatenation.
Why? Template strings give you a readable, concise syntax with proper newlines and string interpolation features.
# bad
def say_hi(name):
return 'How are you, ' + name + '?'
# bad
def say_hi(name):
return ''.join(['How are you, ', name, '?'])
# bad
def say_hi(name):
return f'How are you, { name }?'
# good
def say_hi(name):
return f'How are you, {name}?'
For python < 3.6, convert f-string to template string by
format()
:
a = 1
b = 2
c = 3
# python >=3.6
f'a: {a} b: {b} c: {c}'
# python <3.6
'a: {a} b: {b} c: {c}'.format(a=a, b=b, c=c)
param = {'a': a, 'b': b, 'c': c}
'a: {a} b: {b} c: {c}'.format(**param)
'a: {} b: {} c: {}'.format(a, b, c)
eval()
on a string, it opens too many vulnerabilities.6.5 Do not unnecessarily escape characters in strings.
Why? Backslashes harm readability, thus they should only be present when necessary.
# bad
foo = '\'this\' \i\s \"quoted\"'
# good
foo = '\'this\' is "quoted"'
foo = f'my name is "{name}"'
7.1 Use default parameter syntax rather than mutating function arguments.
# really bad
def do_something(opt):
# No! We shouldn’t mutate function arguments.
# Double bad: if opt is falsy it'll be set to an object which may
# be what you want but it can introduce subtle bugs.
opt = opt or 'foo'
# ...
# still bad
def do_something(opt):
if (opt is None):
opt = 'foo'
# ...
# good
def do_something(opt='foo'):
# ...
7.2 Do not use complex data type as default parameter.
Why? Variable to a complex type is a reference, and so the single instance will be modified.
# bad
def init_list(value, new_list=[]):
new_list.append(value)
return new_list
init_list(1)
# => [1]
init_list(2)
# => [1, 2], instead of the new init [2]
# good
def init_list(value, new_list=None):
if new_list is None:
new_list = []
new_list.append(value)
return new_list
init_list(1)
# => [1]
init_list(2)
# => [2]
7.3 No spacing in a function signature.
Why? Consistency is good, and eases code search.
# bad
def foo(a): print(a)
def bar (b): print(b)
# good
def foo(a): print(a)
def bar(b): print(b)
7.4 Never reassign parameters.
Why? Reassigning parameters can lead to unexpected behavior.
# bad
def fn_1(a):
a = 1
# ...
def fn_2(a):
if (!a): a = 1
# ...
# good
def fn_3(a):
b = a or 1
# ...
def fn_4(a=1):
# ...
7.5 Functions with multiline signatures, or invocations, should be indented just like every other multiline list in this guide: with each item on a line by itself, with a trailing comma on the last item.
# bad
def some_fn(foo,
bar,
baz):
# ...
# good
def some_fn(
foo,
bar,
baz,
):
# ...
# bad
some_fn(foo,
bar,
baz)
# good
some_fn(
foo,
bar,
baz,
)
7.6 Call function with parameters by specifying their names.
Why? Clarity of parameters and future-proofing. When updating source code function parameters, it can be done reliably with minimal propagation.
def move(x, y, roll=False):
# ...
# bad - unclear what the params mean
move(1, 0, True)
# good
move(x=1, y=0, roll=True)
# later when updating method, no need to propagate function calls since they will auto-assume z=0 reliably
def move(x, y, z=0, roll=False):
# ...
7.7 Break down code logic into digestible chunks, and refactor a lot.
Why? Other programmers and your future self will thank you for writing understandable code.
# bad - short but extremely confusing
def long_logic():
return [a for a in small_list for small_list in large_matrix if len(small_list) else ['replacement'] if len(a) > 2]
# good - longer but very clear
def long_logic():
result = []
for small_list in large_matrix:
if len(small_list) > 0:
used_list = small_list
else:
used_list = ['replacement']
for a in used_list:
if len(a) > 2:
result.append(a)
return result
8.1 Avoid duplicate class members.
Why? Duplicate class member declarations will silently prefer the last one - having duplicates is almost certainly a bug.
# bad
class Foo():
def bar(): return 1
def bar(): return 2
# good
class Foo():
def bar(): return 1
9.1 Do not use wildcard imports.
Why? To prevent namespace pollution and conflicts, and to know which modules your variables or functions come from.
# bad
from common.util import *
# good
from common import util
Why? Performance, reliability, containment. If a module breaks, your code that should be isolated from the module will break too. This causes more errors and makes it harder to debug.
9.3 Only import from a path in one place.
Why? Having multiple lines that import from the same path can make code harder to maintain.
# bad
from foo import bar
# ... some other imports
from foo import baz, qux
# good
from foo import bar, baz, qux
# good
from foo import (
bar,
baz,
qux,
)
9.4 Put all import
s above non-import statements.
Why? Since
import
s are hoisted, keeping them all at the top prevents surprising behavior.
# bad
import a_module
from b_module import foo
foo.init()
from c_module import bar
# good
import a_module
from b_module import foo
from c_module import bar
foo.init();
9.5 Sort the import
s by import
then from
, and sort alphabetically.
Why?
import
are often more generic thatfrom
; sort to ease manual inspection and for maintainability.
# bad
from a_module import foo
import e_module
import b_module
from c_module import c_fn, b_fn
# good
import b_module
import e_module
from a_module import foo
from c_module import b_fn, c_fn
9.6 Multiline imports should be indented just like multiline list and dictionary literals.
Why? The parentheses follow the same indentation rules as every other bracket or brace block in the style guide, as do the trailing commas.
# bad
from a_module import long_name_a, long_name_b, long_name_c, long_name_d
# good
from a_module import (
long_name_a,
long_name_b,
long_name_c,
long_name_d,
)
(Pending)
11.1 Use UPPERCASE to declare constants, and observe the convention - do not modify them in the program. Python has no constant
type, so it must be observed manually.
# bad
a_constant = 1
os.environ['py_env'] = 'development'
# good
A_CONSTANT = 1
os.environ['PY_ENV'] = 'development'
11.2 Declare one constant per line.
Why? For clarity, and it’s easier to add/remove declarations this way, and with minimal git-diffs. You can also step through each declaration with the debugger, instead of jumping through all of them at once.
# bad
FOO, BAR, BAZ = 1, 2, 3
# good
FOO = 1
BAR = 2
BAZ = 3
11.3 Group all your CONST
s and then group all your var
s.
Why? For clarity and ease of reference. This is also helpful when later on you might need to assign a variable depending on one of the previous assigned variables.
# bad
FOO = 1
counter = 0
BAR = 2
length = counter
# good
FOO = 1
BAR = 2
counter = 0
length = counter
11.4 Assign variables with the minimally sufficient scope at where you need them, but place them in a reasonable place.
Why? Prevent variable scope-leak and conflicts
# bad - leak to sibling
counter = 0 # mean to count group_a only
for list_a in group_a:
counter += len(list_a)
for list_b in group_b:
counter += len(list_b)
# bad - leak into smaller scope
counter = 0 # mean to count within groups
for group in super_group:
counter += len(group)
for list in group:
counter += len(list)
# good
counter_a = 0
for list_a in group_a:
counter_a += len(list_a)
counter_b = 0
for list_b in group_b:
counter_b += len(list_b)
# good
group_counter = 0 # mean to count within groups
for group in super_group:
group_counter += len(group)
list_counter = 0 # mean to count within lists
for list in group:
list_counter += len(list)
11.5 Prepend underscore _
when naming variables that are unused. Also a part of PEP8.
Why? To be aware of data usage and side effects.
# bad
first, unused, last = [1, 2, 3]
# bad - finder is not used though expected to be
for finder, replacer in some_map.items():
do_something_without_key(replacer)
# bad - lose track of what the first key is
for _, replacer in some_map.items():
do_something_without_key(replacer)
# good
first, _unused, last = [1, 2, 3]
# good - we know what the variable is, and it is unused
for _finder, replacer in some_map.items():
do_something_without_key(replacer)
12.1 Use concise boolean conditionals, refactor long compound statements.
Why? Long boolean statements are hard to read and understand.
# bad
if (can_move_x() and can_move_y() or is_light() or is_dry() or has_high_drag()):
execute_operation_tumbleweed()
else:
execute_operation_cactus()
# good
can_move = can_move_x() and can_move_y()
movable = is_light() or is_dry() or has_high_drag()
if (can_move or movable):
execute_operation_tumbleweed()
else:
execute_operation_cactus()
12.2 Be direct with booleans, avoid unnecessary negations.
Why? Negations are harder to understand and longer to write.
# bad
if not a_is_legal():
do_b()
else:
do_a()
# good
if a_is_legal():
do_a()
else:
do_b()
12.3 Use shortcuts for booleans, but explicit comparisons for strings and numbers.
# bad
if is_valid == true:
# ...
# good
if is_valid:
# ...
# bad
if name:
# ...
# good
if name != '':
# ...
# bad
if len(a_list):
# ...
# good
if len(a_list) > 0:
# ...
12.4 Ternaries should not be nested and generally be single line expressions.
# bad
foo = 'bar' if maybe_1 > maybe_2 else 'baz' if value_1 > value_2 else None
# best
maybe_none = 'baz' if value1 > value2 else None
foo = 'bar' if maybe1 > maybe2 else maybe_none
12.5 Avoid unneeded ternary statements.
# bad
foo = a if a else b
bar = True if c else False
baz = False if c else True
# good
foo = a or b
bar = c
baz = not c
13.1 Use '''...'''
for multi-line comments.
# bad
def make(tag):
# make() returns a new element
# based on the passed in tag name
#
# @param {string} tag
# @return {Element} element
# ...
return element
# good
def make(tag):
'''
make() returns a new element
based on the passed in tag name
@param {string} tag
@return {Element} element
'''
# ...
return element
13.2 Use #
for single line comments. Place single line comments on a newline above the subject of the comment. Put an empty line before the comment unless it’s on the first line of a block.
# bad
active = True # is current tab
# good
# is current tab
active = True
# bad
def get_type():
print('fetching type...')
# set the default type to 'no type'
type = self.type or 'no type'
return type
# good
def get_type():
print('fetching type...')
# set the default type to 'no type'
type = self.type or 'no type'
return type
# also good
def get_type():
# set the default type to 'no type'
type = self.type or 'no type'
return type
13.3 Start all comments with a space to make it easier to read.
# bad
#is current tab
active = True
# good
# is current tab
active = True
13.4 Prefixing your comments with TODO
helps yourself and other developers be aware of items to revisit or implement. It keeps the issues visible and easy to find. These are different than regular comments because they are actionable.
def complex_calculator():
compute_basic()
# TODO figure out improvements to the logic
return compute_core_logic()
14.1 Use soft tabs (space character) set to 4 spaces as per PEP8.
# bad
def foo():
∙∙return bar
# bad
def foo():
∙return bar
# good
def foo():
∙∙∙∙return bar
14.2 Set off operators with spaces.
# bad
x=y+5
# good
x = y + 5
14.3 End files with a single newline character.
# bad
import util
# ...
def foo():
return bar
# bad
import util
# ...
def foo():
return bar↵
↵
# bad
import util
# ...
def foo():
return bar↵
14.4 Leave a blank line after blocks and before the next statement.
# bad
if foo:
return bar
return baz
# good
if foo:
return bar
return baz
# bad
results = [
fn_1(),
fn_2(),
]
return results
# good
results = [
fn_1(),
fn_2(),
]
return results
14.5 Do not pad your blocks with blank lines.
# bad
def bar():
print(foo)
# bad
if (baz):
print(qux)
else:
print(foo)
# good
def bar():
print(foo)
# good
if (baz):
print(qux)
else:
print(foo)
14.6 Do not add spaces inside parentheses.
# bad
def bar( foo ):
return foo
# good
def bar(foo):
return foo
# bad
if ( foo and fux ):
print(foo)
# good
if (foo and fux):
print(foo)
14.7 Do not add spaces inside brackets or braces.
# bad
foo = [ 1, 2, 3 ]
print(foo[ 0 ])
bar = { 'a': 1 }
# good
foo = [1, 2, 3]
print(foo[0])
bar = {'a': 1}
14.8 Avoid having lines of code that are longer than 79 characters (including whitespace) as per PEP8. Note: per above, long strings are exempt from this rule, and should not be broken up.
Why? This ensures readability and maintainability.
# bad
foo = nested_object and nested_object.foo and nested_object.foo.bar and nested_object.foo.bar.baz and nested_object.foo.bar.baz.quux and nested_object.foo.bar.baz.quux.xyzzy
# bad
http_call({'method': 'POST', 'url': 'https://airbnb.com/', 'data': {name: 'John', 'age': 20}})
# good
foo = (nested_object and
nested_object.foo and
nested_object.foo.bar and
nested_object.foo.bar.baz and
nested_object.foo.bar.baz.quux and
nested_object.foo.bar.baz.quux.xyzzy)
# good
http_call({
'method': 'POST',
'url': 'https://airbnb.com/',
'data': {name: 'John', 'age': 20},
})
15.1 Leading commas: Nope.
# bad
story = [
once
, upon
, a_time
]
# good
story = [
once,
upon,
a_time,
]
# bad
hero = {
'first_name': 'Ada'
, 'last_name': 'Lovelace'
, 'birth_year': 1815
, 'super_power': 'computers'
}
# good
hero = {
'first_name': 'Ada',
'last_name': 'Lovelace',
'birth_year': 1815,
'super_power': 'computers',
}
15.2 Additional trailing comma: Yup.
Why? This leads to cleaner git diffs during code change.
# bad - git diff without trailing comma
hero = {
'first_name': 'Ada',
- 'last_name': 'Lovelace'
+ 'last_name': 'Lovelace',
+ 'super_power': 'computers',
}
# good - git diff with trailing comma
hero = {
'first_name': 'Ada',
'last_name': 'Lovelace',
+ 'super_power': 'computers',
}
# bad
hero = {
'first_name': 'Ada',
'last_name': 'Lovelace'
}
heroes = [
'Batman',
'Superman'
]
# good
hero = {
'first_name': 'Ada',
'last_name': 'Lovelace',
}
heroes = [
'Batman',
'Superman',
]
# bad
def create_hero(
first_name,
last_name,
superpower
):
# ...
# good
def create_hero(
first_name,
last_name,
superpower,
):
# ...
# good - note that a comma must not appear after a "spread" element
def create_hero(
first_name,
last_name,
superpower,
**kwargs
):
# ...
# bad
create_hero(
first_name,
last_name,
superpower
)
# good
create_hero(
first_name,
last_name,
superpower,
)
# good - note that a comma must not appear after a "spread" element
create_hero(
first_name,
last_name,
superpower,
**kwargs
)
16.1 Use snake_case when naming variables, functions, and instances. Use it for file names too as they will be used in imports.
# bad
import myModule
OBJEcttsssss = {}
thisIsMyObject = {}
def thisIsMyFunction():
# good
import my_module
objects = {}
this_is_my_object = {}
def this_is_my_function():
16.2 Use PascalCase only when naming classes.
# bad
class prioritizedMemoryReplay():
# ...
memory = prioritizedMemoryReplay()
# good
class PrioritizedMemoryReplay():
# ...
memory = PrioritizedMemoryReplay()
16.3 Avoid single letter names. Use descriptive and meaningful names - tell what the function does, or what data type an object is. Use description_object
instead of object_description
.
# bad
def q():
# ...
# good
def query():
# ...
# bad - no convention to know what data type it is
df_raw_data = pd.DataFrame(some_data)
id_map_num = {'a': 1, 'b': 2}
# good - convention to tell data type by the last term
raw_data_df = pd.DataFrame(some_data)
id_num_map = {'a': 1, 'b': 2}
# bad - meaningless names, lost context
LIST_1 = ['Jack', 'Alice', 'Emily']
# ... many lines of code later
for item in LIST_1:
register_human(item)
# good
NAME_LIST = ['Jack', 'Alice', 'Emily']
# ... many lines of code later
for name in NAME_LIST:
register_human(name)
16.4 Avoid using close naming to prevent typo and confusion.
# bad
objects = ['rock', 'paper', 'scissors']
for object in objects:
register_item(object)
close_box(objects)
# okay
objects = ['rock', 'paper', 'scissors']
for obj in objects:
register_item(obj)
close_box(objects)
# best
object_list = ['rock', 'paper', 'scissors']
for object in object_list:
register_item(object)
close_box(object_list)
16.5 Use singular or base words in naming; avoid using plural and instead append singular with the data type.
Why? To prevent inconsistencies and second-guesses when using variables. Also, plurals are 1 letter away from a typo, are hard to read, and are ambiguous on the data type.
# bad
def moves_object(x, y):
# ...
# good
def move_object(x, y):
# ...
# bad - inconsistent naming for same data type and usage
teacher = ['Michael']
students = ['Jack', 'Alice', 'Emily']
books = pd.DataFrame({'title': ['lorem', 'ipsum']})
for t in teacher:
register_human(t)
for student in students:
register_human(student)
for book in books:
register_item(book) # wrong; iterate column name instead of book
# good
teacher_list = ['Michael']
student_list = ['Jack', 'Alice', 'Emily']
book_df = pd.DataFrame({'title': ['lorem', 'ipsum']})
for teacher in teacher_list:
register_human(teacher)
for student in student_list:
register_human(student)
# naming as df suggests it shall be treated as a dataframe
for _idx, book in book_df.iterrow():
register_item(book)
16.6 Use singular naming for modules and source files.
# bad
from commons import utils
utils.read_string()
# good
from common import util
util.read_string()
16.7 Use abbreviations if they are clear and make for more readable and writable code.
Why? Names are for humans, so always make code readable and easy to spell.
# bad
flight_prerequisites_checklist = ['landing gear', 'engine', 'flaps']
initialize_flight(flight_prerequisites_checklist)
# good
flight_prereq_checklist = ['landing gear', 'engine', 'flaps']
init_flight(flight_prereq_checklist)
16.8 Use simple, concise names over long, explicit ones.
Why? Names are for humans to read, and should make the code clean.
# bad - explicit Java-style naming clutters code and harms readability
poscode_to_city_name_map = {11223: 'brooklyn'}
poscode_to_city_name_to_state_name_map = map_city_to_state(poscode_to_city_name_map)
poscode_to_city_name_to_state_name_to_country_map = {}
# good - understandable and fast to read
poscode_city_map = {11223: 'brooklyn'}
poscode_state_map = map_city_to_state(poscode_city_map)
poscode_country_map = {}
17.3 Use direct assertations and explicit comparisons; avoid negations.
Why? Make the expected result for comparison explicit and avoid any implicit type conversion.
# bad - Other values can be falsy too: `[], 0, '', None`
assert not result
assert result_list
# good
assert result == False
assert len(result_list) > 0
Learning Python
Tools
Other Style Guides
We encourage you to fork this guide and change the rules to fit your team’s style guide. Below, you may list some amendments to the style guide. This allows you to periodically update your style guide without having to deal with merge conflicts.