Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
367: The Singleton Pattern in Python
You'll hear a lot of debates about the Singleton pattern. Some developers call it an "anti-pattern" because it introduces global state, which can make unit testing a nightmare. But in the real world, there are times when having two versions of the same thing is just plain wrong. Think of a configuration manager: you don't want three different objects reading your settings.json file and potentially holding different values in memory.
The goal: A single source of truth for settings
Let's build a AppSettings class. The idea is that no matter where in your application you try to instantiate this class, you always get back the exact same object. I want to be able to set a theme or an API key in one module and have it immediately reflected in another without passing the object through ten different function calls.
Where I tripped up on the implementation
When I first started doing this in Python, I tried to handle the logic inside __init__. It seemed intuitive—check if an instance exists, and if so, return it. My code looked something like this:
class AppSettings:
_instance = None
def __init__(self):
if AppSettings._instance is None:
AppSettings._instance = self
self.theme = "light"
I quickly realized this was a failure. In Python, __init__ is an initializer, not a constructor. By the time __init__ is called, Python has already created the object. So, every time I called AppSettings(), I was creating a new object and then just overwriting the _instance reference. I wasn't stopping the creation of new objects; I was just losing track of the old ones.
Controlling instantiation with __new__
To actually implement a Singleton, we have to move upstream to the __new__ method. This is the method that actually creates the instance in memory. If we intercept the process here, we can tell Python: "Wait, I already have an object for this class, just use that one instead of making a new one."
class AppSettings:
_instance = None
def __new__(cls):
if cls._instance is None:
print("Creating the actual instance for the first time...")
# We call the superclass __new__ to actually allocate the memory
cls._instance = super(AppSettings, cls).__new__(cls)
# Initialize our data here since __init__ will run every time
cls._instance.theme = "light"
cls._instance.api_key = "12345-ABCDE"
return cls._instance
Notice that I put the default values inside the if block in __new__. If I had put them in __init__, they would be reset to "light" every single time I called AppSettings(), even if the instance was the same!
Proving the identity
The best way to verify this is using the is operator, which checks if two variables point to the exact same memory address.
# Let's simulate two different parts of an app
config1 = AppSettings()
config1.theme = "dark"
config2 = AppSettings()
print(f"Config 1 theme: {config1.theme}") # dark
print(f"Config 2 theme: {config2.theme}") # dark (It changed!)
print(f"Are they the same object? {config1 is config2}") # True
Now, no matter how many times you call AppSettings(), you're just getting a pointer to that one original object. It's simple, effective, and keeps your configuration synchronized across your entire codebase.
📋 Practical Task
Implementing a Global Database Connection Pool
In a real application, opening a new connection to a database every time you need to run a query is incredibly expensive. You usually want a "Connection Pool" that is shared across the entire app.
Your Task: Create a class named DatabasePool that implements the Singleton pattern using the __new__ method. It should meet these requirements:
- It must ensure that only one instance of
DatabasePoolever exists. - The instance should have an attribute called
connection_count, initialized to0when the instance is first created. - Add a method called
get_connection()that incrementsconnection_countby 1 and returns a string:"Connection established. Total active: [count]".
Test your implementation by creating two different variables (e.g., pool_a = DatabasePool() and pool_b = DatabasePool()). Call get_connection() on pool_a, and then verify that pool_b.connection_count has also increased.
There are no comments for now.