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.

Use correct indentation

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.

Place a line break after every `:`
# bad
if on_beeper(): pick_beeper()
# good
if on_beeper():
    pick_beeper()
Avoid long lines

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).

Place two blank lines between functions
def foo():
    ...
                # blank line here         
                # blank line here
def bar():
    ...
Use descriptive names

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.

Keep your 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.

Minimize redundant code

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.

Avoid long functions

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.

Consider short functions for common tasks

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()
Write an overall comment for every function

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.
    """
    ...
Use inline comments within functions to describe complex blocks of code

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()
    ...
Avoid empty 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()
Rewrite 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()
Beware of infinite loops

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()
Always include file comments

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.

Write an overall comment for every function

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.
    """
    ...
Use inline comments within functions to describe complex blocks of code

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()
    ...
Avoid trivial comments

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
Use complete, original sentences

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).

Remove TODOs and commented-out code

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:

Variables

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.

Parameters

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
Same as for the previous assignment.
Everything from the previous assignment, plus:

Return boolean expressions directly

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
Eliminate redundancy by introducing functions with parameters

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
Everything from the previous assignment, plus:

Avoid using break and continue
We won't explicitly discuss 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.
Avoid unnecessary control flow checks

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!')
...
                
Avoid testing boolean values for equality

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:
    ...
                
Same as for the previous assignment.
Make smart type decisions and utilize type conversions

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.

Follow variable naming conventions

Name variables with snake case like_this. Use concise, descriptive names for variables that precisely describe what information they're storing.

Appropriately scope variables

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().

Create one variable per line of code

Never initialize more than one variable in a single statement.

// bad
a, b, c = 7, -43, 19
// good
a = 7
b = -43
c = 19
Use constants when appropriate

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
Save function call results in a variable

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!')
You may now use all the Python features we have learned about in class. However, you may not use the following:

quit()

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.

Same as for the previous assignment.
Same as for the previous assignment.
Everything from the previous assignment, plus:

Choose the right 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
Same as for the previous assignment.
Same as for the previous assignment.
Same as for the previous assignment.
Same as for the previous assignment.
Avoid needlessly returning data structures that can be modified directly

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)  

Same as for the previous assignment.
Same as for the previous assignment.
Save the result of coordinate calculations in variables

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)

Assign interior nested data structures to variables

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 are not allowed to use the campy library for Assignment 4.
Same as for the previous assignment.
Same as for the previous assignment.
Same as for the previous assignment.
Same as for the previous assignment.
Same as for the previous assignment.
Do not access object attributes directly in the client (abstraction)

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.

Keep related tasks bundled together within the appropriate class (encapsulation)

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.

Avoid unnecessary instance variables (attributes)

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.

Don't print the output of one-line expressions in a Jupyter notebook

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.

Use tuples to store small amounts of related data

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.

Avoid unnecessary lambda functions

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)
Same as for the previous assignment.