如何使用点“.”访问字典成员?

发布于 2024-08-24 01:12:14 字数 258 浏览 5 评论 0原文

如何使 Python 字典成员可以通过点“.”访问?

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

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

mydict.mydict2.val 

将参考

mydict = { 'mydict2': { 'val': ... } }

How do I make Python dictionary members accessible via a dot "."?

For example, instead of writing mydict['val'], I'd like to write mydict.val.

Also I'd like to access nested dicts this way. For example

mydict.mydict2.val 

would refer to

mydict = { 'mydict2': { 'val': ... } }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

画▽骨i 2024-08-31 01:12:14

我一直将其保存在 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'

I've always kept this around in a util file. You can use it as a mixin on your own classes too.

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'
(り薆情海 2024-08-31 01:12:14

你可以使用我刚刚制作的这个课程来做到这一点。通过此类,您可以像另一个字典(包括 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']

You can do it using this class I just made. With this class you can use the Map object like another dictionary(including json serialization) or with the dot notation. I hope to help you:

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]

Usage examples:

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']
谜泪 2024-08-31 01:12:14

通过 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< /code> 以便于访问:

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

最后,它会自动创建新的子 DotMap 实例,因此您可以执行以下操作:

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

与 Bunch 的比较

全面披露:我是 DotMap。我创建它是因为 Bunch 缺少这些功能,

  • 记住添加项目的顺序并按该顺序迭代
  • 自动子 DotMap 创建,这可以节省时间并在您有 大量层次结构,并将所有子 dict 实例递归地转换为 DotMap
  • dict 构建

Install dotmap via pip

pip install dotmap

It does everything you want it to do and subclasses dict, so it operates like a normal dictionary:

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'

On top of that, you can convert it to and from dict objects:

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

This means that if something you want to access is already in dict form, you can turn it into a DotMap for easy access:

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

Finally, it automatically creates new child DotMap instances so you can do things like this:

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

Comparison to Bunch

Full disclosure: I am the creator of the DotMap. I created it because Bunch was missing these features

  • remembering the order items are added and iterating in that order
  • automatic child DotMap creation, which saves time and makes for cleaner code when you have a lot of hierarchy
  • constructing from a dict and recursively converting all child dict instances to DotMap
抚笙 2024-08-31 01:12:14

使用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'])

Use 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'])
扬花落满肩 2024-08-31 01:12:14

从 dict 派生并实现 __getattr____setattr__

或者您可以使用 Bunch 非常相似。

我认为不可能对内置字典类进行猴子修补。

Derive from dict and and implement __getattr__ and __setattr__.

Or you can use Bunch which is very similar.

I don't think it's possible to monkeypatch built-in dict class.

少女的英雄梦 2024-08-31 01:12:14

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__”。 现在你知道怎么做了!

Fabric has a really nice, minimal implementation. Extending that to allow for nested access, we can use a defaultdict, and the result looks something like this:

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

Make use of it as follows:

keys = AttributeDict()
keys.abc.xyz.x = 123
keys.abc.xyz.a.b.c = 234

That elaborates a bit on Kugel's answer of "Derive from dict and and implement __getattr__ and __setattr__". Now you know how!

空城旧梦 2024-08-31 01:12:14

我尝试过:

class dotdict(dict):
    def __getattr__(self, name):
        return self[name]

您也可以尝试 __getattribute__

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

I tried this:

class dotdict(dict):
    def __getattr__(self, name):
        return self[name]

you can try __getattribute__ too.

make every dict a type of dotdict would be good enough, if you want to init this from a multi-layer dict, try implement __init__ too.

寄居者 2024-08-31 01:12:14

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

安装命令: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)更有效,当你有大型嵌套字典时,它会生成 python 递归错误。

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

I recently came across the 'Box' library which does the same thing.

Installation command : pip install python-box

Example:

from box import Box

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

print(mydict.key1.v1)

I found it to be more effective than other existing libraries like dotmap, which generate python recursion error when you have large nested dicts.

link to library and details: https://pypi.org/project/python-box/

时光倒影 2024-08-31 01:12:14

