Behavior lives in methods
A method is a function defined inside a class. It always takes self first, which is how it reaches the object's own attributes. You have been calling methods all along: "hi".upper() and my_list.append(3) from Python for Beginners are methods on str and list objects.
class Player: def __init__(self, name): self.name = name self.score = 0 def add_points(self, points): self.score += points
When you write mia.add_points(10), Python translates it to Player.add_points(mia, 10). That is the whole mystery of self: it is the object before the dot.
A method that updates its own object
A method is just a function defined inside the class body, with self as its first parameter. It can change the object it was called on and read the rest of that object's attributes.
class Player: def __init__(self, name): self.name = name self.score = 0 def add_points(self, points): self.score += points def summary(self): return f"{self.name} has {self.score} points" mia = Player("mia") mia.add_points(30) mia.add_points(25) print(mia.summary())
Output
mia has 55 pointsTwo calls to add_points accumulate on the same object, so mia.score reaches 55. summary never takes any arguments beyond self, because everything it needs to build the sentence is already stored on the instance.
Choosing between a dict and a class
Both model "a thing with named parts", so the choice comes down to what else the data has to carry.
| Situation | Reach for |
|---|---|
| Loose data passing through: records read from a file, configuration values, counting | dict |
| Data plus behavior and invariants (rules the data must always obey, like a balance never going negative) | class |
A class gives you three wins over the dict version:
- Typo safety:
mia.scorraisesAttributeErrorimmediately, whileplayer["scor"]silently creates a new key on assignment. - A home for logic:
add_pointslives next to the data it changes. - A contract: every
Playeris guaranteed to havenameandscore, because__init__sets them.
In unit 8 you will parse JSON, a text format for structured data defined there, into dicts, then convert the important pieces to objects at the boundary. That combination is everyday production Python.
withdraw
An Account gets two methods. deposit always adds, while withdraw subtracts only when there is enough money and otherwise prints insufficient funds and leaves the balance alone. That guard is an invariant in action: the balance can never go negative, and the rule lives inside the class rather than in every caller.
class Account: def __init__(self, owner): self.owner = owner self.balance = 0 def deposit(self, amount): self.balance += amount def withdraw(self, amount): if amount > self.balance: print("insufficient funds") else: self.balance -= amount acct = Account("mia") acct.deposit(100) acct.withdraw(30) acct.withdraw(500) print(acct.balance)
Output
insufficient funds
70Both methods take self first and then amount. deposit is a single line, self.balance += amount. The interesting part is the order inside withdraw: the comparison amount > self.balance happens before any subtraction, so the failed 500 withdrawal cannot leave a partial change behind. The balance ends at 70, which is 100 deposited minus the one withdrawal that was allowed.
is_square
Methods can report facts as well as change state. Rectangle gets an area method returning width * height, and an is_square method that is True exactly when the two sides match.
class Rectangle: def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height def is_square(self): return self.width == self.height r = Rectangle(3, 4) print(r.area(), r.is_square()) s = Rectangle(4, 4) print(s.area(), s.is_square())
Output
12 False 16 True
Both methods take only self and return a value instead of mutating anything. is_square needs no if statement at all, because the comparison self.width == self.height already evaluates to True or False. Writing if self.width == self.height: return True else: return False would be four lines doing the work of one.
What the dot actually does
mia.add_points(10) is equivalent to Player.add_points(mia, 10). The object in front of the dot is handed to the method as its first argument, which is the parameter you spelled self.
That is the entire mechanism. There is no hidden magic in self: it is an ordinary parameter that Python fills in with whatever object the method was called on, which is also why every method signature has to list it explicitly.