如何使用“。”;访问字典的成员?

我如何使Python字典成员访问通过点“。”?

例如,我想写mydict.val而不是mydict['val']

我还想以这种方式访问嵌套字典。例如

mydict.mydict2.val

会提到

mydict = { 'mydict2': { 'val': ... } }
264456 次浏览

派生自dict和并实现__getattr____setattr__

或者你可以使用,这是非常相似的。

我不认为这是可能的monkeypatch内置字典类。

不喜欢。在Python中,属性访问和索引是分开的事情,您不应该希望它们执行相同的操作。创建一个类(可能是由namedtuple创建的类),如果你有一些应该具有可访问属性的东西,并使用[]符号从字典中获取一个项。

语言本身不支持这一点,但有时这仍然是一个有用的需求。除了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 dotdict(dict):
def __getattr__(self, name):
return self[name]

你也可以试试__getattribute__

使每个字典都是一种类型的dotdict就足够了,如果你想从多层字典初始化它,也可以尝试实现__init__

基于Kugel的回答,并考虑到Mike Graham的警告,如果我们制作一个包装器呢?

class DictWrap(object):
""" Wrap an existing dict, or create a new one, and access with either dot
notation or key lookup.


The attribute _data is reserved and stores the underlying dictionary.
When using the += operator with create=True, the empty nested dict is
replaced with the operand, effectively creating a default dictionary
of mixed types.


args:
d({}): Existing dict to wrap, an empty dict is created by default
create(True): Create an empty, nested dict instead of raising a KeyError


example:
>>>dw = DictWrap({'pp':3})
>>>dw.a.b += 2
>>>dw.a.b += 2
>>>dw.a['c'] += 'Hello'
>>>dw.a['c'] += ' World'
>>>dw.a.d
>>>print dw._data
{'a': {'c': 'Hello World', 'b': 4, 'd': {}}, 'pp': 3}


"""


def __init__(self, d=None, create=True):
if d is None:
d = {}
supr = super(DictWrap, self)
supr.__setattr__('_data', d)
supr.__setattr__('__create', create)


def __getattr__(self, name):
try:
value = self._data[name]
except KeyError:
if not super(DictWrap, self).__getattribute__('__create'):
raise
value = {}
self._data[name] = value


if hasattr(value, 'items'):
create = super(DictWrap, self).__getattribute__('__create')
return DictWrap(value, create)
return value


def __setattr__(self, name, value):
self._data[name] = value


def __getitem__(self, key):
try:
value = self._data[key]
except KeyError:
if not super(DictWrap, self).__getattribute__('__create'):
raise
value = {}
self._data[key] = value


if hasattr(value, 'items'):
create = super(DictWrap, self).__getattribute__('__create')
return DictWrap(value, create)
return value


def __setitem__(self, key, value):
self._data[key] = value


def __iadd__(self, other):
if self._data:
raise TypeError("A Nested dict will only be replaced if it's empty")
else:
return other

我一直把它保存在util文件中。您也可以在自己的类中使用它作为mixin。

class dotdict(dict):
"""dot.notation access to dictionary attributes"""
__getattr__ = dict.get
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__


mydict = {'val':'it works'}
nested_dict = {'val':'nested works too'}
mydict = dotdict(mydict)
mydict.val
# 'it works'


mydict.nested = dotdict(nested_dict)
mydict.nested.val
# 'nested works too'

通过pip安装dotmap

pip install dotmap

它做所有你想让它做的事情,并继承dict的子类,所以它的操作就像一个普通的字典:

from dotmap import DotMap


m = DotMap()
m.hello = 'world'
m.hello
m.hello += '!'
# m.hello and m['hello'] now both return 'world!'
m.val = 5
m.val2 = 'Sam'

最重要的是,你可以将它转换为dict对象:

d = m.toDict()
m = DotMap(d) # automatic conversion in constructor

这意味着如果你想访问的东西已经是dict形式,你可以将它转换为DotMap以便访问:

import json
jsonDict = json.loads(text)
data = DotMap(jsonDict)
print data.location.city

最后,它会自动创建新的子DotMap实例,所以你可以这样做:

