Как правильно вызвать `__rsub__` с массивом numpy? - PullRequest
1 голос
/ 16 октября 2019

Рассмотрим следующий фрагмент:

import numpy as np

class MyClass:
    def __rsub__(self, other):
        print(type(other), other)

obj = MyClass()
arr = np.ones(3)

arr - obj

Я ожидаю, что он напечатает <class 'numpy.ndarray'> [1. 1. 1.]. Но на самом деле, кажется, для каждого элемента вызывается __rsub__:

<class 'float'> 1.0
<class 'float'> 1.0
<class 'float'> 1.0

Есть ли способ сказать numpy, что я хочу first поведение ,то есть делегирование всего вычитания в MyClass?

1 Ответ

2 голосов
/ 16 октября 2019

На NumPy 1.13+ вы можете сделать это с помощью специфических для NumPy хуков, но в целом вы не можете использовать __rsub__ beat __sub__ методы.


__rsub__если левый операнд __sub__ не может обработать операцию, но левый операнд может обрабатывать эту операцию. __sub__ массива NumPy примет любую RHS и выполнит широковещательное вычитание. __rsub__ вашего объекта вступает в игру только для отдельных операций в транслируемом вычитании.

Существует один очень ограниченный случай, когда сначала проверяется __rsub__, если класс RHS является подклассомкласс LHS. Технически вы можете подкласс numpy.ndarray, но это будет с много дополнительного багажа и все равно ничего не делать для numpy.matrix([[1]]) - obj или других подклассов.

Нет никакого способа сказать "яхочу, чтобы мой __rsub__ победил все ". Его не существует, и для него не будет смысла существовать, потому что что произойдет, если вы попытаетесь вычесть два объекта, которые оба хотят объявить, что их метод бьет все?


Так вотобщий случай. Тем не менее, специально для NumPy можно подключиться к механизмам, которым делегируется numpy.ndarray.__sub__.

Делегат массивов NumPy __sub__ механизму NumPy ufunc . Там есть куча странных опций настройки, но нас интересует конкретное использование одной конкретной опции: установив __array_ufunc__ на None на уровне класса, вы можете объявить класс, несовместимый сufuncs. Это означает, что все перегрузки оператора NumPy вернут NotImplemented, что позволяет вашему классу обрабатывать операцию. Это влияет на все операторов и кучу других вещей, но таким образом, что вы, вероятно, захотите:

class MyClass:
    __array_ufunc__ = None
    def __rsub__(self, other):
        print(type(other), other)

Если вы хотите что-то более целевое, чем блокирование всех ufuncs, вы можете реализовать фактический__array_ufunc__ метод и просто обработайте случай, когда ufunc вычитание и экземпляр вашего класса RHS:

class MyClass:
    def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
        if ufunc is not numpy.subtract:
            return NotImplemented
        if method != '__call__':
            return NotImplemented
        if len(inputs) != 2 or inputs[1] is not self:
            return NotImplemented
        if kwargs:
            return NotImplemented
        return self.__rsub__(inputs[0])
    def __rsub__(self, other):
        print(type(other), other)
...