Guidelines for writing Python code in learning resources
Largely based on guidelines in PEP8
Good Python code should be easy to read, explicit not implicit, and things should be well named.
Sometimes it's appropriate to neglect better coding practices to accommodate for younger or less experienced programmers. This is ok. Some exercises start with simpler code and build up explaining new concepts, other exercises only use the simpler code, as they are aimed at young people or beginners and only basic concepts are used.
However, much of this guide is regarding presentation rather than actual use of code. Following the presentation guidelines should be encouraged.
Good Python code:
import random
from time import sleep
def number_is_over_ten(n):
return n > 10
numbers = [1, 5, 7, 11, 21]
number = random.choice(numbers)
for n in range(number):
if number_is_over_ten(n):
print("%s is over ten" % n)
else:
print("%s is not over ten" % n)
Bad Python code:
import os, time, random, RPi.GPIO as GPIO, numpy as n, sys, picamera as dave
from pygame import *
def Bob(): print "Hello"
def checkIt(thing):
if thing>0:
if thing+1:
return 1
myVAR=1
while myVAR<10:
print myVar
if checkIt(myVAR):print("ok")
else:print("not ok")
i=i+1
Python 3 should be the target version.
If a library is unavailable with Python 3, it should be reported to be investigated for porting. If Python 2 is used then code should be Python 3 styled where possible (e.g. print("Hello world")
rather than print "Hello world"
) as this still works in both.
-
Each import should be on a new line
import time import random
not:
import time, random
-
Line space between groups of imports if many imports are used
import time import random import picamera import RPi.GPIO import pygame.mixer import pygame.locals
-
from module import *
should be avoidedImports should be explicit, not implicit. Consider the example:
from module_a import * from module_b import * some_function()
It is unclear whether
some_function
was defined inmodule_a
ormodule_b
.from module_a import some_function, some_other_function from module_b import another_function some_function()
Now it is clear
some_function
belongs tomodule_a
.Alternatively, import the whole module so all functions are namespaced:
import module_a import module_b module_a.some_function()
This example also makes it clear where the function comes from.
-
Specific imports are preferred
Consider the example:
from picamera import PiCamera
While this is longer than simply
import picamera
, it allows following references toPiCamera
to be shorter and less repetitive.from picamera import PiCamera with PiCamera() as camera: ...
is better than:
import picamera with picamera.PiCamera() as camera: ...
But sometimes even if only one function is used, it's better left namespaced.
from time import sleep sleep(1)
is ok, and preferable.
import time time.sleep(1)
is ok, but less preferable.
Whereas
import random numbers = [1, 5, 7] number = random.choice(numbers)
is fine, but
from random import choice numbers = [1, 5, 7] number = choice(numbers)
is less clear.
-
Renaming an import is ok, but keep it sensibly named.
import numpy as np
is ok.
import time as sleep
is confusing.
-
Avoid unnecessary renaming
import RPi.GPIO as GPIO import mcpi.minecraft as minecraft
These are unnecessary renames as the module reference is being renamed to what it is already named. The (easier) preferred method is:
from RPi import GPIO from mcpi import minecraft
Case should adhere to the following style:
Type | Case | Examples |
---|---|---|
Variables and all objects | Snake case | led , elec_hi_snare |
Functions | Snake case | setup_gpio |
Class names | Title case | Dog , GameWindow , GameOfLife |
Constants (variables intended not to be changed) | All caps | BLACK , WHITE , HEIGHT , WIDTH |
Magic numbers are those used with no explanation. Variables should be used instead.
Bad:
GPIO.setup(17, GPIO.IN, GPIO.PUD_UP)
GPIO.wait_for_edge(17, GPIO.FALLING)
Good:
button = 17
GPIO.setup(button, GPIO.IN, GPIO.PUD_UP)
GPIO.wait_for_edge(button, GPIO.FALLING)
This means if the button pin is changed, it only needs changing in one place.
Unused variables should be removed or corrected. They make code hard to read, and sometimes misleading. For example:
def setup_button(pin):
GPIO.setup(17, GPIO.IN)
setup_button(13)
The last line here claims to set up pin 13. The function takes pin
as an argument but ignores it and sets up pin 17 regardless of what was passed in.
The code should read:
def setup_button(pin):
GPIO.setup(pin, GPIO.IN)
setup_button(13)
-
Spaces not tabs
-
Four spaces per tab.
You don't have to hit space four times - you should configure your editor to insert four spaces when you hit the tab key.
Example:
for i in range(10): if n < 2: return False if n == 2: return True if n % 2 == 0: return False for i in range(3, int(n**0.5) + 1, 2): if n % i == 0: return False return True
-
A single space should be used on each side of an operator for readability.
Good:
a = 1 b = 2 c = a + b print(c % 3 == 0)
Bad:
a=1 b=2 c=a+b print(c%3==0)
-
Sometimes spaces can be left out to aid readability, for example in this case spaces would usually be around the
*
multiplication operator but can be left out for clarity of priority:x*x + y*y
is better than:
x * x + y * y
-
Avoid extra spaces immediately inside parentheses, brackets or braces:
Bad:
call (1) dct [1] dct[ 1 ]
Good:
call(1) dct[1] dct[1]
-
Also before a comma or colon:
Bad:
if x == 4 : print(x , y)
Good:
if x == 4 : print(x, y)
-
Multiple spaces around operators should not be used unless necessary for alignment aiding readability.
a = 10 b = 16 angle = 2.51
is fine
a = 10 b = 16 angle = 2.51
is unnecessary.
However, in this case
on = '10101010' off = '01010101'
The misalignment makes comparison difficult and
on = '10101010' off = '01010101'
is better.
-
The same applies to nested lists and such:
pixels = [[r, g, r], [g, g, g], [r, g, r]]
is ok, but
pixels = [ [r, g, r], [g, g, g], [r, g, r], ]
is more readable as it's presented practically.
Line length should not exceed 79 characters and usually should be shortened or rearranged to fit this limit to aid readability and avoid horizontal scrolling or line wrapping.
Bad:
result = some_function_that_takes_arguments('foo', 'bar', 'spam', 'eggs', 'alice', 'bob')
Good:
result = some_function_that_takes_arguments(
'foo', 'bar', 'spam', 'eggs', 'alice', 'bob'
)
or:
result = some_function_that_takes_arguments(
'foo',
'bar',
'spam',
'eggs',
'alice',
'bob'
)
The with
keyword should be used where possible, for example when opening files. This ensures cleanup is completed.
Bad:
f = open('file.txt', 'r')
text = f.read()
print(text)
f.close()
Good:
with open('file.txt', 'r') as f:
text = f.read()
print(text)
xxx
When catching exceptions, mention specific exceptions whenever possible instead of using a bare except
clause.
Bad:
try:
a = dct['a']
except:
a = lookup('a')
Good:
try:
a = dct['a']
except KeyError:
a = lookup('a')
A bare except
clause will catch SystemExit
and KeyboardInterrupt
exceptions, making it harder to interrupt a program with Ctrl-C
, and can disguise other problems.
Use comments to explain code, but consider the following:
-
Are you only using the comment to repeat what the code says? Don't do that. Example:
import time # import the time module a = 1 # assign the value 1 to a
-
Would a well named variable help?
Bad:
GPIO.setup(17, GPIO.IN) # pin 17 is the button
Good:
button = 17 GPIO.setup(button, GPIO.IN)
This also means if you change the button pin you only change it once in the whole program
-
Would a function be better?
Bad:
# check if the number is prime for i in range(3, int(101**0.5) + 1, 2): if n % i == 0: print("no") break print("yes")
Good:
def is_prime(n): ... n = 101 if is_prime(n): print("yes") else: print("no")
-
If a function is used, would a better function name help? Examples:
Bad:
# check if n is prime if my_function(n): print("yes")
Good:
if is_prime(n): print("yes")
Dictionaries should be used in place of large if/else blocks
Don't repeat yourself.