简体   繁体   English

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

[英]How to use a dot "." to access members of dictionary?

How do I make Python dictionary members accessible via a dot "."?如何使 Python 字典成员可以通过点“.”访问?

For example, instead of writing mydict['val'] , I'd like to write mydict.val .例如,我不想写mydict['val'] ,而是写mydict.val

Also I'd like to access nested dicts this way.我也想以这种方式访问嵌套的字典。 For example例如

mydict.mydict2.val 

would refer to会指

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

I've always kept this around in a util file.我一直把它保存在一个 util 文件中。 You can use it as a mixin on your own classes too.你也可以在你自己的类中使用它作为一个 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'

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.使用此类,您可以像使用另一个字典(包括 json 序列化)或使用点符号一样使用Map对象。 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']

Install dotmap via pip通过dotmap安装pip

pip install dotmap

It does everything you want it to do and subclasses dict , so it operates like a normal dictionary:它做你想做的所有事情,并继承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'

On top of that, you can convert it to and from dict objects:最重要的是,您可以将其与dict对象相互转换:

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:这意味着如果您要访问的内容已经是dict形式,您可以将其转换为DotMap以便于访问:

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:最后,它会自动创建新的子DotMap实例,因此您可以执行以下操作:

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

Comparison to Bunch与束比较

Full disclosure: I am the creator of the DotMap .全面披露:我是DotMap的创建者。 I created it because Bunch was missing these features我创建它是因为Bunch缺少这些功能

  • 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自动创建子DotMap ,当您有很多层次结构时,这可以节省时间并让代码更简洁
  • constructing from a dict and recursively converting all child dict instances to DotMapdict构造并递归地将所有子dict实例转换为DotMap

Derive from dict and and implement __getattr__ and __setattr__ .从 dict 派生并实现__getattr____setattr__

Or you can use Bunch which is very similar.或者你可以使用非常相似的Bunch

I don't think it's possible to monkeypatch built-in dict class.我认为不可能对内置的 dict 类进行monkeypatch。

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

Fabric has a really nice, minimal implementation . Fabric有一个非常好的、最小的实现 Extending that to allow for nested access, we can use a defaultdict , and the result looks something like this:扩展它以允许嵌套访问,我们可以使用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

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__ ".这详细说明了 Kugel 对“从 dict 派生并实现__getattr____setattr__ ”的回答。 Now you know how!现在你知道怎么做了!

I tried this:我试过这个:

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

you can try __getattribute__ too.你也可以试试__getattribute__

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.使每个 dict 成为一种 dotdict 就足够了,如果您想从多层 dict 初始化它,请尝试实现__init__

I recently came across the ' Box ' library which does the same thing.我最近遇到了做同样事情的' Box '库。

Installation command : pip install python-box安装命令: 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.我发现它比其他现有库(如 dotmap)更有效,当你有大型嵌套字典时,它会生成 python 递归错误。

link to library and details: https://pypi.org/project/python-box/链接到库和详细信息: https ://pypi.org/project/python-box/

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

Don't.不。 Attribute access and indexing are separate things in Python, and you shouldn't want them to perform the same.属性访问和索引在 Python 中是不同的事情,您不应该希望它们执行相同的操作。 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.如果您有一些应该具有可访问属性并使用[]表示法从字典中获取项目的东西,请创建一个类(可能由namedtuple创建一个)。

Building on Kugel's answer and taking Mike Graham's words of caution into consideration, what if we make a wrapper?基于 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

To build upon epool's answer, this version allows you to access any dict inside via the dot operator:为了建立 epool 的答案,此版本允许您通过点运算符访问内部的任何 dict:

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

For instance, foo.bar.baz[1].baba returns "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]

You can achieve this using SimpleNamespace您可以使用 SimpleNamespace 实现此目的

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

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

Use __getattr__ , very simple, works in Python 3.4.3使用__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)

Output:输出:

10000
StackOverflow

I like the Munch and it gives lot of handy options on top of dot access.我喜欢Munch ,它在点访问之上提供了许多方便的选项。

import munch进口蒙克

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

dict_munch = munch.munchify(temp_1) dict_munch = munch.munchify(temp_1)

dict_munch.person.fname dict_munch.person.fname

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:除了 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

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

I ended up trying BOTH the AttrDict and the Bunch libraries and found them to be way to slow for my uses.我最终尝试了AttrDictBunch库,发现它们会降低我的使用速度。 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. 1)我们使属性延迟加载 2)我们不是创建字典对象的副本,而是创建轻量级代理对象的副本。 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!?).当使用 AttrDict 或 Bunch 时,这两个库分别消耗了我请求时间的 1/2 和 1/3(什么!?)。 This code reduced that time to almost nothing(somewhere in the range of 0.5ms).这段代码将该时间减少到几乎没有(在 0.5 毫秒的范围内)。 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 .请参阅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.您需要根据需要在 DictProxy 或 ListProxy 对象上编写这些内容。

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.该解决方案是对epool提供的解决方案的改进,以解决 OP 以一致方式访问嵌套字典的要求。 The solution by epool did not allow for accessing nested dicts. 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]

