Как я могу добавить `sizes.Quantity`-поведение, не наследуя его с помощью` __array__`? - PullRequest
4 голосов
/ 02 марта 2012

quantities.Quantity является подклассом numpy.ndarray, который обрабатывает арифметику и преобразования физических величин.Как я могу использовать его арифметику без подкласса?В следующем подходе используется __array__ -метод - но он работает на 80%, как вы можете видеть в конце:

class Numeric(object):
  def __init__(self, signal):
    self.signal = signal
    self._dimensionality = self.signal._dimensionality
    self.dimensionality = self.signal.dimensionality
  def __array__(self):
    return self.signal
  def __mul__(self, obj):
    return self.signal.__mul__(obj)
  def __rmul__(self, obj):
    return self.signal.__rmul__(obj)

С этим я могу сделать:

import quantities as pq
import numpy as np

num = Numeric(pq.Quantity([1,2,3], 'mV'))
q = pq.Quantity([2,3,4], 'mV')
n = np.array([3,4,5])

Все следующие операции возвращают правильную единицу - , кроме последней, там единица отсутствует :

print num * num
# [1 4 9] mV**2
print num * q
# [ 2  6 12] mV**2
print num * n
# [ 3  8 15] mV
print q * num
# [ 2  6 12] mV**2
print n * num
# [ 3  8 15] <------- no unit!

Есть идеи, что исправить, чтобы сохранить правильную единицу?

edit : Возвращаемый тип / значение арифметической операции должно быть эквивалентно:

  • num.signal * num.signal
  • num.signal * q
  • num.signal * n
  • q * num.signal
  • n * num.signal # this doesn't work

Ответы [ 3 ]

4 голосов
/ 06 марта 2012

Когда Python видит x * y, вот что происходит:

  • если y является подклассом x -> y.__rmul__(x) называется

в противном случае:

  • x.__mul__(y) называется

IF x.__mul__(y) возвращает NotImplemented (что отличается от raise NotImplementedError

  • y.__rmul__(x) называется

Таким образом, есть два способа, которыми можно назвать __rmul__ - подкласс ndarray, или ndarray не может быть умножен на Numeric.

Вы не можете создать подкласс, и, очевидно, ndarray рад работать с Numeric, поэтому . , .

К счастью, люди numpy подготовлены к таким ситуациям - ответ лежит в методе __array_wrap__:

def __array_wrap__(self, out_arr, context=None):
    return type(self.signal)(out_arr, self.dimensionality)

Мы используем исходный класс signal вместе с исходной размерностью, чтобы создать новый сигнал для нового объекта Numeric.

Весь бит выглядит так:

import quantities as pq
import numpy as np

class Numeric(object):
    def __init__(self, signal):
        self.signal = signal
        self.dimensionality = self.signal.dimensionality
        self._dimensionality = self.signal._dimensionality
    def __array__(self):
        return self.signal
    def __array_wrap__(self, out_arr, context=None):
        return type(self.signal)(out_arr, self.dimensionality)
    def __mul__(self, obj):
        return self.signal.__mul__(obj)
    def __rmul__(self, obj):
        return self.signal.__rmul__(obj)


num = Numeric(pq.Quantity([1,2,3], 'mV'))
q = pq.Quantity([2,3,4], 'mV')
n = np.array([3,4,5])

t = num * num
print type(t), t
t = num * q
print type(t), t
t = num * n
print type(t), t
t = q * num
print type(t), t
t = n * num
print type(t), t

А при запуске:

<class 'quantities.quantity.Quantity'> [1 4 9] mV**2
<class 'quantities.quantity.Quantity'> [ 2  6 12] mV**2
<class 'quantities.quantity.Quantity'> [ 3  8 15] mV
<class 'quantities.quantity.Quantity'> [ 2  6 12] mV**2
<class 'quantities.quantity.Quantity'> [ 3  8 15] mV
3 голосов
/ 02 марта 2012

Вам нужно определить __array_wrap__.См. документацию здесь .

В качестве быстрого примера, использующего ваш пример (но не требующий quantities):

class Numeric(object):
  def __init__(self, signal):
    self.signal = signal
  def __array__(self):
    return self.signal
  def __mul__(self, obj):
    return type(self)(self.signal.__mul__(obj))
  def __rmul__(self, obj):
    return type(self)(self.signal.__rmul__(obj))

import numpy as np

num = Numeric(np.arange(10))
n = np.arange(10)

print type(num * n)
print type(n * num)

Это дает:

<class '__main__.Numeric'>
<type 'numpy.ndarray'>

Если мы включим __array_wrap__:

class Numeric(object):
  def __init__(self, signal):
    self.signal = signal
  def __array__(self):
    return self.signal
  def __mul__(self, obj):
    return type(self)(self.signal.__mul__(obj))
  def __rmul__(self, obj):
    return type(self)(self.signal.__rmul__(obj))
  def __array_wrap__(self, out_arr, context=None):
    return type(self)(out_arr)

import numpy as np

num = Numeric(np.arange(10))
n = np.arange(10)

print type(num * n)
print type(n * num)

Это даст:

<class '__main__.Numeric'>
<class '__main__.Numeric'>

Однако я все еще не понимаю, почему нельзя просто создать подкласс ndarray во-первых ... я подозреваю, что в долгосрочной перспективе это будет намного чище.Если вы не можете, вы не можете, однако.

Чтобы полностью имитировать ndarray без подкласса ndarray, вам нужно очень хорошо ознакомиться с деталями их подкласса .

1 голос
/ 06 марта 2012

В соответствии с ссылкой на python, __rmul__ правого операнда «вызывается только , только если левый операнд не поддерживает соответствующую операцию и операнды имеют разные типы."

Итак, проблема в том, что когда вы запускаете n * num.signal, numpy.array поддерживает умножение и вступает во владение.Единственный способ увидеть это, если сделать способ сделать Numeric несовместимым с numpy.array.__mul__

...