Переопределить диктат с поддержкой NumPy

Используя базовую идею из раздела "Идеально" переопределить диктовку? Я кодировал класс на основе словарей, которые должны поддерживать назначение ключей с разделителями, т.е. Extendeddict('level1.level2', 'value') == {'level1':{'level2':'value'}}

Код

import collections
import numpy

class Extendeddict(collections.MutableMapping):
    """Dictionary overload class that adds functions to support chained keys, e.g. A.B.C          
    :rtype : Extendeddict
    """
    # noinspection PyMissingConstructor
    def __init__(self, *args, **kwargs):
        self._store = dict()
        self.update(dict(*args, **kwargs))

    def __getitem__(self, key):
        keys = self._keytransform(key)
        print 'Original key: {0}\nTransformed keys: {1}'.format(key, keys)
        if len(keys) == 1:
            return self._store[key]
        else:
            key1 = '.'.join(keys[1:])
            if keys[0] in self._store:
                subdict = Extendeddict(self[keys[0]] or {})
                try:
                    return subdict[key1]
                except:
                    raise KeyError(key)
            else:
                raise KeyError(key)

    def __setitem__(self, key, value):
        keys = self._keytransform(key)
        if len(keys) == 1:
            self._store[key] = value
        else:
            key1 = '.'.join(keys[1:])
            subdict = Extendeddict(self.get(keys[0]) or {})
            subdict.update({key1: value})
            self._store[keys[0]] = subdict._store

    def __delitem__(self, key):
        keys = self._keytransform(key)
        if len(keys) == 1:
            del self._store[key]
        else:
            key1 = '.'.join(keys[1:])
            del self._store[keys[0]][key1]
            if not self._store[keys[0]]:
                del self._store[keys[0]]

    def __iter__(self):
        return iter(self._store)

    def __len__(self):
        return len(self._store)

    def __repr__(self):
        return self._store.__repr__()

    # noinspection PyMethodMayBeStatic
    def _keytransform(self, key):
        try:
            return key.split('.')
        except:
            return [key]

Но с Python 2.7.10 и numpy 1.11.0, работает

basic = {'Test.field': 'test'}
print 'Normal dictionary: {0}'.format(basic)
print 'Normal dictionary in a list: {0}'.format([basic])
print 'Normal dictionary in numpy array: {0}'.format(numpy.array([basic], dtype=object))
print 'Normal dictionary in numpy array.tolist(): {0}'.format(numpy.array([basic], dtype=object).tolist())

extended_dict = Extendeddict(basic)
print 'Extended dictionary: {0}'.format(extended_dict)
print 'Extended dictionary in a list: {0}'.format([extended_dict])
print 'Extended dictionary in numpy array: {0}'.format(numpy.array([extended_dict], dtype=object))
print 'Extended dictionary in numpy array.tolist(): {0}'.format(numpy.array([extended_dict], dtype=object).tolist())

Я получил:

Normal dictionary: {'Test.field': 'test'}
Normal dictionary in a list: [{'Test.field': 'test'}]
Normal dictionary in numpy array: [{'Test.field': 'test'}]
Normal dictionary in numpy array.tolist(): [{'Test.field': 'test'}]
Original key: Test
Transformed keys: ['Test']
Extended dictionary: {'Test': {'field': 'test'}}
Extended dictionary in a list: [{'Test': {'field': 'test'}}]
Original key: 0
Transformed keys: [0]
Traceback (most recent call last):
  File "/tmp/scratch_2.py", line 77, in <module>
    print 'Extended dictionary in numpy array: {0}'.format(numpy.array([extended_dict], dtype=object))
  File "/tmp/scratch_2.py", line 20, in __getitem__
    return self._store[key]
KeyError: 0

В то время как я ожидал print 'Extended dictionary in numpy array: {0}'.format(numpy.array([extended_dict], dtype=object)) привести к Extended dictionary in numpy array: [{'Test': {'field': 'test'}}]

Любые предложения о том, что может быть не так для этого? Это даже правильный способ сделать это?

2 ответа

Решение

Проблема в np.array шаг конструктора. Он копается в своих входах, пытаясь создать массив более высокой размерности.

In [99]: basic={'test.field':'test'}

In [100]: eb=Extendeddict(basic)

In [104]: eba=np.array([eb],object)
<keys: 0,[0]>
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-104-5591a58c168a> in <module>()
----> 1 eba=np.array([eb],object)

<ipython-input-88-a7d937b1c8fd> in __getitem__(self, key)
     11         keys = self._keytransform(key);print key;print keys
     12         if len(keys) == 1:
---> 13             return self._store[key]
     14         else:
     15             key1 = '.'.join(keys[1:])

KeyError: 0 

Но если я сделаю массив и назначу объект, он будет работать нормально

In [105]: eba=np.zeros((1,),object)

In [106]: eba[0]=eb

In [107]: eba
Out[107]: array([{'test': {'field': 'test'}}], dtype=object)

np.array сложная функция для использования с dtype=object, сравнить np.array([[1,2],[2,3]],dtype=object) а также np.array([[1,2],[2]],dtype=object), Один из них (2,2), другой (2,). Он пытается создать массив 2d и обращается к 1d с элементами списка, только если это не удается. Нечто подобное происходит здесь.

Я вижу 2 решения - одно из них о способе построения массива, который я использовал в других случаях. Другой - выяснить, почему np.array не копается в dict но делает с твоим. np.array компилируется, поэтому может потребоваться чтение жесткого кода GITHUB.


Я попробовал решение с f=np.frompyfunc(lambda x:x,1,1), но это не работает (подробности см. в моей истории редактирования). Но я обнаружил, что смешивание Extendeddict с dict работает:

In [139]: np.array([eb,basic])
Out[139]: array([{'test': {'field': 'test'}}, {'test.field': 'test'}], dtype=object)

Так что смешивать это с чем-то еще, как None или пустой список

In [140]: np.array([eb,[]])
Out[140]: array([{'test': {'field': 'test'}}, []], dtype=object)

In [142]: np.array([eb,None])[:-1]
Out[142]: array([{'test': {'field': 'test'}}], dtype=object)

Это еще одна распространенная уловка для создания массива списков объектов.

Это также работает, если вы даете ему два или более Extendeddict с разной длиной

np.array([eb, Extendeddict({})]), Другими словами, если len(...) отличаются (как со смешанными списками).

Numpy пытается сделать то, что должен:

Numpy проверяет для каждого элемента, является ли он повторяемым (используя len а также iter) потому что то, что вы передаете, может быть интерпретировано как многомерный массив.

Здесь есть одна загвоздка: dict-подобные занятия isinstance(element, dict) == True) не будет интерпретироваться как другое измерение (поэтому передача в [{}] работает). Вероятно, они должны проверить, если это collections.Mapping вместо dict, Может быть, вы можете сообщить об ошибке на их трекере.

Если вы измените определение своего класса на:

class Extendeddict(collections.MutableMapping, dict):
     ...

или измени свой __len__-метод:

    def __len__(self):
        raise NotImplementedError

оно работает. Ничто из этого не может быть чем-то, что вы хотите сделать, но Numpy просто использует Duck Typing для создания массива без подклассов непосредственно из dict или делая len недоступный NumPy видит ваш класс как нечто, что должно быть другое измерение. Это довольно умно и удобно, если вы хотите передать в пользовательских последовательностях (подклассы из collections.Sequence) но неудобно для collections.Mapping или же collections.MutableMapping, Я думаю, что это ошибка.

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