避免Python和默认启动arg行为

时间:2012-07-24 05:57:43

标签: python sum

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

在有人要求之前,该对象不像列表或类似字符串,因此使用join()或itertools无济于事。

编辑以获取详细信息:模块具有SimpleLocation和CompoundLocation。我将把位置缩写为Loc。 SimpleLoc包含一个右开间隔,即[开始,结束]。添加SimpleLoc会产生CompoundLoc,其中包含间隔列表,例如[[3, 6), [10, 13)]。最终用途包括遍历联合,例如[3, 4, 5, 10, 11, 12],检查长度和检查会员资格。

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

我看过的问题:

我正在考虑两种解决方案。一种是避免sum()并使用此comment中提供的循环。我不明白为什么sum()开始时将iterable的第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()既不能添加到整数也不能为空?

5 个答案:

答案 0 :(得分:10)

使用:

而不是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 matrices
  • 字符串

加上形成Monoid - 即它们是关联的并且具有某种中性元素。

如果你的操作不是关联的,并且没有中性元素,那么它就不会“类似”添加。因此,不要指望它与{{1 }}

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


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

这里有两个概念:

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

确实可以添加简单的位置,但它们不会形成一个monoid,因为它们的加法不满足闭包的基本属性 - 两个SimpleLocs的总和不是SimpleLoc。通常,它是一个CompoundLoc。

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

如果您同意我(以及上述内容与您的实施相符),那么您将能够使用+,如下所示:

sum

确实,appears to work


  

我现在暂时想到尝试子集set,使得该位置被构造为set(xrange(start,end))。但是,添加集将使Python(和数学家)适合。

嗯,位置是一些数字集合,所以在它们之上抛出一个类似于集合的界面是有意义的(所以sum( [SimpleLoc1, SimpleLoc2, SimpleLoc3], start=ComplexLoc() ) __contains____iter__,也许是{{ 1}}作为__len____or__的别名作为产品等。)

至于+的建筑,你真的需要吗?如果您知道存储了多组间隔,那么您可以通过坚持__and__对的表示来节省空间。您可以引入一个实用程序方法,该方法采用任意整数序列并将其转换为最佳xrange[start, end),如果您觉得它会有所帮助。

答案 1 :(得分:4)

我认为完成此的最佳方法是以提供__radd__方法,或者将start对象明确地传递给sum。

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

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

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

答案 2 :(得分: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?

答案 3 :(得分:2)

你可以使用普遍中立的对象。此外:

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

print(sum("A BC D EFG".split(), Neutral())) # ABCDEFG

答案 4 :(得分: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...