使my_average(a,b)与定义了f_add和d_div的任何a和b一起使用。以及内置
问题内容:
简而言之:我想要的是我编写的大多数数学函数(例如my_average(a, b)
)都可以使用a
和定义b
了f_add
和的任何数学函数f_div
。没有重载+和/且没有破坏my_average(built_in_type, built_in_type)
python 3。
具体来说,我正在使用我创建的颜料对象的实例。这些对象的重载运算符并不简单。例如:
两个实例之间的差异(出于距离目的)可能是a.lab - b.lab
。(Lab色彩空间在感知距离和欧几里得距离之间具有良好的相关性。)
两个实例的总和(出于混合目的)可能是a.srgb + b.srgb
。(srgb颜色空间是线性的,适用于数学操作。)
对于其他目的,总和与差额可能意味着其他。
因此,在现有模块中键入鸭子将不起作用。
pigment_instance.distance(self, b)
pigment_instance.mix(self, b)
只要我不介意在每次有这样的新对象时都重写每个函数(作为一种方法)就可以了。我想做的就是再次重写我的函数,以使其更强大。
我已经尝试了几件事:
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
可以,但是最后我将整个模块都埋在了一个类中。
def mix(a, b, f_mix=lambda x, y: x + y, f_distance=lambda x, y: x - y)
# or, same as above with decorators.
同样,可以正常工作,但是每次我要计算2 + 2时,我必须保留长的默认参数或提供一个f_add。
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)
与第二个相似的选择。
def mix(a, b):
try: a + b
except TypeError: # look for some global f_mix
也可以,但是我在每个函数中都有全局变量和混乱
其中哪一个(或其他)有意义?
问题答案:
如果您已经my_average(a, b)
按照add
和div
功能实现了,例如:
def my_average(a, b):
return div(add(a, b), 2)
然后为不同的类型提供不同的实现,可以使用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
其中Addable
,Divisable
可以定义为:
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
例
>>> 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'
内置数字,例如int
define
__add__
,__truediv__
method,因此它们自动得到支持。如类A
所示,即使它们没有定义特定的方法,也可以使用类,例如,如果仍然可以在给定的实现中使用它们,则可以通过显式__add__
调用.register
方法。
如有必要,使用add.register
和div.register
定义其他类型的实现,例如:
@div.register(str)
def _(x, y):
return x % y
之后:
>>> my_average("%s", "b") # -> `("%s" + "b") % 2`
'2b'