chapter 10.1

Methods & Attributes

Instance vs class attributes, __str__, and __repr__

Now that you can create classes, let's dig deeper into how data lives on them. There's an important distinction between instance attributes (unique to each object) and class attributes (shared by all objects of that class).
class Player:
    # Class attribute — shared by ALL players
    game = "RPG Quest"
    player_count = 0

    def __init__(self, name, hp=100):
        # Instance attributes — unique to each player
        self.name = name
        self.hp = hp
        Player.player_count += 1

p1 = Player("Aria", 120)
p2 = Player("Kael")

print(p1.game)           # "RPG Quest" (accessed via instance)
print(Player.game)       # "RPG Quest" (accessed via class)
print(Player.player_count)  # 2
print(p1.name)           # "Aria" (instance-specific)
print(p2.hp)             # 100 (default)
tip
Rule of thumb: If the value should be the same for every instance, make it a class attribute. If each instance needs its own copy, make it an instance attribute in __init__.
Dunder methods (double underscore, aka "magic methods") let your objects work with Python's built-in operations. The two most important ones for now:
class Item:
    def __init__(self, name, value):
        self.name = name
        self.value = value

    def __str__(self):
        """Human-readable — used by print()"""
        return f"{self.name} (${self.value})"

    def __repr__(self):
        """Developer-readable — used by the REPL and debugging"""
        return f'Item("{self.name}", {self.value})'

sword = Item("Iron Sword", 150)
print(sword)        # Iron Sword ($150)  — uses __str__
print(repr(sword))  # Item("Iron Sword", 150)  — uses __repr__
info
Without __str__, printing an object gives you something useless like <__main__.Item object at 0x7f...>. Always define __str__ for classes you'll print.
Methods that modify state — methods can change the object's attributes:
class Counter:
    def __init__(self):
        self.count = 0

    def increment(self, amount=1):
        self.count += amount

    def reset(self):
        self.count = 0

    def __str__(self):
        return f"Counter({self.count})"

c = Counter()
c.increment()
c.increment(5)
print(c)        # Counter(6)
c.reset()
print(c)        # Counter(0)
Comparison with __eq__ — define when two objects are "equal":
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __str__(self):
        return f"({self.x}, {self.y})"

a = Point(3, 4)
b = Point(3, 4)
c = Point(1, 2)

print(a == b)  # True  (without __eq__, this would be False!)
print(a == c)  # False
your turn
challenge

Create an `Inventory` class with: - A class attribute `max_slots = 10` - Instance attributes `owner` and `items` (empty list) - An `add(item)` method that checks capacity before adding - `__str__` that shows a readable summary - `__repr__` that shows a developer-friendly representation

loading editor...

output
$ Press RUN to execute your code
AI Tutor

Ask about Methods & Attributes

Hey! I'm your AI tutor.

Ask me anything about this lesson, or paste code you're stuck on.

AI tutor is a premium feature