Избегание поведения по умолчанию для запуска Python

Я работаю с объектом Python, который реализует __add__, но не подклассом int. MyObj1 + MyObj2 работает нормально, но sum([MyObj1, MyObj2]) приводит к TypeError, потому что sum() сначала пытается 0 + MyObj. Чтобы использовать sum(), моей функции требуется __radd__ для обработки MyObj + 0 или. Мне нужно предоставить пустой объект в качестве параметра start. Объект, о котором идет речь, не предназначен для того, чтобы быть пустым.

Прежде чем кто-нибудь спросит, объект не похож на список или не похож на строку, поэтому использование join() или itertools не помогло бы.

Изменить для деталей: модуль имеет SimpleLocation и CompoundLocation. Я сокращу местоположение в Loc. A SimpleLoc содержит один правый открытый интервал, т.е. [Начало, конец]. Добавление 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() и использовать цикл, предложенный в этом comment. Я не понимаю, почему 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() для объектов, которые не могут быть добавлены в целые числа и не пусты?

Ответ 1

Вместо sum используйте:

import operator
reduce(operator.add, seq)

Уменьшение, как правило, более гибкое, чем sum - вы можете предоставить любую двоичную функцию, а не только add, и вы можете опционально предоставить начальный элемент, а sum всегда использует его.


Также обратите внимание: (Warning: maths rant ahead)

Предоставление поддержки объектов add w/r/t, не имеющих нейтрального элемента, немного неудобно с точки зрения алгебраической точки зрения.

Обратите внимание, что все:

  • натуралы
  • реалов
  • комплексные номера
  • N-d векторы
  • Матрицы NxM
  • строки

вместе с добавлением формы Monoid - то есть они являются ассоциативными и имеют какой-то нейтральный элемент.

Если ваша операция не является ассоциативной и не имеет нейтрального элемента, то она не похожа на добавление. Следовательно, не ожидайте, что она будет хорошо работать с sum.

В таком случае вам может быть лучше использовать функцию или метод вместо оператора. Это может быть менее запутанным, так как пользователи вашего класса, видя, что он поддерживает +, скорее всего, ожидают, что он будет вести себя моноидно (как правило, это нормально).


Спасибо за расширение, теперь я обращусь к вашему конкретному модулю:

Здесь есть 2 понятия:

  • Простые местоположения,
  • Расположение соединений.

Действительно имеет смысл, что простые места могут быть добавлены, но они не образуют моноид, потому что их добавление не удовлетворяет основному свойству замыкания - сумма двух SimpleLocs не является SimpleLoc. Это, как правило, CompoundLoc.

OTOH, CompoundLocs с добавлением выглядит как моноид для меня (коммутативный моноид, пока мы на нем): сумма из них тоже CompoundLoc, а их добавление ассоциативно, коммутативно и нейтральный элемент представляет собой пустой CompoundLoc, содержащий нулевые SimpleLocs.

Если вы согласны со мной (и выше соответствует вашей реализации), вы сможете использовать sum следующим образом:

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

Действительно, этот работает.


Теперь я мысленно подумываю о попытке подкласса, чтобы местоположение было построено как set (xrange (начало, конец)). Однако добавление наборов даст Python (и математикам) подходит.

Ну, местами являются некоторые наборы чисел, поэтому имеет смысл бросить на них набор-подобный интерфейс (так __contains__, __iter__, __len__, возможно __or__ как псевдоним +, __and__ как произведение и т.д.).

Что касается построения из xrange, вам это действительно нужно? Если вы знаете, что вы сохраняете множество интервалов, то вы, скорее всего, сэкономите пространство, придерживаясь своего представления пар [start, end). Вы можете использовать метод полезности, который принимает произвольную последовательность целых чисел и переводит его в оптимальный SimpleLoc или CompoundLoc, если вы чувствуете, что это поможет.

Ответ 2

Я думаю, что лучший способ добиться этого - предоставить метод __radd__ или передать исходный объект в явном виде.

Если вы действительно не хотите переопределять __radd__ или предоставлять начальный объект, как насчет переопределения sum()?

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

Предпочтительно использовать функцию с именем типа my_sum(), но я думаю, что это одна из вещей, которые вы хотите избежать (хотя глобальное переопределение встроенных функций, вероятно, является тем, что будущий сопровождающий проклинает вас)

Ответ 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?

Ответ 4

Вы можете использовать объект, который универсально нейтрален. дополнение:

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

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

Ответ 5

Вы могли бы что-то вроде:

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...