Free Handbook · Runs in your browser

Objects & Classes

Classes and objects, __init__ and self, inheritance and super(), multiple inheritance with the MRO traced, polymorphism and duck typing, operator overloading with dunder methods, @property, and dataclasses — plus AttributeError and the missing-self TypeError.

0 / 108 lessons🔥 0 day streak
ShareXLinkedIn

Module 08 · what you'll be able to do

  • Define a class with __init__, attributes and methods, and explain self
  • Extend a class with inheritance and call the parent with super()
  • Read a method resolution order and predict which method runs
  • Make your objects print, compare and add with dunder methods
  • Use @property and @dataclass to write less boilerplate
  • Fix AttributeError and the "takes 1 positional argument but 2 were given" error
01

Classes and objects

A class is a blueprint: it says what attributes (data) and methods (functions) every object of that kind has. An object is one instance built from it. __init__ runs when an object is created and sets up its attributes. self is the object the method was called on — Python passes it automatically, which is why every method's first parameter is self.

pythonEdit it. ⌘/Ctrl + Enter runs.
You should see
Ada 153.0
BankAccount True
{'owner': 'Ada', 'balance': 153.0}
Your turn
Add a withdraw(amount) method that refuses to overdraw, and a second account. Confirm the two balances are independent.
VisualizeWhat happens when you write acct.deposit(50)Step 1 / 5
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
acct = BankAccount("Ada", 100)
acct.deposit(50)
Line 9

Calling the class creates an empty object, then calls __init__(new_object, "Ada", 100). self is that new object.

Variables now
self<BankAccount>
All 5 steps as a table
StepLineWhat happenedVariables now
19Calling the class creates an empty object, then calls __init__(new_object, "Ada", 100). self is that new object.self = <BankAccount>
23Set an attribute on the object: self.owner = "Ada".self.owner = 'Ada'
34self.balance = 100. __init__ returns; the finished object is bound to acct.self.balance = 100 acct = <BankAccount>
410acct.deposit(50) is sugar for BankAccount.deposit(acct, 50) — the object goes in as self, 50 as amount.amount = 50
57Read self.balance (100), add 50, store it back on the same object.self.balance = 150
Error you will hit

TypeError: deposit() takes 1 positional argument but 2 were given

python
class BankAccount:
    def deposit(amount):          # forgot self
        print("depositing", amount)

BankAccount().deposit(50)
Traceback (most recent call last):
  File "your code", line 5, in <module>
    BankAccount().deposit(50)
TypeError: BankAccount.deposit() takes 1 positional argument but 2 were given
Why the interpreter said that

Python always passes the object as the first argument. Your method declared one parameter (amount), so the object filled it and the 50 had nowhere to go. Whenever an argument count is off by exactly one inside a class, it is a missing self.

The fix

Add self as the first parameter of every method.

python
class BankAccount:
    def deposit(self, amount):
        print("depositing", amount)

BankAccount().deposit(50)
Error you will hit

AttributeError: 'BankAccount' object has no attribute 'balence'

python
class BankAccount:
    def __init__(self):
        self.balance = 0

acct = BankAccount()
print(acct.balence)
Traceback (most recent call last):
  File "your code", line 6, in <module>
    print(acct.balence)
AttributeError: 'BankAccount' object has no attribute 'balence'. Did you mean: 'balance'?
Why the interpreter said that

The object has no attribute by that name. A typo, or an attribute that __init__ only sets on some code path, or — very often — a method you forgot to call with parentheses and then tried to use as a value. The famous variant is 'NoneType' object has no attribute …: something returned None and you kept going (Module 11).

The fix

Check the spelling against __init__; use dir(acct) or acct.__dict__ to see what actually exists.

python
class BankAccount:
    def __init__(self):
        self.balance = 0

acct = BankAccount()
print(acct.balance)
02

Inheritance and super()

A subclass inherits everything from its parent and can add or override. super() calls the parent's version — nearly always in __init__, so the parent still sets up its attributes. Inheritance is for "is a" relationships (a SavingsAccount is a BankAccount). For "has a", use an attribute.

pythonEdit it. ⌘/Ctrl + Enter runs.
You should see
Thing says ...
Rex says Woof
Bit says Woof!
True True True
Dog Dog
Your turn
Add a Cat(Animal) that says "Meow" and put all four in the loop. intro() is inherited and never changes — that is polymorphism, next lesson.
Forgetting super().__init__()
If a subclass defines its own __init__ and does not call the parent's, the parent's attributes are never set — and you get AttributeError: 'Puppy' object has no attribute 'name' the first time something uses one.
03

Multiple inheritance and the MRO

