Python Classes: Build Vacation Plans with Objects and Methods

Vacation itinerary components organized as connected Python class objects

What You’ll Learn

In this lesson, you’ll learn how to use Python classes to model vacation plans and keep related data and behavior together. You’ll create objects, define methods, validate values, and work with multiple instances.

  • Understand the relationship between a class and its instances.
  • Define an initializer with __init__.
  • Store object state with instance attributes.
  • Write methods that update and calculate information for an object.
  • Recognize common class design mistakes.

The Concept

A class is a reusable design for creating objects. An object created from a class is called an instance. For example, a vacation-planning application might use a VacationPlan class to represent one trip.

A class usually combines:

  • State: data belonging to an object, such as its destination, duration, and budget.
  • Behavior: actions the object can perform, such as adding an activity or calculating remaining money.

The __init__ method runs when a new instance is created. The first parameter of an instance method is conventionally named self; it refers to the particular object using the method. Attributes written as self.destination or self.budget belong to that instance.

Classes become useful when an application needs to work with multiple similar things. Instead of keeping separate variables and functions for every vacation, you can create one class and then make as many vacation plan objects as needed.

Basic Example

The following class tracks a vacation destination, its budget, planned activities, and the amount already spent on those activities.

class VacationPlan:
    def __init__(self, destination, days, budget):
        self.destination = destination
        self.days = days
        self.budget = budget
        self.activities = []
        self.spent = 0

    def add_activity(self, name, cost):
        self.activities.append(name)
        self.spent += cost

    def remaining_budget(self):
        return self.budget - self.spent

    def estimated_daily_budget(self):
        return self.budget / self.days

    def summary(self):
        activity_text = ", ".join(self.activities)
        return (
            f"{self.destination}: {self.days} days, "
            f"{len(self.activities)} activities ({activity_text})"
        )


plan = VacationPlan("Kyoto", 6, 1800)
plan.add_activity("Fushimi Inari visit", 0)
plan.add_activity("Tea ceremony", 75)
plan.add_activity("Bicycle tour", 45)

print(plan.summary())
print(f"Daily budget: ${plan.estimated_daily_budget():.2f}")
print(f"Remaining budget: ${plan.remaining_budget():.2f}")

Expected Output

Kyoto: 6 days, 3 activities (Fushimi Inari visit, Tea ceremony, Bicycle tour)
Daily budget: $300.00
Remaining budget: $1680.00

How the Code Works

Relationship diagram showing the VacationPlan class creating multiple vacation plan instances. Its initializer establishes instance state such as destination, days, budget, activities, and spending. Instance methods update that state or calculate derived values such as remaining and daily budgets.
A Python class combines reusable structure, per-instance state, and methods that update or calculate vacation-plan data.

The class definition begins with class VacationPlan:. Its initializer receives the values needed to create one plan:

  • destination stores the location.
  • days stores the trip length.
  • budget stores the total available amount.
  • activities starts as an empty list for this specific instance.
  • spent starts at zero.

Calling VacationPlan(“Kyoto”, 6, 1800) creates an instance and automatically calls __init__. The variable plan then refers to that object.

add_activity changes two pieces of state: it appends a name to the instance’s activity list and increases the amount spent. Because the method uses self, it updates the plan on which it was called. A second vacation plan would have its own list and spending total.

remaining_budget and estimated_daily_budget return calculated values instead of storing duplicate values. This helps keep the object consistent: if the spending changes, the calculations automatically use the new amount.

The formatting expression :.2f displays each money value with two decimal places. The summary method uses join to turn the activity list into readable text.

Another Example

Classes can also represent a collection of related objects. This example uses a TravelGroup class to manage several vacation plans and calculate the group’s combined budget.

