Reputation: 23
I have a python dataclass in which I want to conditionally assign certain decorators depending on some global variable.
The condition is checked at the top of the script, but for my example below, I've simply supplied the result of that checking. If the check is True
, I want to give those methods the @functools.cached_property
decorator. If it is False
, I just want them to receive the standard @property
decorator.
The issue I keep running into is that I can't quite figure out how (or if it's even possible) to make this work as a simple decorator. I mostly get errors about method objects when calling or manipulating test.x_times_y
, and I'm not sure if it is possible to write the function in such a way that calling test.x_times_y
in the example below actually yields the result that I want.
import functools
import dataclasses
_value_checked = False
def myDecorator(func):
def decorator(self):
if not _value_checked:
return property(func)(self)
else:
return functools.cached_property(func)(self)
return decorator
@dataclasses.dataclass
class MyClass():
x: int
y: int
z: int = 0
@myDecorator
def x_times_y(self):
return self.x*self.y
test = MyClass(5,6,7)
I'd also like to avoid getter and setter methods, so I'm hopeful that that is possible. I've looked at many answers on here (such as this one) but haven't been able to find an answers that actually works, as most don't apply to decorating methods. I'm using Python 3.8 for this.
Upvotes: 2
Views: 645
Reputation: 14434
The behavior you want can be implemented with a simple conditional assignment:
my_decorator = functools.cached_property if _value_checked else property
or
if _value_checked:
my_decorator = functools.cached_property
else:
my_decorator = property
If you need to do more complex logic at each use of the decorator, you can use a function that returns the decorator you want:
def my_decorator():
if not _value_checked:
return property
else
return functools.cached_property
No complex argument forwarding required. Just delegate to the decorators you already have.
Upvotes: 3
Reputation: 71517
The way you've written myDecorator
it can only be applied to functions that take a single argument:
def myDecorator(func):
def decorator(self):
if not _value_checked:
return property(func)(self)
else:
return functools.cached_property(func)(self)
return decorator
The simplest thing is to just return the function and not call it inside a wrapper:
def myDecorator(func):
if not _value_checked:
return property(func)
else
return functools.cached_property(func)
If you did need to build a wrapper, the generally correct way is to have the wrapper function take arbitrary *args, **kwargs
arguments so you can invoke the wrapped function with them:
def myDecorator(func):
def wrapper(*args, **kwargs):
if not _value_checked:
return property(func)(*args, **kwargs)
else:
return functools.cached_property(func)(*args, **kwargs)
return wrapper
Note that the function that myDecorator
returns is not itself a decorator, it's a wrapper that replaces the decorated function -- that's why I've renamed it in the above implementation.
Note also that there is a practical difference between these implementations, which is that the second version (with the wrapper) evaluates _value_checked
at the time the function is called, whereas the first version evaluates it at the time the function is defined. If that value is a constant it doesn't matter, but if you want to be able to toggle it at runtime and have the behavior change dynamically, you want the second version.
Upvotes: 0