Right, let’s get into the patterns you’ll actually use in Python, not the ones you had to memorize for some interview. These aren’t your grandfather’s Gang of Four patterns. These are patterns that have either emerged from the unique quirks of the language or have been twisted into a distinctly Pythonic shape. We’re going to cover three of the most useful: the Borg, the Registry, and the Fluent Interface.

The Borg: When You Absolutely Need Shared State (and Hate Singletons)

First, the Singleton pattern. Ugh. In most languages, it’s a global variable in an expensive tuxedo. In Python, we’re adults. We don’t need to enforce a single instance with private constructors and static methods; we can just tell each other “don’t make more than one of these, okay?” and use a module. It’s simpler.

But sometimes, you genuinely need a class that, when instantiated, shares state across all its instances. Not a single instance, but many instances that are all in cahoots. You want a hive mind. Enter the Borg. It’s a pattern that leverages the fact that classes in Python are objects themselves, and instances have a __dict__ which is just a dictionary holding their attributes.

The trick is simple: point every new instance’s __dict__ to the class’s __dict__. That way, any attribute set on any instance is set on the class, and is therefore visible to every other instance. It’s shared state, achieved not by inheritance, but by a good old-fashioned shove.

class Borg:
    _shared_state = {}  # This is the communal backpack

    def __init__(self):
        # Tell every instance to use the class's shared state dict
        self.__dict__ = self._shared_state

# Let's see it in action
borg_1 = Borg()
borg_2 = Borg()

# They start empty, but share everything
print(borg_1 is borg_2)  # False - they are different objects in memory
print(borg_1.__dict__ is borg_2.__dict__)  # True - they share the same attribute dict

borg_1.some_attribute = "I'm felt up by everyone!"
print(borg_2.some_attribute)  # Prints "I'm felt up by everyone!"

Why this works: It’s brutally simple and effective. You’re not preventing instantiation; you’re just making all instances fundamentally the same under the hood. It’s less about control and more about cooperation.

Pitfalls and Best Practices: The main pitfall is, well, it’s global mutable state. Use it sparingly, for things like application-wide configuration where the Borg’s behavior is actually a feature, not a bug. Also, note that because we’re assigning __dict__ in __init__, any attributes you set before calling super().__init__() in a subclass will be in your instance’s own dictionary, which then gets thrown away and replaced. So always initialize your subclass-specific attributes after the Borg’s __init__ has run.

The Registry Pattern: A Class That Knows Its Children

The Registry pattern is your go-to when you need to keep track of all subclasses of a base class, or all instances of a class, without some external manager getting its grubby hands all over your code. It’s fantastic for plugin systems, factory patterns, or any situation where discoverability is key.

The classic way to do this is with a metaclass. A metaclass’s __init__ method runs when a class is created, not when an instance is. It’s the perfect place to slap that new class onto a registry list.

class PluginRegistry(type):
    """A metaclass that registers every subclass."""
    registry = {}

    def __init__(cls, name, bases, attrs):
        super().__init__(name, bases, attrs)
        # Don't register the base class itself
        if not hasattr(cls, 'abstract') or not cls.abstract:
            # Use the class's chosen name, or its __name__ as the key
            key = getattr(cls, 'registry_name', cls.__name__.lower())
            PluginRegistry.registry[key] = cls

class BasePlugin(metaclass=PluginRegistry):
    abstract = True  # Mark this as an abstract base class we don't want to register

class PDFExportPlugin(BasePlugin):
    registry_name = "pdf_exporter"

class CSVExportPlugin(BasePlugin):
    pass  # Will use the default key, 'csvexportplugin'

# The registry is automatically populated!
print(PluginRegistry.registry)
# Output: {'pdf_exporter': <class '__main__.PDFExportPlugin'>, 'csvexportplugin': <class '__main__.CSVExportPlugin'>}

# Your factory function becomes trivial
def get_plugin(plugin_name):
    return PluginRegistry.registry[plugin_name]()

pdf_plugin_class = get_plugin('pdf_exporter')

Why this works: Metaclasses are often overkill, but here they are the perfect tool for the job. They centralize the registration logic, keeping it out of the individual subclasses. The base class defines the rules of the game, and every subclass automatically plays by them.

Pitfalls and Edge Cases: Be mindful of namespace collisions. What if two different subclasses try to register under the same key? The above code will silently overwrite the first one with the second. You might want to add a check and raise an error if a key already exists. Also, remember that this registers classes as they are defined (i.e., when the module is imported), so if you have dynamically generated classes, they’ll be handled too.

Fluent Interface: Making Your Code Read Like a Sentence

A Fluent Interface is less of a formal pattern and more of an API style. The goal is to make your code readable and expressive by chaining method calls together. You see it in libraries like pandas (df.query().groupby().mean()) and SQLAlchemy. Each method returns self (or a relevant object) allowing the next call to be made immediately.

The key insight is that the methods are changing state but returning a reference to the object that has that new state, enabling the next step.

class QueryBuilder:
    def __init__(self):
        self._selects = []
        self._from_table = ''

    def select(self, *columns):
        self._selects.extend(columns)
        return self  # This is the magic

    def from_table(self, table_name):
        self._from_table = table_name
        return self  # This is the magic again

    def build(self):
        return f"SELECT {', '.join(self._selects)} FROM {self._from_table}"

# Usage reads left-to-right, like a sentence.
query = QueryBuilder().select('name', 'age').from_table('users').build()
print(query)  # "SELECT name, age FROM users"

Why this works: It caters to how our brains parse language. The sequence of operations is clear and linear. It also lets you hide intermediate variables, making the code look cleaner, though sometimes at the expense of easy debugging.

Pitfalls and Best Practices: The biggest pitfall is that you can’t use it for everything. If a method is a “terminal” operation that returns a result (like our build() method), it shouldn’t return self. Also, because each method returns a modified object, it can be tempting to create immutable objects that return a new instance on each call (like how Python’s f-string works). This is often better but can be less memory efficient. The choice between mutating self or returning a new instance depends on your use case—mutability for performance, immutability for clarity and thread-safety. And for the love of Guido, don’t chain more than four or five methods; it becomes a nightmare to debug.