Let's say I have a class like this.
from scipy import interpolate
import numpy as np
class Bar:
def __init__(self,data):
self.data = data
def mean(self):
return self.data.mean(axis=0)
def sd(self):
return self.data.std(axis=0)
bar = Bar(np.random.rand(10,5))
print(bar.mean())
print(bar.sd())
The class Bar
may have many such methods such as mean()
, sd()
etc. I want to add sampled versions of those methods, so that I can simply get results equivalent to this:
new_ids = np.linspace(bar.ids[0],bar.ids[-1],100)
sampled_mean = interpolate.interp1d(bar.ids,bar.mean(),axis=0)(new_ids)
Current workaround: manually add new methods with help of decorators.
from scipy import interpolate
import numpy as np
def sample(func):
def wrapper(self,n_sample=100,*args,**kwargs):
new_ids = np.linspace(self.ids[0],self.ids[-1],n_sample)
vals = func(self,*args,**kwargs)
return interpolate.interp1d(self.ids,vals,axis=0)(new_ids)
return wrapper
class Bar:
def __init__(self,ids,data):
self.ids = ids
self.data = data
def mean(self):
return self.data.mean(axis=0)
def sd(self):
return self.data.std(axis=0)
@sample
def spl_mean(self):
return self.mean()
@sample
def spl_sd(self):
return self.sd()
bar = Bar(ids=np.arange(5),data=np.random.rand(10,5))
new_ids = np.linspace(bar.ids[0],bar.ids[-1],100)
sampled_mean = interpolate.interp1d(bar.ids,bar.mean(),axis=0)(new_ids)
assert np.all(sampled_mean == bar.spl_mean())
However, I have many such methods. Of course I can write them with the help of LLM but what I want to know is:
One DRYer approach that avoids repeating method names for the sampled versions is to make the decorator a custom descriptor whose __get__
method returns an object that calls the decorated function when called, but also provides a sample
method that calls the decorated function with the sampling logics around it:
class Samplable:
def __init__(self, method):
self.method = method
def __call__(self, *args, **kwargs):
return self.method(*args, **kwargs)
def sample(self, *args, **kwargs):
return f'sampled {self(*args, **kwargs)}'
class sample:
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype=None):
return Samplable(self.func.__get__(obj))
class Bar:
def __init__(self, data):
self.data = data
@sample
def mean(self):
return f'mean {self.data}'
@sample
def sd(self):
return f'sd {self.data}'
so that:
bar = Bar('data')
print(bar.mean())
print(bar.mean.sample())
print(bar.sd())
print(bar.sd.sample())
outputs:
mean data
sampled mean data
sd data
sampled sd data