16

我正在使用实现__add__但不子类化的 Python 对象intMyObj1 + MyObj2工作正常,但sum([MyObj1, MyObj2])导致TypeError, 因为sum()第一次尝试0 + MyObj. 为了使用sum(),我的对象需要__radd__处理MyObj + 0 或者我需要提供一个空对象作为start参数。有问题的对象并非设计为空的。

在任何人问之前,该对象不是类似列表或类似字符串的,因此使用 join() 或 itertools 将无济于事。

编辑细节:该模块有一个 SimpleLocation 和一个 CompoundLocation。我将 Location 缩写为 Loc。ASimpleLoc包含一个右开区间,即[start, end)。添加SimpleLoc产生 a CompoundLoc,其中包含间隔列表,例如[[3, 6), [10, 13)]。最终用途包括遍历联合,例如[3, 4, 5, 10, 11, 12]检查长度和检查成员资格。

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

我看过的问题:

我正在考虑两种解决方案。一是避免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()既不能添加到整数也不能为空的对象?

4

5 回答 5

15

代替sum, 使用:

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

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

import operator
reduce(operator.add, seq)

Reduce 通常比 sum 更灵活——你可以提供任何二进制函数,不仅是add,而且你可以选择提供一个初始元素,同时sum总是使用一个。


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

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

请注意,所有:

  • 自然
  • 雷亚尔
  • 复数
  • Nd 向量
  • NxM 矩阵
  • 字符串

与加法一起形成一个Monoid - 即它们是关联的并且具有某种中性元素。

如果您的操作不是关联的并且没有中性元素,那么它就不会“类似于”加法。因此,不要期望它与sum.

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


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

这里有两个概念:

  • 简单的地点,
  • 复合地点。

可以添加简单的位置确实是有道理的,但它们不会形成幺半群,因为它们的添加不满足闭包的基本属性——两个 SimpleLoc 的总和不是 SimpleLoc。通常,它是一个 CompoundLoc。

OTOH,带有加法的 CompoundLocs 对我来说看起来像一个幺半群(一个可交换的幺半群,而我们正在使用它):它们的总和也是一个 CompoundLoc,它们的加法是关联的、可交换的,中性元素是一个空的 CompoundLoc,它包含零 SimpleLocs。

如果您同意我的观点(并且上述内容与您的实现相匹配),那么您将能够使用sum如下:

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

确实,这似乎有效


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

嗯,位置是一些数字集合,所以在它们之上抛出一个类似集合的界面是有意义的(所以__contains__,,,,也许作为产品的别名__iter__,等等)。__len____or__+__and__

至于从 构造xrange,你真的需要吗?如果您知道要存储间隔集,那么您可能会通过坚持对的表示来节省空间[start, end)。您可以输入一个实用方法,该方法采用任意整数序列并将其转换为最佳值SimpleLoc,或者CompoundLoc如果您觉得它会有所帮助。

于 2012-07-24T06:29:16.427 回答
4

我认为实现这一点的最好方法提供__radd__方法,或者将 start 对象传递给显式求和。

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

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

最好使用名称为 的函数my_sum(),但我想这是您要避免的事情之一(即使全局重新定义内置函数可能是未来的维护者会诅咒您的事情)

于 2012-07-24T06:15:12.107 回答
3

实际上,__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?
于 2012-07-24T06:16:22.777 回答
2

你可以使用一个普遍中立的对象。添加:

class Neutral:
    def __add__(self, other):
        return other

print(sum("A BC D EFG".split(), Neutral())) # ABCDEFG
于 2012-07-24T07:36:43.223 回答
0

你可以这样:

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...
于 2012-07-24T06:35:53.157 回答