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

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

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

mydict.mydict2.val 

会提到

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

当前回答

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

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

    return Struct(**dick)

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

d = dict_to_object(d)

其他回答

我最近遇到了“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)更有效,当你有大量嵌套字典时,dotmap会产生python递归错误。

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

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

https://github.com/Kaggle/kaggle-environments/blob/master/kaggle_environments/utils.py

这可能有助于在《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

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

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

它是一个相对较小的库,在基准测试中,它在get(访问)和设置(创建)时间方面也表现得非常好,至少与其他备选方案相比是这样。

通过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形式的,你可以把它变成一个dotwz来方便访问:

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

与点图比较

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

首先,用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

这是我对@derek73的回答。我用字典。__getitem__作为__getattr__,因此它仍然抛出KeyError,并且im重命名字典公共方法以“”前缀(“”包围导致特殊方法名称冲突,如__get__将被视为一个描述符方法)。无论如何,由于关键的dict基方法,您无法将键作为属性获得完全清晰的命名空间,因此解决方案并不完美,但您可以拥有键属性,如get, pop, items等。

class DotDictMeta(type):                                                          
    def __new__(                                                                  
        cls,                                                                      
        name,                                                                     
        bases,                                                                    
        attrs,                                         
        rename_method=lambda n: f'__{n}__',                            
        **custom_methods,                                                         
    ):                                                                            
        d = dict                                                                  
        attrs.update(                                                             
            cls.get_hidden_or_renamed_methods(rename_method),           
            __getattr__=d.__getitem__,                                            
            __setattr__=d.__setitem__,                                            
            __delattr__=d.__delitem__,                                            
            **custom_methods,                                                     
        )                                                                         
        return super().__new__(cls, name, bases, attrs)                           
                                                                                  
    def __init__(self, name, bases, attrs, **_):                                  
        super().__init__(name, bases, attrs)                                      
                                                                                  
    @property                                                                     
    def attribute_error(self):                                                    
        raise AttributeError                                                      
                                                                                  
    @classmethod                                                                  
    def get_hidden_or_renamed_methods(cls, rename_method=None):                  
        public_methods = tuple(                                                   
            i for i in dict.__dict__.items() if not i[0].startswith('__')         
        )                                                                         
        error = cls.attribute_error                                               
        hidden_methods = ((k, error) for k, v in public_methods)                  
        yield from hidden_methods                                                 
        if rename_method:                                                       
            renamed_methods = ((rename_method(k), v) for k, v in public_methods) 
            yield from renamed_methods                                             
                                                                                  
                                                                                  
class DotDict(dict, metaclass=DotDictMeta):                                       
    pass  

                                                                    
                                                                              

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

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