class TravelGroup:
    def __init__(self, name):
        self.name = name
        self.plans = []

    def add_plan(self, plan):
        self.plans.append(plan)

    def total_budget(self):
        return sum(plan.budget for plan in self.plans)

    def destinations(self):
        return [plan.destination for plan in self.plans]

    def print_overview(self):
        print(f"{self.name} has {len(self.plans)} vacation plans.")
        print(f"Destinations: {', '.join(self.destinations())}")
        print(f"Combined budget: ${self.total_budget():.2f}")


summer_group = TravelGroup("Summer family trips")
summer_group.add_plan(VacationPlan("Lisbon", 5, 1400))
summer_group.add_plan(VacationPlan("Reykjavik", 4, 2200))

summer_group.print_overview()

This example demonstrates that an object can contain other objects. Each item in plans is a VacationPlan instance, while TravelGroup provides operations across the collection. This pattern is useful when a real application has relationships between entities, such as a trip containing reservations or a traveler managing multiple itineraries.

Common Mistakes

Forgetting self

Instance methods need self as their first parameter. Without it, Python cannot receive the object that called the method. You also need to use self when accessing instance attributes inside the method.

Sharing a mutable default value

Do not use a list as a default argument when each instance should have its own list. A default list is created once and can accidentally be shared by every object. Create the list inside __init__, as the basic example does with self.activities = [].

Mixing up class attributes and instance attributes

An attribute assigned inside __init__ belongs to each instance. An attribute assigned directly in the class body is shared as class-level data unless an instance overrides it. Use instance attributes for vacation-specific values such as a destination or budget.

Allowing invalid state

The basic example assumes that the caller provides a positive number of days and valid costs. In a larger application, methods should validate input before changing the object’s state. For example, a negative activity cost should usually be rejected rather than reducing the spending total.

Try It Yourself

Complete the class below so it can track a vacation’s packing items. Add an add_item method and a missing_items method that returns the items from required_items that have not been packed.

class PackingList:
    def __init__(self, traveler, required_items):
        self.traveler = traveler
        self.required_items = required_items
        self.packed_items = []

    # Add your methods here


packing = PackingList(
    "Maya",
    ["passport", "walking shoes", "rain jacket", "camera"]
)

packing.add_item("passport")
packing.add_item("camera")

print(packing.missing_items())

Challenge

Create a VacationDay class for one day in an itinerary. Your class must:

  • Store a date and a location when initialized.
  • Start with an empty list of activities.
  • Provide an add_activity method that accepts an activity name and its cost.
  • Provide a total_cost method that returns the day’s total activity cost.
  • Provide a summary method that reports the date, location, activities, and total cost.
  • Reject negative activity costs by raising ValueError.

Create one day, add two activities, and print its summary.

Solution

class VacationDay:
    def __init__(self, date, location):
        self.date = date
        self.location = location
        self.activities = []

    def add_activity(self, name, cost):
        if cost < 0:
            raise ValueError("Activity cost cannot be negative.")

        self.activities.append({"name": name, "cost": cost})

    def total_cost(self):
        return sum(activity["cost"] for activity in self.activities)

    def summary(self):
        activity_names = ", ".join(
            activity["name"] for activity in self.activities
        )
        return (
            f"{self.date} in {self.location}: "
            f"{activity_names}; total cost ${self.total_cost():.2f}"
        )


day = VacationDay("2025-07-14", "Barcelona")
day.add_activity("Park Guell", 18)
day.add_activity("Tapas dinner", 42)

print(day.summary())

The class creates a separate activity list for each vacation day. Each activity is stored as a dictionary containing its name and cost, which allows total_cost to calculate the sum across all activities. The validation happens before the activity is appended, so an invalid cost cannot partially update the object’s state.

Key Takeaways

  • A class defines reusable state and behavior, while an instance is one object created from that class.
  • __init__ initializes each new instance.
  • self gives an instance method access to that particular object’s attributes.
  • Methods can update state or calculate values from current state.
  • Create mutable attributes such as lists inside __init__ to avoid accidentally sharing them between instances.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top