20. Clean code and OOP tasks - MantsSk/CA_PTUA14 GitHub Wiki

PEP8

As any programming language has naming standards and standards for the ways it does certain things - Python is not an exception. With the naming standards we communicate better with other developers on what certain things must be and what they should not. It was written in 2001 by Guido van Rossum, Barry Warsaw, and Nick Coghlan. The primary focus of PEP 8 is to improve the readability and consistency of Python code.

PEP stands for Python Enhancement Proposal, and there are several of them. A PEP is a document that describes new features proposed for Python and documents aspects of Python, like design and style, for the community.

Why do we need it, simply as the Zen of Python states: Readability counts. There are many benefits of standardizing the way people write code:

  1. If you follow a certain standard it is easier for others to understand your code and the other way around.
  2. Improved code readability.
  3. Makes code clean.

These all sound like synonyms and of course they are, but let's see a few examples where the naming standards .

NOTE :

Never ๐Ÿ›‘ use l, O single letter names as they might be mistakes for 1 and 0:

O = 2  # This may look like you're trying to reassign 2 to zero

Naming conventions

It is never too much of revisiting naming conventions as it really separates a good python programmer from great one. You might understand how language works but if you do not follow the naming conventions, nobody will understand you code to full extent.

Attempt #1 #2
Function Use a lowercase word or words. Separate words by underscores to improve readability. function, my_function
Variable Use a lowercase single letter, word, or words. Separate words with underscores to improve readability. variable, my_variable
Class Start each word with a capital letter. Do not separate words with underscores. This style is called camel case or pascal case. Model, MyClass
Method Use a lowercase word or words. Separate words with underscores to improve readability. class_method, method
Constant Use an uppercase single letter, word, or words. Separate words with underscores to improve readability. CONSTANT, MY_CONSTANT, MY_LONG_CONSTANT
Module Use a short, lowercase word or words. Separate words with underscores to improve readability. module.py, my_module.py
Package Use a short, lowercase word or words. Do not separate words with underscores. package, mypackage

Variable naming

Sometimes it ain't that easy to come up with good variable naming and thus many jokes are written about it:

IMG

Why is this important? Readability.

Imagine getting to read code like this:

x = input()
y, z = x.split()
print(z, y, sep=', ')

Is it easy to understand what is happening? What is the aim of the code? What is it supposed to do? Now compare it with:

name = input()
first_name, last_name = name.split()
print(last_name, first_name, sep=', ')

Which Zen of Python line does this reflect the best? Discuss.

The same rules apply to everything, functions, classes etc. Do not ๐Ÿ›‘ go around abbreviating names like an example below, because this just brings chaos and misinterpretation of what was actually meant:

def db(x):
  return x * 2

after some time it might not be obvious what db does, it was when you were writing the function and using it, but after a week or two, you see usage of db in your code and it is not that obvious anymore. Instead you can do something like:

def multiply_by_two(x):
    return x * 2

Imagine now reviewing the code of your colleague:

pi = 3.14
CustomerName = "John Doe"
CUSTOMER_AGE = 15

if later on these variable were used in the code, what would be the expectations of the developers for these variables? How would You do it? Discuss by looking into naming conventions table above.

Documentation strings

