2012-03-02 5 views
4

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

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! 

Любая идея, что исправить, чтобы сохранить правильное Ед. изм?

редактировать: Возвращение тип/значение в arithmentic opperation должно быть эквивалентно:

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

Если вы ищете пакет Python блоков с большой поддержкой NumPy, но без необходимости NumPy работать, принимать посмотрите на http://pint.readthedocs.org/ (Отказ от ответственности: я один из авторов) – Hernan

ответ

4

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

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

иначе:

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

IFx.__mul__(y) возвращает NotImplemented (который отличается от raise NotImplementedError

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

Итак, есть два способа, которые __rmul__ можно назвать - подкласс ndarray, или ndarray не в состоянии t o умножить на 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 
+0

Отличный ответ! Один вопрос для numpy поведения: 'n * num' использует' num .__ array__' для вычисления (проверьте с помощью 'print 'в __array __" 'внутри этого метода). НО: 'n * num .__ array __()' предоставляет единицу, 'n * num' - нет. Разве это не ошибка в numpy (или, по крайней мере, очень неинтуитивная/неожиданная)? –

+0

@PhilippderRautenberg: Нет. Метод '__array__' просто возвращает« я ».signal', который является объектом «Quantity», а элементы «Quantity» знают об единицах, а 'ndarray' - нет. –

+0

@PhilippderRautenberg, что вы просите, странно. Вы хотите numpy сделать * и вернуть . Вероятно, было бы разумнее, если '__array__' вернул ndarray, а не количество, а' __array_wrap__' вернул Numberic, а не количество. На самом деле, рассмотрели sub-classing Quantity? –

3

Вам необходимо определить __array_wrap__. См. the documentation here.

Как простой пример, используя свой пример (но не требующее 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) 

она дает:

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

Чтобы полностью имитировать ndarray без подкласса ndarray, вам нужно ознакомиться с the details of subclassing them.

+0

Мне нужно написать небольшой пакет для сообщества, которое должно работать на 'Quantity'. В дополнение, мне нужно резервное решение для 'ndarray', чтобы оставаться независимым от' quantity', чтобы предоставить пакет debian ('количества' еще не упакованы). Это одна из нескольких причин (например, SQLAlchemy-Mapping), почему я хотел бы инкапсулировать числовую часть в 'signal'-attribut. –

+0

Спасибо, что указали мне на '__array_wrap__'. Однако проблема с единицей остается. По какой-то причине '__rmul__' не вызывается для' n * num'. Может, это ошибка? Numpy? –

+0

Также возвращаемый тип может быть таким же, как 'n * num.signal'. –

1

Согласно the python reference,__rmul__ правого операнда «только называется , если левый операнд не поддерживает соответствующую операцию и операнды различных типов.»

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