Hacer que my_average (a, b) funcione con cualquier a y b para las que f_add y d_div están definidos. Así como los construidos.

En resumen: lo que quiero es que la mayoría de las funciones matemáticas que he escrito (p. Ej., my_average(a, b) ) funcionen con cualquier a y b para las que se haya definido f_add y f_div . Sin sobrecargar + y / y sin romper my_average(built_in_type, built_in_type) En Python 3.

Específicamente, estoy trabajando con instancias de un objeto Pigmento que he creado. Sobrecargar los operadores para estos objetos no es sencillo. Por ejemplo:

La diferencia (para propósitos de distancia) entre dos instancias podría ser a.lab - b.lab . (El espacio de color Lab tiene una buena correlación entre la distancia perceptiva y la euclidiana).

La sum (para propósitos de mezcla) de dos instancias podría ser a.srgb + b.srgb . (El espacio de color srgb es lineal y apropiado para la manipulación matemática).

Para otros propósitos, la sum y la diferencia pueden significar algo más.

Por lo tanto, el pato escribiendo en mis módulos existentes no funcionará.

 pigment_instance.distance(self, b) pigment_instance.mix(self, b) 

están bien siempre y cuando no me importe volver a escribir cada función (como método) que necesito cada vez que tengo un nuevo objeto como este. Lo que me gustaría hacer es volver a escribir mis funciones una vez más para que sean más robustas.

He intentado algunas cosas:

 class Averager(): __init__(self, f_mix, f_distance): self.f_mix = f_mix ... def __call__(self, a, b): # return the average calculated with self.f_something functions 

Eso funciona bien, pero acabo de enterrar un módulo completo en una clase.

 def mix(a, b, f_mix=lambda x, y: x + y, f_distance=lambda x, y: x - y) # or, same as above with decorators. 

Una vez más, funciona bien, pero tengo que mantener los argumentos predeterminados largos o suministrar un f_add cada vez que quiera calcular 2 + 2.

 def pigment_functions(f_mix, f_distance): return [ functools.partial(mix, f_mix=somefunc, f_distance=somefunc), functools.partial(distance, f_mix=somefunc, f_distance=somefunc)] mix, difference = pigment_functions(f_mix, f_distance) 

Una opción similar a la segunda.

 def mix(a, b): try: a + b except TypeError: # look for some global f_mix 

También funciona bien, pero tengo variables globales y un desorden dentro de cada función

¿Cuál de estos (o algo más) tiene sentido?

si tiene my_average(a, b) que se implementa en términos de funciones add y div , por ejemplo:

 def my_average(a, b): return div(add(a, b), 2) 

luego, para proporcionar diferentes implementaciones para diferentes tipos, podría usar functools.singledispatch :

 import functools @singledispatch def div(x, y:int): # default implementation raise NotImplementedError('for type: {}'.format(type(x))) @div.register(Divisible) # anything with __truediv__ method def _(x, y): return x / y @singledispatch def add(a, b): raise NotImplementedError('for type: {}'.format(type(a))) @add.register(Addable) # anything with __add__ method def _(a, b): return a + b 

donde Addable , Divisable podría definirse como:

 from abc import ABCMeta, abstractmethod class Divisible(metaclass=ABCMeta): """Anything with __truediv__ method.""" __slots__ = () __hash__ = None # disable default hashing @abstractmethod def __truediv__(self, other): """Return self / other.""" @classmethod def __subclasshook__(cls, C): if cls is Divisible: if any("__truediv__" in B.__dict__ for B in C.__mro__): return True return NotImplemented class Addable(metaclass=ABCMeta): """Anything with __add__ method.""" __slots__ = () __hash__ = None # disable default hashing @abstractmethod def __add__(self, other): """Return self + other.""" @classmethod def __subclasshook__(cls, C): if cls is Addable: if any("__add__" in B.__dict__ for B in C.__mro__): return True return NotImplemented 

Ejemplo

 >>> isinstance(1, Addable) # has __add__ method True >>> isinstance(1, Divisible) # has __truediv__ method True >>> my_average(1, 2) 1.5 >>> class A: ... def __radd__(self, other): ... return D(other + 1) ... >>> isinstance(A(), Addable) False >>> _ = Addable.register(A) # register explicitly >>> isinstance(A(), Addable) True >>> class D: ... def __init__(self, number): ... self.number = number ... def __truediv__(self, other): ... return self.number / other ... >>> isinstance(D(1), Divisible) # via issubclass hook True >>> my_average(1, A()) 1.0 >>> my_average(A(), 1) # no A.__div__ Traceback (most recent call last): ... TypeError: unsupported operand type(s) for +: 'A' and 'int' 

Los números __add__ como int define el método __add__ , __truediv__ para que sean compatibles automáticamente. Como muestra la clase A , puede usar clases incluso si no definen los métodos específicos como __add__ llamando .register método .register explícitamente si aún pueden usarse en la implementación dada.

Utilice add.register y div.register para definir implementaciones para otros tipos si es necesario, por ejemplo:

 @div.register(str) def _(x, y): return x % y 

Después de esto:

 >>> my_average("%s", "b") # -> `("%s" + "b") % 2` '2b' 

Esto podría ser una idea:

 import operator f_add = {} def add(a,b): return f_add.get(type(a),operator.add)(a,b) # example class RGB: def __init__(self, r,g,b): self.r, self.g, self.b = (r,g,b) def __str__(self): return '<%s,%s,%s>'%(self.r,self.g,self.b) f_add[RGB] = lambda a,b: RGB(a.r+br,a.g+bg,a.b+bb) print(add(RGB(0.4,0.7,0.1), RGB(0.1, 0.2, 0.5))) print(add(4,5))