我如何使Python字典成员访问通过点“。”?
例如,我想写mydict.val而不是mydict['val']。
我还想以这种方式访问嵌套字典。例如
mydict.mydict2.val
会提到
mydict = { 'mydict2': { 'val': ... } }
我如何使Python字典成员访问通过点“。”?
例如,我想写mydict.val而不是mydict['val']。
我还想以这种方式访问嵌套字典。例如
mydict.mydict2.val
会提到
mydict = { 'mydict2': { 'val': ... } }
当前回答
语言本身不支持这一点,但有时这仍然是一个有用的需求。除了Bunch recipe,你还可以写一个小方法,可以使用虚线字符串访问字典:
def get_var(input_dict, accessor_string):
"""Gets data from a dictionary using a dotted accessor-string"""
current_data = input_dict
for chunk in accessor_string.split('.'):
current_data = current_data.get(chunk, {})
return current_data
这将支持如下内容:
>> test_dict = {'thing': {'spam': 12, 'foo': {'cheeze': 'bar'}}}
>> output = get_var(test_dict, 'thing.spam.foo.cheeze')
>> print output
'bar'
>>
其他回答
这是我从很久以前的一个项目里挖出来的。它可能还可以再优化一点,但就是这样了。
class DotNotation(dict):
__setattr__= dict.__setitem__
__delattr__= dict.__delitem__
def __init__(self, data):
if isinstance(data, str):
data = json.loads(data)
for name, value in data.items():
setattr(self, name, self._wrap(value))
def __getattr__(self, attr):
def _traverse(obj, attr):
if self._is_indexable(obj):
try:
return obj[int(attr)]
except:
return None
elif isinstance(obj, dict):
return obj.get(attr, None)
else:
return attr
if '.' in attr:
return reduce(_traverse, attr.split('.'), self)
return self.get(attr, None)
def _wrap(self, value):
if self._is_indexable(value):
# (!) recursive (!)
return type(value)([self._wrap(v) for v in value])
elif isinstance(value, dict):
return DotNotation(value)
else:
return value
@staticmethod
def _is_indexable(obj):
return isinstance(obj, (tuple, list, set, frozenset))
if __name__ == "__main__":
test_dict = {
"dimensions": {
"length": "112",
"width": "103",
"height": "42"
},
"meta_data": [
{
"id": 11089769,
"key": "imported_gallery_files",
"value": [
"https://example.com/wp-content/uploads/2019/09/unnamed-3.jpg",
"https://example.com/wp-content/uploads/2019/09/unnamed-2.jpg",
"https://example.com/wp-content/uploads/2019/09/unnamed-4.jpg"
]
}
]
}
dotted_dict = DotNotation(test_dict)
print(dotted_dict.dimensions.length) # => '112'
print(getattr(dotted_dict, 'dimensions.length')) # => '112'
print(dotted_dict.meta_data[0].key) # => 'imported_gallery_files'
print(getattr(dotted_dict, 'meta_data.0.key')) # => 'imported_gallery_files'
print(dotted_dict.meta_data[0].value) # => ['link1','link2','link2']
print(getattr(dotted_dict, 'meta_data.0.value')) # => ['link1','link2','link3']
print(dotted_dict.meta_data[0].value[2]) # => 'link3'
print(getattr(dotted_dict, 'meta_data.0.value.2')) # => 'link3'
I ended up trying BOTH the AttrDict and the Bunch libraries and found them to be way to slow for my uses. After a friend and I looked into it, we found that the main method for writing these libraries results in the library aggressively recursing through a nested object and making copies of the dictionary object throughout. With this in mind, we made two key changes. 1) We made attributes lazy-loaded 2) instead of creating copies of a dictionary object, we create copies of a light-weight proxy object. This is the final implementation. The performance increase of using this code is incredible. When using AttrDict or Bunch, these two libraries alone consumed 1/2 and 1/3 respectively of my request time(what!?). This code reduced that time to almost nothing(somewhere in the range of 0.5ms). This of course depends on your needs, but if you are using this functionality quite a bit in your code, definitely go with something simple like this.
class DictProxy(object):
def __init__(self, obj):
self.obj = obj
def __getitem__(self, key):
return wrap(self.obj[key])
def __getattr__(self, key):
try:
return wrap(getattr(self.obj, key))
except AttributeError:
try:
return self[key]
except KeyError:
raise AttributeError(key)
# you probably also want to proxy important list properties along like
# items(), iteritems() and __len__
class ListProxy(object):
def __init__(self, obj):
self.obj = obj
def __getitem__(self, key):
return wrap(self.obj[key])
# you probably also want to proxy important list properties along like
# __iter__ and __len__
def wrap(value):
if isinstance(value, dict):
return DictProxy(value)
if isinstance(value, (tuple, list)):
return ListProxy(value)
return value
参见https://stackoverflow.com/users/704327/michael-merickel的原始实现。
另一件需要注意的事情是,这个实现非常简单,并且没有实现您可能需要的所有方法。您需要根据需要在DictProxy或ListProxy对象上写入这些内容。
Fabric有一个非常好的、最小的实现。将其扩展为允许嵌套访问,我们可以使用defaultdict,结果看起来像这样:
from collections import defaultdict
class AttributeDict(defaultdict):
def __init__(self):
super(AttributeDict, self).__init__(AttributeDict)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(key)
def __setattr__(self, key, value):
self[key] = value
可以这样使用它:
keys = AttributeDict()
keys.abc.xyz.x = 123
keys.abc.xyz.a.b.c = 234
这详细阐述了Kugel的回答“从dict和派生并实现__getattr__和__setattr__”。现在你知道怎么做了!
获得点访问(但不是数组访问)的一个简单方法是在Python中使用一个普通对象。是这样的:
class YourObject:
def __init__(self, *args, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
...像这样使用它:
>>> obj = YourObject(key="value")
>>> print(obj.key)
"value"
... 把它转换成字典:
>>> print(obj.__dict__)
{"key": "value"}
使用__getattr__,非常简单,适用于 Python 3.4.3
class myDict(dict):
def __getattr__(self,val):
return self[val]
blockBody=myDict()
blockBody['item1']=10000
blockBody['item2']="StackOverflow"
print(blockBody.item1)
print(blockBody.item2)
输出:
10000
StackOverflow