Questions or a demo for your school?Student? Sit a test
BrainBit Infotech TeachingStart free

Python notes · Topic 14 of 22 · 4 min read

Python classes and objects (OOP part 1)

Python classes and objects notes: class and object, __init__ and self, methods, __str__ and __repr__, class vs instance attributes, and lists of objects.

A class is a blueprint; objects are built from it

Object-oriented programming (OOP) groups data and the functions that work on it into one unit.

  • A class is a blueprint, such as the design of a bank account.
  • An object (or *instance*) is one real thing made from that blueprint: Asha's account, Ravi's account.
  • Attributes are an object's data (owner, balance).
  • Methods are functions that belong to the class (deposit, withdraw).

You already use objects: every string is an object of class str, and "hi".upper() calls a method.

Writing your first class

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner          # attribute
        self.balance = balance      # attribute

    def deposit(self, amount):      # method
        self.balance += amount

    def withdraw(self, amount):
        if amount > self.balance:
            print("Not enough balance")
        else:
            self.balance -= amount

a = BankAccount("Asha", 1000)       # create an object
b = BankAccount("Ravi")
a.deposit(500)
print(a.owner, a.balance)           # Asha 1500
print(b.balance)                    # 0

Class names use *CapitalWords* by convention.

__init__ and self

  • __init__ is the constructor. It runs automatically each time you create an object, and sets up the object's starting attributes.
  • self is the object the method is working on. When you call a.deposit(500), Python passes a as self, so self.balance means *a's* balance.
  • Every method needs self as its first parameter, but you never pass it yourself.

What happens on a = BankAccount("Asha", 1000):

  1. Python makes a new, empty object.
  2. It calls __init__(new_object, "Asha", 1000).
  3. __init__ stores owner and balance on the object.
  4. The finished object is stored in a.

Methods that change and check object data

class Student:
    def __init__(self, name, marks):
        self.name = name
        self.marks = marks            # a list of marks

    def add_mark(self, m):
        self.marks.append(m)

    def average(self):
        return sum(self.marks) / len(self.marks)

    def passed(self):
        return self.average() >= 33   # one method can call another through self

s = Student("Meena", [80, 72])
s.add_mark(90)
print(round(s.average(), 1), s.passed())   # 80.7 True

__str__ and __repr__: make objects print nicely

Printing an object normally shows something like <__main__.Student object at 0x0000...>. Define __str__ to control what print() shows:

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __str__(self):                       # for users: print(), str()
        return f"{self.name}: Rs {self.price}"

    def __repr__(self):                      # for programmers: the shell, lists
        return f"Product({self.name!r}, {self.price})"

p = Product("Pen", 10)
print(p)          # Pen: Rs 10
print([p])        # [Product('Pen', 10)]

Class attributes and instance attributes

class Student:
    school = "Model School"           # class attribute: shared by all students

    def __init__(self, name):
        self.name = name              # instance attribute: different for each

a, b = Student("Asha"), Student("Ravi")
print(a.school, b.school)             # Model School Model School
Student.school = "City School"        # changes it for everyone

Use class attributes for values every object shares, such as a GST rate or a school name.

Lists of objects

students = [Student("Asha"), Student("Ravi"), Student("Meena")]
for s in students:
    print(s.name)

products = [Product("Pen", 10), Product("Bag", 450), Product("Book", 120)]
cheap = [p for p in products if p.price < 200]
products.sort(key=lambda p: p.price)

Example: library book

class Book:
    def __init__(self, title):
        self.title = title
        self.issued_to = None

    def issue(self, student):
        if self.issued_to:
            return f"Already issued to {self.issued_to}"
        self.issued_to = student
        return f"{self.title} issued to {student}"

    def give_back(self):
        self.issued_to = None

Common mistakes

  • Forgetting self in a method definition: TypeError: ... takes 0 positional arguments but 1 was given.
  • Writing balance instead of self.balance inside a method, which creates a local variable instead.
  • Misspelling the constructor as __int__ or _init_: it then never runs.
  • Calling a method without brackets: a.deposit names the method; a.deposit(100) runs it.

Key points

  • A class is a blueprint; each object has its own attributes.
  • __init__ sets up a new object; self is the object being used.
  • __str__ controls what print(obj) shows.
  • Class attributes are shared; instance attributes belong to one object.

Practice questions

1. Write a Rectangle class with length and width and an area() method.
class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

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

print(Rectangle(4, 5).area())    # 20
2. What is self in a.deposit(500)?

The object a. Python passes it automatically as the first argument.

3. What does print(obj) show if the class has no __str__ or __repr__?

Something like <__main__.Rectangle object at 0x...>: the class name and a memory address.

4. A class has rate = 18 at class level. Is it shared by all objects?

Yes. It is a class attribute, so every object reads the same value until one object sets its own self.rate.

5. Add a __str__ to Rectangle that prints Rectangle 4 x 5.
def __str__(self):
    return f"Rectangle {self.length} x {self.width}"