Python 递归点播
我有一个实用程序类,它使Python字典在获取和设置属性方面有点像JavaScript对象Python 递归点播,python,recursion,dictionary,Python,Recursion,Dictionary,我有一个实用程序类,它使Python字典在获取和设置属性方面有点像JavaScript对象 class DotDict(dict): """ a dictionary that supports dot notation as well as dictionary access notation usage: d = DotDict() or d = DotDict({'val1':'first'}) set attributes: d.val2 =
class DotDict(dict):
"""
a dictionary that supports dot notation
as well as dictionary access notation
usage: d = DotDict() or d = DotDict({'val1':'first'})
set attributes: d.val2 = 'second' or d['val2'] = 'second'
get attributes: d.val2 or d['val2']
"""
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
我希望它也能将嵌套字典转换为DotDict()实例。我希望能用\uuuu init\uuuuu
或\uuuu new\uuuuuu
做类似的事情,但我没有想出任何有效的方法:
def __init__(self, dct):
for key in dct.keys():
if hasattr(dct[key], 'keys'):
dct[key] = DotDict(dct[key])
如何将嵌套字典递归转换为DotDict()实例
>>> dct = {'scalar_value':1, 'nested_dict':{'value':2}}
>>> dct = DotDict(dct)
>>> print dct
{'scalar_value': 1, 'nested_dict': {'value': 2}}
>>> print type(dct)
<class '__main__.DotDict'>
>>> print type(dct['nested_dict'])
<type 'dict'>
>dct={'scalar_value':1,'nested_dict':{'value':2}
>>>dct=DotDict(dct)
>>>打印dct
{'scalar_value':1,'nested_dict':{'value':2}
>>>打印类型(dct)
>>>打印类型(dct['nested_dict'])
我看不出在构造函数中复制值的位置。因此,DotDict总是空的。当我添加密钥分配时,它起了作用:
class DotDict(dict):
"""
a dictionary that supports dot notation
as well as dictionary access notation
usage: d = DotDict() or d = DotDict({'val1':'first'})
set attributes: d.val2 = 'second' or d['val2'] = 'second'
get attributes: d.val2 or d['val2']
"""
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
def __init__(self, dct):
for key, value in dct.items():
if hasattr(value, 'keys'):
value = DotDict(value)
self[key] = value
dct = {'scalar_value':1, 'nested_dict':{'value':2, 'nested_nested': {'x': 21}}}
dct = DotDict(dct)
print dct.nested_dict.nested_nested.x
它看起来有点危险,而且容易出错,更不用说给其他开发人员带来无数惊喜的原因了,但它似乎正在发挥作用。公认的答案有一些缺陷,比如在
hasattr()上失败。
。使用这些键来模拟属性意味着您需要做的不仅仅是分配\uuuuu getattr\uuuuu=dict.\uuuuu getitem\uuuu
。下面是一个更健壮的测试实现:
from collections import OrderedDict, Mapping
class DotDict(OrderedDict):
'''
Quick and dirty implementation of a dot-able dict, which allows access and
assignment via object properties rather than dict indexing.
'''
def __init__(self, *args, **kwargs):
# we could just call super(DotDict, self).__init__(*args, **kwargs)
# but that won't get us nested dotdict objects
od = OrderedDict(*args, **kwargs)
for key, val in od.items():
if isinstance(val, Mapping):
value = DotDict(val)
else:
value = val
self[key] = value
def __delattr__(self, name):
try:
del self[name]
except KeyError as ex:
raise AttributeError(f"No attribute called: {name}") from ex
def __getattr__(self, k):
try:
return self[k]
except KeyError as ex:
raise AttributeError(f"No attribute called: {k}") from ex
__setattr__ = OrderedDict.__setitem__
以及测试:
class DotDictTest(unittest.TestCase):
def test_add(self):
exp = DotDict()
# test that it's not there
self.assertFalse(hasattr(exp, 'abc'))
with self.assertRaises(AttributeError):
_ = exp.abc
with self.assertRaises(KeyError):
_ = exp['abc']
# assign and test that it is there
exp.abc = 123
self.assertTrue(hasattr(exp, 'abc'))
self.assertTrue('abc' in exp)
self.assertEqual(exp.abc, 123)
def test_delete_attribute(self):
exp = DotDict()
# not there
self.assertFalse(hasattr(exp, 'abc'))
with self.assertRaises(AttributeError):
_ = exp.abc
# set value
exp.abc = 123
self.assertTrue(hasattr(exp, 'abc'))
self.assertTrue('abc' in exp)
self.assertEqual(exp.abc, 123)
# delete attribute
delattr(exp, 'abc')
# not there
self.assertFalse(hasattr(exp, 'abc'))
with self.assertRaises(AttributeError):
delattr(exp, 'abc')
def test_delete_key(self):
exp = DotDict()
# not there
self.assertFalse('abc' in exp)
with self.assertRaises(KeyError):
_ = exp['abc']
# set value
exp['abc'] = 123
self.assertTrue(hasattr(exp, 'abc'))
self.assertTrue('abc' in exp)
self.assertEqual(exp.abc, 123)
# delete key
del exp['abc']
# not there
with self.assertRaises(KeyError):
del exp['abc']
def test_change_value(self):
exp = DotDict()
exp.abc = 123
self.assertEqual(exp.abc, 123)
self.assertEqual(exp.abc, exp['abc'])
# change attribute
exp.abc = 456
self.assertEqual(exp.abc, 456)
self.assertEqual(exp.abc, exp['abc'])
# change key
exp['abc'] = 789
self.assertEqual(exp.abc, 789)
self.assertEqual(exp.abc, exp['abc'])
def test_DotDict_dict_init(self):
exp = DotDict({'abc': 123, 'xyz': 456})
self.assertEqual(exp.abc, 123)
self.assertEqual(exp.xyz, 456)
def test_DotDict_named_arg_init(self):
exp = DotDict(abc=123, xyz=456)
self.assertEqual(exp.abc, 123)
self.assertEqual(exp.xyz, 456)
def test_DotDict_datatypes(self):
exp = DotDict({'intval': 1, 'listval': [1, 2, 3], 'dictval': {'a': 1}})
self.assertEqual(exp.intval, 1)
self.assertEqual(exp.listval, [1, 2, 3])
self.assertEqual(exp.listval[0], 1)
self.assertEqual(exp.dictval, {'a': 1})
self.assertEqual(exp.dictval['a'], 1)
self.assertEqual(exp.dictval.a, 1) # nested dotdict works
为了好玩,您可以使用以下方法将对象转换为DotDict:
def to_dotdict(obj):
''' Converts an object to a DotDict '''
if isinstance(obj, DotDict):
return obj
elif isinstance(obj, Mapping):
return DotDict(obj)
else:
result = DotDict()
for name in dir(obj):
value = getattr(obj, name)
if not name.startswith('__') and not inspect.ismethod(value):
result[name] = value
return result
我对这个问题的所有不同答案都有点不满意。我的实施目标是: 1) 不要创建过多的新对象属性。 2) 不允许覆盖对内置属性的访问。 3) 该类转换添加的项以保持一致性
class attrdict(dict):
"""
Attribute Dictionary.
Enables getting/setting/deleting dictionary keys via attributes.
Getting/deleting a non-existent key via attribute raises `AttributeError`.
Objects are passed to `__convert` before `dict.__setitem__` is called.
This class rebinds `__setattr__` to call `dict.__setitem__`. Attributes
will not be set on the object, but will be added as keys to the dictionary.
This prevents overwriting access to built-in attributes. Since we defined
`__getattr__` but left `__getattribute__` alone, built-in attributes will
be returned before `__getattr__` is called. Be careful::
>>> a = attrdict()
>>> a['key'] = 'value'
>>> a.key
'value'
>>> a['keys'] = 'oops'
>>> a.keys
<built-in method keys of attrdict object at 0xabcdef123456>
Use `'key' in a`, not `hasattr(a, 'key')`, as a consequence of the above.
"""
def __init__(self, *args, **kwargs):
# We trust the dict to init itself better than we can.
dict.__init__(self, *args, **kwargs)
# Because of that, we do duplicate work, but it's worth it.
for k, v in self.iteritems():
self.__setitem__(k, v)
def __getattr__(self, k):
try:
return dict.__getitem__(self, k)
except KeyError:
# Maintain consistent syntactical behaviour.
raise AttributeError(
"'attrdict' object has no attribute '" + str(k) + "'"
)
def __setitem__(self, k, v):
dict.__setitem__(self, k, attrdict.__convert(v))
__setattr__ = __setitem__
def __delattr__(self, k):
try:
dict.__delitem__(self, k)
except KeyError:
raise AttributeError(
"'attrdict' object has no attribute '" + str(k) + "'"
)
@staticmethod
def __convert(o):
"""
Recursively convert `dict` objects in `dict`, `list`, `set`, and
`tuple` objects to `attrdict` objects.
"""
if isinstance(o, dict):
o = attrdict(o)
elif isinstance(o, list):
o = list(attrdict.__convert(v) for v in o)
elif isinstance(o, set):
o = set(attrdict.__convert(v) for v in o)
elif isinstance(o, tuple):
o = tuple(attrdict.__convert(v) for v in o)
return o
class属性(dict):
"""
属性字典。
允许通过属性获取/设置/删除字典键。
通过属性获取/删除不存在的键会引发“AttributeError”。
对象在调用'dict.\uuuuu setitem.\uuuuuu'之前传递给'\uuuu convert'。
此类重新绑定`\uuuu setattr`,以调用` dict.\uuuuu setitem.\uuuuu`.属性
不会在对象上设置,但将作为键添加到字典中。
这可以防止覆盖对内置属性的访问
`__getattr\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
在调用`\u getattr\u`之前返回。请小心::
>>>a=attrdict()
>>>一个['key']='value'
>>>钥匙
“价值”
>>>a['keys']='oops'
>>>a.钥匙
由于上述原因,请在``中使用`'key',而不是`'hasattr(a,'key')``。
"""
定义初始化(self,*args,**kwargs):
#我们比我们所能做的更好地相信dict本身。
dict.uuu init_uuu(self,*args,**kwargs)
#正因为如此,我们做了重复的工作,但这是值得的。
对于self.iteritems()中的k,v:
self.\uuuu设置项(k,v)
def uu getattr uu(self,k):
尝试:
返回指令。获取项目(self,k)
除KeyError外:
#保持一致的句法行为。
提高属性错误(
“'attrdict'对象没有属性”“+str(k)+”“”
)
定义设置项(self、k、v):
指令集(self,k,attrict.\uuuu convert(v))
__setattr\uuuuuuuuuuuuuuu=setitem__
定义(自我,k):
尝试:
指令(self,k)
除KeyError外:
提高属性错误(
“'attrdict'对象没有属性”“+str(k)+”“”
)
@静力学方法
定义转换(o):
"""
递归转换'dict'、'list'、'set'和'dict'中的'dict'对象
`tuple`objects到`attrdict`对象。
"""
如果存在(o,dict):
o=属性(o)
elif isinstance(o,列表):
o=列表(属性转换为o中的v)
elif isinstance(o,集合):
o=设置(属性转换为o中的v)
elif isinstance(o,元组):
o=元组(属性转换为o中的v)
返回o
无耻地插入我自己的软件包
有一个软件包正是做你想要的,还有更多的东西,它被称为
从prodict导入prodict
生活格言={'bigBang':
{'stars':
{'planets':[]}
}
}
生活=从生活中获得的产品(生活)
印刷品(生命、大爆炸、恒星、行星)
#打印[]
#您甚至可以动态添加新属性
life.bigBang.galays=[]
附言1:我是这本书的作者
PS 2:这是另一个问题答案的直接复制粘贴。您正在替换
dct
中的值,这是您传入的原始词典。新对象是原始对象的副本,因此它保留原始值。如果您替换self[key]
,它应该可以工作。这看起来像是它的一个副本,看起来不错,只需要添加一个默认值dct={},这样它就可以像这样使用:d=DotDict()。有人对确保嵌套值是某种字典的更好方法有什么建议吗?我希望它能够支持任何像OrderedDict一样的字典,所以我不一定要检查它是否是字典实例。为什么不检查\uuuu getitem\uuuuuuuu
而不是键
?或者甚至isinstance(value,dict)
@Eric,但这就不太灵活了。它只会检测dict实例,而不会检测行为类似于字典的对象。“如果它像鸭子一样呱呱叫”等等。不适用于字典对象的嵌套列表。请确定它确实适用。这就是为什么\uuuu init\uuuu
循环遍历值并分配嵌套的DotDict对象的原因。它应该是val=DotDict(val)
in\uuuu init\uuuu
@Thomas-很好,但打字错误实际上在下面一行。永远不要更改迭代器变量。不管怎样,我已经修好了。@mattmc3是的,那更好。谢谢