Tengo varias clases que implementan técnicas de suavizado en una serie de precios.
Estoy tratando de encontrar la mejor manera de implementar cualquiera de estas clases de suavizado dentro de la función __call__
de otra clase, donde luego se realiza alguna operación en la serie suavizada:
es decir
class NoSmoother: def __init__(self, prices): self.prices = prices def __call__(self): return self.prices class MASmoother: def __init__(self, prices): self.prices = prices def __call__(self, window): return self.prices.rolling(window).mean().to_frame("price") class ThatDoesSomethingWithSmoothedPrices(): def __init__(self, prices): self.prices = prices def __call__(self, smoother=ma_smoother, window=3) smoothed_prices = SomeBuilderClassThatCallsTheCorrectSmootherClass()
Como puede ver, necesitaría la clase de fábrica/constructor para implementar NoSmoother
si dice smoother = None, de lo contrario, llamaría a la clase de suavizado adecuada. Por supuesto, el objeto devuelto puede variar de simple a complejo, por ejemplo, si suavizo los precios usando un filtro de Kalman, la clase puede esperar muchos más parámetros.
Actualmente, mi código instancia class ThatDoesSomethingWithSmoothedPrices()
con una serie de precios, luego llama pasando un **config.
Salida deseada:
Idealmente, me gustaría poder llamar a cualquier clase de suavizado desde la función de llamada de
class ThatDoesSomethingWithSmoothedPrices()
.
Ejemplo de implementación:
configs = {'smoother': MASmoother, 'window': 3} processor = ThatDoesSomethingWithSmoothedPrices(prices) output = processor(**config)
Mi intento:
class Smoother: def __init__(self, prices): self.prices = prices def __call__(self, smoother, *args, **kwargs): return partial(smoother, **kwargs) def ma_smoother(self, window: int = 3): return self.prices.rolling(window).mean().to_frame("price") def no_smoother(self): return self.prices class ThatDoesSomethingWithSmoothedPrices: def __init__(self, prices): self.prices = prices def __call__(self, smooth_method = 'no_smoother'): smoother = Smoother(prices) prices_smoothed = smoother(**configs) # do other things if __name__ == '__main__': configs = {'smoother': 'ma_smoother', window=3} label = ThatDoesSomethingWithSmoothedPrices(**configs)
Cualquier ayuda muy apreciada.
Para simplificar, si no tiene mucho estado, solo usaría funciones regulares.
Puede usar functools.partial()
para aplicar parcialmente una función , es decir, en este caso, configure la ventana MA:
from functools import partial def no_smoother(values): return values def ma_smoother(values, *, window): return values.rolling(window).mean().to_frame("price") def get_prices(): ... def get_smoothed_prices(smoother): prices = get_prices() return smoother(prices) get_smoothed_prices(smoother=no_smoother) get_smoothed_prices(smoother=partial(ma_smoother, window=3))
Basado en la edición en la pregunta:
configs = {'smoother': MASmoother, 'window': 3} processor = ThatDoesSomethingWithSmoothedPrices(prices) output = processor(**config)
se expresaría como algo así
def construct_smoother(smoother, **kwargs): return partial(smoother, **kwargs) smoother = construct_smoother(**configs) # ...
Dada su última actualización, también debe pasar las configs
:
configs = {'smoother': MASmoother, 'window': 3} processor = ThatDoesSomethingWithSmoothedPrices(configs, prices) output = processor(**config)
Eso significa que ThatDoesSomethingWithSmoothedPrices
podría ser así:
def ThatDoesSomethingWithSmoothedPrices(configs, prices): smoother = configs['smoother'](prices) return smoother
Para crear una fábrica, puede crear un método de clase dentro de una clase.
# declaration class Factory: # class variables (static) id = 0 @classmethod def new(cls): return object() # usage obj = Factory.new()
Si tiene una clase que necesita argumentos en el constructor, puede pasar un número variable de argumentos. Básicamente *
elimina los corchetes alrededor de la list
o el dict
que está pasando.
# declaration def function(var1, var2, *args, **kwargs): # transfer variable arguments to other function return SomeObejct(*args, **kwargs) # usage obj = function(1, 2, a, b, c, key=value)
En tu caso, harías algo como esto:
# declaration # you can also pass classes as arguments def __call__(self, smoother=MASmoother, window=3, *args, **kwargs) smoothed_prices = smoother(*args, **kwargs) return smoothed_prices # usage smth = ThatDoesSomethingWithSmoothedPrices() smoother1 = smth() smoother2 = smth(NoSmoother, 2) smoother3 = smth(NoSmoother, 2, arg1, arg2, key=value)