m = DotMap()
m.people.steve.age = 31

与Bunch的比较

完全披露:我是DotMap. xml的创建者。我创建它是因为Bunch缺少这些特性

  • 记住添加的顺序项并按此顺序迭代
  • 自动创建子DotMap,当你有很多层次结构时,这节省了时间并使代码更干净
  • dict构造并递归地将所有dict子实例转换为DotMap

我最终尝试了AttrDict库,发现它们对我的使用来说太慢了。在我和一个朋友深入研究之后,我们发现编写这些库的主要方法是在库中积极递归地遍历嵌套对象,并在整个过程中复制字典对象。考虑到这一点,我们做了两个关键更改。2)不是创建字典对象的副本,而是创建轻量级代理对象的副本。这是最终的实现。使用此代码的性能提高是令人难以置信的。当使用AttrDict或Bunch时,这两个库分别占用了我请求时间的1/2和1/3(什么!?)这段代码将时间减少到几乎为零(在0.5ms范围内)。当然,这取决于您的需要,但如果您在代码中经常使用此功能,那么一定要使用像这样简单的功能。

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对象上写入这些内容。

你可以用我刚做的这个类来做。在这个类中,你可以像使用另一个字典(包括json序列化)一样使用Map对象,或者使用点表示法。希望对大家有所帮助:

class Map(dict):
"""
Example:
m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
"""
def __init__(self, *args, **kwargs):
super(Map, self).__init__(*args, **kwargs)
for arg in args:
if isinstance(arg, dict):
for k, v in arg.iteritems():
self[k] = v


if kwargs:
for k, v in kwargs.iteritems():
self[k] = v


def __getattr__(self, attr):
return self.get(attr)


def __setattr__(self, key, value):
self.__setitem__(key, value)


def __setitem__(self, key, value):
super(Map, self).__setitem__(key, value)
self.__dict__.update({key: value})


def __delattr__(self, item):
self.__delitem__(item)


def __delitem__(self, key):
super(Map, self).__delitem__(key)
del self.__dict__[key]

使用例子:

m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
# Add new key
m.new_key = 'Hello world!'
# Or
m['new_key'] = 'Hello world!'
print m.new_key
print m['new_key']
# Update values
m.new_key = 'Yay!'
# Or
m['new_key'] = 'Yay!'
# Delete key
del m.new_key
# Or
del m['new_key']

此解决方案是对epool提供的解决方案的改进,以解决OP以一致的方式访问嵌套字典的需求。epool的解决方案不允许访问嵌套字典。

class YAMLobj(dict):
def __init__(self, args):
super(YAMLobj, self).__init__(args)
if isinstance(args, dict):
for k, v in args.iteritems():
if not isinstance(v, dict):
self[k] = v
else:
self.__setattr__(k, YAMLobj(v))




def __getattr__(self, attr):
return self.get(attr)


def __setattr__(self, key, value):
self.__setitem__(key, value)


def __setitem__(self, key, value):
super(YAMLobj, self).__setitem__(key, value)
self.__dict__.update({key: value})


def __delattr__(self, item):
self.__delitem__(item)


def __delitem__(self, key):
super(YAMLobj, self).__delitem__(key)
del self.__dict__[key]

有了这个类,现在可以这样做:A.B.C.D

def dict_to_object(dick):
# http://stackoverflow.com/a/1305663/968442


class Struct:
def __init__(self, **entries):
self.__dict__.update(entries)


return Struct(**dick)

如果一个人决定永久地将dict转换为对象,这应该做到。您可以在访问之前创建一个丢弃对象。

d = dict_to_object(d)

如果你想pickle你修改后的字典,你需要添加几个状态方法到上面的答案:

class DotDict(dict):
"""dot.notation access to dictionary attributes"""
def __getattr__(self, attr):
return self.get(attr)
__setattr__= dict.__setitem__
__delattr__= dict.__delitem__


def __getstate__(self):
return self


def __setstate__(self, state):
self.update(state)
self.__dict__ = self

织物有一个非常好的,最小的实现。扩展它以允许嵌套访问,我们可以使用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和派生并实现__ABC0和__setattr__”。的回答,现在你知道怎么做了!

