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
379: Choosing Patterns Pythonically
I see this all the time with developers moving from Java or C# into Python: they arrive with a deep knowledge of the "Gang of Four" design patterns and try to implement them with surgical precision. They build elaborate class hierarchies, abstract base classes, and strict interfaces because that's how they were taught to write "professional" code. The misconception is that a design pattern is a rigid blueprint that must be mirrored exactly in every language to be effective.
In Python, following those blueprints too literally often leads to "Enterprise Java Syndrome"—code that is bloated, difficult to read, and fights against the language's nature. Python is dynamic. It treats functions as first-class objects. When you try to force a rigid GoF pattern onto Python, you're usually adding boilerplate that the language has already solved internally.
The Strategy Pattern Trap vs. First-Class Functions
Take the Strategy Pattern. In a strictly typed language, you'd create an interface (e.g., PaymentStrategy), implement several concrete classes (CreditCardPayment, PayPalPayment), and then create a context class to hold a reference to the strategy. It's a lot of typing for something very simple.
Here is how that looks when someone is "over-patterning" in Python:
from abc import ABC, abstractmethod
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount):
pass
class CreditCardPayment(PaymentStrategy):
def pay(self, amount):
print(f"Paying {amount} using Credit Card")
class PayPalPayment(PaymentStrategy):
def pay(self, amount):
print(f"Paying {amount} using PayPal")
class Order:
def __init__(self, amount, strategy: PaymentStrategy):
self.amount = amount
self.strategy = strategy
def process(self):
self.strategy.pay(self.amount)
# Usage
order = Order(100, PayPalPayment())
order.process()
Now, let's be honest: that's a lot of scaffolding. In Python, a "strategy" is just a callable. You don't need a class hierarchy if the "strategy" doesn't need to maintain complex internal state. You can just pass the function.
def pay_with_credit_card(amount):
print(f"Paying {amount} using Credit Card")
def pay_with_paypal(amount):
print(f"Paying {amount} using PayPal")
class Order:
def __init__(self, amount, payment_func):
self.amount = amount
self.payment_func = payment_func
def process(self):
self.payment_func(self.amount)
# Usage
order = Order(100, pay_with_paypal)
order.process()
I prefer the second approach every time. It's leaner, faster to write, and easier to test. You've achieved the exact same architectural goal—decoupling the payment logic from the order logic—without the ceremony.
The Singleton Obsession vs. Module-Level State
Another classic is the Singleton. People spend hours debating the "best" way to implement a Singleton in Python, using __new__ or metaclasses to ensure only one instance of a class ever exists. They're treating the class as a container for global state.
But here is the secret: Modules in Python are already Singletons.
If you need a single point of configuration or a shared resource (like a database connection pool) across your entire app, don't build a DatabaseConnectionManager class with a get_instance() method. Just put the instance in a module.
# db_config.py
class DatabaseConnection:
def __init__(self):
self.connected = False
def connect(self):
self.connected = True
print("Connected to DB")
# We instantiate it right here in the module
connection = DatabaseConnection()
Now, anywhere else in your project, you just do from db_config import connection. Python's import machinery ensures that the module is executed only once. Every other file that imports connection gets the exact same object. You get the Singleton pattern for free, without the confusing metaclass magic.
The rule of thumb I use is this: if you find yourself writing a class that only ever has one instance, or a class that only contains one method, stop. You're probably trying to solve a Python problem with a Java solution. Look for a way to use a function, a dictionary, or a module instead.
📋 Practical Task
Refactoring a Rigid File-Exporter to a Pythonic Registry
You have been handed a piece of legacy code that uses a rigid "Exporter" class hierarchy. The current implementation is over-engineered and makes adding new export formats tedious. Your task is to refactor this into a Pythonic "Registry" pattern using a dictionary of functions.
Current Rigid Implementation:
from abc import ABC, abstractmethod
class BaseExporter(ABC):
@abstractmethod
def export(self, data):
pass
class JSONExporter(BaseExporter):
def export(self, data):
return f"Exporting {data} as JSON"
class CSVExporter(BaseExporter):
def export(self, data):
return f"Exporting {data} as CSV"
class ExportManager:
def __init__(self, exporter: BaseExporter):
self.exporter = exporter
def run(self, data):
return self.exporter.export(data)
Requirements for your refactor:
- Remove all
ABCand class-based exporter logic. - Create standalone functions for
export_jsonandexport_csv. - Create a dictionary called
EXPORT_REGISTRYthat maps strings (e.g., "json", "csv") to these functions. - Implement a single function
handle_export(format_type, data)that looks up the correct function in the registry and executes it. - Include a check to handle cases where an unsupported
format_typeis requested (e.g., raise aValueError).
There are no comments for now.