37  Character Actions: Adding Behaviors with Methods

In our last lesson, we learned how to create classes and give objects attributes. Today, we’ll learn how to make our objects do things by adding methods. Just as a warrior needs both equipment (attributes) and skills (methods), our objects need both data and behaviors to be truly useful.

37.1 What are Methods?

Methods are functions that belong to a class. They define what objects of that class can do. Let’s enhance our Warrior class from last time by adding some actions:

class Warrior:
    def __init__(self, name, health, strength):
        self.name = name
        self.health = health
        self.strength = strength
    
    def battle_cry(self):
        print(f"{self.name} shouts: For glory and honor!")

# Create a warrior and make them shout
hero = Warrior("Parzival", 100, 15)
hero.battle_cry()

This will output:

Parzival shouts: For glory and honor!

Notice how the method battle_cry can access the warrior’s name using self.name. The self parameter refers to the specific warrior doing the shouting.

37.2 Methods with Parameters

Methods can also take additional parameters, just like regular functions. Let’s add an attack method:

class Warrior:
    def __init__(self, name, health, strength):
        self.name = name
        self.health = health
        self.strength = strength
    
    def battle_cry(self):
        print(f"{self.name} shouts: For glory and honor!")
    
    def attack(self, target):
        print(f"{self.name} attacks {target} with {self.strength} strength!")

# Create warriors and make them fight
hero = Warrior("Parzival", 100, 15)
dragon = "Ancient Dragon"
hero.attack(dragon)

This will output:

Parzival attacks Ancient Dragon with 15 strength!

37.3 Methods that Change Object State

Methods can modify the object’s attributes. Let’s add healing and damage methods:

class Warrior:
    def __init__(self, name, health, strength):
        self.name = name
        self.health = health
        self.strength = strength
    
    def take_damage(self, amount):
        self.health -= amount
        print(f"{self.name} takes {amount} damage!")
        print(f"{self.name}'s health: {self.health}")
    
    def heal(self, amount):
        self.health += amount
        print(f"{self.name} heals for {amount} health!")
        print(f"{self.name}'s health: {self.health}")

# Create a warrior and simulate combat
hero = Warrior("Parzival", 100, 15)
hero.take_damage(20)
hero.heal(10)

This will output:

Parzival takes 20 damage!
Parzival's health: 80
Parzival heals for 10 health!
Parzival's health: 90

37.4 Methods that Return Values

Just like regular functions, methods can return values:

class Warrior:
    def __init__(self, name, health, strength):
        self.name = name
        self.health = health
        self.strength = strength
    
    def is_alive(self):
        return self.health > 0
    
    def get_attack_power(self):
        return self.strength * 2

# Check warrior status
hero = Warrior("Parzival", 100, 15)
if hero.is_alive():
    print(f"{hero.name} can attack for {hero.get_attack_power()} damage!")

This will output:

Parzival can attack for 30 damage!

37.5 A Complete Character Class

Let’s put it all together with a more complete character class:

class Character:
    def __init__(self, name, health, strength):
        self.name = name
        self.health = health
        self.strength = strength
        self.level = 1
        self.experience = 0
    
    def battle_cry(self):
        print(f"{self.name} shouts: For glory and honor!")
    
    def take_damage(self, amount):
        self.health -= amount
        print(f"{self.name} takes {amount} damage!")
        print(f"Health remaining: {self.health}")
    
    def heal(self, amount):
        self.health += amount
        print(f"{self.name} heals for {amount} health!")
        print(f"Health now: {self.health}")
    
    def gain_experience(self, amount):
        self.experience += amount
        print(f"{self.name} gains {amount} experience!")
        
        # Level up if experience is high enough
        if self.experience >= 100:
            self.level_up()
    
    def level_up(self):
        self.level += 1
        self.strength += 5
        self.health += 20
        self.experience = 0
        print(f"{self.name} reaches level {self.level}!")
        print(f"Strength increased to {self.strength}")
        print(f"Health increased to {self.health}")

# Create and use a character
hero = Character("Parzival", 100, 15)
hero.battle_cry()
hero.take_damage(30)
hero.heal(20)
hero.gain_experience(120)  # This will trigger a level up

37.6 Practice Time: Adding Methods to Your Classes

Now it’s your turn to create classes with methods! Try these challenges:

  1. Create a Spell class with methods for casting the spell and checking if there’s enough mana:
class Spell:
    def __init__(self, name, damage, mana_cost):
        # Your code here
        pass
    
    def cast(self, caster, target):
        # Your code here
        pass
    
    def has_enough_mana(self, caster_mana):
        # Your code here
        pass
  1. Make a Potion class with methods for using the potion and checking if it’s expired:
class Potion:
    def __init__(self, name, healing_power, uses):
        # Your code here
        pass
    
    def use(self, target):
        # Your code here
        pass
    
    def is_useable(self):
        # Your code here
        pass
  1. Design a Quest class with methods for starting, completing, and failing the quest:
class Quest:
    def __init__(self, name, difficulty, reward):
        # Your code here
        pass
    
    def start(self):
        # Your code here
        pass
    
    def complete(self):
        # Your code here
        pass
    
    def fail(self):
        # Your code here
        pass

37.7 Common Bugs to Watch Out For

As you work with methods, be wary of these common pitfalls:

  1. Forgetting self: Always include self as the first parameter in method definitions:

    def wrong_method(name):  # Missing self!
        print(name)
    
    def correct_method(self, name):
        print(name)
  2. Not Using self to Access Attributes: Within methods, you must use self. to access object attributes:

    def wrong_method(self):
        print(name)  # NameError: name is not defined
    
    def correct_method(self):
        print(self.name)  # Correctly accesses the object's name
  3. Including self When Calling Methods: When calling a method, don’t include self:

    hero.battle_cry(self)  # Wrong!
    hero.battle_cry()      # Correct!
  4. Modifying Attributes Without self: When changing object attributes, remember to use self:

    def wrong_heal(self, amount):
        health += amount  # Wrong! Creates a local variable
    
    def correct_heal(self, amount):
        self.health += amount  # Correctly modifies the object's health

37.8 Conclusion and Further Resources

You’ve now learned how to add behaviors to your objects using methods. This makes your classes much more powerful and useful. Just as a warrior needs both equipment and skills to be effective, objects need both attributes and methods to be truly useful in your programs.

To learn more about Python methods and object-oriented programming, check out these resources:

  1. Python’s Official Tutorial on Classes and Methods
  2. Real Python’s Guide to Object-Oriented Programming
  3. W3Schools Python Methods

In our next lesson, we’ll learn about inheritance - how to create new classes that build upon existing ones. Until then, keep practicing with your objects and methods!