Introducing a new Python library `conditional-method`!

The Magic of @conditional_method: Selective Method Implementation in Python

:snake: Excited to share my new open-source Python library “conditional-method” that enables conditional method implementation based on runtime conditions and class building time constraints.

:bulb: With a simple @conditional_method decorator, you can define multiple versions of the same method and have only the appropriate one activated based on conditions like environment variables, feature flags, or platform specifics.

:arrows_counterclockwise: Rather than cluttering your code with complex if/else statements or strategy patterns, this library automatically selects the right implementation during class construction.

:hammer_and_wrench: Your classes become leaner and more focused - each method exists only post-class construction only when needed, reducing cognitive load and eliminating the possibility of selecting the wrong implementation at runtime.

:jigsaw: Each conditional method clearly declares its own activation criteria, making the code self-documenting and easier to maintain as your application evolves.

:iphone: For example, a single DatabaseConnector class can handle connection logic (note the keyword here is logic, not configurations) differently across environments without maintaining separate implementations or complex factories.

:zap: The result? Cleaner, more maintainable code with no runtime overhead, strong type safety, and elegant solutions to problems that typically require complex design patterns.

:link: Check out conditional-method on GitHub and soon PyPI - zero dependencies, type-safe, and ready to simplify your conditional implementation needs!

Example:

Links:
GitHub

    @conditional_method(condition=ENVIRONMENT == "production")
    def work(self, *args, **kwargs):
        print("Working in production")
        print(f"Args: {args}")
        print(f"Kwargs: {kwargs}")
        return "production"

How is this different from

if ENVIRONMENT == "production":
    def work(self, *args, **kwargs):
        print("Working in production")
        print(f"Args: {args}")
        print(f"Kwargs: {kwargs}")
        return "production"

? That seems like the more natural way to conditionally create methods.

4 Likes

No difference at all. Just less indentation. :laughing:

REALLY not convinced that we need a library just to save us the need to indent after an if.

1 Like

But I’m not here to convince, I am here to provide a tool when someone needs it.

1 Like

Well, if you use the decorator it:

  • is quite a bit of magic behind the scenes to get it to work at all
  • only works within classes
  • probably noticably increases import time (runtime cost should vanish)
  • has the potential to break in new an exciting ways if you use the same function name more than once across different classes and/or files.
  • is going to have issues with e.g. property setters

(Some of these are guesses based on reading the source code and my fairly-good-but-not-perfect understanding of python semantics)

But it does complain early if no condition is true which can be a genuine benefit. (By default it doesn’t complain early if more than one condition is true, although I think it should and thr code is there)

2 Likes

Okay, yes, that IS a real benefit. I’m not sure if there’s an easy way to enforce that otherwise. But there’s a lot of complexity for something fairly minor.

Hi @MegaIng

Actually I enhanced the source codes after you posted this comment, now it works on:

  1. functions that are not declared in a class (but declared in the global scope of a module)
  2. no magic involved as there is now only functions in the implementation with only a single instance of a descriptor to use the __set_name__ hook

Hi @Rosuav

Of course the safest way is to use if CONDITION: ..., I am just providing a tool if anyone needs it. If most people don’t need it, can leave the tool untouched.

And my question was, in effect: what kind of people need this tool?

Anyway, thank you for offering constructive feedback on how property.setter will not work - I will change the selection rule to match Python’s default (the last one is the selected one) and this will allow property.setter to work.

Very nice! One instant thought for me was when I was a student doing performance studies or other experiments, we always had things like “fun_1”, “fun_1_faster”, “fun_1_more_faster”… Okay crude but at the end of the day you wanted to produce a chart with different features turned on and going back in the code could make it painful to reproduce. This seems like a cleaner solution to implement the same functionality for experimentation.

Additionally, I could see feature toggles working with this nicely in a backend.

I downloaded this package (at 02MAR2025), unzip it and run the following script.
It seem to disturb the reload function.
-----test.py-----

from conditional_method import conditional_method

@conditional_method(condition = True)
def foo():
    print('Hello')
    
foo()

-----end-----

d:\Temp\conditional-method-main\src>py
Python 3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:34:34) [MSC v.1928 32 bit (Intel)] on win32
Type “help”, “copyright”, “credits” or “license” for more information.

import test
Hello

modify file, change string ‘Hello’ to ‘World’

from importlib import reload
reload(test)
Hello
<module ‘test’ from ‘d:\Temp\conditional-method-main\src\test.py’>

modify file, comment out @conditional_method(condition = True)

reload(test)
World
<module ‘test’ from ‘d:\Temp\conditional-method-main\src\test.py’>

This has more to do with the fact that the reload function can not work in general and should not be relied upon. References to objects/functions/classes from the module that are held outside of the module (as is happening in this case) will not get changed.

I didn’t see reload has problem on this script

from functools import lru_cache

@lru_cache
def foo():
    print('Hello')

foo()

The primary use case of your library appears to be of switching code definitions between development and production, which is sort of weird to me.

In my opinion, the core logics between development and production should be kept identical, with the only variables being things like credentials, logging target, directory names, and number of workers, etc., which can all be parameterized and read from a configuration file or service based on the environment settings.

Conditionally defining methods for different environments you like do is almost always going to end up with lots of duplicate code for the core logics in all of the conditionallly defined methods. And it’ll become a maintenance nightmare when you have to update any of the core logics, which you’ll have to apply to all the duplicate codes.

I think you should try coming up with a different use case (what that might be I have no idea of) for the library to be more compelling.

I saw it as a Python preprocessor, sort of:-)

Hi, thanks for your comment.

It’s not a new comment and it is also a comment that can be applied to most people’s attempt to write a library of sort.

I appreciate the time you took to pen these words.

I would say, if for your use case, you’re able to have the same logics all environments, differing only on configurations - then you’re right, you don’t have a need for this library.

I would also say that there are use cases for such a library because what I am attempting to create is similar to compilation attribute macros, e.g. #[cfg(..)] in Rust and even the preprocesser macro#DEFINE in C - albeit, these are aimed at different builds on different targets but fundamentally, the idea is similar, you want a different logic when something (build target, environment, etc.) is different.