Yesterday Item stored data. What if you want behaviour that uses the data — say, "is this item's value above 10?"
class Item:
def __init__(self, name, value):
self.name = name
self.value = value
def is_high(self):
return self.value > 10is_high looks like a normal function — except it has self as its first parameter.
That's the only difference. A function defined inside a class block is a method. When you call a.is_high(), Python automatically passes a in as self. You write a.is_high() — no explicit argument — and the method body sees self.value as a.value.
So a.is_high() is the same as Item.is_high(a)?
Yes — that's exactly the desugaring. Both forms work; the dotted form is what you'll write in 99% of code. The class form is occasionally useful for explicit dispatch.
When does method-on-class beat function-that-takes-an-instance?
Two reasons. First, discoverability — a.<TAB> in an editor lists every method on the class. Second, the method belongs to the shape — when you look at the class, you see all the operations defined for that data in one place. With free functions, the operations scatter across files.
A method is a function defined inside a class block. The first parameter is conventionally self — the instance on which the method was called.
class Item:
def __init__(self, name, value):
self.name = name
self.value = value
def is_high(self):
return self.value > 10
def doubled(self):
return self.value * 2a = Item("a", 5)
b = Item("b", 12)
print(a.is_high()) # False
print(b.is_high()) # True
print(b.doubled()) # 24a.is_high() is sugar for Item.is_high(a) — Python passes a as self automatically.
self is just the first parameter. You can declare more:
class Item:
def __init__(self, name, value):
self.name = name
self.value = value
def above(self, threshold):
return self.value > threshold
b = Item("b", 12)
print(b.above(10)) # True
print(b.above(20)) # FalseA method can change self's attributes:
class Item:
def __init__(self, name, value):
self.name = name
self.value = value
def increment(self):
self.value = self.value + 1
a = Item("a", 5)
a.increment()
print(a.value) # 6Mutating methods usually return None (implicitly). Don't try result = a.increment() — result will be None, just like list.append.
| Free function | Method | |
|---|---|---|
| Discoverability | grep for the function name | a.<TAB> autocompletes |
| Locality | scattered across files | inside the class block |
| Coupling | weak — works on any compatible shape | tight — bound to the class |
The scattering pain is what motivated classes. The coupling is the cost. Tomorrow's @dataclass shows the concise version of the same idea.
Yesterday Item stored data. What if you want behaviour that uses the data — say, "is this item's value above 10?"
class Item:
def __init__(self, name, value):
self.name = name
self.value = value
def is_high(self):
return self.value > 10is_high looks like a normal function — except it has self as its first parameter.
That's the only difference. A function defined inside a class block is a method. When you call a.is_high(), Python automatically passes a in as self. You write a.is_high() — no explicit argument — and the method body sees self.value as a.value.
So a.is_high() is the same as Item.is_high(a)?
Yes — that's exactly the desugaring. Both forms work; the dotted form is what you'll write in 99% of code. The class form is occasionally useful for explicit dispatch.
When does method-on-class beat function-that-takes-an-instance?
Two reasons. First, discoverability — a.<TAB> in an editor lists every method on the class. Second, the method belongs to the shape — when you look at the class, you see all the operations defined for that data in one place. With free functions, the operations scatter across files.
A method is a function defined inside a class block. The first parameter is conventionally self — the instance on which the method was called.
class Item:
def __init__(self, name, value):
self.name = name
self.value = value
def is_high(self):
return self.value > 10
def doubled(self):
return self.value * 2a = Item("a", 5)
b = Item("b", 12)
print(a.is_high()) # False
print(b.is_high()) # True
print(b.doubled()) # 24a.is_high() is sugar for Item.is_high(a) — Python passes a as self automatically.
self is just the first parameter. You can declare more:
class Item:
def __init__(self, name, value):
self.name = name
self.value = value
def above(self, threshold):
return self.value > threshold
b = Item("b", 12)
print(b.above(10)) # True
print(b.above(20)) # FalseA method can change self's attributes:
class Item:
def __init__(self, name, value):
self.name = name
self.value = value
def increment(self):
self.value = self.value + 1
a = Item("a", 5)
a.increment()
print(a.value) # 6Mutating methods usually return None (implicitly). Don't try result = a.increment() — result will be None, just like list.append.
| Free function | Method | |
|---|---|---|
| Discoverability | grep for the function name | a.<TAB> autocompletes |
| Locality | scattered across files | inside the class block |
| Coupling | weak — works on any compatible shape | tight — bound to the class |
The scattering pain is what motivated classes. The coupling is the cost. Tomorrow's @dataclass shows the concise version of the same idea.
Create a free account to get started. Paid plans unlock all tracks.