Переопределить диктат с поддержкой 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
, Я думаю, что это ошибка.