不是对OP问题的直接回答,但受到启发,也许对一些人有用。我已经使用内部__dict__创建了一个基于对象的解决方案(在任何方式优化代码)

payload = {
"name": "John",
"location": {
"lat": 53.12312312,
"long": 43.21345112
},
"numbers": [
{
"role": "home",
"number": "070-12345678"
},
{
"role": "office",
"number": "070-12345679"
}
]
}




class Map(object):
"""
Dot style access to object members, access raw values
with an underscore e.g.


class Foo(Map):
def foo(self):
return self.get('foo') + 'bar'


obj = Foo(**{'foo': 'foo'})


obj.foo => 'foobar'
obj._foo => 'foo'


"""


def __init__(self, *args, **kwargs):
for arg in args:
if isinstance(arg, dict):
for k, v in arg.iteritems():
self.__dict__[k] = v
self.__dict__['_' + k] = v


if kwargs:
for k, v in kwargs.iteritems():
self.__dict__[k] = v
self.__dict__['_' + k] = v


def __getattribute__(self, attr):
if hasattr(self, 'get_' + attr):
return object.__getattribute__(self, 'get_' + attr)()
else:
return object.__getattribute__(self, attr)


def get(self, key):
try:
return self.__dict__.get('get_' + key)()
except (AttributeError, TypeError):
return self.__dict__.get(key)


def __repr__(self):
return u"<{name} object>".format(
name=self.__class__.__name__
)




class Number(Map):
def get_role(self):
return self.get('role')


def get_number(self):
return self.get('number')




class Location(Map):
def get_latitude(self):
return self.get('lat') + 1


def get_longitude(self):
return self.get('long') + 1




class Item(Map):
def get_name(self):
return self.get('name') + " Doe"


def get_location(self):
return Location(**self.get('location'))


def get_numbers(self):
return [Number(**n) for n in self.get('numbers')]




# Tests


obj = Item({'foo': 'bar'}, **payload)


assert type(obj) == Item
assert obj._name == "John"
assert obj.name == "John Doe"
assert type(obj.location) == Location
assert obj.location._lat == 53.12312312
assert obj.location._long == 43.21345112
assert obj.location.latitude == 54.12312312
assert obj.location.longitude == 44.21345112


for n in obj.numbers:
assert type(n) == Number
if n.role == 'home':
assert n.number == "070-12345678"
if n.role == 'office':
assert n.number == "070-12345679"

基于epool的答案,这个版本允许你通过点操作符访问任何字典:

foo = {
"bar" : {
"baz" : [ {"boo" : "hoo"} , {"baba" : "loo"} ]
}
}

例如,foo.bar.baz[1].baba返回"loo"

class Map(dict):
def __init__(self, *args, **kwargs):
super(Map, self).__init__(*args, **kwargs)
for arg in args:
if isinstance(arg, dict):
for k, v in arg.items():
if isinstance(v, dict):
v = Map(v)
if isinstance(v, list):
self.__convert(v)
self[k] = v


if kwargs:
for k, v in kwargs.items():
if isinstance(v, dict):
v = Map(v)
elif isinstance(v, list):
self.__convert(v)
self[k] = v


def __convert(self, v):
for elem in range(0, len(v)):
if isinstance(v[elem], dict):
v[elem] = Map(v[elem])
elif isinstance(v[elem], list):
self.__convert(v[elem])


def __getattr__(self, attr):
return self.get(attr)


def __setattr__(self, key, value):
self.__setitem__(key, value)


def __setitem__(self, key, value):
super(Map, self).__setitem__(key, value)
self.__dict__.update({key: value})


def __delattr__(self, item):
self.__delitem__(item)


def __delitem__(self, key):
super(Map, self).__delitem__(key)
del self.__dict__[key]

我喜欢蒙克,它在点访问之上提供了很多方便的选项。

进口蒙克

Temp_1 = {'person': {' fname': 'senthil', 'lname': 'ramalingam'}}

Dict_munch = munch.munchify(temp_1)

dict_munch.person.fname

获得点访问(但不是数组访问)的一个简单方法是在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 < / p >

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

