class SupervisedEstimator(...):
def __init__(self, hyperparameter_1, ...):
self.hyperparameter_1
...
def fit(self, X, y):
...
self.fit_attributes
return self
def predict(self, X):
...
return y_pred
def score(self, X, y):
...
return score
OOP Concepts
- Classes
- Instances
- Instance Attributes
- Class Attributes
- Methods
- The ‘self’ parameter
- Inheritance
- Docstrings
- Special Methods
- Projects - Build a Dice Game & a Tic-Tac-Toe Game using Python OOP!
OOP Projects
»» Play Tic-Tac-Toe Game
»» Play Dice Game
1. INTRO
Object-Oriented Programming : Programming paradigm (an approach / style of programming) that organizes software design around objects
Other paradigms:
- Imperative
- Functional
- Declarative
- Logic
OOP is based on messages sent to objects (objects have their state and behavior)
Class is a blueprint for creating objects
Objects are like legos which can be combines to create more complex structures
Advantages:
-
Modularity - Classes work as blueprints / modules isolated from other part of programs
-
Extensability - Objects can be extended to include new attributes and behavior
-
Reusability - Objects can be resused within the program and in future projects. Faster development and lower cost of development
-
Easier to Maintain - Make changes to module without large scale changes. Higher quality software
2. CLASSES
A blueprint to define the attributes and behavior of an object
Process:
- Identify an object
- Analyze its attributes and behavior
- Create a class as blueprint of the object
EXAMPLE PROBLEMS -
- Our stores serve fast food - pizza, burger, hot dogs, soda. Clients choose to buy one or more items. More than 1 item purchase gets 20% discount. The store has 5 employees.
- Nouns - Clients, Employees, pizza, burger, hot dogs, soda
CLASSES STRUCTURE
Header (first line in class definition)
class <ClassName>:
class Backpack:
pass
Body - defines the attributes and behavior of objects
Elements of a class:
- Class Attribues
- init()
- Methods
Notes:
- Class is singular
- First letter in upper case
- Indent the elements
class Backpack:
# Class Attribues
# __init__()
# Methods
3. OBJECTS
Objects - are instance of a class
Difference:
- Class - Abstract
- Object - Concrete instances of a class
Notes:
- Instance attributes are independent
- Attributes can have unique value for a instance
- init() - special method used to define the initial state of an object. Called automatically when an instance is created.
- init() always has to take self as the first parameter
Example 1:
class House:
# Class Attribues
# __init__()
def __init__(self, price):
# self.price is attribute
# price is parameter
self.price = price
# Methods
Example 2:
class Backpack:
# Class Attribues
# __init__()
def __init__(self):
# self.items is attribute - initiall all backpacks are empty demonstrated by an empty list
self.items = []
# Methods
4. SELF & DEFINE INSTANCE ATTRIBUTES
Notes:
- Self - is a generic way of referring to the current instance of the class
- Go to style guide for Python for reference
Example 1: Backpack
class Backpack:
# Class Attribues
# __init__()
def __init__(self, color, size):
# self.items is attribute - initiall all backpacks are empty demonstrated by an empty list
self.items = []
# self.color is attribute
# color is parameter
self.color = color
self.size = size
# Methods
Example 2: Circle
class Circle:
# Class Attribues
# __init__()
def __init__(self, radius):
# self.radius is attribute
# radius is parameter
self.radius = radius
self.color = "Blue" # assign all circle instance color blue
# Methods
Example 2: Rectangle
class Rectangle:
# Class Attribues
# __init__()
def __init__(self, length, width):
# self.length is attribute
# length is parameter
self.length = length
self.width = width
# Methods
Example 3: Movie
class Movie:
# Class Attribues
# __init__()
def __init__(self, title, year, language, rating):
# self.title is attribute
# title is parameter
self.title = title
self.year = year
self.language = language
self.rating = rating
# Methods
5. HOW TO CREATE AN INSTANCE
Notes:
- self is skipped in the argument list when we create an instance and you don’t have to pass a value for that parameter
Example 1: Backpack
class Backpack:
def __init__(self):
self.items = []
my_backpack = Backpack()
print(my_backpack)
Example 2: Circle
class Circle:
def __init__(self, radius):
self.radius = radius
my_circle = Circle(5)
print(my_circle)
Example 3: Rectangle
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
my_rectangle = Rectangle(3, 6)
print(my_rectangle)
6. HOW TO ACCESS INSTANCE ATTRIBUTES (VARIABLES & METHODS) OF AN OBJECT
Example 1: Backpack
# Define Class
class Backpack:
def __init__(self):
self.items = ["Water Bottle", "Pencils"] # Initially has two items
print(self.items)
# Main Program (outside of class)
my_backpack = Backpack()
print(my_backpack.items)
7. HOW TO ACCESS INSTANCE ATTRIBUTES (VARIABLES & METHODS) OF AN OBJECT
Example 1: Movie
class Movie:
def __init__(self, title, year, language, rating):
self.title = title
self.year = year
self.language = language
self.rating = rating
# First instance of the class Movie
my_favorite_movie = Movie("Pride and Prejudice", 2005, "English", 4.8)
print(my_favorite_movie.title)
print(my_favorite_movie.year)
print(my_favorite_movie.language)
print(my_favorite_movie.rating)
# Second instance of the class Movie
your_favorite_movie = Movie("Titanic", 1997, "English", 4.6)
print(your_favorite_movie.title)
print(your_favorite_movie.year)
print(your_favorite_movie.language)
print(your_favorite_movie.rating)
8. DEFAULT ARGUMENTS
Example 1: Circle
class Circle:
def __init__(self, radius=5):
self.radius = radius
my_circle = Circle()
print(my_circle.radius)
my_circle = Circle(8)
print(my_circle.radius)
Notes:
- Default parameter should always follow after Non-Default parameter
Example 2: Circle
class Circle:
def __init__(self, color, radius=5):
self.color = color
self.radius = radius
my_circle = Circle("Blue", 7)
print(my_circle.color)
print(my_circle.radius)
9. HOW TO UPDATE INSTANCE ATTRIBUTES
Example 1: Backpack
# Define Class
class Backpack:
def __init__(self, color):
self.items = []
self.color = color
# Main Program (outside of class)
my_backpack = Backpack("Blue")
print(my_backpack.color)
my_backpack.color = "Green"
print(my_backpack.color)
Example 2: Circle
class Circle:
def __init__(self, color, radius=5):
self.color = color
self.radius = radius
my_circle = Circle("Yellow", 6)
print(my_circle.color)
print(my_circle.radius)
my_circle.color = "Black"
my_circle.radius = 15
print(my_circle.color)
print(my_circle.radius)
10. INTRO TO CLASS ATTRIBUTES
Notes:
- Defined before init() attributes
class Backpack:
# Class Attribues
# __init__()
# Methods
11. DEFINE CLASS ATTRIBUTES
Notes:
- Defined before init() attributes
- 4 spaces preferred over tab
Example 1: Dog
class Dog:
# Class Attribues
species = "Canis lupus"
# __init__() or Instance Attributes
def __init()__(self, name, age, breed)
self.name = name
self.age = age
self.breed = breed
# Methods
Example 2: Backpack
class Backpack:
max_num_items = 10
def __init__(self):
self.items = ["Water Bottle", "Pencils"] # Initially has two items
print(self.items)
# Main Program (outside of class)
my_backpack = Backpack()
print(my_backpack.items)
12. HOW TO ACCESS CLASS ATTRIBUTES
Example 1: Dog
class Dog:
# Class Attribues
species = "Canis lupus"
# __init__() or Instance Attributes
def __init()__(self, name, age, breed)
self.name = name
self.age = age
self.breed = breed
# Methods
print(Dog.species)
Example 2: Movie
class Movie:
# Class attributes shared across all instances
id_counter = 1
def __init__(self, title, rating):
self.id = Movie.id_counter
self.title = title
self.rating = rating
# Increment the class attribute or counter
Movie.id_counter += 1
my_movie = Movie("Sense and Sensibility", 4.5)
your_movie = Movie("Lengends of the Fall", 4.7)
print(my_movie.id)
print(your_movie.id)
Example 3: Backpack
class Backpack:
max_num_items = 10
def __init__(self):
self.items = []
# Main Program (outside of class)
my_backpack = Backpack()
your_backpack = Backpack()
# Access the attribute through class
print(Backpack.max_num_items)
# Access the attribute through instance
print(my_backpack.max_num_items)
print(your_backpack.max_num_items)
13. HOW TO MODIFY CLASS ATTRIBUTES
Changing the value of class attribute affects all instance
Example 1: Circle
class Circle:
radius = 5
def __init__(self, color):
self.color = color
print(Circle.radius)
my_circle = Circle("Blue")
your_circle = Circle("Green")
print(my_circle.radius)
print(your_circle.radius)
Circle.radius = 10
print(my_circle.color)
print(my_circle.radius)
# values will get updated for class as well as instances
print(Circle.radius)
print(my_circle.radius)
print(your_circle.radius)
Example 2: Pizza
class Pizza:
price = 12.99
def __init__(self, description, toppings, crust):
self.color = color
self.description = description
self.crust = crust
print(Pizza.price)
my_pizza = Circle("Margherita", ["Basil", "Mushrooms"], "New York Style")
print(my_pizza.price)
Pizza.price = 13.99
print(Pizza.price)
print(my_pizza.price)
Example 3: Backpack
class Backpack:
max_num_items = 10
def __init__(self):
self.items = []
# Main Program (outside of class)
print(Backpack.max_num_items)
my_backpack = Backpack()
print(my_backpack.max_num_items)
Backpack.max_num_items = 15
print(Backpack.max_num_items)
print(my_backpack.max_num_items)
14. ENCAPSULATION
Two basic pillars of OOPS - Encapsulation & Abstraction
Encapsulation - Bundling of data and methods that act on the data into a single class Class - Shields direct access to the attributes in order to avoid making potentially problematic changes to the state
Making members of the class public or non-public
Getters
Setters
15. ABSTRACTION
-
Show only the essential members and hide the complexity
-
Example - GUI of a phone abstracts away the complexity. Turning on the key of car without knowing complexity of engine
Two part of class
- Interface
- Implementation
Abstract out common part of the code
16. PUBLIC & NON-PUBLIC ATTRIBUTES
Public attributes - An attribute that can be accessed and modified directly without access restrictions
Example 1: Car
class Car:
def __init__(self, brand, model, year):
self.brand = brand
self.model = model
self.year = year
# Main Program (outside of class)
my_car = Car("Porsche", "911 Carrera", 2020)
print(my_car.year)
# Can set a public-attribute invalid value
my_car.year = 5600
print(my_car.year)
Non-public attributes - An attribute that shouldn’t be accessed or modified outside of the class
Private - No attribute is ever private in Python (Python doesnt have access modifiers like Java)
Notes:
- Two ways to make an attribute Non-Public
- By naming convention : _variable (self._variable)
- Changing name (name mangling) : __variable (self.__variable)
Example 2: Car
class Car:
def __init__(self, brand, model, year):
self.brand = brand
self.model = model
self._year = year
# Main Program (outside of class)
my_car = Car("Porsche", "911 Carrera", 2020)
# Will display a warning if try to access non-public attribute
my_car._year = 5600
print(my_car._year)
Example 3: Student
class Student:
def __init__(self, student_id, name, age, gpa):
self.student_id = student_id
self.name = name
self._age = age
self.gpa = gpa
# Main Program (outside of class)
student_nora = Car("245AFS", "Nora Nav", 15, 3.96)
# Will display a warning if try to access non-public attribute | Student object has no attribute age
print(student_nora.age)
# In theory you shouldn't access non-public attribute but technically you can as attribute is never private in Python
print(student_nora._age)
Example 4: Backpack
class Backpack:
max_num_items = 10
def __init__(self):
self._items = []
# Main Program (outside of class)
my_backpack = Backpack()
# Error - Backpack object has no attribute items
print(my_backpack.items)
# Warning - Will display a warning if try to access non-public attribute
print(my_backpack._items)
Example 5: Movie
class Movie:
# Class attributes shared across all instances
id_counter = 1
def __init__(self, title, year, language, rating):
# Non-public instance attribute
self._id = Movie.id_counter
# Public instance attribute
self.title = title
self.year = year
self.language = language
self.rating = rating
# Increment the class attribute or counter
Movie.id_counter += 1
my_movie = Movie("Sense and Sensibility", 2005, "English", 4.5)
your_movie = Movie("Lengends of the Fall", 1995, "English", 4.7)
# Error - Movie object has no attribute id
print(my_movie.id)
print(your_movie.id)
# Warning - Will display a warning if try to access non-public attribute
print(my_movie._id)
print(your_movie._id)
17. Name Mangling
Notes:
- Since there is a valid use-case for class-private members (namely to avoid name clashes of names with names defined by subclasses), there is limited support for such a mechanism, called name mangling. Any identifier of the form __spam (at least two leading underscores, at most one trailing underscore) is textually replaced with _classname__spam, where classname is the current class name with leading underscore(s) stripped.
# __attribute is transformed to _class__attribute
# __engine_serial_num : _Car__engine_serial_num
# But you can still access _Car__engine_serial_num as no variable is private in Python
Example 1: Backpack
class Backpack:
max_num_items = 10
def __init__(self):
self.__items = ["Water Bottle", "First Aid Kit"]
# Main Program (outside of class)
my_backpack = Backpack()
# Error - Backpack object has no attribute items
print(my_backpack.items)
# Error - Backpack object has no attribute items
print(my_backpack._items)
# Error - Backpack object has no attribute items
print(my_backpack.__items)
# Warning - Will display a warning if try to access non-public attribute
print(my_backpack._Backpack__items)
18. GETTERS & SETTERS
Getters & Setters - Members of a class, particularly they’re methods
Methods - Are like functions associated to a specific object or a class
- Acts as intermediary to access attributes Getters - Get the value of an attribute Setters - Set the value of an attribute
# get_ + attribute
# Examples:
get_name
get_address
get_color
get_age
get_id
Example 1: Movie
class Movie:
# Class attributes shared across all instances
id_counter = 1
def __init__(self, title, rating):
# Non-public instance attribute
self._id = Movie.id_counter
# Public instance attribute
self._title = title
self.rating = rating
# Increment the class attribute or counter
Movie.id_counter += 1
# Getters
def get_title(self):
return self._title
my_movie = Movie("The Godfather", 4.8)
# Error - Movie object has no attribute title
print(my_movie.title)
# Correct Way
print("My favorite movie is: ", my_movie.get_title())
Setters - Methods that we can call to set the value of an instance attribute
Notes: Advantage - With setters we can validate the new value before assigning it to the attribute
set_ +
Example 2: Dog
class Dog:
def __init()__(self, name, age)
self._name = name
self.age = age
def get_name(self):
return self._name
def set_name(self, new_name):
# Setters allow validation of values allowed
if isinstance(new_name, str) and new_name.isalpha():
self._name = new_name
else:
print("Please enter a valid name.")
my_dog = Dog("Nora", 8)
print("My dog is:", my_dog.get_name())
# My dog is: Nora
my_dog.set_name("Norita")
print("Her new name is:", my_dog.get_name())
# Her new name is: Norita
my_dog.set_name("4567")
print("Her new name is:", my_dog.get_name())
# Please enter a valid name
# Her new name is: Norita
Example 3: Backpack
class Backpack:
def __init__(self):
self.__items = []
def get_items(self):
return self._items
def set_items(self, new_items):
# Setters allow validation of values allowed
if isinstance(new_name, list):
self._items = new_items
else:
print("Please enter a valid list of items.")
# Main Program (outside of class)
my_backpack = Backpack()
# Get initial list of items
print(my_backpack.get_items())
# Message - Please enter a valid list of items.
my_backpack.set_items("Hello, world!"])
Example 4: Circle
class Circle:
def __init__(self, radius):
self.__radius = radius
def get_radius(self):
return self._radius
def set_radius(self, new_radius):
# Setters allow validation of values allowed
if isinstance(new_radius, float) and new_radius > 0:
self._radius = new_radius
else:
print("Please enter a valid value for the radius.")
# Main Program (outside of class)
my_circle = Circle(5.0)
# 5.0
print(my_circle.get_radius())
my_circle.set_radius(10.5)
# 10.5
print(my_circle.get_radius())
# Message - Please enter a valid value for the radius.
my_circle.set_radius(0)
19. PROPERTIES
Getters + Setters
Example 1: Dog
class Dog:
def __init()__(self, age)
self._age = age
def get_age(self):
return self._age
def set_age(self, new_age):
# Setters allow validation of values allowed
if isinstance(new_age, int) and 0 < new_age < 30:
self._age = new_age
else:
print("Please enter a valid age.")
# Connecting age property to the getter and setter methods
# Use the same name as attribute
age = property(get_age, set_age)
my_dog = Dog(8)
print(f"My dog age is {my_dog.age} years old.")
print("One year later...")
# We avoided an error when directly setting age because we defined the propert age and connected it to getter & setter methods
# More concise than my_dog.set_age = my_dog.get_age + 1
my_dog.age += 1
print(f"My dog age is {my_dog.age} years old.")
class Circle:
# Class Contant
# Write in caps to indicate other developers that it should be treated as constant
# Define using tuple as tuples are immutable
VALID_COLORS = ()
def __init__(self, radius, color):
self.__radius = radius
def get_radius(self):
return self._radius
def set_radius(self, new_radius):
# Setters allow validation of values allowed
if isinstance(new_radius, int) and new_radius > 0:
self._radius = new_radius
else:
print("Please enter a valid radius.")
# Connecting age property to the getter and setter methods
# Use the same name as attribute
radius = property(get_radius, set_radius)
def get_color(self):
return self._color
def set_color(self, new_color):
# Setters allow validation of values allowed
if new_color in Circle.VALID_COLORS:
self._color = new_color
else:
print("Please enter a valid color.")
# Connecting age property to the getter and setter methods
# Use the same name as attribute
color = property(get_color, new_color)
# Main Program (outside of class)
my_circle = Circle(10, "Blue")
# Radius
print(my_circle.radius)
my_circle.radius = 0
# Attribute is protected by setter
print(my_circle.radius)
# Color
print(my_circle.color)
my_circle.radius = "Red"
# Attribute is protected by setter
print(my_circle.color)
my_circle.radius = "White"
# Attribute is protected by setter
# Color not in the list of valid colors
# Message - Please enter a valid value for the color.
print(my_circle.color)
@property DECORATOR
Decorator - A function that takes a function and extends its behavior without explicitly modifying it
@property syntax is more concise and readable
Example 2: Movie
class Movie:
def __init__(self, title, rating):
# Public instance attribute
self._title = title
self.rating = rating
# PROPERTY
## First define getter
@property
def rating(self):
print("Calling the getter...")
return self._rating
## Then define setter
@rating.setter
def rating(self, new_rating):
print("Calling the setter...")
if isinstance(new_rating, float) and 1.0 <= new_rating <= 5.0:
self._rating = new_rating
else:
print(favorite_movie.rating)
my_movie = Movie("Titanic", 4.3)
print(my_movie.rating)
# Message - rating is not valid
my_movie.rating = -5.6
20. Methods
Class defined the state + behavior of the object
Methods determine the behavior of the objects created from a class and how they can interact with their state and other objects Method is a function which belongs to an object
3 main types of methods:
-
- Instance Methods - Methods that belong to a particular object because they’ve access to the state of the object
-
- Class Methods
-
- Static Methods
Instance Methods
Notes:
- Self
Structure
class MyClass:
# Class Attributes
# __init()__
def method_name(self, param1, param2, param3=value):
# code
Attribute names usually include nouns
Method names usually include verbs since they represent actions
Example 1: Circle
class Circle:
def __init()__(self, radius):
self.radius = radius
def find_diameter(self):
print(f"Diameter: {self.radius * 2}")
return self.radius * 2
Example 2: Backpack
class Backpack:
def __init__(self):
self.items = []
def add_item(self, item):
if isinstance(item, str):
self._items.append(item)
else:
print("Please provide a valid item.")
def remove_item(self, item)
if item in self._items:
self._items.remove(item)
return 1
else:
print("This item is not in the backpack.")
return 0
def has_item(self, item):
return item in self._items
my_backpack = Backpack()
print(my_backpack.items)
# []
# Before camping trip add items to the backpack
my_backpack.add_item("Water Bottle")
print(my_backpack.items)
# ["Water Bottle"]
my_backpack.add_item("Sleeping Bag")
print(my_backpack.items)
# ["Water Bottle", "Sleeping Bag"]
has_water = my_backpack.has_item("Water Bottle")
print(has_water)
# True
# After camping trip remove items from the backpack
my_backpack.remove_item("Sleeping Bag")
print(my_backpack.items)
# ["Water Bottle"]
my_backpack.remove_item("Water Bottle")
print(my_backpack.items)
# []
Example 3: Method of a List
# append, sort, pop and extend are methods of a list
my_list = [4, 5, 6, 7, 8]
my_list.append(14)
my_list.sort()
my_list.pop()
my_list.extend([1, 2, 3])
Example 4: Circle
class Circle:
def __init()__(self, radius):
self.radius = radius
def find_diameter(self):
print(f"Diameter: {self.radius * 2}")
return self.radius * 2
my_circle = Circle(5)
diameter = my_circle.find_diameter()
print(diameter)
21. DEFAULT ARGUMENTS TO METHODS
Example 1: Player who can move
class Player:
def __init()__(self, x, y):
self.x = x
self.y = y
def move_up(self, change=5):
self.y += change
def move_down(self, change=5):
self.y -= change
def move_right(self, change=2):
self.x += change
def move_left(self, change=2):
self.x -= change
my_player = Player(5, 10)
print(my_player.y)
# will return 10
my_player.move_up()
print(my_player.y)
# will return 15
my_player.move_up(8)
print(my_player.y)
# will return 23
Example 2: Backpack
class Backpack:
def __init__(self):
self.items = []
def add_item(self, item):
if isinstance(item, str):
self._items.append(item)
else:
print("Please provide a valid item.")
def remove_item(self, item)
if item in self._items:
self._items.remove(item)
return 1
else:
print("This item is not in the backpack.")
return 0
def has_item(self, item):
return item in self._items
def show_items(self, sorted_list=False):
if sorted_list:
print(sorted(self._items))
else:
print(self._items)
my_backpack = Backpack()
print(my_backpack.items)
# []
# Before camping trip add items to the backpack
my_backpack.add_item("Water Bottle")
my_backpack.add_item("Sleeping Bag")
my_backpack.add_item("Candy")
print("Not Sorted:"")
my_backpack.show_items()
["Water Bottle", "Sleeping Bag", "Candy"]
print("Sorted:"")
my_backpack.show_items(True)
["Candy", "Sleeping Bag", "Water Bottle"]
Calling Methods from another Method
Example 1: Backpack
class Backpack:
def __init__(self):
self.__items = []
@property
def items(self):
return self._items
def add_multiple_items(self, items):
for item in items:
self.add_item(item)
def add_item(self, item):
if isinstance(item, str):
self._items.append(item)
else:
print("Please provide a valid item.")
@property.setter
def items(self, new_items):
# Setters allow validation of values allowed
if isinstance(new_name, list):
self._items = new_items
else:
print("Please enter a valid list of items.")
# Main Program (outside of class)
my_backpack = Backpack()
print(my_backpack.items)
# []
# Before camping trip add items to the backpack
my_backpack.add_items(["Water Bottle","Candy"])
print(my_backpack.items)
# ["Water Bottle","Candy"]
22. AGGREGATION
- Aggregation - Concept in OOP that describes relationship between two classes
- Aggregation - Build complex objects from simple objects of simple classes
- Aggregation - Class B’s instance uses some functionality of Class A’s instance
Example: Vehicle & Employee
- Employee has a Vehicle
- Two separate Classes
# Vehicle class
class Vehicle:
def __init()__(self, color, license_plate, is_electric):
self.color = color
self.license_plate = license_plate
self.is_electric = is_electric
def show_license_plate(self):
print(self.license_plate)
def show_info(self):
print("My vehicle:")
print(f"Color: {self.color}")
print(f"License Plate: {self.license_plate}")
print(f"Electric: {self.is_electric}")
# Emploee class
class Employee:
def __init__(self, name, vehicle):
self.name = name
self.vehicle = vehicle
def show_vehicle_info(self):
self.vehicle.show_info()
# more readable than just writing False
employee_vehicle = Vehicle("black", "AXY 245", is_electric=False)
employee = Employee("Gino", employee_vehicle)
print(employee.name)
# vehicle instance inside employee instance
print(employee.vehicle)
employee.show_vehicle_info()
employee.vehicle.show_info()
print(employee.vehicle.color)
print(employee.vehicle.license_plate)
print(employee.vehicle.is_electric)
employee.vehicle.show_license_plate()
23. COMPOSITION
Create the instance of Engine inside the instance of Car. Engine object cannot exist without Car object
24. PROJECT: Build a Dice Game with Python OOPS
Game:
- 2 players - human and computer
- 2 dice
- Roll the dice; record the value; compare the value; player with the greater value wins the round; decrease the counter of winner by 1; increase the counter of loser by 1; if values are equal no winner and counter remains unchanged; the one whose counter reaches zero is winner
- Dice Class :
- attributes - value
- methods - roll
- instances - player_die, computer_die
- Player Class :
- attributes - counter, die (Composition), is_computer
- methods - increment_counter, decrement_counter, roll_die
- instances - my_player, computer_player
- Game Class :
- attributes - [player, computer] (Composition)
- methods - play, play_round, check_game_over
- instance - game
- Interactivity - Human player to roll the round
- Messages - Game Starts, Round Starts, Game Ends, Value of Dice, Mention the Winner, Mention the Counter
Die Class
import random
class Die:
def __init__(self):
# make value non-public by adding underscore
self._value = None
# define getter
@property
def value(self):
return self._value
# not-defining setter because user only has access to reading the value
def roll(self):
new_value = random.randint(1, 6)
self._value = new_value
return new_value
# Testing the class
die = Die()
# should return None
print(die.value)
die.roll()
# should return an integer between 1 and 6
print(die.value)
Player Class
class Player:
def __init__(self, die, is_computer=False):
# using "Composition" as die is instance of Die class
self._die = die
self._is_computer = is_computer
self._counter = 10
@property
def die(self):
return self._die
@property
def counter(self):
return self._counter
def increment_counter(self):
self._counter += 1
def decrement_counter(self):
self._counter -= 1
def roll_die(self):
# using "Aggregation" by calling roll method of Die class inside Player class
return self._die.roll()
# Testing the class
my_die = Die()
my_player = Player(my_die, is_computer=True)
# check the data type of my_player
print(my_player)
# check the die attribute of player
print(my_die)
print(my_player.die)
# check if the player is computer
print(my_player.is_computer)
# check the player counter
print(my_player.counter)
print(my_player.counter)
# check the increment method
my_player.increment_counter()
print(my_player.counter)
# check the decrement method
my_player.decrement_counter()
print(my_player.counter)
# check the roll method
print(my_die.value)
my_player.roll_die()
print(my_die.value)
print(my_player.die.value)
Game Class
class DiceGame:
def __init__(self, player, computer):
self._player = player
self._computer = computer
# we will not write any getters & setters for player and computer as they should not be accessed outside the class
# entry point to game
def play(self):
print("================================================================")
print("🎲 Welcome to Roll the Dice!")
print("================================================================")
while True:
self.play_round()
game_over = self.check_game_over()
if game_over:
break
def play_round(self):
# Welcome the user
self.print_round_welcome()
# Roll the dice
player_value = self._player.roll_die()
computer_value = self._computer.roll_die()
# Show the values
self.show_dice(player_value, computer_value)
# Determine the winner and loser of round
if player_value > computer_value:
print("You won the round! 🎊")
self.update_counters(winner=self._player, loser=self._computer)
elif computer_value > player_value:
print("The computer won this round. Try again. 😦")
self.update_counters(winner=self._computer, loser=self._player)
else:
print("It's a tie! 😎")
# Show counters
self.show_counters()
def print_round_welcome(self):
print("\n--------------------- New Round ----------------------")
input("🎲 Press any key to roll the dice. 🎲")
def show_dice(self, player_value, computer_value):
print(f"Your die: {player_value}\n")
print(f"Computer die: {computer_value}\n")
def update_counters(self, winner, loser):
# will show warning since not using self. but its fine
winner.decrement_counter()
loser.increment_counter()
def show_counters(self):
print(f"\nYour counter: {self._player.counter}")
print(f"\nComputer counter: {self._computer.counter}")
def check_game_over(self):
if self._player.counter == 0:
self.show_game_over(self._player)
return True
elif self._computer.counter == 0:
self.show_game_over(self._computer)
return True
else:
return False
def show_game_over(self, winner):
if winner._is_computer:
print("\n=========================")
print(" G A M E O V E R ✨")
print("===========================")
print("The computer won the game. Sorry...")
print("================================")
else:
print("\n=========================")
print(" G A M E O V E R ✨")
print("===========================")
print("You won the game! Congratulations.")
print("================================")
# Play the game infintely
while True:
# Testing the class
player_die = Die()
computer_die = Die()
my_player = Player(player_die, is_computer=False)
computer_player = Player(computer_die, is_computer=True)
game = Game()
# Start the game
game.play()
25. OBJECTS IN MEMORY
In Python, everything is an object
# object is the base class
print(object)
print(isinstance(5, object))
print(isinstance([1, 5, 2, 6], object))
print(isinstance((1, 5, 2, 6), object))
print(isinstance("Hello, World!", object))
print(isinstance(True, object))
def f(x):
return x * 2
print(isinstance(f, object))
class Movie:
def __init__(self, title):
self.title = title
print(isinstance(Movie, object))
Notes:
- An object is stored in memory with a unique id
- Variables in Python store references to objects in memory
id() Function
# id(object)
print(id(15))
print(id("Hello, World!"))
Example 1: Backpack
class Backpack:
def __init()__(self):
self._items = []
@property
def items(self):
return self._items
my_backpack = Backpack()
your_backpack = Backpack()
print(id(my_backpack))
print(id(your_backpack))
“is” Function & “is not” Function
# object1 is object2
# object1 is not object2
# is operator checks the memory location of object
# == operator checks the value of object
a = [1, 6, 2, 6]
b = [1, 6, 2, 6]
# print memory location
print(id(a))
print(id(b))
# False
print(a is b)
print(id(a) === id(b))
# True
print(a == b)
a = [5, 2, 1, 8, 3]
b = a
# True
print(a is b)
“is” opeartor unexpected results depending upon Python version and programming environment
a = 5
b = 5
# small integers in the range [-5, 256] - just get a reference to existing objects in the memory
# True
print(a is b)
a = 257
b = 257
# You could also get this True in Pycharm environment due to its memory optimization
# True
print(a is b)
# "String Interning" - strings are immutable and doesn't makes sense to create separate copy in memory
# a,b,c reference the same object
a = "Hi"
b = "Hi"
c = "Hi"
# True
print(a is b)
print(id(a))
print(id(b))
print(id(c))
my_list = [6, 2, 8, 2]
# print
def print_data(seq):
print("Inside the function:", id(seq))
for elem in seq:
print(elem)
print("Outside the function:", id(my_list))
print_data(my_list)
# mutiply
def multiply_by_two(seq):
print("Inside the function:", id(seq))
for i in range(len(seq)):
seq[i] *= 2
print("Outside the function:", id(my_list))
multiply_by_two(my_list)
print(my_list)
26. combine object oriented programming with imperative programming
# Class Sale
class Sale:
def __init__(self, amount):
self.amount = amount
# Function outside a class
def find_total(sales):
total = 0
for sale in sales:
print("New sale...")
total += sale.amount
return total
january_sales = [Sale(400), Sale(345), Sale(45)]
print(find_total(january_sales))
27. ALIASING, MUTATION & CLONING
Aliasing - different name assigned to the same object
Notes:
- Implications - if you make changes to one alias you will end up changing all the aliases and underlying object
a = [6, 2, 3, 4]
b = a
c = b
d = c
# True
print(a is b is c is d)
print(id(a))
print(id(b))
print(id(c))
print(id(d))
class Circle:
def __init__(self, radius):
self.radius = radius
my_circle = Circle(4)
your_circle = my_circle
# True
print(my_circle is your_circle)
print(id(my_circle))
print(id(your_circle))
# Both are 4
print("Before:...")
print(my_circle.radius)
print(your_circle.radius)
my_circle.radius = 18
# Both updated to 18
print("After:...")
print(my_circle.radius)
print(your_circle.radius)
Mutability & Immutability
- Example of mutable object ```python a = [7, 3, 2, 1] a[0] = 5
will return [5, 3, 2, 1]
print(a)
* Example of immutable object - tuple, string, boolean, integer, float
```python
a = (7, 3, 2, 1)
# error: 'tuple' object doesn't supports item assignment
a[0] = 5
- Advantages VERSUS Disadvantages
Mutable Objects:
- Don’t have to create new copy and can be modified in memory
- Might introduce bugs as you might unintentionally mutate
def add_absolute_values(deq):
for i in range(len(seq)):
# example bug - we are accidentally mutating the list and that is not what we intended
seq[i] = abs(seq[i])
return sum(seq)
values = [-5, -6, -7, -8]
print("Values Before:", values)
result = add_absolute_values(values)
print("Values After:", values)
Potential risk of aliasing:
- We might unintentially mutate the object and affect all other aliases
a = [1, 2, 3, 4]
b = a
b[0] = 15
print(b)
# list a is also unintentionally modified
print(a)
Imutable Objects:
- Safe from bugs
- Easier to understand and know their exact values
- Less efficient in terms of memory usage as you need to create a new copy of object
a = (1, 2, 3, 4)
print(id(a))
a = a[:2] + (7, ) + a[2:]
# will return (1, 2, 7, 3, 4)
print(a)
# a is now refering to new tuple
print(id(a))
def remove_even_values(dictionary):
for key, value in dictionary.items()
if value % 2 == 0:
del dictionary[key]
my_dict = {"a":1, "b":2, "c":3, "d":4}
# runtime error: dictionary changed size during iteration
remove_even_values(my_dict)
# now loop doesn't know how many iterations are left as its size changed
Cloning - creating an exact copy of an object and when you modify the clone, original is not affected
a = [1, 2, 3, 4]
# syntax to clone a list
b = a[:]
b[0] = 15
# a is not affected
# [1, 2, 3, 4]
print(a)
# [15, 2, 3, 4]
print(b)
def remove_even_values(dictionary):
for key, value in dictionary.copy().items()
if value % 2 == 0:
del dictionary[key]
my_dict = {"a":1, "b":2, "c":3, "d":4}
# no runtime error: since we created a copy
remove_even_values(my_dict)
# {"a":1, "c":3|
28. PROJECT: Tic-Tac-Toe project
move.py
class Move:
def __init__(self, value):
self._value = value
@property
def value(self):
return self._value
def is_valid(self):
return isinstance(self._value, int) and 1 <= self._value <= 9
def get_row(self):
if self._value in (1, 2, 3):
return 0 # First row
elif self._value in (4, 5, 6):
return 1 # Second row
else:
return 2 # Third row
def get_column(self):
if self._value in (1, 4, 7):
return 0 # First column
elif self._value in (2, 5, 8):
return 1 # Second column
else:
return 2 # Third column
# | 1 | 2 | 3 |
# | 4 | 5 | 6 |
# | 7 | 8 | 9 |
# Testing the Move class
move = Move(6)
# Should return True for 6
print(move.is_valid())
# Should return row 1 for 6
print(move.get_row())
# Should return column 2 for 6
print(move.get_column())
player.py
import random
from move import Move
class Player:
PLAYER_MARKER = "X"
COMPUTER_MARKER = "O"
def __init__(self, is_human=True):
self._is_human = is_human
if is_human:
self._marker = PLAYER_MARKER
else:
self._marker = COMPUTER_MARKER
@property
def is_human(self):
return self._is_human
@property
def marker(self):
return self._marker
def get_move(self):
if self._is_human:
return self.get_human_move()
else:
return self.get_computer_move()
def get_human_move()
while True:
user_input = int(input("Please enter your move (1-9): "))
move = Move(user_input)
if move.is_valid():
break
else:
print("Please enter an integer between 1 and 9.")
return move
def get_computer_move():
random_choice = random.choice(list(range(1, 10))) # 10 is excluded
move = Move(random_choice)
print("Computer move (1-9):", move.value)
return move
# Testing the Player class
player = Player(is_human=True) # Human player
# Should return True
print(player.is_human())
# Should return "X"
print(player.marker())
# Should trigger get_human_move()
move = player.get_move()
# "Please enter your move (1-9): "
print(move.value)
computer = Player(is_human=False) # Computer player
# Should return False
print(computer.is_human())
# Should return "0"
print(computer.marker())
# Should trigger get_computer_move()
move = computer.get_move()
# "Computer move (1-9):", move.value
board.py
from move import Move
from player import Player
class Board:
EMPTY_CELL = 0
def __init__(self):
self.game_board = [[0, 0, 0],
[0, 0, 0],
[0, 0, 0]]
def print_board(self):
print("\nPositions:")
self.print_board_with_positions()
print("Board:")
for row in self.game_board:
print("|", end="")
for column in row:
if columns == Board.EMPTY_CELL:
print(" |", end="")
else:
print(f" {column} |", end="")
print()
print()
def print_board_with_positions(self):
print("| 1 | 2 | 3 |\n| 4 | 5 | 6 |\n| 7 | 8 | 9 |")
def submit_move(self, player, move):
row = move.get_row()
col = move.get_column()
value = self.game_board[row][col]
if value == Board.EMPTY_CELL:
self.game_board[row][col] = player.marker
else:
print("This position is already taken. Please enter another one.")
def check_is_game_over(self, player, last_move):
return ((self.check_row(player, last_move) or
(self.check_column(player, last_move) or
(self.check_diagonal(player) or
(self.check_antidiagonal(player))
def check_row(self, player, last_move):
row_index = last_move.get_row()
baord_row = self.game_board[row_index]
return baord_row.count(player.marker) == 3
def check_column(self, player, last_move):
markers_count = 0
column_index = last_move.get_column()
for i in range(3):
if self.game_board[i][column_index] == player.marker:
markers_count += 1
return markers_count == 3
def check_diagonal(self, player):
markers_count = 0
for i in range(3):
if self.game_board[i][i] == player.marker:
markers_count += 1
return markers_count == 3
def check_antidiagonal(self, player):
markers_count = 0
for i in range(3):
if self.game_board[i][2 - i] == player.marker:
markers_count += 1
return markers_count == 3
def check_is_tie(self):
empty_counter = 0
for row in self.board_game:
empty_counter += row.count(Board.EMPTY_CELL)
return empty_counter == 0
def reset_board(self):
self.game_board = [[0, 0, 0],
[0, 0, 0],
[0, 0, 0]]
# Testing the Board class
board = Board()
player = Player()
move1 = player.get_move()
move2 = player.get_move()
move3 = player.get_move()
board.print_board()
# any of below choices will finish the game; should return True
# 1,2,3 | 4,5,6 | 7,8,9
# 1,4,7 | 2,5,8 | 3,6,9
# 1,5,9 | 3,5,7
# any of below choices will not finish the game; should return False
# 1,2,4
board.submit_move(player, move1)
board.submit_move(player, move2)
board.submit_move(player, move3)
board.print_board()
board.check_is_game_over()
# Testing the Tie and Reset Board
board = Board()
player = Player()
computer = Player(False)
board.print_board()
while not board.check_is_tie():
human_move = player.get_move()
board.submit_move(player, human_move)
board.print_board()
computer_move = computer.get_move()
board.submit_move(computer, computer_move)
board.print_board()
print("It's a tie!")
board.reset_board()
board.print_board()
game.py
from board import Board
from player import Player
class TictactoeGame:
def start(self):
print("********************************")
print(" Welcome to Tic-Tac-Toe ")
print("********************************")
board = Board()
player = Player()
computer = Player(False)
board.print_board()
# Ask the user if he/she would like to
# play another round.
while True: # Game
while True: # Round
player_move = player.get_move()
board.submit_move(player, player_move)
board.print_board()
if board.check_is_tie():
print("It's a tie! 👍 Try again.")
break
elif board.check_is_game_over(player, player_move):
print("Awesome! You won the game. 🎊")
break
else:
computer_move = computer.get_move()
board.submit_move(computer, computer_move)
board.print_board()
if board.check_is_game_over(computer, computer_move):
print("Oops... 😦 The computer won. Try again.")
break
play_again = input("Would you like to play again? Enter X for YES or O for NO").upper()
if play_again == "O":
print("Bye! Come back soon 👋")
break
elif play_again == "X":
self.start_new_round(board)
else:
print("Your input was invalid but I will assume that you want to play again. 💡")
self.start_new_round(board)
def start_new_round(self, board):
print("*************************")
print(" New Game ")
print("*************************")
board.reset_game()
board.print_board()
# Testing the game
game - TicTacToeGame()
game.start()
29. INHERITANCE
Definition
- Defining classes that inherit attributes and methods from other classes
Advantages:
- Reduce code repetition
- Reuce code
- Improve readability
Don’t repeat yourself
-
- like sqaure/triangle classes have common attributes sides/color and common methods display side length/color
- Can inherit attributes and methods from a general class like Polygon
- Classes generally inherits from more general classes which represent more abstract concept
- Define common functionality in common class and add new/customize functionality in child class
- Polygon - Triangle / Square
- Vehicle - Car / Truck
- Dessert - Brownie / Ice Cream
- Teaching Assistant Class inheriting fromm multiple classes - Students & Faculty
- Vehicle –> Land Vehicle –> Car, Truck
Attribute Inheritance
class Superclass:
pass
class Subclass(Superclass):
pass
class Polygon:
def __init__(self, num_sides, color):
self.num_sides = num_sides
self.color = color
class Triangle(Polygon):
# if no init method attributes of superclass are inherited by default
pass
my_triangle = Triangle(3, "Blue")
print(my_triangle.num_sides)
print(my_triangle.color)
class Triangle(Polygon):
# define num_sides as class constant
NUM_SIDES = 3
# if has an init method attributes of superclass are not inherited by default
def __init__(self, base, height, color):
Polygon.__init__(self, Triangle.NUM_SIDES, color) # super().__init__(self, Triangle.NUM_SIDES, color)
self.base = base
self.height = height
my_triangle = Triangle(5, 4, "Blue")
print(my_triangle.num_sides)
print(my_triangle.color)
print(my_triangle.base)
print(my_triangle.height)
class Square(Polygon):
pass
class Employee:
def __init__(self, full_name, salary):
self.full_name = full_name
self.salary = salary
class Programmer(Employee):
def __init__(self, full_name, salary, programming_language):
Employee.__init__(self, full_name, salary)
self.programming_language = programming_language
nora = Programer("Nora Nav", 60000, "Python")
print(nora.full_name)
print(nora.salary)
print(nora.programming_language)
class Character:
def __init__(self, x, y, num_lives):
self.x = x
self.y = y
self.num_lives = num_lives
class Player(character):
INITITAL_X = 0
INITIAL_Y - 0
INITIAL_NUM_LIVES = 10
def __init__(self, score=0):
Character.__init__(self, Player.INITITAL_X, Player.INITIAL_Y, Player.INITIAL_NUM_LIVES)
self.score = score
# we should be able to customize enemy attributes
class Enemy(character):
def __init__(self, x=15, y=15, num_lives=0, is_poisonous=False):
Character.__init__(self, x, y, num_lives)
self.is_poisonous = is_poisonous
Method Inheritance
class Superclass:
pass
class Subclass(Superclass):
pass
class Polygon:
def __init__(self, num_sides, color):
self.num_sides = num_sides
self.color = color
def describe_polygon(self):
print(f"This polygon has {self.num_sides} sides")
class Triangle(Polygon):
# define num_sides as class constant
NUM_SIDES = 3
# if has an init method attributes of superclass are not inherited by default
def __init__(self, base, height, color):
Polygon.__init__(self, Triangle.NUM_SIDES, color) # super().__init__(self, Triangle.NUM_SIDES, color)
self.base = base
self.height = height
def find_area():
return (self.base * self.height) / 2
class Square(Polygon):
# define num_sides as class constant
NUM_SIDES = 4
# if has an init method attributes of superclass are not inherited by default
def __init__(self, side_length, color):
Polygon.__init__(self, Square.NUM_SIDES, color) # super().__init__(self, Square.NUM_SIDES, color)
self.side_length = side_length
def find_area():
return (self.side_length ** 2)
my_triangle = Triangle(5, 4, "Blue")
my_triangle.describe_polygon()
my_square = Square(4, "Green")
my_square.describe_polygon()
30. IMPORT STATEMENTS IN PYTHON
import math
class Circle:
def __init__(self, radius):
self.radius = radius
def find_area(self):
return math.pi * (self.radius ** 2)
import random
class Die:
def __init__(self, value):
self.value = value
def roll_die(self):
random_value = random.randint(1, 6)
self.value = random_value
my_die = Die(4)
print(my_die.value)
my_die.roll_die()
print(my_die.value)
from math import pi
class Circle:
def __init__(self, radius):
self.radius = radius
def find_area(self):
return pi * (self.radius ** 2)
from random import randint
class Die:
def __init__(self, value):
self.value = value
def roll_die(self):
random_value = randint(1, 6)
self.value = random_value
my_die = Die(4)
print(my_die.value)
my_die.roll_die()
print(my_die.value)
from math import *
print(pi)
print(sin(54))
print(cos(34))
31. DOCSTRINGS
Documentation Strings
- Doctrings unlike comments are not linked to the elements they describe
- Can be used to generate documentation automatically
- Can be read via help() function
- two types : one-line & multi-line
- DocString ends with period
Example 1:
help(len)
print(len.__doc__)
help(sorted)
help(sorted.__doc__)
help(list.sort)
print(list.sort.__doc__)
def add(a, b):
"""Return the sum of a and b."""
return a + b
print(add.__doc__)
help(tuple.count)
help(str.capitalize)
print(list.sort.__doc__)
Example 2:
def print_floyds_triangle(n):
"""Print Floyd's Triangle with n rows."""
count = 1
for i in range(1, n+1):
for j in range(i):
print(count, end=" ")
count += 1
print()
Example 3:
def make_frequency_dict(sequence):
"""Return a dictionary that maps each element in sequence to its frequency.
Create a dictionary that maps each element in the list sequence
to the number of times the element occurs in the list. The element
will be the key of the key-value pair in the dictionary and its frequency
will be the value of the key-value pair.
Argument:
sequence: A list of values. These values have to be of an
immutable data type because they will be assigned as the keys
of the dictionary. For example, they can be integers, booleans,
tuples, or strings.
Return:
A dictionary that maps each element in the list to its frequency.
For example, this function call:
make_frequency_dict([1, 6, 2, 6, 2])
returns this dictionary:
{1: 1, 6: 2, 2: 2}
Raise:
ValueError: if the list is empty.
"""
if not sequence:
raise ValueError("The list cannot be empty")
freq = {}
for elem in sequence:
if elem not in freq:
freq[elem] = 1
else:
freq[elem] += 1
return freq
Example 4:
class Backpack:
"""A class that represents a Backpack.
Attribute:
items (list): the list of items in the backpack (initially empty).
Methods:
add_item(self, item):
Add the item to the backpack.
remove_item(self, item):
Remove the item from the backpack.
has_item(self, item):
Return True if the item is in the backpack. Else, return False.
"""
def __init__(self):
self.items = []
def add_item(self, item):
self.items.append(item)
def remove_item(self, item):
if item in self.items:
self.items.remove(item)
else:
print("This item is not in the backpack")
def has_item(self, item):
return item in self.items
Example 5:
import math
class Circle:
"""A class that represents a circle.
Attributes:
radius (float): the distance from the center of the circle
to its circumference.
color (string): the color of the circle.
diameter (float): the distance through the center of the circle
from one side to the other.
Methods:
find_area(self):
Return the area of the circle.
find_perimeter(self):
Return the perimeter of the circle.
"""
def __init__(self, radius, color):
"""Initialize an instance of Circle.
Arguments:
radius (float): the distance from the center
of the circle to its circumference.
color (string): the color of the circle.
"""
self._radius = radius
self._color = color
@property
def radius(self):
"""Return the radius of the circle.
This is a float that represents the distance from
the center of the circle to its circumference."""
return self._radius
@property
def color(self):
"""Return the color of the circle.
The color is described by a string that must be capitalized.
For example: "Red", "Blue", "Green", "Yellow".
"""
return self._color
@color.setter
def color(self, new_color):
self._color = new_color
@property
def diameter(self):
"""Return the diameter of the circle.
This is a float that represents the distance through
the center of the circle from one side to the other.
"""
return 2 * self._radius
def find_area(self):
"""Find and return the area of a circle.
The area is calculated with the circle radius
using the formula Pi * (radius ** 2).
"""
return math.pi * (self._radius ** 2)
def find_perimeter(self):
"""Find and return the perimeter of a circle.
The perimeter is calculated with the circle radius
using the formula (2 * Pi * radius).
"""
return 2 * math.pi * self._radius
help(Circle)
print(Circle.__doc__)
Example 6:
help(len)
print(len.__doc__)
help(sorted)
help(sorted.__doc__)
help(list.sort)
print(list.sort.__doc__)
def add(a, b):
"""Return the sum of a and b."""
return a + b
print(add.__doc__)
help(tuple.count)
help(str.capitalize)
print(list.sort.__doc__)
32. SPECIAL METHODS (Also called magic methods)
Notes:
- Not called directly
- Operator Overloading
Examples:
__methodname__()
__add__()
__str__()
__repr__()
__len__()
__bool__()
__eq__()
__lt__()
__sizeof__()
Example 1 : Intro to Special Methods
result = 5 + 6
print(result)
# Behind the scenes
result = (5).__add__(6)
print(result)
Example 2 : str
# Example: Point2D
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({x}, {y})"
my_point = Point2D(56, 60)
print(my_point)
# Example: Student
class Student:
def __init__(self, student_id, name, age, gpa):
self.student_id = student_id
self.name = name
self.age = age
self.gpa = gpa
def __str__(self):
return f"Student: {self.name} " \
f"| Student ID: {self.student_id} " \
f"| Age: {self.age} " \
f"| GPA: {selg.gpa}"
student = Student("42AB9", "Nora Nav", 34, 3.76)
print(student)
Example 3 : len
# Example: Length of Built-in Data Types
my_string = "Hello, World!"
print(len(my_string))
print(my_string.__len__())
my_list = [1, 2, 3, 4, 5]
print(len(my_list))
print(my_list.__len__())
my_tuple = (1, 2, 3, 4, 5)
print(len(my_tuple))
print(my_tuple.__len__())
my_dict = {"a": 1, "b": 2, "c": 3}
print(len(my_dict))
print(my_dict.__len__())
# Example: Customizing Length
class Backpack:
def __init__(self):
self.items = []
def add_item(self, item):
self.items.append(item)
def remove_item(self, item):
if item in self.items:
self.items.remove(item)
else:
print("This item is not in the backpack")
def __len__(self):
return len(self.items)
my_backpack = Backpack()
my_backpack.add_item("Water Bottle")
my_backpack.add_item("First Aid Kit")
my_backpack.add_item("Sleeping Bag")
print(len(my_backpack))
my_backpack.remove_item("Sleeping Bag")
print(len(my_backpack))
my_backpack.remove_item("Water Bottle")
my_backpack.remove_item("First Aid Kit")
print(len(my_backpack))
my_backpack.remove_item("Water Bottle")
Example 4 : add
# Examples with Built-in Functions
print(3 + 4)
print((3).__add__(4))
print("Hello " + "World!")
print("Hello ".__add__("World!"))
print([1, 2, 3] + [4, 5, 6])
print([1, 2, 3].__add__([4, 5, 6]))
# Example with User-Defined Class
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
new_x = self.x + other.x
new_y = self.y + other.y
return Point2D(new_x, new_y)
def __str__(self):
return f"({self.x}, {self.y})"
pointA = Point2D(5, 6)
print(pointA)
pointB = Point2D(2, 3)
print(pointB)
pointC = pointA + pointB
print(pointC)
Example 5 : getitem
# Examples with List Data Type
my_list = ["a", "b", "c", "d"]
print(my_list[0])
print(my_list[1])
print(my_list[2])
print(my_list[3])
print("-----")
print(my_list.__getitem__(0))
print(my_list.__getitem__(1))
print(my_list.__getitem__(2))
print(my_list.__getitem__(3))
# Example with User-Defined Class
class Bookshelf:
def __init__(self):
self.content = [[],
[],
[]]
def add_book(self, book, location):
self.content[location].append(book)
def take_book(self, book, location):
self.content[location].remove(book)
def __getitem__(self, location):
return self.content[location]
my_bookshelf = Bookshelf()
my_bookshelf.add_book("Les Miserables", 0)
my_bookshelf.add_book("Pride and Prejudice", 0)
my_bookshelf.add_book("Frankenstein", 0)
my_bookshelf.add_book("Sense and Sensibility", 1)
my_bookshelf.add_book("Jane Eyre", 1)
my_bookshelf.add_book("The Little Prince", 1)
my_bookshelf.add_book("Moby Dick", 2)
my_bookshelf.add_book("The Adventures of Huckleberry Finn", 2)
my_bookshelf.add_book("Dracula", 2)
print(my_bookshelf[0])
print(my_bookshelf[1])
print(my_bookshelf[2])
Example 6 : bool
# Example before defining the __bool__() method.
class BankAccount:
def __init__(self, account_owner, account_number, initial_balance):
self.account_owner = account_owner
self.account_number = account_number
self.balance = initial_balance
def make_deposit(self, amount):
self.balance += amount
def make_withdrawal(self, amount):
if self.balance - amount >= 0:
self.balance -= amount
else:
print("Insufficient funds.")
my_account = BankAccount("Nora Nav", "356-2456-2455", 45045.23)
print(bool(my_account))
if my_account:
print("True")
else:
print("False")
my_account.balance = 0
print(bool(my_account))
if my_account:
print("True")
else:
print("False")
# Example after defining the __bool__() method.
class BankAccount:
def __init__(self, account_owner, account_number, initial_balance):
self.account_owner = account_owner
self.account_number = account_number
self.balance = initial_balance
def make_deposit(self, amount):
self.balance += amount
def make_withdrawal(self, amount):
if self.balance - amount >= 0:
self.balance -= amount
else:
print("Insufficient funds.")
def __bool__(self):
return self.balance > 0
my_account = BankAccount("Nora Nav", "356-2456-2455", 45045.23)
print(bool(my_account))
if my_account:
print("True")
else:
print("False")
my_account.balance = 0
print(bool(my_account))
print(my_account.balance)
if my_account:
print("True")
else:
print("False")
# Examples of where the length determines the boolean value.
# List
my_list = [1, 2, 3, 4, 5]
print(len(my_list))
if my_list:
print("The list is not empty.")
print(bool(my_list))
else:
print("The list is empty.")
print(bool(my_list))
# String
my_string = "Hello, World!"
print(len(my_string))
if my_string:
print("The string is not empty.")
print(bool(my_string))
else:
print("The string is empty.")
print(bool(my_string))
Example 7 : rich comparison methods
# Examples of Comparison Operators and Built-in Data Types
print(15 <= 8)
print(4 > 4)
print(5 == 5)
print(6 != 8)
print("Hello" < "World")
print("Python" >= "Java")
print("Hello" == "Hello")
print([1, 2, 3, 4] < [1, 2, 3, 5])
print([4, 5, 6] > [1, 2, 3, 4])
# Example: Comparing instances of the Circle Class
class Circle:
def __init__(self, radius, color):
self.radius = radius
self.color = color
def __lt__(self, other):
return self.radius < other.radius
def __le__(self, other):
return (self.radius <= other.radius
and self.color == other.color)
def __gt__(self, other):
return self.radius > other.radius
def __ge__(self, other):
return (self.radius >= other.radius
and self.color == other.color)
def __eq__(self, other):
return (self.radius == other.radius
and self.color == self.color)
def __ne__(self, other):
return (self.radius != other.radius
or self.radius != other.radius)
circleA = Circle(5, "Blue")
circleB = Circle(5, "Green")
circleC = Circle(7, "Red")
circleD = Circle(5, "Blue")
print(circleA < circleB)
print(circleA <= circleB)
print(circleA > circleD)
print(circleA >= circleD)
print(circleA == circleB)
print(circleA == circleD)
print(circleA != circleD)
Leave a Comment