Избегание поведения по умолчанию для запуска 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...