一个很微妙的解

class DotDict(dict):


__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__


def __getattr__(self, key):


def typer(candidate):
if isinstance(candidate, dict):
return DotDict(candidate)


if isinstance(candidate, str):  # iterable but no need to iter
return candidate


try:  # other iterable are processed as list
return [typer(item) for item in candidate]
except TypeError:
return candidate


return candidate


return typer(dict.get(self, key))

这也适用于嵌套字典,并确保后面追加的字典行为相同:

class DotDict(dict):


def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Recursively turn nested dicts into DotDicts
for key, value in self.items():
if type(value) is dict:
self[key] = DotDict(value)


def __setitem__(self, key, item):
if type(item) is dict:
item = DotDict(item)
super().__setitem__(key, item)


__setattr__ = __setitem__
__getattr__ = dict.__getitem__

我最近遇到了'盒子'库,它做同样的事情。

安装命令:pip install python-box

例子:

from box import Box


mydict = {"key1":{"v1":0.375,
"v2":0.625},
"key2":0.125,
}
mydict = Box(mydict)


print(mydict.key1.v1)

我发现它比其他现有的库(如dotmap)更有效,当你有大量嵌套字典时,dotmap会产生python递归错误。

链接到库和详细信息:https://pypi.org/project/python-box/

@derek73的答案非常简洁,但它不能被pickle或(深度)复制,并且它返回None用于缺少键。下面的代码修复了这个问题。

编辑:我没有看到上面的答案解决了完全相同的问题(赞)。我把答案留在这里供参考。

class dotdict(dict):
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__


def __getattr__(self, name):
try:
return self[name]
except KeyError:
raise AttributeError(name)

使用SimpleNamespace:

>>> from types import SimpleNamespace
>>> d = dict(x=[1, 2], y=['a', 'b'])
>>> ns = SimpleNamespace(**d)
>>> ns.x
[1, 2]
>>> ns
namespace(x=[1, 2], y=['a', 'b'])

使用namedtuple允许点访问。

它就像一个轻量级对象,也具有元组的属性。

它允许定义属性并使用点运算符来访问它们。

from collections import namedtuple
Data = namedtuple('Data', ['key1', 'key2'])


dataObj = Data(val1, key2=val2) # can instantiate using keyword arguments and positional arguments

使用点运算符访问

dataObj.key1 # Gives val1
datObj.key2 # Gives val2

使用元组索引进行访问

dataObj[0] # Gives val1
dataObj[1] # Gives val2

但记住这是一个元组;不是字典。因此下面的代码将给出错误

dataObj['key1'] # Gives TypeError: tuple indices must be integers or slices, not str

参考:namedtuple

我只需要使用虚线路径字符串访问字典,所以我想到了:

def get_value_from_path(dictionary, parts):
""" extracts a value from a dictionary using a dotted path string """


if type(parts) is str:
parts = parts.split('.')


if len(parts) > 1:
return get_value_from_path(dictionary[parts[0]], parts[1:])


return dictionary[parts[0]]


a = {'a':{'b':'c'}}
print(get_value_from_path(a, 'a.b')) # c
这是一个老问题,但我最近发现sklearn有一个可通过键访问的实现版本dict,即Bunch https://scikit-learn.org/stable/modules/generated/sklearn.utils.Bunch.html#sklearn.utils.Bunch < / p >

这是我的@derek73回答版本。我使用dict.__getitem__作为__getattr__,所以它仍然抛出KeyError,并且我用"“;前缀(以"包围;"重命名字典公共方法;导致特殊方法名称冲突,如__get__将被视为描述符方法)。无论如何,由于关键的dict基方法,你不能为键作为属性获得完全清晰的命名空间,所以解决方案并不完美,但你可以拥有像getpopitems等键属性。

class DotDictMeta(type):
def __new__(
cls,
name,
bases,
attrs,
rename_method=lambda n: f'__{n}__',
**custom_methods,
):
d = dict
attrs.update(
cls.get_hidden_or_renamed_methods(rename_method),
__getattr__=d.__getitem__,
__setattr__=d.__setitem__,
__delattr__=d.__delitem__,
**custom_methods,
)
return super().__new__(cls, name, bases, attrs)
                                                                                  

