Usage: @cfg
@cfg (aliases @cm, @if_) selects which of
several identically named method implementations survives on a class —
at class build time. Only the selected implementation remains in the
class's attribute set, so the class is well-formed: no runtime branching
inside method bodies, exactly one attribute per name.
Forms
1. Decorator factory (recommended)
from conditional_method import cfg
class Worker:
@cfg(condition=ENV == "production")
def work(self):
return "production"
@cfg(condition=ENV == "development")
def work(self):
return "development"
2. Direct application
def work(self):
return "production"
work = cfg(work, condition=True)
3. Without brackets
@cfg without brackets raises TypeError immediately (a condition is
required):
@cfg # TypeError: `@cfg` must be used as a decorator and
def work(self): # `condition` must be specified ...
...
Conditions
A condition can be:
- a bool (or any truthy/falsy object) — evaluated once at decoration time;
- a callable taking the decorated function and returning a truthy value — evaluated lazily each time the implementation is selected.
@cfg(condition=True) # static
@cfg(condition=lambda f: os.environ["X"]) # dynamic
Selection rules
- Each implementation is evaluated in source order.
- The last implementation whose condition is true wins.
- If no condition is true, the class build fails with
TypeError: None of the conditions is true for .... - If a condition callable raises
TypeError, it is re-raised wrapped asError calling \condition` for: ...`; other exceptions propagate unchanged.
Where it works
- Methods — instance,
@classmethod,@staticmethod,@property - Module-level functions
- Classes themselves
- Dunder methods (
__enter__,__exit__,__new__, ...)
How it works
Each @cfg decorator evaluates its condition and either:
- true → caches the implementation under its qualified name
(
module.Class.method) and returns it unchanged, or - false → returns a
TypeErrorRaiserplaceholder.
When the class body executes, the raiser's __set_name__ fires: if a
condition was true for that qualname, the cached implementation replaces the
raiser; otherwise class creation fails.
Selections are cached per qualified name and survive only as weak
references (they are swept once their class is garbage-collected and the
cache passes an internal high-water mark), so the module caches do not pin
methods -- or their modules -- alive for the lifetime of the process. The
cache entry for a true winner must persist long enough for a later
condition=False decoration of the same name to resolve it during class
build; it is never cleared or deleted on a true result.