Skip to content
Close Menu

    Subscribe to Updates

    Get the latest news from tastytech.

    What's Hot

    Syrian authorities arrest ex-officer accused of chemical weapons crimes | Human Rights News

    July 15, 2026

    Stop Using If-Else Chains: Use the Registry Pattern in Python Instead

    July 15, 2026

    Microsoft’s Secure Boot has been broken for a decade and no one noticed until now

    July 15, 2026
    Facebook X (Twitter) Instagram
    Facebook X (Twitter) Instagram
    tastytech.intastytech.in
    Subscribe
    • AI News & Trends
    • Tech News
    • AI Tools
    • Business & Startups
    • Guides & Tutorials
    • Tech Reviews
    • Automobiles
    • Gaming
    • movies
    tastytech.intastytech.in
    Home»Business & Startups»Stop Using If-Else Chains: Use the Registry Pattern in Python Instead
    Stop Using If-Else Chains: Use the Registry Pattern in Python Instead
    Business & Startups

    Stop Using If-Else Chains: Use the Registry Pattern in Python Instead

    gvfx00@gmail.comBy gvfx00@gmail.comJuly 15, 2026No Comments9 Mins Read
    Share
    Facebook Twitter LinkedIn Pinterest Email



     

    Table of Contents

    Toggle
    • # Introduction
    • # The Problem With If-Else Chains
    • # Going From If-Else to a Dictionary
    • # Building a Decorator-Based Registry
    • # Building a Reusable Registry Class
    • # Auto-Registering Classes With __init_subclass__
    • # Where the Registry Pattern Is Useful in Practice
    • # Practical Considerations and Things to Watch Out For
    • # Wrapping Up
      • Related posts:
    • Beginner’s Guide to Automating ML Workflows
    • I Vibe Coded a Tool to That Analyzes Customer Sentiment and Topics From Call Recordings
    • 5 AI Coding Subscription Plans That Give Developers the Best Value

    # Introduction

     
    Every Python codebase has this problem. A function that starts small. Two branches, maybe three. Then someone adds a case, someone else adds another, and a year later you’ve got 200 lines of if/elif/else that nobody wants to touch. Here’s an example:

    def get_model(name):
        if name == "logreg":
            return LogisticRegression()
        elif name == "random_forest":
            return RandomForestClassifier()
        elif name == "svm":
            return SVC()
        elif name == "xgboost":
            return XGBClassifier()
        # ... 15 more branches
        else:
            raise ValueError(f"Unknown model: {name}")

     

    And yeah, it works. But it also breaks the Open/Closed Principle, which states that software entities (classes, modules, and functions) should be open for extension but closed for modification. There is a better way to handle this problem: the registry pattern. This article covers what the registry pattern is, how to build it up from a five-line dictionary to a production-grade reusable class, and when it actually earns its place in your code. So, let’s get started.

     

    # The Problem With If-Else Chains

     
    A long conditional chain fails in a few specific ways:

    • It violates the Open/Closed Principle. New case, new edit to a function that already worked. Yesterday’s tested code gets cracked open, retested, and reviewed again. The unit of change should be “add a file,” not “modify the central dispatcher.”
    • It piles unrelated logic into one place. Say your payment dispatcher covers credit cards, PayPal, and crypto. Now three domains that have nothing to do with each other are sharing one function. The elif ladder forces them to share a room anyway.
    • It scales badly. Every new branch adds to the cognitive weight of the whole function. Twenty branches is twenty things to scroll past every time you are debugging branch number three.
    • It cannot be extended from outside. Ship a library with a hardcoded get_model() chain and your users are stuck. They cannot add their own model without monkey-patching or forking. The logic is sealed shut.

    The registry pattern fixes all four by flipping the relationship. Instead of the dispatcher knowing about every option, each option announces itself to the dispatcher.

    What is the registry pattern?

    It is basically a central lookup table that maps keys to objects (functions, classes, instances), where each object registers itself instead of being hardcoded into some conditional. In Python, that lookup table is almost always a dictionary, and “registering” is usually done with a decorator.

     

    # Going From If-Else to a Dictionary

     
    The smallest possible win is to swap the chain for a dictionary lookup. One step, and the linear scan is gone:

    MODEL_REGISTRY = {
        "logreg": LogisticRegression,
        "random_forest": RandomForestClassifier,
        "svm": SVC,
        "xgboost": XGBClassifier,
    }
    
    def get_model(name):
        try:
            return MODEL_REGISTRY[name]
        except KeyError:
            raise ValueError(
                f"Unknown model: {name!r}. "
                f"Available: {list(MODEL_REGISTRY)}"
            ) from None

     

    This is already a registry — just a hand-maintained one. Dispatch is O(1), the options are introspectable with list(MODEL_REGISTRY), and the dispatcher never changes. One wart remains: every new model still means editing the dict and importing its class at the top of the file. You can do better by letting each component register itself.

     

    # Building a Decorator-Based Registry

     
    This is the version you’ll actually use day to day. Registration happens in a decorator, so every function or class declares its own key right where it is defined:

    PAYMENT_HANDLERS = {}
    
    def register(payment_type):
        def decorator(func):
            PAYMENT_HANDLERS[payment_type] = func
            return func
        return decorator
    
    @register("credit_card")
    def charge_credit_card(amount):
        return f"Charged ${amount} to credit card"
    
    @register("paypal")
    def charge_paypal(amount):
        return f"Charged ${amount} via PayPal"
    
    @register("crypto")
    def charge_crypto(amount):
        return f"Charged ${amount} in crypto"
    
    def process_payment(payment_type, amount):
        handler = PAYMENT_HANDLERS.get(payment_type)
        if handler is None:
            raise ValueError(f"Unknown payment type: {payment_type!r}")
        return handler(amount)

     

    Look at what changed. The process_payment dispatcher is four lines, and it will never grow. Want Apple Pay? Write a new function, slap @register("apple_pay") on it, put it in whatever file you like, and you’re done. No central list to edit. No merge conflict. No reopening tested code. The handler sits right next to its own key, which is exactly where the next reader will look for it.

     

    # Building a Reusable Registry Class

     
    Once you have two or three registries lying around, you will get tired of rewriting the same decorator boilerplate. Wrap it in a small class and you get collision detection, better error messages, and a clean API for free:

    class Registry:
        """A reusable name-to-object registry."""
    
        def __init__(self, name):
            self.name = name
            self._registry = {}
    
        def register(self, key):
            def decorator(obj):
                if key in self._registry:
                    raise KeyError(
                        f"{key!r} already registered in {self.name!r}"
                    )
                self._registry[key] = obj
                return obj
            return decorator
    
        def get(self, key):
            if key not in self._registry:
                raise KeyError(
                    f"{key!r} not found in {self.name!r}. "
                    f"Available: {list(self._registry)}"
                )
            return self._registry[key]
    
        def __contains__(self, key):
            return key in self._registry
    
        def keys(self):
            return self._registry.keys()

     

    Now use it to build a text-processing pipeline driven entirely by config:

    transforms = Registry("transforms")
    
    @transforms.register("lowercase")
    def to_lower(text):
        return text.lower()
    
    @transforms.register("strip")
    def strip_whitespace(text):
        return text.strip()
    
    @transforms.register("remove_digits")
    def remove_digits(text):
        return "".join(c for c in text if not c.isdigit())
    
    # The pipeline is now just data. It could come from a YAML file,
    # a CLI argument, or a database row.
    pipeline = ["strip", "lowercase", "remove_digits"]
    text = "  Order #4521 CONFIRMED  "
    
    for step in pipeline:
        text = transforms.get(step)(text)
    
    print(repr(text))
    
    Output:
    'order # confirmed'

     

    This is where the pattern pays for itself. The behavior of the program is now described by data — a list of strings — not by code. Reordering the pipeline, adding a step, or handing the whole thing to a non-programmer through a config file all become trivial.

     

    # Auto-Registering Classes With __init_subclass__

     
    When your registry holds classes instead of functions, Python has an even slicker trick. The __init_subclass__ hook (available since Python 3.6) fires automatically every time a subclass is defined, so subclasses register themselves with no decorator at all:

    class DataLoader:
        _registry = {}
    
        def __init_subclass__(cls, fmt=None, **kwargs):
            super().__init_subclass__(**kwargs)
            if fmt:
                DataLoader._registry[fmt] = cls
    
        @classmethod
        def get_loader(cls, fmt):
            if fmt not in cls._registry:
                raise ValueError(
                    f"No loader for {fmt!r}. "
                    f"Available: {list(cls._registry)}"
                )
            return cls._registry[fmt]
    
    class CSVLoader(DataLoader, fmt="csv"):
        def load(self, path):
            return f"Loading CSV from {path}"
    
    class JSONLoader(DataLoader, fmt="json"):
        def load(self, path):
            return f"Loading JSON from {path}"
    
    class ParquetLoader(DataLoader, fmt="parquet"):
        def load(self, path):
            return f"Loading Parquet from {path}"
    
    loader = DataLoader.get_loader("parquet")
    print(loader.load("sales.parquet"))   # Loading Parquet from sales.parquet

     

    No decorator anywhere. Subclassing DataLoader with a fmt= argument is enough to register the new class. This is how a lot of frameworks build their plugin systems under the hood.

     

    # Where the Registry Pattern Is Useful in Practice

     
    This is not an academic exercise. It is the backbone of tools you already use.

    • Machine learning experiment configs. Hugging Face Transformers, Detectron2, and MMDetection all use registries so you can pick a model, optimizer, or augmentation by string name in a YAML file. build_model({"model": "resnet50"}) beats a giant if backbone == ... block, and it lets researchers add architectures without ever touching the trainer.
    • File format and parser dispatch. Map extensions like "csv", "json", and "parquet" to loader classes. Supporting a new format becomes “write one class,” not “edit the loader.”
    • Web framework routing. Flask‘s @app.route("/users") and Click‘s @cli.command() are registries in disguise. The decorator maps a URL or command name to the function that handles it.
    • Plugin architectures. Any “drop a file in this folder and it just works” system — whether pytest fixtures, Airflow operators, or serializer backends — is almost always a registry collecting components at import time.
    • Event handlers and state machines. Map event names or states to handler functions instead of branching on them. The transition table turns into a readable dictionary rather than a nest of conditionals.

     

    # Practical Considerations and Things to Watch Out For

     

    • Registration only happens on import. A decorator runs when Python executes the file it lives in. If your handlers sit in handlers/apple_pay.py and nothing ever imports that module, the @register decorator never fires and the handler quietly goes missing. The fix is to make sure registration modules get imported — usually through an explicit import in a package’s __init__.py, or a small discovery loop with pkgutil.iter_modules that imports everything in a plugin folder.
    • Guard against silent overwrites. With a plain dict, two components registering the same key clobber each other without a peep. As the Registry class above shows, raising on a duplicate key turns a baffling runtime bug into an obvious error at import time.
    • Show people what is available. Always expose the keys with list(registry.keys()) and put them in your error messages. “Unknown model: ‘lgbm’. Available: [‘logreg’, ‘random_forest’, ‘xgboost’]” saves far more debugging time than a bare KeyError.
    • Do not reach for it too early. A registry is overkill for two or three stable branches whose logic genuinely differs. If the branches share no common signature, or the conditions are ranges rather than discrete keys (if score > 0.9 ... elif score > 0.5 ...), a plain conditional is clearer. The registry wins in one specific situation: you are dispatching on a discrete key to interchangeable behaviors, and you expect that set of behaviors to grow.

     

    # Wrapping Up

     
    The registry pattern trades a growing, central, hard-to-extend if/elif/else chain for a lookup table that components fill in themselves. The payoff is concrete. Your dispatcher stops changing. New features show up as new files instead of edits to old ones. Behavior becomes something you can drive from a config. And users of your code get a real extension point instead of a locked door.

    Start small. Next time you catch yourself typing a third elif name == ..., stop and ask whether a dictionary would do. Usually it would. From there, the decorator and class versions are a short hop away.

    # Before
    if kind == "a": ...
    elif kind == "b": ...
    elif kind == "c": ...
    
    # After
    @registry.register("a")
    def handle_a(): ...

     

    Your future self, scrolling past a four-line dispatcher instead of a 200-line ladder, will thank you.
     
     

    Kanwal Mehreen is a machine learning engineer and a technical writer with a profound passion for data science and the intersection of AI with medicine. She co-authored the ebook “Maximizing Productivity with ChatGPT”. As a Google Generation Scholar 2022 for APAC, she champions diversity and academic excellence. She’s also recognized as a Teradata Diversity in Tech Scholar, Mitacs Globalink Research Scholar, and Harvard WeCode Scholar. Kanwal is an ardent advocate for change, having founded FEMCodes to empower women in STEM fields.

    Related posts:

    API Development for Web Apps and Data Products

    10 GitHub Repositories to Master System Design

    Humanity’s Last Exam is a Distraction

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email
    Previous ArticleMicrosoft’s Secure Boot has been broken for a decade and no one noticed until now
    Next Article Syrian authorities arrest ex-officer accused of chemical weapons crimes | Human Rights News
    gvfx00@gmail.com
    • Website

    Related Posts

    Business & Startups

    7 Python Frameworks for Orchestrating Local AI Agents

    July 15, 2026
    Business & Startups

    Claude Fable 5’s Leaked System Prompt Decoded

    July 15, 2026
    Business & Startups

    Getting Started with Conductor for Gemini CLI

    July 15, 2026
    Add A Comment
    Leave A Reply Cancel Reply

    Top Posts

    Black Swans in Artificial Intelligence — Dan Rose AI

    October 2, 2025207 Views

    Every Clue That Tony Stark Was Always Doctor Doom

    October 20, 2025132 Views

    We let ChatGPT judge impossible superhero debates — here’s how it ruled

    December 31, 2025100 Views
    Stay In Touch
    • Facebook
    • YouTube
    • TikTok
    • WhatsApp
    • Twitter
    • Instagram

    Subscribe to Updates

    Get the latest tech news from tastytech.

    About Us
    About Us

    TastyTech.in brings you the latest AI, tech news, cybersecurity tips, and gadget insights all in one place. Stay informed, stay secure, and stay ahead with us!

    Most Popular

    Black Swans in Artificial Intelligence — Dan Rose AI

    October 2, 2025207 Views

    Every Clue That Tony Stark Was Always Doctor Doom

    October 20, 2025132 Views

    We let ChatGPT judge impossible superhero debates — here’s how it ruled

    December 31, 2025100 Views

    Subscribe to Updates

    Get the latest news from tastytech.

    Facebook X (Twitter) Instagram Pinterest
    • Homepage
    • About Us
    • Contact Us
    • Privacy Policy
    © 2026 TastyTech. Designed by TastyTech.

    Type above and press Enter to search. Press Esc to cancel.

    Ad Blocker Enabled!
    Ad Blocker Enabled!
    Our website is made possible by displaying online advertisements to our visitors. Please support us by disabling your Ad Blocker.