def __init__(self, name, bases, attrs, **_):
super().__init__(name, bases, attrs)
                                                                                  

@property
def attribute_error(self):
raise AttributeError
                                                                                  

@classmethod
def get_hidden_or_renamed_methods(cls, rename_method=None):
public_methods = tuple(
i for i in dict.__dict__.items() if not i[0].startswith('__')
)
error = cls.attribute_error
hidden_methods = ((k, error) for k, v in public_methods)
yield from hidden_methods
if rename_method:
renamed_methods = ((rename_method(k), v) for k, v in public_methods)
yield from renamed_methods
                                                                                  

                                                                                  

class DotDict(dict, metaclass=DotDictMeta):
pass


                                                                    

                                                                              

你可以从DotDict命名空间中删除dict方法,并继续使用dict类方法,当你想操作其他dict实例并希望使用相同的方法而不需要额外检查它是否为DotDict时,它也很有用。

dct = dict(a=1)
dot_dct = DotDict(b=2)
foo = {c: i for i, c in enumerate('xyz')}
for d in (dct, dot_dct):
# you would have to use dct.update and dot_dct.__update methods
dict.update(d, foo)
    

assert dict.get(dot, 'foo', 0) is 0

这是我从很久以前的一个项目里挖出来的。它可能还可以再优化一点,但就是这样了。

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'

我的2分:出于我自己的目的,我开发了minydra,一个简单的命令行解析器,其中包括一个自定义类MinyDict(灵感来自addict):


In [1]: from minydra import MinyDict


In [2]: args = MinyDict({"foo": "bar", "yes.no.maybe": "idontknow"}).pretty_print(); args
╭──────────────────────────────╮
│ foo          : bar           │
│ yes.no.maybe : idontknow     │
╰──────────────────────────────╯
Out[2]: {'foo': 'bar', 'yes.no.maybe': 'idontknow'}


In [3]: args.resolve().pretty_print(); args
╭──────────────────────────╮
│ foo : bar                │
│ yes                      │
│ │no                      │
│ │ │maybe : idontknow     │
╰──────────────────────────╯
Out[3]: {'foo': 'bar', 'yes': {'no': {'maybe': 'idontknow'}}}


In [4]: args.yes.no.maybe
Out[4]: "idontknow"


In [5]: "foo" in args
Out[5]: True


In [6]: "rick" in args
Out[6]: False


In [7]: args.morty is None
Out[7]: True


In [8]: args.items()
Out[8]: dict_items([('foo', 'bar'), ('yes', {'no': {'maybe': 'idontknow'}})])

它比addict更进一步,通过向json yamlpickle添加转储/加载方法,并且在MinyDict.update()中也有strict模式,以防止创建新键(这对于防止命令行中的错别字非常有用)

您可以使用SimpleNamespace来实现这一点

from types import SimpleNamespace
# Assign values
args = SimpleNamespace()
args.username = 'admin'


# Retrive values
print(args.username)  # output: admin

用于字典、列表、字典的列表和列表的字典的无限层次的嵌套

它还支持酸洗

这是这个答案的扩展。

class DotDict(dict):
# https://stackoverflow.com/a/70665030/913098
"""
Example:
m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])


Iterable are assumed to have a constructor taking list as input.
"""


def __init__(self, *args, **kwargs):
super(DotDict, self).__init__(*args, **kwargs)


args_with_kwargs = []
for arg in args:
args_with_kwargs.append(arg)
args_with_kwargs.append(kwargs)
args = args_with_kwargs


for arg in args:
if isinstance(arg, dict):
for k, v in arg.items():
self[k] = v
if isinstance(v, dict):
self[k] = DotDict(v)
elif isinstance(v, str) or isinstance(v, bytes):
self[k] = v
elif isinstance(v, Iterable):
klass = type(v)
map_value: List[Any] = []
for e in v:
map_e = DotDict(e) if isinstance(e, dict) else e
map_value.append(map_e)
self[k] = klass(map_value)






def __getattr__(self, attr):
return self.get(attr)