With this class, one can now do something like: ABCD .有了这个类,现在可以做类似的事情: ABCD

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.如果一个人决定将该dict永久转换为对象,则应该这样做。 You can create a throwaway object just before accessing.您可以在访问之前创建一次性对象。

d = dict_to_object(d)

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

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__

Using namedtuple allows dot access.使用namedtuple允许点访问。

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 .不是 dict So the below code will give error所以下面的代码会报错

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

Refer: namedtuple参考: namedtuple

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

Simplest solution.最简单的解决方案。

Define a class with only pass statement in it.定义一个只有 pass 语句的类。 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

One simple way to get dot access (but not array access), is to use a plain object in Python.获得点访问(但不是数组访问)的一种简单方法是在 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"}

The answer of @derek73 is very neat, but it cannot be pickled nor (deep)copied, and it returns None for missing keys. @derek73 的答案非常简洁,但它不能被腌制或(深度)复制,并且它返回None丢失键。 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)

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

I'd like to throw my own solution into the ring:我想把我自己的解决方案扔进戒指:

https://github.com/skorokithakis/jsane https://github.com/skorokithakis/jsane

It allows you to parse JSON into something you can access with.attribute.lookups.like.this.r() , mostly because I hadn't seen this answer before starting to work on it.它允许您将 JSON 解析为您可以使用with.attribute.lookups.like.this.r()访问的with.attribute.lookups.like.this.r() ,主要是因为我在开始研究之前没有看到这个答案。

Not a direct answer to the OP's question, but inspired by and perhaps useful for some.. I've created an object-based solution using the internal __dict__ (In no way optimized code)不是对 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"

Here's my version of @derek73 answer .这是我的@derek73 answer版本。 I use dict.__getitem__ as __getattr__ so it still throws KeyError , and im renaming dict public methods with " " prefix (surrounded with " " leads to special methods name conflict, like __get__ which would be treated as a descriptor method).我使用dict.__getitem__作为__getattr__所以它仍然抛出KeyError ,并且我用 " " 前缀重命名 dict 公共方法(用 " " 包围会导致特殊方法名称冲突,如__get__将被视为描述符方法)。 Anyway you can't get completely clear namespace for keys as attributes due to crucial dict base methods, so the solution isn't perfect but you can have keys - attributes like get , pop , items etc.无论如何,由于关键的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  

                                                                    
                                                                              

You can remove dict methods from DotDict namespace and keep using dict class methods, its useful also when you want to operate on other dict instances and want to use the same methods without extra check whether its DotDict or not, eg.您可以从 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

I just dug this up from a project I was working on a long time ago.我只是从很久以前从事的一个项目中挖掘出来的。 It could probably be optimized a bit, but here it goes.它可能会被优化一点,但它就在这里。

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'

My 2 cents: for my own purposes I developed minydra , a simple command-line parser which includes a custom class MinyDict (inspired by addict ):我的 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'}})])

It goes further than addict by adding dumping/loading methods to/from json yaml and pickle and also has a strict mode in MinyDict.update() to prevent the creation of new keys (this is useful to prevent typos in the command-line)它通过向/从json yamlpickle添加转储/加载方法比addict更进一步,并且在MinyDict.update()中还有一个strict模式以防止创建新密钥(这对于防止命令行中的拼写错误很有用)

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.我不喜欢将另一个日志添加到(超过)10 年的火灾中,但我也会查看我最近发布的dotwiz库 - 实际上就在今年。

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安装dotwiz

pip install dotwiz

It does everything you want it to do and subclasses dict , so it operates like a normal dictionary:它做你想做的所有事情,并继承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'

On top of that, you can convert it to and from dict objects:最重要的是,您可以将其与dict对象相互转换:

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:这意味着如果您要访问的内容已经是dict形式,您可以将其转换为DotWiz以便于访问:

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:最后,我正在处理的令人兴奋的事情是现有功能请求,以便它自动创建新的子DotWiz实例,以便您可以执行以下操作:

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

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

Comparison with dotmapdotmap

I've added a quick and dirty performance comparison with dotmap below.我在下面添加了与dotmap的快速而肮脏的性能比较。

First, install both libraries with pip :首先,使用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:结果,在我的 M1 Mac 上,运行 Python 3.10:

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

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

This may be useful for testing AI simulation agents in games like ConnectX这对于在ConnectX等游戏中测试 AI 模拟代理可能很有用

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

If you're already using pandas, you can construct a pandas Series or DataFrame from which you would be able to access items via the dot syntax:如果您已经在使用 pandas,您可以构建一个 pandas 系列或 DataFrame 可以通过点语法访问项目:

1-level dictionary: 1级字典:

import pandas as pd

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

print(my_dictionary.key1)
# Output: value1

2-level dictionary: 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

Be aware that this probably works better with a normalised data structure (where each dictionary entry has the same structure).请注意,这可能更适用于规范化的数据结构(其中每个字典条目具有相同的结构)。 In the second example above, the resulting DataFrame is:在上面的第二个示例中,生成的 DataFrame 是:

              key1    key2
inner_key1  value1     NaN
inner_key2     NaN  value2

One could use dotsi , for full list, dict and recursive support, with some extension methods可以使用dotsi来获得完整列表、字典和递归支持,以及一些扩展方法

pip install dotsi

and

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

A solution kind of delicate一种微妙的解决方案

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM