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

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

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

mydict.mydict2.val 

会提到

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

当前回答

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

其他回答

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

进口蒙克 Temp_1 = {'person': {' fname': 'senthil', 'lname': 'ramalingam'}} Dict_munch = munch.munchify(temp_1) dict_munch.person.fname

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

基于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

这是我对@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

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

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__