These are the general Python style qualities that we expect your programs to have in order to receive full credit. It is certainly possible to write good code that violates these guidelines, and you may feel free to contact us if you are unclear about or disagree with some of them. However, we do expect you to follow these rules (unless there is an error in this document). In most professional work environments, you are expected to follow that company's style standards. Learning to carefully obey a style guide and writing code with a group of other developers where the style is consistent among them are valuable job skills.
Python's PEP8 style guidelines are a more comprehensive guide to Python programming. The practices outlined on their website are encouraged for use in CS 106AP.
This style guide is a work-in-progress.
All blocks of code within a given function or control flow structure must be distinguished from the preceding code by one indentation level.
Use 4 spaces per indentation level. PyCharm does this automatically for you when you hit the Tab key.
:
`
# bad
if on_beeper(): pick_beeper()
# good
if on_beeper():
pick_beeper()
Avoid lines longer than 80 characters. When you have a long line, break it into two shorter lines. Make sure to maintain correct indentation when doing this (see PEP8 for more details on this).
def foo(): ... # blank line here # blank line here def bar(): ...
Give functions descriptive names, such as walk_to_edge()
or
grab_all_beepers()
. Avoid one-letter names and non-descriptive
names, like x()
or go()
or
function1()
.
Function names should be all lowercase, with words separated by underscores to improve readability.
main()
function a concise summary
As much as possible, avoid having too much of your program's functionality
directly in main()
. When writing your programs, try to break
the problem down into smaller sub-problems. Consider creating functions for
these individual sub-problems rather than calling built-in Karel commands
(like move()
or turn_left()
) directly in
main()
. This makes main()
easy to read and forms a
concise summary of the overall behavior of the program.
If you repeat the same code block two or more times, find a way to remove the redundant code so that it appears only once. For example, you can place it into a function that is called from both places.
Each function should perform a single, clear, coherent task. If you have a single function that is very long or that is doing too much of the overall work, break it into smaller sub-functions. If you try to describe the function's purpose and find yourself using the word "and" a lot, that probably means the function is doing too many things and should be split into sub-functions.
It can be useful to have a short function if it wraps another function with some additional control flow or if it contributes to an easier to understand name.
# bad
def make_left_turn():
turn_left()
# good
def safe_pickup():
if on_beeper():
pick_beeper()
Use `"""` to create a comment heading on each function in your file. The
heading should describe the function's behavior. If your function makes any
assumptions (pre-conditions), mention this in your comments. If it leaves
the program in a certain state when it is done (post-condition), mention
this as well. While you should describe the function's behavior, you don't
need to include great detail about how it is implemented. Don't mention
language-specific details like if it uses an if/else
statement,
etc. Here is an example of a good function header comment:
def jump_hurdle():
"""
Jumps Karel over one hurdle of arbitrary height by walking north over it,
and then back down to his original row.
Pre: Karel is next to a jumpable hurdle.
Post: Karel will be over the hurdle, one column to the right, facing east.
"""
...
If you have sections of code that are lengthy, complex or non-trivial inside a given function, place short inline comments near these code blocks describing what they are doing. Use `#` to start an inline comment.
def jump_hurdle():
"""
Jumps Karel over one hurdle of arbitrary height by walking north over it,
and then back down to his original row.
Pre: Karel is next to a jumpable hurdle
Post: Karel will be over the hurdle, one column to the right, facing east.
"""
# Before jumping the hurdle, make Karel face North
while not facing_north():
turn_left()
...
if
or else
branches
When using if/else
statements, you should not have `if` or
`else` branches that are blank. Rephrase your condition to avoid this.
# bad if not front_is_clear(): # do nothing pass else: move()
# good
if front_is_clear():
move()
if/else
statements to reduce repetition
Move common code out of if/else
statements to reduce repeated
lines of code.
# bad
if on_beeper():
pick_beeper()
move()
turn_left()
turn_left()
move()
else:
move()
turn_left()
turn_left()
move()
# good
if on_beeper():
pick_beeper()
move()
turn_left()
turn_left()
move()
Except for infinite animation (covered later in this class), avoid writing loops that never stop, such as the example below. An infinite loop will cause your program to never stop executing. Replace infinite loops with loops that terminate.
# bad if front is blocked
# but left is clear!
while left_is_clear():
if front_is_clear():
move()
# good
while front_is_clear():
move()
Place a descriptive comment heading on the top of every .py
file
in your project with your name, section leader, a description of that file's
purpose, and a citation of all sources you used to help
write your program. Assume that the reader of your comments is an
intelligent programmer but not someone who has seen this program or
assignment before. The header should describe behavior but should not go
into great detail about how each piece is implemented.
Use `"""` to create a comment heading on each function in your file. The
heading should describe the function's behavior. If your function makes any
assumptions (pre-conditions), mention this in your comments. If it leaves
the program in a certain state when it is done (post-condition), mention
this as well. While you should describe the function's behavior, you don't
need to include great detail about how it is implemented. Don't mention
language-specific details like if it uses an if/else
statement,
etc. Here is an example of a good function header comment:
def jump_hurdle():
"""
Jumps Karel over one hurdle of arbitrary height by walking north over it,
and then back down to his original row.
Pre: Karel is next to a jumpable hurdle.
Post: Karel will be over the hurdle, one column to the right, facing east.
"""
...
If you have sections of code that are lengthy, complex or non-trivial inside a given function, place short inline comments near these code blocks describing what they are doing. Use `#` to start an inline comment.
def jump_hurdle():
"""
Jumps Karel over one hurdle of arbitrary height by walking north over it,
and then back down to his original row.
Pre: Karel is next to a jumpable hurdle
Post: Karel will be over the hurdle, one column to the right, facing east.
"""
# Before jumping the hurdle, make Karel face North
while not facing_north():
turn_left()
...
Do not include obvious or redundant comments about the meaning of statements.
# bad turn_left() # turns Karel counter-clockwise move() # tells Karel to move forward
Your comment headers should be written in complete sentences and should be written in your own words, not copied from other sources (such as copied verbatim from the assignment spec document).
TODOs are a good way to plan out your coding process and to remind yourself
of what code you still need to write in your program. However, you should
remove any # TODO:
comments
from your programs before turning them in.
It is also considered bad style to turn in a program with chunks of code "commented out." It's fine to comment out code as you are working on a program, but if the program is done and the commented-out code is not needed, just remove it.
On Assignment 1, you must limit yourself to the Python features listed in the Karel Reference guide and covered in Lectures 1-4. There are too many features to list them all, but in general, you should match the Python features used in the sample Karel programs in class and not use any Python features outside those. If you are at all confused about what Python features are allowed on Assignment 1, please reach out to the course staff. In particular, you should not use the following:
You are not allowed to use any variables, such as:
# bad
steps = 42;
while steps > 0:
move();
turn_left()
steps = steps - 1
break
, return
, and continue
You should not use any of these keywords on Assignment 1.
Python functions are able to accept parameters that affect their behavior. These are not permitted on Assignment 1.
# bad
def walk(num_steps):
while num_steps > 0:
move()
num_steps = num_steps - 1
If you have an if/else
statement that returns a boolean value
based on a test, just directly return the test's result instead.
// bad
if score1 == score2:
return True
else:
return False
// good
return score1 == score2
If you repeat the same code two or more times that is nearly but not entirely the same, try making a helper function that accepts a parameter to represent the differing part.
// bad
x = foo(10)
y = x - 1
print(x * y)
...
x = foo(15)
y = x - 1
print(x * y)
// good
print(helper(10))
print(helper(15))
...
def helper(p):
x = foo(p)
y = x - 1
return x * y
break
and continue
statements
until the lectures preceding Assignment 3. All parts of this assignmen can be
done
without use of these statements. In general, you should avoid using the
break
or
continue
statements in loops unless absolutely necessary. A
break
can often be avoided by rewriting the loop test or reordering
the statements in/near the loop.
When using if/else
statements, properly choose between various
if
and else
patterns depending on whether the
conditions are related to each other. Avoid redundant or unnecessary
if
tests. Ask yourself if all of the conditions always
need to be checked.
// bad
if points >= 90:
print('You got Gold!')
if points >= 70 and points < 90:
print('You got Silver!')
if points >= 50 and points < 70:
print('You got Bronze!')
...
// good
if (points >= 90):
print('You got Gold!')}
elif points >= 70:
print('You got Silver!')
elif points >= 50:
print('You got Bronze!')
...
Don't test whether a boolean
value is ==
or
!=
to True
or False
. It's not
necessary! Remember that all booleans (expressions and variables) evaluate
to True
or False
on their own.
// bad
if x == True:
...
elif x != True:
...
// good
if x:
...
else:
...
Store data using the appropriate data types (making sure to use the
appropriate
literals as well), and make sure to use
the type conversion functions (str(), int(), float()
) when
necesaary.
If you ask the user to input the temperature outside and you want to use
that value in a calculation, you will have to convert their input
to a float
before you can use it.
Name variables with snake case like_this
. Use
concise, descriptive names for variables that precisely describe
what information they're storing.
Declare variables in the narrowest possible scope. For example, if a
variable
is used only inside a specific if
statement, declare it inside
that if
statement rather than at the top of the function or in
main()
.
Never initialize more than one variable in a single statement.
// bad
a, b, c = 7, -43, 19
// good
a = 7
b = -43
c = 19
If a particular constant value is used frequently in your code, declare it as
a constant, and always refer to the constant in the rest of your code rather
than referring to the corresponding literal value. Name constants in
uppercase with
underscores between words LIKE_THIS
.
DAYS_IN_WEEK = 7 DAYS_IN_YEAR = 365 HOURS_IN_DAY = 24
If you are calling a function and using its result multiple times, save that result in a variable rather than having to call the function multiple times.
// bad
if fahrenheit_to_celsius(90) > 30:
print('It is ' + str(fahrenheit_to_celsius(90)) + ' degrees C outside and that is too hot!')
// good
temp_in_celsius = fahrenheit_to_celsius(90)
if temp_in_celsius > 30:
print('It is ' + str(temp_in_celsius) + ' degrees C outside and that is too hot!')
Python contains a quit()
function that immediately exits your
entire program. You should never call this method in our assignments for any
reason. Your program should always exit naturally by reaching the end of
your main
function.
for
loop
Consider when it would be more appropriate to use a for-each loop as compared to a for-range loop. In particular, avoid unecessary double for-range loops for images unless you explicilty need access to the pixel coordinates provided by the two loops.
// bad
for y in range(image.height):
for x in range(image.width):
pixel = image.get_pixel(x,y)
average = (pixel.red + pixel.blue + pixel.green) // 3
if pixel.green > 1.2 * average:
pixel.blue = 0
pixel.red = 0
pixel.green = 255
// good
for pixel in image:
average = (pixel.red + pixel.blue + pixel.green) // 3
if pixel.green > 1.2 * average:
pixel.blue = 0
pixel.red = 0
pixel.green = 255
If you are writing a function that modifies a mutable data structure (list, dict, etc), you should not also return that data structure.
# bad
def add_feeding_time(d, animal, time):
if animal in d:
d[animal].append(time)
else:
d[animal] = [time]
return d
def main():
d = {}
d = add_feeding_time(d, "hansa", 3)
# good
def add_feeding_time(d, animal, time):
if animal in d:
d[animal].append(time)
else:
d[animal] = [time]
def main():
d = {}
add_feeding_time(d, "hansa", 3)
If you are using proportionate math to do coordinate calculations, save your intermediate and final results in appropriately named variables rather than repeating the same expression in multiple places. This improves readability and reduced the potential for mistakes down the line.
# bad
x1 = width / 2 - DELTA * (width / 2)
y1 = height / 2 - DELTA * (height / 2)
x2 = width / 2 + DELTA * (width / 2)
y2 = height / 2 + DELTA * (height / 2)
canvas.create_line(x1, y1, x2, y2)
# good
midpoint_x = width / 2
midpoint_y = height / 2
x1 = midpoint_x - DELTA * midpoint_x
y1 = midpoint_y - DELTA * midpoint_y
x2 = midpoint_x + DELTA * midpoint_x
y2 = midpoint_y + DELTA * midpoint_y
canvas.create_line(x1, y1, x2, y2)
If you are dealing with nested data structures, you may find it helpful to assign the interior data structure to a variable before performing operations on it.
def should_feed_animal(zoo_info, animal, time): if animal in zoo_info: animal_info = zoo_info[animal] feeding_times = animal_info['times'] return time in feeding_times return False
You should avoid directly acccessing attributes of classes you write. Instead, use getter and setter methods to access the appropriate pieces of information when necessary.
You should design your classes in a such a way that all of the appropriate data (e.g. GWindow, GObjects, etc.) and the methods that act upon that data (e.g. mouse listeners and other methods that modify graphical objects) are all encapsulated within the class. For example, the client of a graphics program should not be implementing its own graphical behavior or directly accessing graphical objects.
Use instance variables (attributes) only when they are required to implement certain functionality, or if it makes sense for that variable to persist outside of any given method. Don't use them to store temporary values used in only a single call to one method, for instance. You should assess whether a variable is needed across mulitple methods before making it an instance variable.
Since Jupyter notebook cells operate in a similar manner to the Python interpreter, the return value of a one-line Python expression will be automatically displayed to screen. Therefore, it is unnecessary to print the output of one-line expressions in a Jupyter notebook.
Tuples are useful when you are storing small amounts of data that is logically bundled together and is potentially of different types. If you are storing a lot of data of the same type, you should choose to use a list instead.
If you are using a lambda function to simply wrap an existing built-in Python function, you should avoid using the lambda and pass the built-in function as an argument directly.
# bad
lst = ["apple", "banana", "pineapple", "berry", "nut"]
sorted_fruits = sorted(lst, key=lambda s: len(s))
# good
lst = ["apple", "banana", "pineapple", "berry", "nut"]
sorted_fruits = sorted(lst, key=len)