不。在 Python 中,属性访问和索引是不同的事情,您不应该希望它们执行相同的操作。如果您有一些应该具有可访问属性并使用 [] 表示法从字典中获取项目的内容,请创建一个类(可能是由 namedtuple 创建的类)。

Don't. Attribute access and indexing are separate things in Python, and you shouldn't want them to perform the same. Make a class (possibly one made by namedtuple) if you have something that should have accessible attributes and use [] notation to get an item from a dict.

蓝眸 2024-08-31 01:12:14

如果你想腌制修改后的字典,你需要在上面的答案中添加一些状态方法:

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

If you want to pickle your modified dictionary, you need to add few state methods to above answers:

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
江心雾 2024-08-31 01:12:14

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

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

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

You can achieve this using SimpleNamespace

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

# Retrive values
print(args.username)  # output: admin
ぽ尐不点ル 2024-08-31 01:12:14

为了构建 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]

To build upon epool's answer, this version allows you to access any dict inside via the dot operator:

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

For instance, foo.bar.baz[1].baba returns "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]
温柔戏命师 2024-08-31 01:12:14

根据 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

Building on Kugel's answer and taking Mike Graham's words of caution into consideration, what if we make a wrapper?

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
带刺的爱情 2024-08-31 01:12:14

使用__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

Use __getattr__, very simple, works in
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)

Output:

10000
StackOverflow
云裳 2024-08-31 01:12:14

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

导入咀嚼

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

dict_munch = munch.munchify(temp_1)

dict_munch.person.fname

I like the Munch and it gives lot of handy options on top of dot access.

import munch

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

dict_munch = munch.munchify(temp_1)

dict_munch.person.fname

情绪 2024-08-31 01:12:14

语言本身不支持这一点,但有时这仍然是一个有用的要求。除了 Bunch 配方之外,您还可以编写一个小方法,可以使用点分字符串访问字典:

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'
>>

The language itself doesn't support this, but sometimes this is still a useful requirement. Besides the Bunch recipe, you can also write a little method which can access a dictionary using a dotted string:

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

which would support something like this:

>> test_dict = {'thing': {'spam': 12, 'foo': {'cheeze': 'bar'}}}
>> output = get_var(test_dict, 'thing.spam.foo.cheeze')
>> print output
'bar'
>>
遗弃M 2024-08-31 01:12:14

我不喜欢在(超过)10 年的火灾中添加另一根木头,但我也会查看 < code>dotwiz 库,我最近发布了 - 实际上就在今年。

这是一个相对较小的库,在 get(访问)和 set(创建)次数方面也表现得非常好 在基准测试中,至少与其他替代方案相比。

通过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< /code> 以便于访问:

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

I dislike adding another log to a (more than) 10-year old fire, but I'd also check out the dotwiz library, which I've recently released - just this year actually.

It's a relatively tiny library, which also performs really well for get (access) and set (create) times in benchmarks, at least as compared to other alternatives.

Install dotwiz via pip

pip install dotwiz

It does everything you want it to do and subclasses dict, so it operates like a normal dictionary:

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'

On top of that, you can convert it to and from dict objects:

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

This means that if something you want to access is already in dict form, you can turn it into a DotWiz for easy access:

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

Finally, something exciting I am working on is an existing feature request so that it automatically creates new child DotWiz instances so you can do things like this:

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

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

Comparison with dotmap

I've added a quick and dirty performance comparison with dotmap below.

First, install both libraries with pip:

pip install dotwiz dotmap

I came up with the following code for benchmark purposes:

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))

Results, on my M1 Mac, running Python 3.10:

dotwiz (create):   0.189
dotmap (create):   1.085
dotwiz (get):   0.014
dotmap (get):   0.335
咆哮 2024-08-31 01:12:14

我最终尝试了 AttrDictBunch 库,发现它们对我的使用来说速度很慢。在我和一个朋友研究之后,我们发现编写这些库的主要方法会导致库积极地递归嵌套对象并始终复制字典对象。考虑到这一点,我们做出了两项关键改变。 1) 我们使属性延迟加载 2) 我们不创建字典对象的副本,而是创建轻量级代理对象的副本。这是最终的实现。使用这段代码的性能提升是令人难以置信的。当使用 AttrDict 或 Bunch 时,这两个库分别消耗了我请求时间的 1/2 和 1/3(什么!?)。这段代码将时间减少到几乎为零(在 0.5 毫秒的范围内)。这当然取决于您的需求,但是如果您在代码中大量使用此功能,那么一定要使用这样简单的东西。

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

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

