PHOT 110: Introduction to programming

LECTURE 11: Object Oriented Programming: Classes (Ch. 7 & 9)

Michaël Barbier, Spring semester (2023-2024)

Classes

What is a class ?

  • Part of object-oriented programming
  • A class combines
    • Attributes (parameters of the class), and
    • Methods (functions of the class)
  • An object is an instance of a class
text = "This is an object of class string"
print(type(text))
<class 'str'>

What is a class ?

  • Part of object-oriented programming
  • A class combines
    • Attributes (parameters of the class), and
    • Methods (functions of the class)
  • An object is an instance of a class
a_list = ["Green", "Yellow", "Orange"]
print(a_list.__doc__)
Built-in mutable sequence.

If no argument is given, the constructor creates a new empty list.
The argument must be an iterable if specified.

Class definition

  • Attributes (parameters of the class), and
  • Methods (functions of the class)
class SimpleClass:

    # An attribute 
    secret = "4567"

    # A method
    def tell_the_secret(self):
        return "The code = " + self.secret

Classes & objects

An object is an instance of a class

class SimpleClass:

    secret = "4567"

    def tell_the_secret(self):
        return "The code = " + self.secret


c = SimpleClass()
print(c.secret)
print(c.tell_the_secret())
4567
The code = 4567

Constructors

class SimpleClass:

    def __init__(self, code):
        self.secret = str(code)

    def tell_the_secret(self):
        return "The code = " + self.secret


A constructor creates an instance of a class with parameters

c = SimpleClass(123456)
print(c.tell_the_secret())
The code = 123456

A slightly more complex example class

class spaceship:

  def __init__(self, pos_0, orient_0, image):
    self.position = pos_0
    self.orientation = orientation_0
    self.image = image

  def teleport(self, displacement):
    self.position = self.position + displacement
        
  def test_collision(pos, R):
    if (pos - self.position)**2  < R ** 2:
      return True
    return False

Extra attributes

c = SimpleClass(512)
print(c.tell_the_secret())

# Define attributes after creation
c.color = "Green"

# Test the attribute is there
print(c.color)
The code = 512
Green

Objects of a class

Adding attributes per object

c1 = SimpleClass(123)
c1.color = "Yellow"

c2 = SimpleClass(456)
c2.name = "Mehmet" 


print("c1 says: " + c1.tell_the_secret())
c1 says: The code = 123


print("c2 says: " + c2.tell_the_secret())
c2 says: The code = 456

Objects of a class

Adding attributes per object

c1 = SimpleClass(123)
c1.color = "Yellow"

c2 = SimpleClass(456)
c2.name = "Mehmet" 


print(c1.color)
Yellow


print(c2.color)
AttributeError: 'SimpleClass' object has no attribute 'color'

Methods versus functions

  • Methods act on objects: You need an object!
a_list = [1, 2, 3]
a_list.reverse()
print(a_list)
[3, 2, 1]
  • functions are not bound to an object
a_list = [1, 2, 3]
print(list(reversed(a_list)))
[3, 2, 1]

Special methods

Special methods of a class

Special methods use double underscores in their name:

  • Constructor: __init__()
  • Callable: __call__()
  • Printing a class instance: __str__()

Operator overloading: Using operators +, -, * between objects

  • Not equal sign: __ne__()
  • Plus operator: __add__()

Callables

  • Especially for classes defining a formula
  • Call a class instance like a function
class Formula:

    def __init__(self, a):
        self.a = a

    def __call__(self, a, x):
        return self.a * x

Callables

class Formula:

    def __init__(self, a):
        self.a = a

    def __call__(self, a, x):
        return self.a * x


Formula is a class:

f = Formula(6)
print(f)
<__main__.Formula object at 0x0000023543DEEF30>

Callables

class Formula:

    def __init__(self, a):
        self.a = a

    def __call__(self, a, x):
        return self.a * x


Formula can be called like a function:

f_called = f(6, 4)
print(f_called)
24

Telling how to print an instance: str()

class Formula:

    def __init__(self, a):
        self.a = a

    def __str__(self):
        return f"My special format: {self.a} + 5"


f = Formula(6)
print(f)
My special format: 6 + 5

Operator overloading

Using for example “+” between custom class objects

class Figure:

    def __init__(self, a):
        self.a = a

    def __add__(self, a, x):
        return self.a * x

Class hierarchy

Example class

class Vehicle:

    location = None
    city_list = ["Izmir", "Istanbul", "Bursa"]

    def __init__(self, location):
        self.location = str(location)

    def drive_to_city(self, city):
        self.location = city

    def tell_current_location(self, city):
        return self.location

If you need something similar but not exactly the same

Cargo delivery service: We want to transport goods

  • A list of stock of goods in each city ? as an attribute ?
  • A method deliver() ?

Bus transport of persons

  • time tables
  • number of seats
  • ticket price per person

If you need something similar but not exactly the same

Do we need to write code for a whole new class ?

  • How to add functionality to the vehicle class ?
  • Change methods ?

We can derive a class from another class

  • Keeping the old functionality
  • Extending with new methods/attributes
  • Adapting methods

Family of classes

class DerivedClass(BaseClass):
    <statement>
    ...
    <statement>

Example class

class Vehicle:

    city_list = ["Izmir", "Istanbul", "Bursa"]

    def __init__(self, location):
        self.city_list = ["Izmir", "Istanbul", "Bursa"]
        self.location = location

    def drive_to_city(self, city):
        print(f"Driving from {self.location} to {city}")
        self.location = city


car = Vehicle("Izmir")
car.drive_to_city("Istanbul")
Driving from Izmir to Istanbul

Example class

class Vehicle:

    city_list = ["Izmir", "Istanbul", "Bursa"]

    def __init__(self, location):
        self.city_list = ["Izmir", "Istanbul", "Bursa"]
        self.location = location

    def drive_to_city(self, city):
        print(f"Driving from {self.location} to {city}")
        self.location = city


bus = Vehicle("Izmir")
bus.drive_to_city("Istanbul")
Driving from Izmir to Istanbul

Derived classes

Make a derived class (subclass)

  • Use super() to access BaseClass
  • Use super().__init__() to have BaseClass constructor
class DerivedClass(BaseClass):

    <attribute>
    <attribute>

    <method>
    <method>
    ...

Derived classes

class Vehicle:

    def __init__(self, location):
        self.city_list = ["Izmir", "Istanbul", "Bursa"]
        self.location = location

    def drive_to_city(self, city):
        print(f"Driving from {self.location} to {city}")
        self.location = city


class Bus(Vehicle):
  
    def __init__(self, location, passengers):
        super().__init__(location)
        self.max_passengers = 4
        self.passengers = passengers

Derived classes

class Bus(Vehicle):
  
    def __init__(self, location, passengers):
        super().__init__(location)
        self.max_passengers = 4
        self.passengers = passengers


bus = Bus("Izmir", ["Ahmet", "Zeynep"])
bus.drive_to_city("Istanbul")
Driving from Izmir to Istanbul

Derived classes, use superclass method

class Bus(Vehicle):
  
    def __init__(self, location, passengers):
        super().__init__(location)
        self.max_passengers = 4
        self.passengers = passengers

    def accept_passenger(self, name):
        if len(self.passengers) < self.max_passengers:
            self.passengers.append(name)
        else:
            print("Sorry, the bus is full !")


bus = Bus("Izmir", ["Ahmet", "Zeynep"])
bus.drive_to_city("Istanbul")
Driving from Izmir to Istanbul

Derived classes, use subclass method

class Bus(Vehicle):
  
    def __init__(self, location, passengers):
        super().__init__(location)
        self.max_passengers = 4
        self.passengers = passengers

    def accept_passenger(self, name):
        if len(self.passengers) < self.max_passengers:
            self.passengers.append(name)
        else:
            print("Sorry, the bus is full !")


bus = Bus("Izmir", ["Ahmet", "Zeynep"])
bus.accept_passenger("Kemal"); print(bus.passengers)
['Ahmet', 'Zeynep', 'Kemal']

Derived classes, use subclass method

bus = Bus("Izmir", ["Ahmet", "Zeynep"])
bus.accept_passenger("Mehmet")
print(bus.passengers)
['Ahmet', 'Zeynep', 'Mehmet']


bus.accept_passenger("Rani")
print(bus.passengers)
['Ahmet', 'Zeynep', 'Mehmet', 'Rani']


bus.accept_passenger("Bob")
print(bus.passengers)
Sorry, the bus is full !
['Ahmet', 'Zeynep', 'Mehmet', 'Rani']

Classes