def __setattr__(self, key, value):
self.__setitem__(key, value)


def __setitem__(self, key, value):
super(DotDict, self).__setitem__(key, value)
self.__dict__.update({key: value})


def __delattr__(self, item):
self.__delitem__(item)


def __delitem__(self, key):
super(DotDict, self).__delitem__(key)
del self.__dict__[key]


def __getstate__(self):
return self.__dict__


def __setstate__(self, d):
self.__dict__.update(d)




if __name__ == "__main__":
import pickle
def test_map():
d = {
"a": 1,
"b": {
"c": "d",
"e": 2,
"f": None
},
"g": [],
"h": [1, "i"],
"j": [1, "k", {}],
"l":
[
1,
"m",
{
"n": [3],
"o": "p",
"q": {
"r": "s",
"t": ["u", 5, {"v": "w"}, ],
"x": ("z", 1)
}
}
],
}
map_d = DotDict(d)
w = map_d.l[2].q.t[2].v
assert w == "w"


pickled = pickle.dumps(map_d)
unpickled = pickle.loads(pickled)
assert unpickled == map_d


kwargs_check = DotDict(a=1, b=[dict(c=2, d="3"), 5])
assert kwargs_check.b[0].d == "3"


kwargs_and_args_check = DotDict(d, a=1, b=[dict(c=2, d="3"), 5])
assert kwargs_and_args_check.l[2].q.t[2].v == "w"
assert kwargs_and_args_check.b[0].d == "3"






test_map()


可以使用dotsi来支持完整的列表、字典和递归,并使用一些扩展方法

pip install dotsi

而且

>>> import dotsi
>>>
>>> d = dotsi.Dict({"foo": {"bar": "baz"}})     # Basic
>>> d.foo.bar
'baz'
>>> d.users = [{"id": 0, "name": "Alice"}]   # List
>>> d.users[0].name
'Alice'
>>> d.users.append({"id": 1, "name": "Becca"}); # Append
>>> d.users[1].name
'Becca'
>>> d.users += [{"id": 2, "name": "Cathy"}];    # `+=`
>>> d.users[2].name
'Cathy'
>>> d.update({"tasks": [{"id": "a", "text": "Task A"}]});
>>> d.tasks[0].text
'Task A'
>>> d.tasks[0].tags = ["red", "white", "blue"];
>>> d.tasks[0].tags[2];
'blue'
>>> d.tasks[0].pop("tags")                      # `.pop()`
['red', 'white', 'blue']
>>>
>>> import pprint
>>> pprint.pprint(d)
{'foo': {'bar': 'baz'},
'tasks': [{'id': 'a', 'text': 'Task A'}],
'users': [{'id': 0, 'name': 'Alice'},
{'id': 1, 'name': 'Becca'},
{'id': 2, 'name': 'Cathy'}]}
>>>
>>> type(d.users)       # dotsi.Dict (AKA dotsi.DotsiDict)
<class 'dotsi.DotsiList'>
>>> type(d.users[0])    # dotsi.List (AKA dotsi.DotsiList)
<class 'dotsi.DotsiDict'>
>>>

最简单的解决方案。

定义一个只有pass语句的类。为该类创建对象并使用点表示法。

class my_dict:
pass


person = my_dict()
person.id = 1 # create using dot notation
person.phone = 9999
del person.phone # Remove a property using dot notation


name_data = my_dict()
name_data.first_name = 'Arnold'
name_data.last_name = 'Schwarzenegger'


person.name = name_data
person.name.first_name # dot notation access for nested properties - gives Arnold

我不喜欢在(超过)10年前的火灾中添加另一个日志,但我也会检查dotwiz库,它是我最近发布的——实际上就在今年。

它是一个相对较小的库,对于得到(访问)和(创建)乘以在基准,它的性能也非常好,至少与其他替代方案相比是这样的。

通过pip安装dotwiz

pip install dotwiz

它做所有你想让它做的事情,并继承dict的子类,所以它的操作就像一个普通的字典:

from dotwiz import DotWiz


dw = DotWiz()
dw.hello = 'world'
dw.hello
dw.hello += '!'
# dw.hello and dw['hello'] now both return 'world!'
dw.val = 5
dw.val2 = 'Sam'

