Classes and Objects
Summary
This chapter introduces object-oriented programming in Python. Students will learn to define classes, create objects, work with instance and class attributes, write constructors and methods, and understand the self parameter. The chapter covers encapsulation through private attributes, getter/setter methods, and the property decorator. These concepts enable students to model real-world entities in their programs.
Concepts Covered
This chapter covers the following 20 concepts from the learning graph:
- Object-Oriented Programming
- Classes
- Objects
- Class Definition
- Class Instantiation
- Instance Attributes
- Class Attributes
- The Self Parameter
- Init Method
- Constructor
- Methods
- Instance Methods
- Str Method
- Repr Method
- Encapsulation
- Private Attributes
- Getter Methods
- Setter Methods
- Property Decorator
- Class Methods
Prerequisites
This chapter builds on concepts from:
- Chapter 1: Introduction to Computer Science
- Chapter 6: Functions and Modular Design
- Chapter 7: Higher-Order Functions and Recursion
Monty says: Let's code this!
Welcome back, coders! Up until now, you've been storing data in variables, lists, and dictionaries, and organizing your logic with functions. That's powerful stuff. But today we're unlocking a whole new way of thinking about your programs. Get ready to meet classes and objects — the building blocks of modern software!
What Is Object-Oriented Programming?
Imagine you're building a video game with dozens of characters. Each character has a name, a health level, and a special ability. Each character can attack, defend, and move. You could store all of that in separate lists and write a bunch of loose functions to manage everything — but it would get messy fast. There has to be a better way.
There is. It's called Object-Oriented Programming (OOP for short), and it's one of the most important ideas in all of computer science. OOP is a programming style where you bundle related data and behavior together into a single unit called an object. Instead of scattering your character's data across multiple lists and writing unrelated functions, you create a self-contained "character package" that knows its own name, tracks its own health, and has its own methods for attacking and defending.
Why does this matter? Because the real world is full of objects. A car has attributes (color, speed, fuel level) and behaviors (accelerate, brake, turn). A student has attributes (name, grade, GPA) and behaviors (enroll, study, graduate). OOP lets you model real-world things in your code in a way that feels natural and organized.
Here's the big-picture analogy that will carry us through the entire chapter:
| Real World | OOP Concept |
|---|---|
| Cookie cutter | Class |
| Cookie | Object |
| Shape, flavor, size | Attributes |
| Eating, decorating | Methods |
A class is like a cookie cutter — it's the template or blueprint that defines what a cookie looks like. An object is the actual cookie you press out of the dough. You can make dozens of different cookies from the same cutter, just like you can create many objects from one class. Each cookie can have different sprinkles on top (different data), but they all share the same basic shape (the same structure).
Let's keep this cookie-cutter analogy in our back pocket. We'll use it throughout the chapter.
Defining Your First Class
Time to write some code. A class definition tells Python the name of your new type and what data and behavior it should have. Here's the simplest possible class:
1 2 | |
That's it! The class keyword followed by a name (capitalized by convention) and a colon creates a brand-new type called Dog. The pass statement is just a placeholder that means "nothing here yet." Think of this as building an empty cookie cutter — it exists, but it doesn't do much.
Let's make it more interesting by adding some real structure. We're going to build a Dog class throughout this chapter and keep adding features as we learn new concepts.
Diagram: Class vs. Object Visual
Class vs. Object Cookie Cutter Diagram
Type: infographic
sim-id: class-vs-object-diagram
Library: p5.js
Status: Specified
Bloom Level: Understand (L2) Bloom Verb: compare, explain
Learning Objective: Students will be able to distinguish between a class (blueprint/template) and an object (instance) by seeing a visual analogy of a cookie cutter producing multiple cookies with different attributes.
Purpose: A visual diagram showing a class as a cookie cutter on the left and multiple objects (cookies) on the right, each with different attribute values. Arrows show the "instantiation" process from class to objects.
Layout:
- Left side: A single "cookie cutter" shape labeled "Dog class" with a list of attributes (name, breed, age) and methods (bark, sit, fetch)
- Right side: Three "cookie" shapes, each labeled as a Dog object with different values:
- Dog 1: name="Buddy", breed="Golden Retriever", age=3
- Dog 2: name="Luna", breed="Poodle", age=5
- Dog 3: name="Max", breed="Beagle", age=1
- Arrows from the cookie cutter to each cookie labeled "instantiation"
Interactive elements:
- Hover over the class to highlight all shared structure
- Hover over any object to see its unique attribute values
- Click a "Create New Dog" button to animate a new cookie being "pressed out" with random attributes
Color scheme: Class in blue, objects in warm cookie-brown tones with colored sprinkle accents Responsive: Layout adjusts from horizontal (wide screens) to vertical (narrow screens)
Instructional Rationale: The concrete visual analogy of cookie cutter to cookies makes the abstract class-vs-object distinction tangible. Interactive creation of new objects reinforces that many instances come from one class.
Creating Objects: Class Instantiation
Now that we have a class, let's make some objects. Class instantiation is the process of creating an actual object from a class. You "call" the class like a function:
1 2 3 4 5 | |
Each call to Dog() creates a brand-new, independent object — a specific instance of the Dog class. Right now our dogs are pretty boring (they have no data), but we've proven the concept: one class, multiple objects.
Think of it this way: Dog is the blueprint, my_dog and your_dog are two separate houses built from that blueprint. They share the same layout, but they're different houses on different streets.
The Constructor: The __init__ Method
When you buy a new phone, it doesn't come blank — it has some starting setup. A constructor does the same thing for objects. It's a special method that runs automatically every time you create a new object, setting up its initial data.
In Python, the constructor is the __init__ method (that's two underscores on each side of "init," short for "initialize"). Let's give our dogs some personality:
1 2 3 4 5 | |
Now when we create a Dog, we pass in the starting values:
1 2 | |
Each dog gets its own name, breed, and age. These are stored right inside the object, and we can access them anytime:
1 2 3 | |
Monty says: You've got this!
You might be wondering about those double underscores around __init__. In Python, methods with double underscores on both sides are called "dunder" methods (short for "double underscore"). They have special powers — Python calls them automatically at certain moments. __init__ is called when an object is born. You'll meet more dunder methods later in this chapter!
Understanding the self Parameter
You probably noticed that weird first parameter in the __init__ method — self. What is that? The self parameter is how an object refers to itself. When you write self.name = name, you're saying "store this value inside me."
Here's the key insight: when you call Dog("Buddy", "Golden Retriever", 3), Python automatically passes the new object as the first argument. You never type self in the function call — Python handles it behind the scenes. You only see self in the method definition.
Think of self like the word "my" in everyday speech. When Buddy the dog says "my name is Buddy," the word "my" refers to Buddy himself. When Luna says "my name is Luna," the same word "my" refers to Luna. self works the same way — it points to whichever object is running the code.
1 2 3 4 5 6 | |
Instance Attributes vs. Class Attributes
The name, breed, and age that we set in __init__ are called instance attributes — they belong to one specific object. Buddy's name is "Buddy" and Luna's name is "Luna." Each instance carries its own copy.
But sometimes you want a piece of data shared by all objects of a class. That's a class attribute. It lives on the class itself, not on any individual object:
1 2 3 4 5 6 7 | |
Every Dog object can access species, but it's defined once and shared:
1 2 3 4 5 6 7 | |
Here's a handy comparison:
| Feature | Instance Attribute | Class Attribute |
|---|---|---|
| Defined in | __init__ using self. |
Directly inside the class body |
| Belongs to | One specific object | The class itself (shared by all) |
| Can differ between objects? | Yes | No (unless overridden) |
| Example | self.name = "Buddy" |
species = "Canis familiaris" |
Diagram: Instance vs. Class Attributes
Instance vs. Class Attributes Memory Diagram
Type: diagram
sim-id: instance-vs-class-attributes
Library: p5.js
Status: Specified
Bloom Level: Understand (L2) Bloom Verb: differentiate, explain
Learning Objective: Students will be able to differentiate between instance attributes (unique per object) and class attributes (shared across all objects) by viewing a memory diagram.
Purpose: An interactive memory model showing a Dog class with a shared class attribute (species) and multiple Dog objects, each with unique instance attributes (name, breed, age). Arrows show that class attributes are referenced from the class, not duplicated per object.
Layout:
- Top: A blue box representing the
Dogclass, showingspecies = "Canis familiaris" - Below: Three green boxes representing Dog instances (buddy, luna, max)
- Each instance box shows its own name, breed, age values
- Dashed arrows from each instance to the class attribute box for
species - Solid borders around instance attributes to show they are "owned" by the object
Interactive elements:
- Hover over
speciesin the class box to highlight all dashed arrows connecting to instances - Hover over any instance attribute to see it highlighted only on that one object
- Click "Add Dog" to create a new instance box with random attributes and a new dashed arrow
- Toggle "Show Memory Addresses" to reveal that all instances reference the same class attribute location
Color scheme: Class box in blue, instance boxes in green, shared attribute arrows in orange dashed lines Responsive: Boxes reflow vertically on narrow screens
Instructional Rationale: Memory diagrams make the invisible visible. Seeing that class attributes live in one place (the class) while instance attributes are stored per object reinforces the conceptual difference. Interactive arrows clarify the reference relationship.
Writing Methods
Data is only half the story. Objects don't just have information — they do things. A method is a function that lives inside a class and describes the behavior of an object.
Instance Methods
An instance method is the most common type of method. It operates on a specific object and has access to that object's data through self:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Now our dogs can do things:
1 2 3 4 5 6 | |
Notice that birthday() actually changes the object's data — it increments self.age. Methods can both read and modify an object's attributes. That's the beauty of bundling data and behavior together.
Monty says: Let's debug this together!
Every instance method must take self as its first parameter. If you forget it, Python will throw an error like TypeError: bark() takes 0 positional arguments but 1 was given. That error message is confusing until you realize Python is secretly passing the object as the first argument. Always include self!
Special Methods: __str__ and __repr__
What happens when you try to print an object?
1 2 3 | |
Yikes. That memory address isn't helpful at all. Python doesn't magically know how to display your custom object — you need to tell it. That's where the __str__ method comes in.
The __str__ Method
The __str__ method defines the "user-friendly" string version of your object. It's what Python uses when you call print() or str() on your object:
1 2 3 4 5 6 7 8 | |
Now printing works beautifully:
1 2 | |
The __repr__ Method
The __repr__ method is similar, but it's meant for developers, not end users. It should return a string that could recreate the object. Think of __str__ as the "pretty" version and __repr__ as the "technical" version:
1 2 3 4 5 6 7 8 9 10 11 | |
Here's the difference in action:
1 2 3 4 | |
When you type an object's name in the Python interactive shell (without print), Python uses __repr__. When you use print(), Python uses __str__. If you only define one, define __repr__ — Python will fall back to it when __str__ isn't available.
| Method | Audience | Called by | Purpose |
|---|---|---|---|
__str__ |
End users | print(), str() |
Readable, friendly display |
__repr__ |
Developers | Interactive shell, repr() |
Precise, recreatable representation |
Diagram: Dog Class Complete Structure
Dog Class UML-Style Diagram
Type: diagram
sim-id: dog-class-uml-diagram
Library: p5.js
Status: Specified
Bloom Level: Understand (L2) Bloom Verb: describe, summarize
Learning Objective: Students will be able to read a class diagram showing the structure of a Python class, identifying its attributes and methods.
Purpose: A UML-style class diagram for the Dog class showing the class name, attributes (instance and class), and methods in a clear three-section box format.
Layout:
- A single box divided into three horizontal sections:
- Top section (header): Class name "Dog"
- Middle section: Attributes listed with types
species: str(labeled "class attribute")name: str(labeled "instance")breed: str(labeled "instance")age: int(labeled "instance")
- Bottom section: Methods
__init__(name, breed, age)bark() -> strdescribe() -> strbirthday() -> str__str__() -> str__repr__() -> str
Interactive elements:
- Hover over any attribute to see its description and whether it is instance or class level
- Hover over any method to see a brief description of what it does
- Click "Show Code" to toggle a code panel beside the diagram showing the full Python class definition
Color scheme: Header in dark blue, attributes in light blue, methods in light green Responsive: Box scales with window width; minimum readable size maintained
Instructional Rationale: Class diagrams are a standard tool in software design. Introducing students to this visual format early helps them plan classes before coding and read documentation that uses UML notation.
A Complete Example: Building It Up
Let's see our full Dog class with everything we've learned so far in one place:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
Let's take it for a spin:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
This is the power of OOP: all of Buddy's data and behavior live together in one tidy package. You don't need to track separate lists of names, breeds, and ages. Everything is organized and self-contained.
Encapsulation: Protecting Your Data
Here's a question: what stops someone from doing this?
1 2 | |
Nothing! Right now, anyone can reach in and set buddy.age to any value, even one that makes no sense. That's a problem if you're building real software.
Encapsulation is the OOP principle of hiding an object's internal data and controlling access to it. Instead of letting the outside world poke around directly, you provide controlled doors (methods) that validate changes before allowing them. Think of it like a bank vault — you don't let customers walk in and grab cash. They go through a teller who checks their identity and balance first.
Private Attributes
In Python, you signal that an attribute is "private" — meaning it shouldn't be accessed directly from outside the class — by prefixing its name with an underscore. These are called private attributes:
1 2 3 4 5 | |
The single underscore _age is a convention, not a hard rule. Python won't actually prevent someone from accessing buddy._age, but the underscore is a clear signal: "Hey, this is internal. Don't touch it directly."
For stronger privacy, you can use a double underscore, which triggers name mangling — Python renames the attribute internally to make it harder to access from outside:
1 2 3 4 5 6 7 8 | |
Monty says: Watch out, coders!
Python's privacy system is based on trust, not enforcement. The single underscore convention (_age) is like a "Do Not Disturb" sign on a hotel door — polite but not a physical barrier. The double underscore (__age) is more like a locked door, but a determined person can still pick the lock. In Python culture, we follow the conventions because we're good citizens, not because we're forced to.
Getter Methods
If attributes are private, how do we read them? We write getter methods — methods that return the value of a private attribute:
1 2 3 4 5 6 7 8 9 | |
Now outside code reads the age through the getter:
1 2 | |
Setter Methods
To change a private attribute safely, we write setter methods — methods that validate the new value before applying it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Now we get protection:
1 2 3 4 5 6 7 | |
The setter acts as a gatekeeper, only allowing valid changes through.
Diagram: Encapsulation Bank Vault Analogy
Encapsulation Bank Vault Analogy
Type: infographic
sim-id: encapsulation-bank-vault
Library: p5.js
Status: Specified
Bloom Level: Understand (L2) Bloom Verb: explain, illustrate
Learning Objective: Students will be able to explain how encapsulation protects an object's internal data by restricting direct access and using getter/setter methods as controlled access points.
Purpose: A visual analogy showing a bank vault (the object) with a teller window (getter/setter methods) as the only way to interact with the money inside (private attributes). Direct access to the vault is blocked.
Layout:
- Center: A large "vault" box representing the Dog object, with private attributes (
_age,_breed) shown inside - Front of vault: Two windows labeled "Getter" and "Setter"
- Outside: A figure representing "outside code" trying to access the vault
- Path 1 (green, allowed): Outside code -> Getter window -> receives age value
- Path 2 (green, allowed): Outside code -> Setter window -> value validated -> stored in vault
- Path 3 (red, blocked): Outside code -> tries to reach directly into vault -> big red X
Interactive elements:
- Click "Try Direct Access" to animate the blocked attempt with a red flash and error message
- Click "Use Getter" to animate a successful read through the getter window
- Click "Use Setter (valid)" to animate a successful write with a green checkmark
- Click "Use Setter (invalid)" to animate a blocked write with a validation error
Color scheme: Vault in dark gray, getter window in green, setter window in blue, blocked paths in red Responsive: Single-column layout on narrow screens
Instructional Rationale: The bank vault analogy makes the abstract concept of encapsulation concrete. Animating both allowed and blocked access paths helps students understand why getters and setters exist and how they protect data integrity.
The Property Decorator: The Best of Both Worlds
Getters and setters work, but calling buddy.get_age() and buddy.set_age(4) feels clunky compared to the clean buddy.age syntax. Wouldn't it be great if you could write buddy.age but still get the protection of a setter?
That's exactly what the property decorator does. It lets you define getter and setter methods that look like regular attribute access. It's the best of both worlds — clean syntax and data validation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Now you get clean, natural syntax with hidden protection:
1 2 3 4 5 6 7 8 9 10 11 | |
The @property decorator is a favorite tool among Python developers. It keeps your code clean on the outside while maintaining all the safety checks on the inside. You'll see it in professional Python code everywhere.
Here's a summary of the three approaches to attribute access:
| Approach | Read | Write | Validation? | Pythonic? |
|---|---|---|---|---|
| Public attribute | buddy.age |
buddy.age = 4 |
No | Simple but risky |
| Getter/setter methods | buddy.get_age() |
buddy.set_age(4) |
Yes | Works but verbose |
| Property decorator | buddy.age |
buddy.age = 4 |
Yes | Clean and safe |
Class Methods
So far, every method we've written operates on an individual object — it uses self to access that specific dog's data. But sometimes you want a method that belongs to the class itself, not to any particular object. That's a class method.
A class method is created using the @classmethod decorator, and its first parameter is cls (the class) instead of self (the object):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Class methods are useful for two main things:
- Factory methods — Alternate ways to create objects (like
from_stringabove) - Class-level operations — Working with class attributes rather than instance attributes
1 2 3 4 5 6 7 8 9 10 | |
Notice that you call class methods on the class itself (Dog.get_dog_count()) rather than on an object. They don't need a specific instance to do their work.
Monty says: Let's debug this together!
If all these decorators and special methods feel like a lot, take a breath. You don't need to memorize every detail right now. The most important things are: __init__ sets up your object, self means "me," and methods define what your object can do. Start with those three ideas, and everything else will click as you practice!
Bringing It All Together: The Complete Dog Class
Here's our finished Dog class with everything from this chapter — class attributes, instance attributes, instance methods, special methods, encapsulation with the property decorator, and a class method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
Let's exercise every feature:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Diagram: Object Interaction Playground
Dog Class Interactive Playground MicroSim
Type: microsim
sim-id: dog-class-playground
Library: p5.js
Status: Specified
Bloom Level: Apply (L3) Bloom Verb: construct, demonstrate
Learning Objective: Students will be able to create Dog objects, call methods, and observe results in an interactive playground that demonstrates class instantiation, method calls, and attribute access.
Purpose: An interactive code playground where students can create Dog objects by filling in attribute fields, then click buttons to call various methods and see the output — without needing to switch to a separate Python environment.
Layout:
- Top section: "Create a Dog" form with input fields for name, breed, and age, plus a "Create" button
- Middle section: A visual "kennel" area showing all created Dog objects as card-style boxes
- Each card shows the dog's name, breed, age, and buttons for bark(), describe(), birthday()
- Bottom section: Output console showing the results of method calls
- A "Dog Count" badge in the corner showing the class method result
Interactive elements:
- Fill in name/breed/age and click "Create" to add a new Dog card to the kennel
- Click "Bark" on any dog card to see the bark() output
- Click "Birthday" to increment that dog's age (visually updates the card)
- Click "Describe" to see the describe() output
- Click "Print" to see the str output
- Click "Repr" to see the repr output
- "Dog Count" badge auto-updates when new dogs are created
Visual style: Colorful dog cards with breed-themed icons, console area with monospace font Color scheme: Cards in warm tones, console in dark theme Responsive: Cards wrap to multiple rows on narrow screens
Instructional Rationale: A no-code playground lets students experiment with OOP concepts interactively. Creating objects and calling methods provides immediate visual feedback, reinforcing the relationship between classes, objects, attributes, and methods without the overhead of setting up a development environment.
Why OOP Matters
You might be thinking: "This is cool, but why go through all this trouble? I could just use dictionaries and functions." That's fair! For small programs, you absolutely could. But OOP really shines as programs grow larger:
- Organization: All related data and behavior live in one place. A
Dogclass bundles everything about dogs together. - Reusability: Once you've written a
Dogclass, you can create as many dogs as you want without rewriting anything. - Encapsulation: You can protect your data from accidental corruption and provide clean interfaces for interacting with objects.
- Real-world modeling: OOP lets you represent real-world things (students, bank accounts, game characters) in a natural way.
- Teamwork: When building software with a team, OOP makes it easier to divide work. One person builds the
Dogclass, another builds theCatclass, and they connect through well-defined methods.
In the next chapter, you'll learn about inheritance — creating new classes based on existing ones. That's where OOP gets really powerful.
Diagram: OOP Benefits Concept Map
OOP Benefits Concept Map
Type: infographic
sim-id: oop-benefits-concept-map
Library: p5.js
Status: Specified
Bloom Level: Understand (L2) Bloom Verb: summarize, explain
Learning Objective: Students will be able to summarize the key benefits of OOP (organization, reusability, encapsulation, real-world modeling, teamwork) and explain why OOP is preferred for larger programs.
Purpose: A concept map with "OOP" at the center and five benefit nodes radiating outward, each with a brief description and a concrete example from the Dog class.
Layout:
- Central node: "Object-Oriented Programming" in a large circle
- Five satellite nodes arranged in a star pattern:
- Organization: "All Dog data + behavior in one class"
- Reusability: "Create unlimited Dog objects from one class"
- Encapsulation: "Property decorators protect age from invalid values"
- Real-world modeling: "Dogs in code match dogs in real life"
- Teamwork: "Each teammate builds a different class"
- Connecting lines from center to each node
Interactive elements:
- Hover over any benefit node to see an expanded explanation and code snippet
- Click a benefit node to highlight the relevant parts of a miniature Dog class code block shown at the bottom
Color scheme: Center in green, benefit nodes in five distinct colors Responsive: Star layout collapses to vertical list on narrow screens
Instructional Rationale: Concept maps help students see the big picture and connect individual concepts to overarching themes. Linking each benefit to a concrete code example from the chapter grounds abstract principles in familiar code.
Monty says: You've got this!
Amazing work, coder! You just learned one of the most important concepts in all of programming. You can now define classes, create objects, write constructors and methods, control access to data, and use Python's property decorator like a pro. You're officially thinking in objects. Next stop: inheritance and polymorphism!
Key Takeaways
- Object-Oriented Programming (OOP) bundles related data (attributes) and behavior (methods) into objects for cleaner, more organized code.
- A class is a blueprint or template. An object is a specific instance created from that class.
- The
__init__method (constructor) runs automatically when you create an object, setting up its initial attributes. selfis how an object refers to itself inside its methods.- Instance attributes belong to individual objects. Class attributes are shared across all objects of a class.
- Instance methods operate on a specific object's data. Class methods operate on the class itself.
__str__gives you a user-friendly string.__repr__gives you a developer-friendly, recreatable string.- Encapsulation protects data by making attributes private and using getter/setter methods or the property decorator to control access.
- The property decorator (
@property) gives you clean attribute-style syntax (buddy.age) with hidden validation logic. - Class methods (
@classmethod) belong to the class rather than any instance, useful for factory methods and class-level operations.
Check Your Understanding: What is the difference between a class and an object?
A class is a blueprint or template that defines the structure (attributes) and behavior (methods) for a type of thing. An object is a specific instance created from that class. For example, Dog is a class, and buddy = Dog("Buddy", "Golden Retriever", 3) creates an object. You can think of a class as a cookie cutter and an object as a cookie — many objects can come from one class, each with its own data.
Check Your Understanding: Why do we use encapsulation and the property decorator?
Encapsulation protects an object's internal data from being changed to invalid values. Without it, anyone could set buddy.age = -5, which doesn't make sense. The property decorator lets you add validation (like checking that age is non-negative) while keeping the syntax clean — you still write buddy.age = 4 instead of buddy.set_age(4). It's the best of both worlds: safety and readability.
Check Your Understanding: What is the difference between __str__ and __repr__?
Both return string representations of an object, but for different audiences. __str__ is for end users — it returns a readable, friendly string and is called by print(). __repr__ is for developers — it returns a precise string that ideally could recreate the object, and is called in the interactive shell. For example: str(buddy) might return "Buddy the Golden Retriever, age 3" while repr(buddy) returns "Dog('Buddy', 'Golden Retriever', 3)".