Python crash course (if you programmed in something else before)

Previously I worked in C#, now switching to Python and Robot Framework. Recently I’ve been doing great Python course. In this post I’ll try to wrap up my basic Python knowledge gained during the course with the main focus on syntax.

Hello world
age_input = input("How old are you?")
age = int(x)
print(f"You live for {xx * 525600} minutes")

Above we wrote a simple sort of hello world program where we defined a variable, converted it to int and printed it with some message.

Lists, method calls
my_list = [3, 2, 7, 1]
my_list.append(9)
greatest = max(my_list)
smallest = min(my_list)
length = len(my_list)

print("Smallest:", smallest)
print("Greatest:", greatest)
print("Length of the list:", length)

my_list.sort()
print(my_list)

Here we created a list, did some operations on ot and printed a sorted version.

Looping
my_list = [3, 2, 4, 5, 2]

#1st way to loop thru a list:
index = 0
while index < len(my_list):
    print(my_list[index])
    index += 1

#2nd way to loop thru a list:
for item in my_list:
    print(item)

Above 2 ways to loop thru

Range
#1st looping with range
for i in range(5):
    print(i)
#prints numbers 0-4

#2nd looping with range
for i in range(3, 7):
    print(i)
#prints numbers 3-6

#3rd looping with range
for i in range(1, 9, 2):
    print(i)
#prints every second number in range 1-8 that is: 1,3,5,7

#4th range can create lists
numbers = list(range(2, 7))
print(numbers)

#5th accesing list elements with Range
my_string = "exemplary"
print(my_string[3:7])
#prints "mpla"

my_list = [3,4,2,4,6,1,2,4,2]
print(my_list[3:7])
#prints 4, 6, 1, 2

Here we had some usages of Range

Functions
def print_reversed(names: list):
    i = len(names) - 1
    while i >= 0:
        print(names[i])
        i -= 1

name_list = ["Steve", "Jean", "Katherine", "Paul"]
print_reversed(name_list)
#prints Paul Katherine Jean Steve

Now we know how to define and call a function with parameter

Global
def print_reversed(names: list):
    i = len(my_list) - 1
    while i >= 0:
        print(my_list[i])
        i -= 1

def global_testing():
    global my_list 
    my_list = ["Steve", "Jean", "Katherine", "Paul"]
    print_reversed(my_list)

global_testing()

Variable defined in the function is local by default. Sometimes we want to make it global and then we can use “global” keyword. Generally using global variables directly in method definitions is a bad approach and we should avoid it.

Class and method
class BankAccount:

    def __init__(self, account_number: str, owner: str, balance: float, annual_interest: float):
        self.account_number = account_number
        self.owner = owner
        self.balance = balance
        self.annual_interest = annual_interest

    def add_interest(self):
        self.balance += self.balance * self.annual_interest


peters_account = BankAccount("12345-678", "Peter Python", 1500.0, 0.015)
peters_account.add_interest()
print(peters_account.balance)

The name of the constructor method is always __init__. Notice the two underscores on both sides of the word init. The first parameter in a constructor definition is always named self. This refers to the object itself, and is necessary for declaring any attributes attached to the object. The assignment
self.balance = balance
assigns the balance received as an argument to the balance attribute of the object. It is a common convention to use the same variable names for the parameters and the data attributes defined in a constructor, but the variable names self.balance and balance above refer to two different variables:
1. The variable balance is a parameter in the constructor method __init__. Its value is set to the value passed as an argument to the method as the constructor is called (that is, when a new instance of the class is created).
2. The variable self.balance is an attribute of the object. Each BankAccount object has its own balance.

Encapsulation
class BankAccount:

    def __init__(self, account_number: str, owner: str, balance: float, annual_interest: float):
        self.account_number = account_number
        self.owner = owner
        self.balance = balance
        self.annual_interest = annual_interest

    # This method adds the annual interest to the balance of the account
    def add_interest(self):
        self.balance += self.balance * self.annual_interest

    # This method "withdraws" money from the account
    # If the withdrawal is successful the method returns True, and False otherwise
    def withdraw(self, amount: float):
        if amount <= self.balance:
            self.balance -= amount
            return True

        return False

peters_account = BankAccount("12345-678", "Peter Python", 1500.0, 0.015)

if peters_account.withdraw(1000):
    print("The withdrawal was successful, the balance is now", peters_account.balance)
else:
    print("The withdrawal was unsuccessful, the balance is insufficient")

# Yritetään uudestaan
if peters_account.withdraw(1000):
    print("The withdrawal was successful, the balance is now", peters_account.balance)
else:
    print("The withdrawal was unsuccessful, the balance is insufficient")


In object oriented programming the word client comes up from time to time. This is used to refer to a section of code which creates an object and uses the service provided by its methods. When the data contained in an object is used only through the methods it provides, the internal integrity of the object is guaranteed. In practice this means that, for example, a BankAccount class offers methods to handle the balance attribute, so the balance is never accessed directly by the client. These methods can then verify that the balance is not allowed to go below zero, for instance. Maintaining the internal integrity of the object and offering suitable methods to ensure this is called encapsulation. The idea is that the inner workings of the object are hidden from the client, but the object offers methods which can be used to access the data stored in the object.

Printing an object
class Rectangle:
    def __init__(self, left_upper: tuple, right_lower: tuple):
        self.left_upper = left_upper
        self.right_lower = right_lower
        self.width = right_lower[0]-left_upper[0]
        self.height = right_lower[1]-left_upper[1]

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return self.width * 2 + self.height * 2

    def move(self, x_change: int, y_change: int):
        corner = self.left_upper
        self.left_upper = (corner[0]+x_change, corner[1]+y_change)
        corner = self.right_lower
        self.right_lower = (corner[0]+x_change, corner[1]+y_change)
        
    def __str__(self):
        return f"rectangle {self.left_upper} ... {self.right_lower}"
        
rectangle = Rectangle((1, 1), (4, 3))
print(rectangle)

#prints rectangle (1, 1) ... (4, 3)

To print a string representation of the object you need to use __str__ in class definition.

Is
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = list1

print(list1 is list2)
print(list1 is list3)
print(list2 is list3)

print()

print(list1 == list2)
print(list1 == list3)
print(list2 == list3)

#prints:
#False True False
#True True True

The operator is is used for checking if the two references refer to the exact same object, while the operator == will tell you if the contents of the objects are the same.

Object reference
class Product:
    def __init__(self, name: int, unit: str):
        self.name = name
        self.unit = unit


if __name__ == "__main__":
    #obj references in list
    shopping_list = []
    milk = Product("Milk", "litre")
    shopping_list.append(milk)
    shopping_list.append(milk)
    shopping_list.append(Product("Cucumber", "piece"))
    #obj references in dictionary
    products = {}
    products["12345"] = Product("Cucumber", "piece")
    products["54321"] = Product("Milk", "litre")

This list doesn’t contain 2 objects “milk” but 2 references to the same object “milk”. Same with dictionary.

Object as argument
class Student:
    def __init__(self, name: str, student_number: str):
        self.name = name
        self.student_number = student_number

    def __str__(self):
        return f"{self.name} ({self.student_number})"

# the type hint here uses the name of the class defined above
def change_name(student: Student):
    student.name = "Saul Student"

# create a Student object
steve = Student("Steve Student", "12345")

print(steve)
change_name(steve)
print(steve)
#prints
#Steve Student (12345) Saul Student (12345)

Above function change_name receives Student object as an argument.