Prerequisites
- Basic understanding of programming concepts ๐
- Python installation (3.8+) ๐
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write clean, Pythonic code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on new vs init in Python! ๐ Have you ever wondered what really happens when you create an object in Python? Today, weโll uncover the magical two-step dance that brings objects to life!
Youโll discover how Pythonโs object creation process works behind the scenes, giving you superpowers to customize object creation for special use cases. Whether youโre building singleton patterns ๐, implementing object pools ๐โโ๏ธ, or creating immutable objects ๐ก๏ธ, understanding new and init is your key to Python mastery!
By the end of this tutorial, youโll confidently know when to use each method and how they work together. Letโs dive in! ๐โโ๏ธ
๐ Understanding Object Creation
๐ค What are new and init?
Think of creating an object like building a house ๐ :
- new is like constructing the foundation and walls (creating the structure)
- init is like decorating and furnishing the house (initializing the attributes)
In Python terms:
- โจ new creates and returns a new instance of the class
- ๐จ init initializes the instance with attributes
- ๐ new runs first, then init runs automatically
๐ก Why Two Methods?
Hereโs why Python separates object creation:
- Flexibility ๐ฏ: Control object creation at different stages
- Immutable Objects ๐ก๏ธ: Can only be set during creation (new)
- Singleton Pattern ๐: Control instance creation
- Object Pooling ๐โโ๏ธ: Reuse existing objects
Real-world example: Imagine a game where you need to ensure only one GameManager exists. With new, you can control this perfectly! ๐ฎ
๐ง Basic Syntax and Usage
๐ The Default Behavior
Letโs see how objects are normally created:
# ๐ Hello, object creation!
class Player:
def __init__(self, name, level=1):
# ๐จ Initialize the player's attributes
self.name = name # ๐ค Player's name
self.level = level # ๐ฏ Starting level
self.health = 100 # โค๏ธ Full health!
print(f"๐ฎ {name} joined the game!")
# ๐ Creating a player (both __new__ and __init__ are called)
hero = Player("Pythonista", 5)
๐ก Explanation: When you call Player()
, Python automatically calls new (inherited from object) to create the instance, then calls init to initialize it!
๐ฏ Custom new Method
Hereโs how to customize object creation:
# ๐๏ธ Custom object creation
class Wizard:
def __new__(cls, name, power_level):
# ๐จ Create the instance
print(f"โจ Creating a new wizard...")
instance = super().__new__(cls)
return instance
def __init__(self, name, power_level):
# ๐ฏ Initialize attributes
print(f"๐ช Initializing {name}...")
self.name = name
self.power_level = power_level
self.spells = []
# ๐ฎ Watch the creation process!
merlin = Wizard("Merlin", 99)
๐ก Practical Examples
๐ Example 1: Singleton Pattern
Letโs create a class that only allows one instance:
# ๐ GameManager singleton
class GameManager:
_instance = None # ๐ฆ Store the single instance
def __new__(cls):
# ๐ Check if instance already exists
if cls._instance is None:
print("๐ฎ Creating the GameManager...")
cls._instance = super().__new__(cls)
else:
print("โป๏ธ Returning existing GameManager!")
return cls._instance
def __init__(self):
# ๐ฏ Only initialize once
if not hasattr(self, 'initialized'):
self.score = 0
self.level = 1
self.players = []
self.initialized = True
print("โ
GameManager initialized!")
def add_player(self, name):
self.players.append(name)
print(f"๐ค {name} joined! Total players: {len(self.players)}")
# ๐ฎ Let's test it!
game1 = GameManager()
game1.add_player("Alice")
game2 = GameManager() # โป๏ธ Same instance!
game2.add_player("Bob")
print(f"๐ค Same object? {game1 is game2}") # True!
print(f"๐ Total players: {len(game1.players)}") # 2 players!
๐ฏ Try it yourself: Create a DatabaseConnection singleton that ensures only one connection exists!
๐ก๏ธ Example 2: Immutable Point Class
Letโs build an immutable 2D point:
# ๐ Immutable Point class
class Point:
def __new__(cls, x, y):
# ๐๏ธ Create the instance
instance = super().__new__(cls)
# ๐ก๏ธ Set attributes during creation (can't change later!)
instance._x = float(x)
instance._y = float(y)
return instance
def __init__(self, x, y):
# ๐ซ Don't set attributes here for immutability!
pass
@property
def x(self):
return self._x
@property
def y(self):
return self._y
def distance_from_origin(self):
# ๐ Calculate distance
return (self._x ** 2 + self._y ** 2) ** 0.5
def __repr__(self):
return f"Point({self._x}, {self._y}) ๐"
# ๐ฎ Using immutable points
p1 = Point(3, 4)
print(f"๐ Point: {p1}")
print(f"๐ Distance from origin: {p1.distance_from_origin()}")
# โ Can't modify!
try:
p1.x = 10
except AttributeError:
print("๐ก๏ธ Points are immutable - can't change x!")
๐โโ๏ธ Example 3: Object Pool
Create a pool of reusable objects:
# ๐โโ๏ธ Connection pool for efficiency
class Connection:
_pool = [] # ๐ฆ Pool of available connections
_in_use = set() # ๐ Currently used connections
def __new__(cls, host="localhost"):
# ๐ Check if we have available connections
if cls._pool:
# โป๏ธ Reuse existing connection
instance = cls._pool.pop()
print(f"โป๏ธ Reusing connection to {host}")
else:
# ๐ Create new connection
instance = super().__new__(cls)
print(f"๐ Creating new connection to {host}")
cls._in_use.add(instance)
return instance
def __init__(self, host="localhost"):
self.host = host
self.connected = True
def release(self):
# ๐ Return connection to pool
if self in self.__class__._in_use:
self.__class__._in_use.remove(self)
self.__class__._pool.append(self)
print(f"๐ Connection released back to pool")
@classmethod
def pool_status(cls):
# ๐ Show pool statistics
print(f"๐ Pool Status:")
print(f" ๐โโ๏ธ Available: {len(cls._pool)}")
print(f" ๐ In use: {len(cls._in_use)}")
# ๐ฎ Test the connection pool
conn1 = Connection("database.com")
conn2 = Connection("api.server.com")
Connection.pool_status()
# ๐ Release connections
conn1.release()
conn2.release()
Connection.pool_status()
# โป๏ธ Reuse connections
conn3 = Connection("new.server.com")
Connection.pool_status()
๐ Advanced Concepts
๐งโโ๏ธ Meta-Programming with new
Control instance creation based on arguments:
# ๐ฏ Factory pattern using __new__
class Animal:
def __new__(cls, animal_type, name):
# ๐ญ Create different classes based on type
if animal_type == "dog":
instance = super().__new__(Dog)
elif animal_type == "cat":
instance = super().__new__(Cat)
else:
instance = super().__new__(cls)
return instance
def __init__(self, animal_type, name):
self.name = name
self.type = animal_type
class Dog(Animal):
def speak(self):
return f"{self.name} says: Woof! ๐"
class Cat(Animal):
def speak(self):
return f"{self.name} says: Meow! ๐ฑ"
# ๐ฎ Magic factory!
buddy = Animal("dog", "Buddy")
whiskers = Animal("cat", "Whiskers")
print(buddy.speak()) # Buddy says: Woof! ๐
print(whiskers.speak()) # Whiskers says: Meow! ๐ฑ
print(f"๐ Buddy is a {type(buddy).__name__}") # Dog
๐๏ธ Custom Memory Management
Optimize memory usage with slots:
# ๐พ Memory-efficient class
class EfficientPlayer:
__slots__ = ['name', 'level', 'health'] # ๐ฆ Fixed attributes
def __new__(cls, name):
# ๐ฏ Custom creation with memory optimization
instance = super().__new__(cls)
return instance
def __init__(self, name):
self.name = name
self.level = 1
self.health = 100
# ๐ Compare memory usage
import sys
regular_player = Player("Regular", 1)
efficient_player = EfficientPlayer("Efficient")
print(f"๐ Regular player size: {sys.getsizeof(regular_player.__dict__)} bytes")
print(f"โจ Efficient player: No __dict__ needed!")
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Forgetting to Return from new
# โ Wrong - forgot to return!
class BrokenClass:
def __new__(cls):
instance = super().__new__(cls)
# ๐ฅ Oops! Forgot to return instance
# โ
Correct - always return the instance!
class WorkingClass:
def __new__(cls):
instance = super().__new__(cls)
return instance # โ
Don't forget this!
๐คฏ Pitfall 2: Wrong init Signature
# โ Dangerous - mismatched signatures!
class Confused:
def __new__(cls, x, y):
return super().__new__(cls)
def __init__(self, x): # ๐ฅ Missing 'y' parameter!
self.x = x
# โ
Safe - matching signatures!
class Clear:
def __new__(cls, x, y):
return super().__new__(cls)
def __init__(self, x, y): # โ
Matches __new__!
self.x = x
self.y = y
๐ Pitfall 3: Infinite Recursion in Singleton
# โ Infinite loop danger!
class BadSingleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = cls() # ๐ฅ Calls __new__ again!
return cls._instance
# โ
Correct singleton pattern!
class GoodSingleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls) # โ
Use super()!
return cls._instance
๐ ๏ธ Best Practices
- ๐ฏ Use init for Most Cases: Only override new when necessary
- ๐ Match Signatures: Keep new and init parameters aligned
- ๐ก๏ธ Call super().new: Always use super() to create instances
- ๐จ Keep new Simple: Do creation in new, initialization in init
- โจ Document Special Behavior: Explain why youโre overriding new
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Card Deck with Limited Cards
Create a playing card system with these features:
๐ Requirements:
- โ Only one instance of each card can exist (e.g., one Ace of Spades)
- ๐ด 52 unique cards in total
- โป๏ธ Reuse existing card instances
- ๐จ Each card has suit and rank
- ๐ Include a special Joker card (max 2 instances)
๐ Bonus Points:
- Add a method to check how many cards have been created
- Implement a deck shuffle method
- Add card comparison methods
๐ก Solution
๐ Click to see solution
# ๐ด Our unique card system!
class Card:
_instances = {} # ๐ฆ Store all card instances
_joker_count = 0 # ๐ Track joker instances
SUITS = ["โ ๏ธ", "โฅ๏ธ", "โฆ๏ธ", "โฃ๏ธ"]
RANKS = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
def __new__(cls, suit=None, rank=None, is_joker=False):
if is_joker:
# ๐ Handle Joker creation
if cls._joker_count >= 2:
print("โ ๏ธ Maximum 2 Jokers allowed!")
return None
key = f"Joker_{cls._joker_count + 1}"
if key not in cls._instances:
instance = super().__new__(cls)
cls._instances[key] = instance
cls._joker_count += 1
return cls._instances[key]
else:
# ๐ด Regular card
key = f"{suit}_{rank}"
if key not in cls._instances:
instance = super().__new__(cls)
cls._instances[key] = instance
else:
print(f"โป๏ธ Reusing existing {rank}{suit}")
return cls._instances[key]
def __init__(self, suit=None, rank=None, is_joker=False):
if not hasattr(self, 'initialized'):
self.suit = suit
self.rank = rank
self.is_joker = is_joker
self.initialized = True
if is_joker:
print(f"๐ Created a Joker!")
else:
print(f"๐ด Created {rank}{suit}")
def __str__(self):
if self.is_joker:
return "๐ Joker"
return f"{self.rank}{self.suit}"
def __repr__(self):
return str(self)
@classmethod
def cards_created(cls):
# ๐ Statistics
regular_cards = len([c for c in cls._instances.values() if not c.is_joker])
print(f"๐ Card Statistics:")
print(f" ๐ด Regular cards: {regular_cards}/52")
print(f" ๐ Jokers: {cls._joker_count}/2")
print(f" ๐ฆ Total instances: {len(cls._instances)}")
# ๐ฎ Test our card system!
# Create some cards
ace_spades = Card("โ ๏ธ", "A")
ace_spades_2 = Card("โ ๏ธ", "A") # โป๏ธ Same instance!
king_hearts = Card("โฅ๏ธ", "K")
# Create jokers
joker1 = Card(is_joker=True)
joker2 = Card(is_joker=True)
joker3 = Card(is_joker=True) # โ ๏ธ Won't create!
# Check statistics
Card.cards_created()
# Verify same instance
print(f"\n๐ Same Ace? {ace_spades is ace_spades_2}") # True!
# Create a full deck
print("\n๐ด Creating full deck...")
deck = []
for suit in Card.SUITS:
for rank in Card.RANKS:
deck.append(Card(suit, rank))
Card.cards_created()
๐ Key Takeaways
Youโve mastered Pythonโs object creation magic! Hereโs what you can now do:
- โ Understand new vs init and when to use each ๐ช
- โ Create singletons to ensure only one instance exists ๐
- โ Build immutable objects that canโt be changed ๐ก๏ธ
- โ Implement object pools for efficient resource management ๐โโ๏ธ
- โ Control object creation with custom logic ๐ฏ
Remember: new and init work together like a perfect team. Use new only when you need to control the creation process itself! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve unlocked the secrets of Python object creation!
Hereโs what to do next:
- ๐ป Practice with the card deck exercise above
- ๐๏ธ Try creating your own singleton logger class
- ๐ Explore metaclasses for even more control
- ๐ Share your custom new implementations!
Keep exploring Pythonโs magic methods - theyโre your gateway to truly Pythonic code! ๐
Happy coding! ๐๐โจ