最重要的是,你可以将它转换为dict对象:

d = dw.to_dict()
dw = DotWiz(d) # automatic conversion in constructor

这意味着如果你想访问的东西已经是dict形式,你可以将它转换为DotWiz以便访问:

import json
json_dict = json.loads(text)
data = DotWiz(json_dict)
print data.location.city

最后,我正在做的一些令人兴奋的事情是现有的功能要求,这样它就会自动创建新的子DotWiz实例,这样你就可以这样做:

dw = DotWiz()
dw['people.steve.age'] = 31


dw
# ✫(people=✫(steve=✫(age=31)))

dotmap的比较

我在下面添加了一个与dotmap的快速而粗略的性能比较。

首先,使用pip安装两个库:

pip install dotwiz dotmap

为了进行基准测试,我编写了以下代码:

from timeit import timeit


from dotwiz import DotWiz
from dotmap import DotMap




d = {'hey': {'so': [{'this': {'is': {'pretty': {'cool': True}}}}]}}


dw = DotWiz(d)
# ✫(hey=✫(so=[✫(this=✫(is=✫(pretty={'cool'})))]))


dm = DotMap(d)
# DotMap(hey=DotMap(so=[DotMap(this=DotMap(is=DotMap(pretty={'cool'})))]))


assert dw.hey.so[0].this['is'].pretty.cool == dm.hey.so[0].this['is'].pretty.cool


n = 100_000


print('dotwiz (create):  ', round(timeit('DotWiz(d)', number=n, globals=globals()), 3))
print('dotmap (create):  ', round(timeit('DotMap(d)', number=n, globals=globals()), 3))
print('dotwiz (get):  ', round(timeit("dw.hey.so[0].this['is'].pretty.cool", number=n, globals=globals()), 3))
print('dotmap (get):  ', round(timeit("dm.hey.so[0].this['is'].pretty.cool", number=n, globals=globals()), 3))

结果,在我的M1 Mac上运行Python 3.10:

dotwiz (create):   0.189
dotmap (create):   1.085
dotwiz (get):   0.014
dotmap (get):   0.335

kaggle_environments使用的实现是一个名为structify的函数。

class Struct(dict):
def __init__(self, **entries):
entries = {k: v for k, v in entries.items() if k != "items"}
dict.__init__(self, entries)
self.__dict__.update(entries)


def __setattr__(self, attr, value):
self.__dict__[attr] = value
self[attr] = value


# Added benefit of cloning lists and dicts.
def structify(o):
if isinstance(o, list):
return [structify(o[i]) for i in range(len(o))]
elif isinstance(o, dict):
return Struct(**{k: structify(v) for k, v in o.items()})
return o

这可能有助于测试游戏中的AI模拟代理,如ConnectX

from kaggle_environments import structify


obs  = structify({ 'remainingOverageTime': 60, 'step': 0, 'mark': 1, 'board': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]})
conf = structify({ 'timeout': 2, 'actTimeout': 2, 'agentTimeout': 60, 'episodeSteps': 1000, 'runTimeout': 1200, 'columns': 7, 'rows': 6, 'inarow': 4, '__raw_path__': '/kaggle_simulations/agent/main.py' })


def agent(obs, conf):
action = obs.step % conf.columns
return action

如果你已经在使用pandas,你可以构造一个pandas Series或DataFrame,从中你可以通过点语法访问项目:

1级字典:

import pandas as pd


my_dictionary = pd.Series({
'key1': 'value1',
'key2': 'value2'
})


print(my_dictionary.key1)
# Output: value1

2级字典:

import pandas as pd


my_dictionary = pd.DataFrame({
'key1': {
'inner_key1': 'value1'
},
'key2': {
'inner_key2': 'value2'
}
})


print(my_dictionary.key1.inner_key1)
# Output: value1

请注意,这可能在规范化数据结构(其中每个字典条目都具有相同的结构)下工作得更好。在上面的第二个例子中,得到的DataFrame是:

              key1    key2
inner_key1  value1     NaN
inner_key2     NaN  value2