避免Python求和默认启动参数行为



我使用的是一个Python对象,它实现了__add__,但不包含int的子类。MyObj1 + MyObj2工作正常,但sum([MyObj1, MyObj2])导致TypeError,因为sum()首先尝试0 + MyObj。为了使用sum(),我的对象需要__radd__来处理MyObj + 0我需要提供一个空对象作为start参数。有问题的对象不是设计为空的。

在有人询问之前,对象不是列表式或字符串式的,所以使用join()或itertools没有帮助。

编辑以获取详细信息:模块有一个SimpleLocation和一个CompoundLocation。我将Location缩写为Loc。一个SimpleLoc包含一个右开区间,即[start,end)。加上SimpleLoc得到一个CompoundLoc,它包含一个区间列表,例如[[3, 6), [10, 13)]。最终用途包括遍历并集,如[3, 4, 5, 10, 11, 12]、检查长度和检查成员关系。

数字可以相对较大(例如,小于2^32,但通常为2^20)。间隔时间可能不会很长(100-2000,但可能更长)。目前,只存储端点。我现在正在尝试尝试对set进行子类化,以便将位置构造为set(xrange(start, end))。然而,添加集合将使Python(和数学家)适合。

我看过的问题:

  • python';sum()和非整数值
  • 为什么有';s是python中的起始参数';s内置求和函数
  • 重写__add__方法后的TypeError

我正在考虑两种解决方案。一种是避免使用sum(),并使用此注释中提供的循环。我不明白为什么sum()一开始就把可迭代的第0项添加到0,而不是添加第0项和第1项(就像链接评论中的循环);我希望有一个神秘的整数优化的原因。

我的另一个解决方案如下;虽然我不喜欢硬编码的零检查,但这是我使sum()工作的唯一方法。

# ...
def __radd__(self, other):
    # This allows sum() to work (the default start value is zero)
    if other == 0:
        return self
    return self.__add__(other)

总之,对于既不能添加到整数也不能为空的对象,是否有其他方法可以使用sum()

使用:而不是sum

import operator
from functools import reduce
reduce(operator.add, seq)

在Python 2中,reduce是内置的,所以看起来像:

import operator
reduce(operator.add, seq)

Reduce通常比sum更灵活-您可以提供任何二进制函数,而不仅仅是add,并且您可以可选地提供初始元素,而sum始终使用一个。


另请注意:(警告:前方数学咆哮)

从代数的角度来看,为没有中性元素的add w/r/t对象提供支持有点尴尬。

注意所有:

  • 自然的
  • 雷亚尔
  • 复数
  • N-d矢量
  • NxM矩阵
  • 字符串

与加法一起形成一个单体-即它们是结合的,并具有某种中性元素。

如果您的操作不是关联的,并且没有中性元素,那么它就不"类似"加法因此,不要指望它能很好地与sum配合使用。

在这种情况下,使用函数或方法而不是运算符可能会更好。这可能不那么令人困惑,因为您的类的用户看到它支持+,可能会期望它将以一种monoid的方式运行(就像加法通常所做的那样)。


感谢您的扩展,我现在将参考您的特定模块:

这里有两个概念:

  • 简单的位置
  • 大院位置

可以添加简单的位置,这确实是有道理的,但它们不形成单半群,因为它们的添加不满足闭包的基本性质——两个SimpleLoc的和不是SimpleLoc。一般来说,它是一个CompoundLoc。

OTOH,带加法的CompoundLocs在我看来像是一个monoid(一个交换的monoid,当我们在做它的时候):这些的和也是一个CompoundLoc,它们的加法是结合的,交换的,并且中性元素是一个空的CompoundLoc,它包含零个SimpleLocs

如果你同意我的观点(并且上面的内容与你的实现相匹配),那么你就可以使用sum如下:

sum( [SimpleLoc1, SimpleLoc2, SimpleLoc3], start=ComplexLoc() )

事实上,这似乎奏效了。


我现在正在尝试尝试对set进行子类化,以便将位置构造为set(xrange(start,end))。然而,添加集合将使Python(和数学家)适合。

位置是一些数字集,所以在它们上面抛出一个类似集合的接口是有意义的(所以__contains____iter____len__,也许__or__+的别名,__and__是乘积,等等)。

至于xrange的施工,你真的需要吗?如果您知道您正在存储间隔集,那么您可能会通过坚持[start, end)对的表示来节省空间。如果你觉得有用的话,你可以引入一个实用方法,它可以获取任意的整数序列,并将其转换为最佳的SimpleLocCompoundLoc

我认为实现这一的最佳方法是提供__radd__方法,或者显式地传递开始对象求和。

如果您真的不想覆盖__radd__或提供启动对象,那么重新定义sum()如何?

>>> from __builtin__ import sum as builtin_sum
>>> def sum(iterable, startobj=MyCustomStartObject):
...     return builtin_sum(iterable, startobj)
... 

最好使用一个名为my_sum()的函数,但我想这是你想要避免的事情之一(尽管全局重新定义内置函数可能是未来维护人员诅咒你的事情)

实际上,在没有"空对象"概念的情况下实现__add__没有什么意义。sum需要一个start参数来支持空序列和单元素序列的和,并且您必须决定在这些情况下预期的结果:

sum([o1, o2]) => o1 + o2  # obviously
sum([o1]) => o1  # But how should __add__ be called here?  Not at all?
sum([]) => ?  # What now?

您可以使用一个通用中性的对象

class Neutral:
    def __add__(self, other):
        return other
print(sum("A BC D EFG".split(), Neutral())) # ABCDEFG

您可以这样做:

from operator import add
try:
    total = reduce(add, whatever) # or functools.reduce in Py3.x
except TypeError as e:
    # I'm not 100% happy about branching on the exception text, but
    # figure this msg isn't likely to be changed after so long...
    if e.args[0] == 'reduce() of empty sequence with no initial value':
        pass # do something appropriate here if necessary
    else:
        pass # Most likely that + isn't usable between objects...

最新更新