Python OOP Part 1: Classes and Objects

Part 7 of the Python for Everyone track. Last updated: September 2026.

Python is object-oriented to its core — even int and str are classes. Part 1 covers classes, objects, constructors, and attributes. Part 2 covers inheritance and the dunder (double-underscore) methods that make your classes feel native.

Your first class

class Dog:
    species = "Canis familiaris"   # CLASS attribute — shared by all dogs

    def __init__(self, name, age):
        self.name = name           # INSTANCE attributes — per-object
        self.age = age

    def bark(self):                # method — self is always the first parameter
        return f"{self.name} says woof!"

rex = Dog("Rex", 3)    # __init__ runs here
print(rex.bark())      # Rex says woof!
print(rex.species)     # Canis familiaris (looked up on the class)

self is just the convention for the first parameter — it refers to the instance the method was called on. __init__ is the constructor.

Class vs instance attributes

class Counter:
    total = 0            # shared across ALL instances

    def __init__(self):
        self.count = 0   # each instance gets its own
        Counter.total += 1

a, b = Counter(), Counter()
a.count = 5
print(a.count, b.count, Counter.total)  # 5 0 2

Changing a.count doesn't touch b.count — but both see the same Counter.total until one of them shadows it.

__str__ and __repr__: make objects printable

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

    def __str__(self):
        return f"'{self.title}' ({self.pages} pages)"   # for users: print(book)

    def __repr__(self):
        return f"Book({self.title!r}, {self.pages!r})" # for developers: in lists/logs

b = Book("Dune", 412)
print(b)        # 'Dune' (412 pages)   — uses __str__
print([b])      # [Book('Dune', 412)]  — containers use __repr__

Rule of thumb: __str__ readable for users, __repr__ unambiguous for developers. If you define only one, define __repr__.

Encapsulation, Python-style

class Account:
    def __init__(self, balance):
        self._balance = balance    # single underscore = "internal, don't touch"

    @property
    def balance(self):             # read-only attribute access
        return self._balance

acc = Account(100)
print(acc.balance)   # 100 — looks like an attribute, runs a method
# acc.balance = 50   # AttributeError — no setter, so read-only

Python has no true private members — the single underscore is a convention ("we're all adults here"). @property gives you controlled access without getter/setter boilerplate.

Key takeaways

  • class defines the blueprint; __init__(self, ...) initializes each instance.
  • Class attributes are shared; instance attributes (self.x) are per-object.
  • Define __str__/__repr__ so your objects print sensibly.
  • Use @property instead of Java-style getters.

Next in this series: Python OOP Part 2: Inheritance, Polymorphism and Dunder Methods.

Comments

Popular posts from this blog

Java Banking Finance Services and Insurance (BFSI) domain interview questions

JSP Servlet Interview Questions For Freshers Series 1

Java program to check even or odd number