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
Post a Comment