Documentation strings, or docstrings, are strings enclosed in double (""") or single (''') quotation marks that appear on the first line of any function, class, method, or module

They are required for more complex public modules, functions and methods. The ones that are exposed for your library consumers to understand how to use certain pieces of code

from typing import Tuple
def quadratic(a, b, c, x):
    """Solve quadratic equation via the quadratic formula.

    A quadratic equation has the following form:
    ax**2 + bx + c = 0

    There always two solutions to a quadratic equation: x_1 & x_2.
    """
    x_1 = (- b+(b**2-4*a*c)**(1/2)) / (2*a)
    x_2 = (- b-(b**2-4*a*c)**(1/2)) / (2*a)

    return x_1, x_2

see this might be extremely useful when you are not entirely sure how function works.

Whitespace around Binary Operators

Surround the following binary operators with a single space on either side:

  • Assignment operators (=, +=, -=, and so forth)
  • Comparisons (==, !=, >, <. >=, <=) and (is, is not, in, not in)
  • Booleans (and, not, or)
# Recommended
y = x**2 + 5
z = (x+y) * (x-y)

# Not Recommended
y = x ** 2 + 5
z = (x + y) * (x - y)
# Not recommended
if x > 5 and x % 2 == 0:
    print('x is larger than 5 and divisible by 2!')

In the above example, the and operator has lowest priority. It may therefore be clearer to express the if statement as below:

# Recommended
if x>5 and x%2==0:
    print('x is larger than 5 and divisible by 2!')

When to Avoid adding whitespace

In some cases, adding whitespace can make code harder to read. Too much whitespace can make code overly sparse and difficult to follow. PEP 8 outlines very clear examples where whitespace is inappropriate.

The most important place to avoid adding whitespace is at the end of a line. This is known as trailing whitespace. It is invisible and can produce errors that are difficult to trace.

  • Immediately inside parentheses, brackets, or braces:
# Recommended
my_list = [1, 2, 3]

# Not recommended
my_list = [ 1, 2, 3, ]
  • Before a comma, semicolon, or colon:
x = 5
y = 6

# Recommended
print(x, y)

# Not recommended
print(x , y)
  • Before the open parenthesis that starts the argument list of a function call:
def double(x):
    return x * 2

# Recommended
double(3)

# Not recommended
double (3)
  • Before the open bracket that starts an index or slice:
# Recommended
list[3]

# Not recommended
list [3]
  • Between a trailing comma and a closing parenthesis:
# Recommended
tuple = (1,)

# Not recommended
tuple = (1, )
  • To align assignment operators:
# Recommended
var1 = 5
var2 = 6
some_long_var = 7

# Not recommended
var1          = 5
var2          = 6
some_long_var = 7

PRO TIP โœ”๏ธ

Donโ€™t compare Boolean values to True or False using the equivalence operator.

# Not recommended
my_bool = 6 > 5
if my_bool == True:
    return '6 is bigger than 5'

# Recommended
if my_bool:
    return '6 is bigger than 5'

Maximum line length

PEP 8 suggests lines should be limited to 79 characters. This is because it allows you to have multiple files open next to one another, while also avoiding line wrapping. Nowadays it is accepted to have between 120-140 characters length in one line. For Example for pylinter not to complain we can create a file in the project directory called .pylintrc :

[FORMAT]
max-line-length=120

And pylint should not complain if the line length is less than 120 characters.

Python will assume line continuation if code is contained within parentheses, brackets, or braces:

def function(arg_one, arg_two,
             arg_three, arg_four):
    return arg_one

If it is impossible to use implied continuation, then you can use backslashes to break lines instead:

from mypkg import example1, \
    example2, example3

Blank lines

How you lay out your code has a huge role in how readable it is. In this section, youโ€™ll learn how to add vertical whitespace to improve the readability of your code.

Surround top-level functions and classes with two blank lines

class MyFirstClass:
    pass


class MySecondClass:
    pass


def top_level_function():
    return None

Surround method definitions inside classes with a single blank line

class MyClass:
    def first_method(self):
        return None

    def second_method(self):
        return None

Use blank lines sparingly inside functions to show clear steps Sometimes, a complicated function has to complete several steps before the return statement. To help the reader understand the logic inside the function, it can be helpful to leave a blank line between each step.

In the example below, there is a function to calculate the variance of a list. This is two-step problem, so I have indicated each step by leaving a blank line between them. There is also a blank line before the return statement. This helps the reader clearly see whatโ€™s returned:

from typing import List

def calculate_variance(number_list):
    sum_list = 0
    for number in number_list:
        sum_list = sum_list + number
    mean = sum_list / len(number_list)

    sum_squares = 0
    for number in number_list:
        sum_squares = sum_squares + number**2
    mean_squares = sum_squares / len(number_list)

    return mean_squares - mean**2

Exercises:

Fix these coding examples according to the standards we learned during this lecture:

Example 1:

class person():
    def __init__(self, name, surname):
        self.name=name
        self.surname=surname
    def sayHello(self):
        print(f"hello, {self.name}")
PERSON = person("first", "person")
PERSON.sayHello()

Example 2:

def x(a):
    """Function greets a person given full name as a string"""

    print(f"Hello {a.split()[0]} {a.split()[1]}, How are you today?")

Example 3:

def Greet_User (age):
    eligiebleTo_enter = age>21
    
    if eligiebleTo_enter == True:
        print("Welcome")
    if eligiebleTo_enter == False:
        print("Access denied")

๐ŸŒ Extra reading:

How to Write Beautiful Python Code With PEP 8

Style Guide for Python Code

more on linters

80 char line in vscode

Exercises

Exercise 1

  • Develop a Python class named "Student" with the following features:

    • The class should have a constructor that takes two parameters, "name" (string) and "age" (integer).
    • Implement a method named "get_age" that returns the value of the "age" attribute.
    • Implement a method named "update_age" that takes a new age as a parameter and updates the "age" attribute if the new age falls within the valid range of 18 to 30 (inclusive). If the new age is outside this range, print an error message indicating that the age update is invalid.

Example:

student1 = Student("Alice", 20)
print(f"Before update: {student1.name}'s age is {student1.get_age()}") # Before update: Alice's age is 20
student1.update_age(35) # Invalid age range. Age not updated.
print(f"After update: {student1.name}'s age is {student1.get_age()}") # After update: Alice's age is 20

Exercise 2

  • Library Class:

    • Initialize the library with a name and an empty list of books.
    • Create a method named "add_book" that takes a title and an author as parameters, creates a new "Book" instance, and adds it to the list of books in the library.
    • Implement a method named "display_books" that prints the available books in the library, excluding those that are currently checked out.
  • Book Class:

    • Initialize a book with a title, an author, and set its availability to True by default.
    • Implement a method named "check_out" that checks if the book is available; if so, set its availability to False and print a success message, otherwise, print a message indicating that the book is not available.
    • Implement a method named "return_book" that checks if the book is not available; if so, set its availability to True and print a message indicating that the book has been returned, otherwise, print a message indicating that the book was not checked out.

Example:

library = Library("City Library")

library.add_book("The Great Gatsby", "F. Scott Fitzgerald")
library.add_book("To Kill a Mockingbird", "Harper Lee")
library.add_book("1984", "George Orwell")

library.display_books()

# Books available in City Library:
# The Great Gatsby by F. Scott Fitzgerald
# To Kill a Mockingbird by Harper Lee
# 1984 by George Orwell

book_to_check_out = library.books[0]
book_to_check_out.check_out()

# Book 'The Great Gatsby' by F. Scott Fitzgerald checked out successfully.

library.display_books()

# Books available in City Library:
# To Kill a Mockingbird by Harper Lee
# 1984 by George Orwell

book_to_check_out.return_book()

# Book 'The Great Gatsby' by F. Scott Fitzgerald returned.

library.display_books()

# Books available in City Library:
# The Great Gatsby by F. Scott Fitzgerald
# To Kill a Mockingbird by Harper Lee
# 1984 by George Orwell

Exercise 3

  • Create a To-Do list program that allows users to add tasks, view the current task list, mark tasks as done, and gracefully exit the program.

  • Overview:

    • The To-Do List Management System consists of two primary classes - Task and ToDoList. The Task class represents individual tasks with attributes for description and status, while the ToDoList class manages a list of tasks.
  • Key Features:

    • Add Task:

      • Users can input task descriptions, and a new task is added to the To-Do List. The program provides confirmation messages upon successful task addition. Display Tasks:
    • Users can view the current tasks in the To-Do List. If no tasks exist, an appropriate message informs the user.

    • Mark Task as Done:

      • Users can mark tasks as "Done" by providing the task description. The system provides feedback on successful task completion.
    • User Interface:

      • Users interact with the system through a simple command-line menu. The menu presents options for adding tasks, displaying tasks, marking tasks as done, and quitting the program. Infinite Loop:
    • The program runs indefinitely, allowing users to perform multiple actions without restarting.

    • Users input their choice through the command line, and the program responds accordingly.