Constructors, Destructors, and Attributes
Now that you can create basic classes, you'll dive deeper into sophisticated patterns: constructors with flexible parameters, the critical distinction between class and instance attributes, and object cleanup with destructors.
In this lesson, you'll discover why these patterns matter through hands-on experiments, learn from AI explanations, challenge the AI with subtle edge cases, and build a comprehensive reference guide.
Part 1: Experience Default Parameters and Attribute Types
Your Role: Code experimenter discovering initialization patterns and attribute scoping
Discovery Exercise 1: Why Default Parameters Matter
Create task_initialization.py and run this:
Loading Python environment...
Output:
Task created: Buy groceries
Task created: Review PR (no deadline)
Now add default parameters:
Loading Python environment...
Output:
task1.title = "Buy groceries"
task1.priority = 3
task2.title = "Review PR"
task2.priority = 5 (default)
task2.description = "" (default)
💬 AI CoLearning Prompt
"I added default parameters to my Task class. Required: title. Optional: description (default empty string), priority (default 5), due_date (default None). Now I can create tasks flexibly. But when should parameters be required vs have defaults? Show me 3 examples: 1) user registration (which fields MUST be provided?), 2) database connection (which have sensible defaults?), 3) API client config. Explain the design principle."
Expected Understanding: AI will explain that critical data should be required (no default), convenience data can have defaults. You'll see the tradeoff between flexibility and enforced completeness. Task.title is always required because every task must have a name; due_date is optional because you can create urgent immediate tasks.
Discovery Exercise 2: Instance vs Class Attributes
Run this experiment:
Loading Python environment...
Output:
task1.priority = 5
task2.priority = 5
task1.priority = 1 (changed only for this task)
task2.priority = 5 (unaffected)
💬 AI CoLearning Prompt
"I have a Task class with default_priority as a class attribute and priority as an instance attribute. When I change task1.priority = 1, only task1 is affected. But status_options is a class attribute shared by all tasks. Explain:
- What's stored in memory differently for class vs instance attributes?
- Give me 3 real-world examples where class attributes make sense (like API base_url, database config, valid status values)
- When should I avoid class attributes? (Hint: think about mutable defaults like lists)"
Expected Understanding: AI will explain that class attributes are shared memory (one copy for all instances), instance attributes are per-object memory (each object gets its own copy). Use class attributes for configuration, constants, and shared validation rules. Avoid mutable class attributes like lists or dicts that should be independent.
Discovery Exercise 3: The Shadowing Trap
Run this and observe strange behavior:
Loading Python environment...
Output:
t1.priority_default = 1 (instance attribute shadows class attribute)
t2.priority_default = 5 (still reading from class)
Task.priority_default = 5 (class attribute unchanged)
💬 AI CoLearning Prompt
"I tried to update a class attribute through an instance (t1.priority_default = 1) but it created an INSTANCE attribute instead, shadowing the class attribute! Now t1 and t2 have different values!
- Why does Python allow this? Is it a bug or a feature?
- How do I update class attributes correctly? (Hint: use ClassName.attribute, not instance.attribute)
- How can I detect if an attribute lives in an instance or the class using dict?"
Expected Understanding: AI will explain that obj.attr = value ALWAYS creates an instance attribute. To modify class attributes, use ClassName.attr = value. Shadowing is intentional but often confusing. Use __dict__ to inspect which attributes belong to which object.
Your Discovery Summary
Instead of manual files, use AI to synthesize:
💬 AI CoLearning Prompt
"Based on my experiments with default parameters, instance/class attributes, and shadowing, summarize these key insights:
- When should constructor parameters be required vs have defaults?
- When should data be instance attributes vs class attributes?
- What's the shadowing trap and how do I avoid it?
Give me 3 bullet points for my reference guide."
Deliverable: Save AI's synthesis in your notes. You've discovered constructor design patterns—now you're ready to learn advanced techniques.
Part 2: Learn Advanced Constructor Patterns
Your Role: Student receiving instruction from AI Teacher
AI Teaching Prompt
Ask your AI companion:
"I'm building a Task management system and I've discovered two types of attributes:
- Instance attributes like
self.titleandself.priority(each task has its own)- Class attributes like
status_options = ["pending", "in_progress", "completed"](shared across all tasks)Explain:
- When should I use default parameters in Task.init?
- What's the memory layout difference between instance and class attributes?
- If task1.priority = 1 creates a new instance attribute, what happens to the class default?
- Show me a Task example where class attributes are useful (configuration, validation rules, etc)."
What You'll Learn from AI
Expected AI Response (summary):
- Default parameters: Make constructors flexible for optional task data (description, due_date)
- Instance attributes: Each task has independent values (title, priority, done)
- Class attributes: All tasks share configuration (status_options, default_priority)
- Shadowing: Creating instance attribute hides class attribute temporarily
- Real use case: Task.status_options (shared validation) vs task.priority (unique per task)
Convergence Activity
After AI explains, ask:
"Walk me through what happens in memory when I create
task = Task('Review PR')in a class that has bothdefault_priority = 5(class attribute) andself.priority = default_priority(instance attribute in init)."
Deliverable: Write a summary explaining when to use default parameters, class vs instance attributes in your Task class, and why shadowing is important.
Part 3: Challenge AI with Edge Cases
Your Role: Student teaching AI by testing edge case understanding
Challenge 1: Default Parameters and Mutability
Your prompt to AI:
"I'm designing a Task class and I want to add an optional tags parameter:
def __init__(self, title: str, tags: list = []):. But I see warnings online that this is dangerous. Why is using a mutable default parameter (list, dict) problematic? Show me an example where this goes wrong with my Task class and explain what's happening."
Expected learning: AI will explain that mutable defaults are shared across all Task instances, causing hidden bugs where all tasks unexpectedly share the same tags list.
Challenge 2: Class Attributes in Task Configuration
Your prompt to AI:
"I'm designing a Task class. Configuration like
status_options = ["pending", "in_progress", "completed"]could be:
- A class attribute (all tasks use same valid statuses)
- An instance attribute in init (each task could have different statuses)
For these scenarios, which should it be and why:
- All tasks in the system must use the same status values
- Each task might have its own custom status workflow
- The priority levels (1-10 scale) for all tasks
- Timestamps when each task was created
Explain your reasoning for each."
Expected learning: AI will explain design decisions about when to share data across tasks vs. keeping data independent per task.
Challenge 3: Attribute Shadowing with Tasks
Your prompt to AI:
"If I create a TaskCategory subclass:
Loading Python environment...
Does this create an instance attribute on ut that shadows Task.status? Or does it modify the class attribute? Show me how to verify which happened using dict."
Deliverable
Document your three challenges and AI's responses with your analysis of correctness.
Part 4: Build Your Attributes and Constructors Reference
Your Role: Knowledge synthesizer creating design patterns
Create task_constructors_guide.md with patterns across multiple domains:
# Constructors with Defaults and Attributes Guide
## Pattern 1: Task Constructor with Defaults (Primary Domain)
**When to use**: Optional parameters for flexible object creation
```python
from datetime import datetime
class Task:
def __init__(
self,
title: str,
description: str = "",
priority: int = 5,
due_date = None
):
self.title = title
self.description = description
self.priority = priority
self.due_date = due_date
self.status = "pending"
self.done = False
self.created_at = datetime.now()
# All these work
Task("Buy groceries", "Fresh vegetables", 3, datetime(2025, 1, 15))
Task("Review PR") # Uses defaults
Task("Write docs", "API documentation") # Partial defaults
Key principle: Required parameters first (title), optional last (due_date)
Pattern 2: Class Attributes for Task Configuration
Class attributes (shared across all tasks):
Loading Python environment...
Rule: If data is configuration, validation rules, or constants → class attribute. If data is per-task → instance attribute.
Pattern 3: Avoiding Mutable Default Parameters
Loading Python environment...
Pattern 4: Updating Class Attributes Correctly
Loading Python environment...
Pattern 5: Detecting Attribute Type with dict
Loading Python environment...
Pattern 6: Transfer to Auxiliary Domains
The same patterns work everywhere:
Loading Python environment...
Key insight: Every domain follows the same constructor patterns. Task, Case, Invoice, Appointment all use:
- Required parameters (task title = case number = invoice id)
- Optional parameters with defaults (due_date, case_type, description)
- Class attributes for shared configuration (status_options)
- Instance attributes for per-object data (priority, status, done)
Constructor Pattern Checklist
When designing ANY constructor, ask:
- What parameters are required? (must provide)
- What parameters are optional? (have sensible defaults)
- Should this be a class attribute? (shared configuration/validation)
- Should this be an instance attribute? (specific to each object)
- Am I using mutable defaults? (use None instead)
Validation with AI
"Review my Task constructor and compare it to the Case and Invoice patterns. Are my decisions about required vs optional parameters consistent across domains? What patterns am I missing?"
Deliverable: Complete guide with Task pattern and at least 2 auxiliary domain patterns.
---
## Try With AI
Ready to master Task constructors with defaults, understand attribute scoping, and avoid mutable default traps?
**🔍 Explore Task Default Parameters:**
> "Show me a Task class with required title parameter and optional description (default=''), priority (default=5), due_date (default=None). Create tasks with: all params, only title, title+priority. Explain when defaults help vs when they hide required data. For a task management system, should all attributes be optional except title?"
**🎯 Practice Instance vs Class Attributes in Tasks:**
> "Build a Task class where: status_options is shared by all tasks (class attribute), but priority is per-task (instance attribute). Create multiple tasks, change Task.status_options on the class, show what happens. When should attributes be shared (like valid statuses) vs independent (like each task's priority)?"
**🧪 Test Mutable Default Danger with Tasks:**
> "Create a Task class with tags parameter defaulting to []. Add tags to task1. Create task2 without tags. What's in task2.tags? Why? Show me the fix (tags=None, then self.tags = tags or []). Explain why this Python gotcha matters for task management."
**🚀 Apply to Other Domains:**
> "Compare Task, Case, and Invoice constructors. They all need required ID-like fields and optional descriptive fields. Design Case.__init__(case_number, title, client_name, case_type='civil') and Invoice.__init__(invoice_id, client_name, amount, description=''). What patterns are the same? When does a field MUST be required vs can have defaults?"
---