See the original implementation here by https://stackoverflow.com/users/704327/michael-merickel.

The other thing to note, is that this implementation is pretty simple and doesn't implement all of the methods you might need. You'll need to write those as required on the DictProxy or ListProxy objects.

骄傲 2024-08-31 01:12:14

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

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__

This also works with nested dicts and makes sure that dicts which are appended later behave the same:

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__
回忆那么伤 2024-08-31 01:12:14

用于字典、列表、字典列表和列表字典的无限级别嵌套

它还支持酸洗

这是这个答案的扩展。

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()

For infinite levels of nesting of dicts, lists, lists of dicts, and dicts of lists.

It also supports pickling

This is an extension of this answer.

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()

你对谁都笑 2024-08-31 01:12:14
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)
def dict_to_object(dick):
    # http://stackoverflow.com/a/1305663/968442

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

    return Struct(**dick)

If one decides to permanently convert that dict to object this should do. You can create a throwaway object just before accessing.

d = dict_to_object(d)
我恋#小黄人 2024-08-31 01:12:14

该解决方案是对 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]

有了这个类,人们现在可以做类似的事情:ABCD

This solution is a refinement upon the one offered by epool to address the requirement of the OP to access nested dicts in a consistent manner. The solution by epool did not allow for accessing nested dicts.

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]

With this class, one can now do something like: A.B.C.D.

玻璃人 2024-08-31 01:12:14

使用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

Using namedtuple allows dot access.

It is like a lightweight object which also has the properties of a tuple.

It allows to define properties and access them using the dot operator.

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

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

Access using dot operator

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

Access using tuple indices

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

But remember this is a tuple; not a dict. So the below code will give error

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

Refer: namedtuple

讽刺将军 2024-08-31 01:12:14

这是一个老问题,但我最近发现 sklearn 有一个可通过按键访问的实现版本 dict,即 Bunch
https://scikit-learn。 org/stable/modules/ generated/sklearn.utils.Bunch.html#sklearn.utils.Bunch

It is an old question but I recently found that sklearn has an implemented version dict accessible by key, namely Bunch
https://scikit-learn.org/stable/modules/generated/sklearn.utils.Bunch.html#sklearn.utils.Bunch

一曲爱恨情仇 2024-08-31 01:12:14

最简单的解决方案。

定义一个类,其中仅包含 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

Simplest solution.

Define a class with only pass statement in it. Create object for this class and use dot notation.

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
飘落散花 2024-08-31 01:12:14

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

One simple way to get dot access (but not array access), is to use a plain object in Python. Like this:

class YourObject:
    def __init__(self, *args, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

...and use it like this:

>>> obj = YourObject(key="value")
>>> print(obj.key)
"value"

... to convert it to a dict:

>>> print(obj.__dict__)
{"key": "value"}
-残月青衣踏尘吟 2024-08-31 01:12:14

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

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

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

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

The answer of @derek73 is very neat, but it cannot be pickled nor (deep)copied, and it returns None for missing keys. The code below fixes this.

Edit: I did not see the answer above that addresses the exact same point (upvoted). I'm leaving the answer here for reference.

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

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)
勿忘初心 2024-08-31 01:12:14

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

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

I just needed to access a dictionary using a dotted path string, so I came up with:

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
飞烟轻若梦 2024-08-31 01:12:14

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

这对于在 连接X

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

The implemention used by kaggle_environments is a function called 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

This may be useful for testing AI simulation agents in games like 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
找回味觉 2024-08-31 01:12:14

最佳且完整的实施

from typing import Any

