Почему скаляры NumPy умножаются с помощью пользовательских последовательностей, а не со списками?

У меня вопрос к экспертам 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)).

Я полагаю, что проверки включают в себя тех, у кого есть ___mul__ (или rmul). Ответ показывает, что определение такого метода меняет поведение.

hpaulj 20.07.2024 18:58

неудачный дубль. stackoverflow.com/a/38230576/901925

hpaulj 20.07.2024 19:01

@hpaulj Большое спасибо, эта ссылка была чрезвычайно полезна. Однако решение через наследование от np.ndarray, безусловно, «грязное». Однако через numpy_ufunc был анзац, который необходимо принять для работы. Сейчас обновлю информацию здесь.

Pavlo Bilous 22.07.2024 08:53
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
3
104
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Так как же 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 своего вопроса).

Pavlo Bilous 20.07.2024 18:09
Ответ принят как подходящий

Как отметил @hpaulj (большое спасибо!), этот вопрос уже был: Массив и оператор __rmul__ в Python Numpy.

Механика проблемы очень хорошо объяснена в этом ответе: stackoverflow.com/a/38230576/901925. Однако предлагаемое решение наследования от np.ndarray, безусловно, «грязное».

В ответе сразу после этого предлагается решение, основанное на функции __numpy_ufunc__. Однако последний в современном NumPy называется __array_ufunc__. Для этой функции можно просто установить значение None в определении класса «S». Это приводит к делегированию умножения s.__rmul__ без попыток выполнить его через c.__mul__.

Другие вопросы по теме