Передача права собственности на данные о количестве

В предыдущем вопросе , я научился изменять размер подкласса ndarray на месте. Ухоженная. К сожалению, это больше не работает, когда массив, который я пытаюсь изменить, является результатом вычисления:

import numpy as np

class Foo(np.ndarray):
    def __new__(cls,shape,dtype=np.float32,buffer=None,offset=0,
                strides=None,order=None):
        return np.ndarray.__new__(cls,shape,dtype,buffer,offset,strides,order)

    def __array_prepare__(self,output,context):
        print output.flags['OWNDATA'],"PREPARE",type(output)
        return np.ndarray.__array_prepare__(self,output,context)

    def __array_wrap__(self,output,context=None):
        print output.flags['OWNDATA'],"WRAP",type(output)

        return np.ndarray.__array_wrap__(self,output,context)

a = Foo((32,))
#resizing a is no problem
a.resize((24,),refcheck=False)

b = Foo((32,))
c = Foo((32,))

d = b+c
#Cannot resize `d`
d.resize((24,),refcheck=False)

Точный вывод (включая трассировку):

True PREPARE <type 'numpy.ndarray'>
False WRAP <class '__main__.Foo'>
Traceback (most recent call last):
  File "test.py", line 26, in <module>
    d.resize((24,),refcheck=False)
ValueError: cannot resize this array: it does not own its data

Я думаю, это потому, что numpy создает новый ndarray и передает его на __array_prepare__. В какой-то момент по пути, кажется, что "output" array получает view-casted в мой Foo тип, хотя документы не кажутся на 100% четкими/точными в этой точке. В любом случае, после каста представления, выход больше не владеет данными, что делает невозможным изменение на месте (насколько я могу судить).

Есть ли какой-либо способ с помощью какого-то numood voodoo (__array_prepare__, __array__) и т.д., чтобы передать право собственности на данные в экземпляр моего подкласса?

Ответ 1

Это вряд ли удовлетворительный ответ, но он не вписывается в комментарий... Вы можете обойти владение данными с помощью параметра ufunc out. Глупый пример:

>>> a = Foo((5,))
>>> b = Foo((5,))
>>> c = a + b # BAD
True PREPARE <type 'numpy.ndarray'>
False WRAP <class '__main__.Foo'>
>>> c.flags.owndata
False

>>> c = Foo((5,))
>>> c[:] = a + b # BETTER
True PREPARE <type 'numpy.ndarray'>
False WRAP <class '__main__.Foo'>
>>> c.flags.owndata
True

>>> np.add(a, b, out=c) # BEST
True PREPARE <class '__main__.Foo'>
True WRAP <class '__main__.Foo'>
Foo([  1.37754085e-38,   1.68450356e-20,   6.91042737e-37,
         1.74735556e-04,   1.48018885e+29], dtype=float32)
>>> c.flags.owndata
True

Я думаю, что вывод выше согласуется с тем, что c[:] = a + b получает собственные данные за счет копирования его в c из временного массива. Но это не должно происходить, если вы используете параметр out.

Так как вы уже беспокоились о промежуточном хранении в своих математических выражениях, возможно, не так уж плохо, если вы управляете процессом управления. То есть, заменяя

g = a + b + np.sqrt(d*d + e*e + f*f)

с

g = foo_like(d) # you'll need to write this function!
np.multiply(d, d, out=g)
g += e * e
g += f * f
np.sqrt(g, out=g)
g += b
g += a

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

Ответ 2

В какой-то момент, по ходу пути, кажется, что массив "output" получает вид-casted к моему типу Foo

Да, ndarray.__array_prepare__ вызывает output.view, который возвращает массив, который не имеет своих данных.

Я немного экспериментировал и не мог найти легкий путь.

Хотя я согласен, что это поведение не является идеальным, по крайней мере, в вашем случае использования, я бы заявил, что для d допустимо не владеть его данными. Numpy широко использует представления, и если вы настаиваете на том, чтобы не создавать какие-либо представления при работе с массивами numpy, вы делаете свою жизнь очень тяжело.

Я также утверждаю, что, основываясь на моем опыте, resize обычно следует избегать. У вас не должно возникнуть проблем с созданием представления, если вы избежите resize ing. Там хакерское чувство, и с ним трудно работать (как вы могли бы начать понимать, столкнувшись с одной из двух классических ошибок при его использовании: it does not own its data, а другой - cannot resize an array that has been referenced). (Еще одна проблема описана в этом вопросе.)

Поскольку ваше решение использовать resize вытекает из ответа на ваш другой вопрос, я отправлю оставшуюся часть своего ответа там.

Ответ 3

Как насчет:

def resize(arr, shape):
    np.require(arr, requirements=['OWNDATA'])
    arr.resize(shape, refcheck=False)

Кажется, удастся изменить размер (и уменьшить потребление памяти):

import array
import numpy as np
import time

class Foo(np.ndarray):
    def __new__(cls, shape, dtype=np.float32, buffer=None, offset=0,
                strides=None, order=None):
        return np.ndarray.__new__(cls, shape, dtype, buffer, offset, strides, order)

    def __array_prepare__(self, output, context):
        print(output.flags['OWNDATA'], "PREPARE", type(output))
        return np.ndarray.__array_prepare__(self, output, context)

    def __array_wrap__(self, output, context=None):
        print(output.flags['OWNDATA'], "WRAP", type(output))
        output = np.ndarray.__array_wrap__(self, output, context)
        return output

def free_memory():
    """
    Return free memory available, including buffer and cached memory
    """
    total = 0
    with open('/proc/meminfo', 'r') as f:
        for line in f:
            line = line.strip()
            if any(line.startswith(field) for field in ('MemFree', 'Buffers', 'Cached')):
                field, amount, unit = line.split()
                amount = int(amount)
                if unit != 'kB':
                    raise ValueError(
                        'Unknown unit {u!r} in /proc/meminfo'.format(u=unit))
                total += amount
    return total


def gen_change_in_memory():
    """
    http://stackoverflow.com/a/14446011/190597 (unutbu)
    """
    f = free_memory()
    diff = 0
    while True:
        yield diff
        f2 = free_memory()
        diff = f - f2
        f = f2
change_in_memory = gen_change_in_memory().next

def resize(arr, shape):
    print(change_in_memory())
    # 0
    np.require(arr, requirements=['OWNDATA'])

    time.sleep(1)
    print(change_in_memory())
    # 200

    arr.resize(shape, refcheck=False)

N = 10000000
b = Foo((N,), buffer = array.array('f',range(N)))
c = Foo((N,), buffer = array.array('f',range(N)))

дает

print(change_in_memory())
# 0

d = b+c
d = np.require(d, requirements=['OWNDATA'])

print(change_in_memory())
# 39136

resize(d, (24,))   # Increases memory by 200 KiB
time.sleep(1)
print(change_in_memory())
# -39116