mirror of
https://github.com/blshaer/python-by-example.git
synced 2026-03-27 23:29:25 +01:00
447 lines
13 KiB
Python
447 lines
13 KiB
Python
"""
|
|
================================================================================
|
|
File: 02_init_methods.py
|
|
Topic: Constructors and Initialization in Python
|
|
================================================================================
|
|
|
|
This file explores the __init__ method and other initialization patterns
|
|
in Python classes. The __init__ method is called when creating new objects
|
|
and sets up the initial state of the instance.
|
|
|
|
Key Concepts:
|
|
- The __init__ constructor
|
|
- Required vs optional parameters
|
|
- Default values
|
|
- Property decorators
|
|
- Alternative constructors (classmethod)
|
|
|
|
================================================================================
|
|
"""
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 1. Basic __init__ Method
|
|
# -----------------------------------------------------------------------------
|
|
# __init__ initializes newly created objects
|
|
|
|
print("--- Basic __init__ ---")
|
|
|
|
class Book:
|
|
"""A class representing a book."""
|
|
|
|
def __init__(self, title, author):
|
|
"""
|
|
Initialize a new book.
|
|
|
|
Args:
|
|
title: The book's title
|
|
author: The book's author
|
|
"""
|
|
self.title = title
|
|
self.author = author
|
|
|
|
# Creating instances calls __init__ automatically
|
|
book = Book("1984", "George Orwell")
|
|
print(f"Book: '{book.title}' by {book.author}")
|
|
|
|
# Without proper arguments, you get an error:
|
|
# book = Book() # TypeError: missing 2 required positional arguments
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 2. Default Parameter Values
|
|
# -----------------------------------------------------------------------------
|
|
# Make some parameters optional
|
|
|
|
print("\n--- Default Values ---")
|
|
|
|
class User:
|
|
"""A class with default parameter values."""
|
|
|
|
def __init__(self, username, email=None, role="user", active=True):
|
|
"""
|
|
Initialize a new user.
|
|
|
|
Args:
|
|
username: Required username
|
|
email: Optional email address
|
|
role: User role (default: "user")
|
|
active: Is user active (default: True)
|
|
"""
|
|
self.username = username
|
|
self.email = email
|
|
self.role = role
|
|
self.active = active
|
|
|
|
def __str__(self):
|
|
email = self.email or "No email"
|
|
status = "Active" if self.active else "Inactive"
|
|
return f"{self.username} ({self.role}) - {email} - {status}"
|
|
|
|
# Various ways to create users
|
|
user1 = User("alice")
|
|
user2 = User("bob", "bob@example.com")
|
|
user3 = User("charlie", "charlie@example.com", "admin")
|
|
user4 = User("dave", active=False)
|
|
|
|
print(user1)
|
|
print(user2)
|
|
print(user3)
|
|
print(user4)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 3. Validation in __init__
|
|
# -----------------------------------------------------------------------------
|
|
# Validate and process data during initialization
|
|
|
|
print("\n--- Validation in __init__ ---")
|
|
|
|
class Temperature:
|
|
"""A class with validation in __init__."""
|
|
|
|
def __init__(self, celsius):
|
|
"""
|
|
Initialize temperature in Celsius.
|
|
|
|
Args:
|
|
celsius: Temperature in Celsius
|
|
|
|
Raises:
|
|
ValueError: If temperature is below absolute zero
|
|
"""
|
|
if celsius < -273.15:
|
|
raise ValueError("Temperature cannot be below absolute zero!")
|
|
self._celsius = celsius
|
|
|
|
@property
|
|
def celsius(self):
|
|
"""Get temperature in Celsius."""
|
|
return self._celsius
|
|
|
|
@property
|
|
def fahrenheit(self):
|
|
"""Get temperature in Fahrenheit."""
|
|
return (self._celsius * 9/5) + 32
|
|
|
|
@property
|
|
def kelvin(self):
|
|
"""Get temperature in Kelvin."""
|
|
return self._celsius + 273.15
|
|
|
|
temp = Temperature(25)
|
|
print(f"Temperature: {temp.celsius}°C = {temp.fahrenheit}°F = {temp.kelvin}K")
|
|
|
|
# Validation in action
|
|
try:
|
|
invalid_temp = Temperature(-300)
|
|
except ValueError as e:
|
|
print(f"Validation error: {e}")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 4. Mutable Default Arguments Warning
|
|
# -----------------------------------------------------------------------------
|
|
# Never use mutable objects as default arguments!
|
|
|
|
print("\n--- Mutable Defaults Warning ---")
|
|
|
|
# BAD - mutable default argument
|
|
class BadStudent:
|
|
def __init__(self, name, grades=[]): # DON'T DO THIS!
|
|
self.name = name
|
|
self.grades = grades
|
|
|
|
s1 = BadStudent("Alice")
|
|
s1.grades.append(90)
|
|
|
|
s2 = BadStudent("Bob")
|
|
print(f"Bob's grades (should be empty): {s2.grades}") # Contains 90!
|
|
|
|
# GOOD - use None and create new list
|
|
class GoodStudent:
|
|
def __init__(self, name, grades=None):
|
|
self.name = name
|
|
self.grades = grades if grades is not None else []
|
|
|
|
g1 = GoodStudent("Carol")
|
|
g1.grades.append(85)
|
|
|
|
g2 = GoodStudent("Dan")
|
|
print(f"Dan's grades (correctly empty): {g2.grades}")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 5. Property Decorators
|
|
# -----------------------------------------------------------------------------
|
|
# Control attribute access with getters and setters
|
|
|
|
print("\n--- Property Decorators ---")
|
|
|
|
class Circle:
|
|
"""A circle with property decorators."""
|
|
|
|
def __init__(self, radius):
|
|
"""Initialize circle with radius."""
|
|
self._radius = radius # Protected attribute
|
|
|
|
@property
|
|
def radius(self):
|
|
"""Get the radius."""
|
|
return self._radius
|
|
|
|
@radius.setter
|
|
def radius(self, value):
|
|
"""Set the radius with validation."""
|
|
if value <= 0:
|
|
raise ValueError("Radius must be positive")
|
|
self._radius = value
|
|
|
|
@property
|
|
def diameter(self):
|
|
"""Get the diameter (read-only computed property)."""
|
|
return self._radius * 2
|
|
|
|
@property
|
|
def area(self):
|
|
"""Get the area (read-only computed property)."""
|
|
import math
|
|
return math.pi * self._radius ** 2
|
|
|
|
circle = Circle(5)
|
|
print(f"Radius: {circle.radius}")
|
|
print(f"Diameter: {circle.diameter}")
|
|
print(f"Area: {circle.area:.2f}")
|
|
|
|
# Using setter
|
|
circle.radius = 10
|
|
print(f"New radius: {circle.radius}")
|
|
print(f"New area: {circle.area:.2f}")
|
|
|
|
# Validation in setter
|
|
try:
|
|
circle.radius = -5
|
|
except ValueError as e:
|
|
print(f"Setter validation: {e}")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 6. Alternative Constructors with @classmethod
|
|
# -----------------------------------------------------------------------------
|
|
# Create objects in different ways
|
|
|
|
print("\n--- Alternative Constructors ---")
|
|
|
|
class Person:
|
|
"""A class with alternative constructors."""
|
|
|
|
def __init__(self, first_name, last_name, age):
|
|
"""Standard constructor."""
|
|
self.first_name = first_name
|
|
self.last_name = last_name
|
|
self.age = age
|
|
|
|
@classmethod
|
|
def from_birth_year(cls, first_name, last_name, birth_year):
|
|
"""Create person from birth year instead of age."""
|
|
import datetime
|
|
age = datetime.datetime.now().year - birth_year
|
|
return cls(first_name, last_name, age)
|
|
|
|
@classmethod
|
|
def from_full_name(cls, full_name, age):
|
|
"""Create person from full name string."""
|
|
parts = full_name.split()
|
|
first_name = parts[0]
|
|
last_name = parts[-1] if len(parts) > 1 else ""
|
|
return cls(first_name, last_name, age)
|
|
|
|
@classmethod
|
|
def from_dict(cls, data):
|
|
"""Create person from dictionary."""
|
|
return cls(
|
|
data.get('first_name', ''),
|
|
data.get('last_name', ''),
|
|
data.get('age', 0)
|
|
)
|
|
|
|
def __str__(self):
|
|
return f"{self.first_name} {self.last_name}, age {self.age}"
|
|
|
|
# Using different constructors
|
|
p1 = Person("John", "Doe", 30)
|
|
p2 = Person.from_birth_year("Jane", "Smith", 1995)
|
|
p3 = Person.from_full_name("Bob Johnson", 25)
|
|
p4 = Person.from_dict({"first_name": "Alice", "last_name": "Williams", "age": 28})
|
|
|
|
print("Created using different constructors:")
|
|
print(f" Standard: {p1}")
|
|
print(f" From birth year: {p2}")
|
|
print(f" From full name: {p3}")
|
|
print(f" From dict: {p4}")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 7. __new__ vs __init__
|
|
# -----------------------------------------------------------------------------
|
|
# __new__ creates the instance, __init__ initializes it
|
|
|
|
print("\n--- __new__ vs __init__ ---")
|
|
|
|
class Singleton:
|
|
"""A singleton class using __new__."""
|
|
|
|
_instance = None
|
|
|
|
def __new__(cls):
|
|
"""Create instance only if one doesn't exist."""
|
|
if cls._instance is None:
|
|
print(" Creating new instance...")
|
|
cls._instance = super().__new__(cls)
|
|
else:
|
|
print(" Returning existing instance...")
|
|
return cls._instance
|
|
|
|
def __init__(self):
|
|
"""Initialize (called every time)."""
|
|
pass
|
|
|
|
# Both variables point to the same instance
|
|
print("Creating s1:")
|
|
s1 = Singleton()
|
|
print("Creating s2:")
|
|
s2 = Singleton()
|
|
print(f"Same instance? {s1 is s2}")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 8. Initialization with Inheritance
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Initialization with Inheritance ---")
|
|
|
|
class Animal:
|
|
"""Base class for animals."""
|
|
|
|
def __init__(self, name, species):
|
|
"""Initialize animal."""
|
|
self.name = name
|
|
self.species = species
|
|
|
|
def speak(self):
|
|
"""Make a sound."""
|
|
return "Some sound"
|
|
|
|
class Dog(Animal):
|
|
"""Dog class inheriting from Animal."""
|
|
|
|
def __init__(self, name, breed):
|
|
"""Initialize dog with name and breed."""
|
|
# Call parent's __init__
|
|
super().__init__(name, species="Canis familiaris")
|
|
self.breed = breed
|
|
|
|
def speak(self):
|
|
"""Dogs bark."""
|
|
return "Woof!"
|
|
|
|
class Cat(Animal):
|
|
"""Cat class inheriting from Animal."""
|
|
|
|
def __init__(self, name, indoor=True):
|
|
"""Initialize cat."""
|
|
super().__init__(name, species="Felis catus")
|
|
self.indoor = indoor
|
|
|
|
def speak(self):
|
|
"""Cats meow."""
|
|
return "Meow!"
|
|
|
|
dog = Dog("Buddy", "Golden Retriever")
|
|
cat = Cat("Whiskers", indoor=True)
|
|
|
|
print(f"Dog: {dog.name} ({dog.breed}) says {dog.speak()}")
|
|
print(f"Cat: {cat.name} (Indoor: {cat.indoor}) says {cat.speak()}")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 9. Complex Initialization Example
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Complex Initialization ---")
|
|
|
|
from datetime import datetime
|
|
|
|
class Order:
|
|
"""A class with complex initialization."""
|
|
|
|
_order_counter = 0
|
|
|
|
def __init__(self, customer_name, items=None, discount=0):
|
|
"""
|
|
Initialize an order.
|
|
|
|
Args:
|
|
customer_name: Name of the customer
|
|
items: List of (item_name, price, quantity) tuples
|
|
discount: Discount percentage (0-100)
|
|
"""
|
|
# Auto-generate order ID
|
|
Order._order_counter += 1
|
|
self.order_id = f"ORD-{Order._order_counter:04d}"
|
|
|
|
# Set basic attributes
|
|
self.customer_name = customer_name
|
|
self.items = items or []
|
|
self.discount = max(0, min(100, discount)) # Clamp to 0-100
|
|
|
|
# Auto-generated attributes
|
|
self.created_at = datetime.now()
|
|
self._subtotal = None # Cached calculation
|
|
|
|
def add_item(self, name, price, quantity=1):
|
|
"""Add an item to the order."""
|
|
self.items.append((name, price, quantity))
|
|
self._subtotal = None # Invalidate cache
|
|
|
|
@property
|
|
def subtotal(self):
|
|
"""Calculate subtotal."""
|
|
if self._subtotal is None:
|
|
self._subtotal = sum(price * qty for _, price, qty in self.items)
|
|
return self._subtotal
|
|
|
|
@property
|
|
def total(self):
|
|
"""Calculate total with discount."""
|
|
return self.subtotal * (1 - self.discount / 100)
|
|
|
|
def __str__(self):
|
|
return f"Order {self.order_id} for {self.customer_name}: ${self.total:.2f}"
|
|
|
|
# Create orders
|
|
order1 = Order("Alice")
|
|
order1.add_item("Widget", 9.99, 2)
|
|
order1.add_item("Gadget", 14.99, 1)
|
|
|
|
order2 = Order("Bob", discount=10)
|
|
order2.add_item("Premium Widget", 29.99, 1)
|
|
|
|
print(order1)
|
|
print(f" Subtotal: ${order1.subtotal:.2f}")
|
|
print(f" Total: ${order1.total:.2f}")
|
|
|
|
print(order2)
|
|
print(f" Subtotal: ${order2.subtotal:.2f}")
|
|
print(f" Total (10% off): ${order2.total:.2f}")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 10. Summary
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Summary ---")
|
|
|
|
summary = """
|
|
Initialization patterns:
|
|
- __init__: Main constructor, initializes attributes
|
|
- Default values: Make parameters optional
|
|
- Validation: Raise errors for invalid input
|
|
- Properties: Computed/validated attributes
|
|
- @classmethod: Alternative constructors
|
|
- __new__: Instance creation (before __init__)
|
|
- super().__init__(): Call parent constructor
|
|
"""
|
|
|
|
print(summary)
|