class DotDict(dict):
    """
    dot.notation access to dictionary attributes
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for key, value in self.items():
            if isinstance(value, dict):
                self[key] = DotDict(value)

    def __setitem__(self, key: Any, item: Any) -> None:
        if isinstance(item, dict):
            item = DotDict(item)
        super().__setitem__(key, item)

    def __getitem__(self, key: Any) -> Any:
        return super().__getitem__(key)

    def __setattr__(self, key: Any, item: Any) -> None:
        return self.__setitem__(key, item)

    def __getattr__(self, key: Any) -> Any:
        return self.__getitem__(key)

    def __missing__(self, key: Any) -> str:
        raise KeyError(f"DotDict object has no item '{key}'")

    def __getstate__(self) -> dict:
        return self

    def __setstate__(self, state: dict) -> None:
        self.update(state)
        self.__dict__ = self

    def __delattr__(self, item: Any) -> None:
        self.__delitem__(item)

    def __delitem__(self, key: Any) -> None:
        super().__delitem__(key)

测试:

# Create a DotDict instance
d = DotDict()

# Test __setattr__ method
d.foo = "bar"
assert d.foo == "bar"

# Test __getattr__ method
assert d.foo == "bar"

# Test __getitem__ method
assert d["foo"] == "bar"

# Test __setitem__ method
d["baz"] = "qux"
assert d.baz == "qux"

# Test nested dictionaries
d.nested = {"nested_key": "nested_value"}
assert d.nested.nested_key == "nested_value"

# Test nested update
d.nested.nested_key = "new_value"
assert d.nested.nested_key == "new_value"

# Test __delattr__ method
del d.foo
try:
    _ = d.foo
except KeyError as e:
    assert e.args[0] == "DotDict object has no item 'foo'"

# Test __delitem__ method
del d["baz"]
try:
    _ = d.baz
except KeyError as e:
    assert e.args[0] == "DotDict object has no item 'baz'"

# Test __getstate__ method
state = d.__getstate__()
assert state == {"nested": {"nested_key": "new_value"}}

# Test __setstate__ method
new_d = DotDict()
new_d.__setstate__(state)
assert new_d.nested.nested_key == "new_value"

Best and complete implementation

from typing import Any

class DotDict(dict):
    """
    dot.notation access to dictionary attributes
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for key, value in self.items():
            if isinstance(value, dict):
                self[key] = DotDict(value)

    def __setitem__(self, key: Any, item: Any) -> None:
        if isinstance(item, dict):
            item = DotDict(item)
        super().__setitem__(key, item)

    def __getitem__(self, key: Any) -> Any:
        return super().__getitem__(key)

    def __setattr__(self, key: Any, item: Any) -> None:
        return self.__setitem__(key, item)

    def __getattr__(self, key: Any) -> Any:
        return self.__getitem__(key)

    def __missing__(self, key: Any) -> str:
        raise KeyError(f"DotDict object has no item '{key}'")

    def __getstate__(self) -> dict:
        return self

    def __setstate__(self, state: dict) -> None:
        self.update(state)
        self.__dict__ = self

    def __delattr__(self, item: Any) -> None:
        self.__delitem__(item)

    def __delitem__(self, key: Any) -> None:
        super().__delitem__(key)

Test:

# Create a DotDict instance
d = DotDict()

# Test __setattr__ method
d.foo = "bar"
assert d.foo == "bar"

# Test __getattr__ method
assert d.foo == "bar"

# Test __getitem__ method
assert d["foo"] == "bar"

# Test __setitem__ method
d["baz"] = "qux"
assert d.baz == "qux"

# Test nested dictionaries
d.nested = {"nested_key": "nested_value"}
assert d.nested.nested_key == "nested_value"

# Test nested update
d.nested.nested_key = "new_value"
assert d.nested.nested_key == "new_value"

# Test __delattr__ method
del d.foo
try:
    _ = d.foo
except KeyError as e:
    assert e.args[0] == "DotDict object has no item 'foo'"

# Test __delitem__ method
del d["baz"]
try:
    _ = d.baz
except KeyError as e:
    assert e.args[0] == "DotDict object has no item 'baz'"

# Test __getstate__ method
state = d.__getstate__()
assert state == {"nested": {"nested_key": "new_value"}}

# Test __setstate__ method
new_d = DotDict()
new_d.__setstate__(state)
assert new_d.nested.nested_key == "new_value"
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文