У меня вопрос к экспертам NumPy. Рассмотрим скаляр NumPy: c = np.arange(3.0).sum()
. Если я попытаюсь умножить его с помощью пользовательской последовательности, например.
class S:
def __init__(self, lst):
self.lst = lst
def __len__(self):
return len(self.lst)
def __getitem__(self, s):
return self.lst[s]
c = np.arange(3.0).sum()
s = S([1, 2, 3])
print(c * s)
это работает, и я получаю: array([3., 6., 9.])
.
Однако я не могу сделать это со списком. Например, если я унаследую S из списка и попробую, это больше не будет работать.
class S(list):
def __init__(self, lst):
self.lst = lst
def __len__(self):
return len(self.lst)
def __getitem__(self, s):
return self.lst[s]
c = np.arange(3.0).sum()
s = S([1, 2, 3])
print(c * s)
и я получаю «невозможно умножить последовательность на не-int типа numpy.float64».
Так как же NumPy различает эти два случая?
Я спрашиваю, потому что хочу предотвратить такое поведение для моего класса «S» без наследования от списка.
УПД
Поскольку вопрос несколько раз был неправильно понят, я стараюсь более точно подчеркнуть, в чем заключается проблема. Речь не о том, почему список не справляется с умножением на число с плавающей запятой и выдается ошибка.
Речь идет о том, почему в первом случае (когда S НЕ наследуется от списка) умножение выполняется объектом «c», а во втором случае (когда S наследуется от списка) умножение делегируется объекту «s».
Судя по всему, метод c.__mul__
выполняет некоторые проверки, которые проходят в первом случае, но не проходят во втором, поэтому вызывается s.__rmul__
. По существу вопрос: что это за проверки? (Я сильно сомневаюсь, что это что-то вроде isinstance(other, list)
).
неудачный дубль. stackoverflow.com/a/38230576/901925
@hpaulj Большое спасибо, эта ссылка была чрезвычайно полезна. Однако решение через наследование от np.ndarray, безусловно, «грязное». Однако через numpy_ufunc был анзац, который необходимо принять для работы. Сейчас обновлю информацию здесь.
Так как же NumPy различает эти два случая?
Я не думаю, что это особенность NumPy; кажется, это Python.
Списки Python определяют умножение с помощью Python int
, но не с другими объектами:
2. * [1, 2, 3]
# TypeError: can't multiply sequence by non-int of type 'float'
Идея всегда одна и та же: списки Python не думают, что их можно умножить на «не-int».
Итак, вопрос в том, почему список Python можно умножать на другие типы объектов, например целые числа NumPy, и это потому, что другой объект также имеет возможность определить умножение.
Встроенное определение чисел с плавающей точкой NumPy аналогично определению чисел с плавающей запятой Python, которые нельзя умножить на списки. Однако, если мы заменим определение умножения на числа с плавающей точкой NumPy, мы сможем заставить работать умножение со списками.
# if we override the multiplication behavior of np.float64
# it works
class T(np.float64):
def __mul__(self, other):
return 2 * other
t = T()
t * [1, 2, 3]
# [1, 2, 3, 1, 2, 3]
Вы можете предотвратить размножение вашего класса с другими типами объектов, вызвав исключение из метода(ов) __mul__
(и __rmul__
).
class S():
def __init__(self, lst):
self.lst = lst
def __len__(self):
return len(self.lst)
def __getitem__(self, s):
return self.lst[s]
def __mul__(self, other):
if not isinstance(other, int):
message = ("can't multiply instance of 'S' by "
f"non-int of type '{type(other).__name__}'")
raise TypeError(message)
return self.lst * other
s = S([1, 2, 3])
print(s * 2)
# [1, 2, 3, 1, 2, 3]
print(s * 2.)
# TypeError: can't multiply instance of 'S' by non-int of type 'float'
Спасибо за ваши мысли, но это не касается моего вопроса. Я обновил его, чтобы более точно подчеркнуть, в чем проблема. В последнем фрагменте кода вы умножаете на скаляры справа, и проблема не возникает. Попробуйте умножить слева, причем не на обычные скаляры Python, а на скаляры NumPy, например np.arange(3).sum(). Потом проблема вернулась (под "проблемой" я имею в виду то, что описал в UPD своего вопроса).
Как отметил @hpaulj (большое спасибо!), этот вопрос уже был: Массив и оператор __rmul__ в Python Numpy.
Механика проблемы очень хорошо объяснена в этом ответе: stackoverflow.com/a/38230576/901925. Однако предлагаемое решение наследования от np.ndarray
, безусловно, «грязное».
В ответе сразу после этого предлагается решение, основанное на функции __numpy_ufunc__
. Однако последний в современном NumPy называется __array_ufunc__
. Для этой функции можно просто установить значение None в определении класса «S». Это приводит к делегированию умножения s.__rmul__
без попыток выполнить его через c.__mul__
.
Я полагаю, что проверки включают в себя тех, у кого есть
___mul__
(илиrmul
). Ответ показывает, что определение такого метода меняет поведение.