A class can inherit from several parents. When a method exists in more than one, Python consults the method resolution order — a deterministic linearisation (C3) that goes left to right, depth before breadth, and never visits a class before its subclasses. Cls.__mro__ shows it. Used well, this is mixins: small classes that each add one capability.

pythonEdit it. ⌘/Ctrl + Enter runs.
You should see
['Both', 'JSONMixin', 'LogMixin', 'Left', 'Right', 'Base', 'object']
Left → Right → Base
[Both] {"id": 7}
VisualizeResolving b.describe() through the MROStep 1 / 4
class Base:
def describe(self): return "Base"
class Left(Base):
def describe(self): return "Left → " + super().describe()
class Right(Base):
def describe(self): return "Right → " + super().describe()
class Both(Left, Right): pass
print(Both().describe())
Line 9

Look up describe on Both's MRO: Both → Left → Right → Base → object. Both has none; Left does.

Variables now
MROBoth, Left, Right, Base, object
All 4 steps as a table
StepLineWhat happenedVariables now
19Look up describe on Both's MRO: Both → Left → Right → Base → object. Both has none; Left does.MRO = Both, Left, Right, Base, object
24Left.describe runs. super() here does not mean "Base" — it means "the next class after Left in this object's MRO", which is Right.super() → = Right
36Right.describe runs. Its super() is the next after Right: Base.super() → = Base
42Base.describe returns "Base". The strings concatenate on the way back up.
Quick check

In class C(A, B), both A and B define save(). Which runs for C().save()?

04

Polymorphism and duck typing

Polymorphism: the same call, shape.area(), does the right thing for whichever shape it is. In Python you do not even need a shared parent class — if it has an area() method, it works. "If it walks like a duck and quacks like a duck…" — duck typing. Protocols (Module 09) let type checkers understand it too.

pythonEdit it. ⌘/Ctrl + Enter runs.
You should see
[3.14, 9, 0]
True
3 2 1
TypeError: Can't instantiate abstract class Shape without an implementation for abstract method 'area'
05

Operator overloading: dunder methods

Methods with double underscores — "dunders" — are how Python asks your object to behave like a built-in. print(obj) calls __str__; a + b calls a.__add__(b); a == b calls __eq__; len(obj) calls __len__; for x in obj calls __iter__. Implement the ones that make sense and your class fits into the language.

pythonEdit it. ⌘/Ctrl + Enter runs.
You should see
20.00 USD
Money(1999, 'USD') [Money(1999, 'USD')]
True True False
[Money(1, 'USD'), Money(500, 'USD'), Money(1999, 'USD')]
False zero is falsy
Your turn
Add __mul__ so Money(250) * 3 works, and __len__-style __hash__ so Money can be a set member (hint: hash the tuple).
You writePython callsNotes
str(x), print(x), f-strings__str__Falls back to __repr__ if missing
repr(x), the REPL, containers__repr__Always define this one; aim for "code that recreates it"
x + y, x - y, x * y__add__ __sub__ __mul__Return a new object; never mutate self
x == y, x < y__eq__ __lt__Defining __eq__ removes the default __hash__
len(x), x[i], for i in x__len__ __getitem__ __iter__Make it a sequence
with x:__enter__ __exit__A context manager
x()__call__A callable object
06

@property

Python has no private fields and does not need getters and setters by default — acct.balance is fine. When you later need validation or a computed value, @property lets you turn an attribute into a method without changing the callers. That is the whole point: start with plain attributes, add properties only when behaviour is needed.

pythonEdit it. ⌘/Ctrl + Enter runs.
You should see
25 77.0
86.0
rejected: below absolute zero
read-only: property 'fahrenheit' of 'Temperature' object has no setter
_single_underscore
Convention for "internal — do not touch from outside". Nothing is enforced.
__double_underscore
Name mangling: self.__x becomes self._ClassName__x. Avoids clashes in subclasses; not privacy. Rarely needed.
07

dataclasses

Most classes are just "a bundle of named fields". Writing __init__, __repr__ and __eq__ for each is boilerplate, and @dataclass generates them from the field list. Use it for records, configs, DTOs, results — anything whose identity is its data. It is the modern replacement for namedtuple when you want mutability or methods.

pythonEdit it. ⌘/Ctrl + Enter runs.
You should see
Order(id=1, customer='Ada', items=['book'], total=12.5)
True
{'id': 1, 'customer': 'Ada', 'items': ['book'], 'total': 12.5}
[Version(major=1, minor=2), Version(major=1, minor=10)]
FrozenInstanceError
Your turn
Write a @dataclass for a Product(sku, name, price_cents, tags) with a safe empty-list default and a price property that returns dollars.
Quick check

Why does items: list = [] fail inside a dataclass?

Practice this — graded problems in your browser

Finish the Python handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.