【问题标题】:Object-like attribute access for nested dictionary嵌套字典的类对象属性访问
【发布时间】:2016-10-28 07:28:51
【问题描述】:

我正在使用一个返回嵌套字典的包。 当其他一切都在对象语法中时,使用字典语法在我的类方法中访问这个返回对象感觉很尴尬。 搜索将我带到了 bundle / neobunch 包,这似乎实现了我所追求的。我也看到过命名元组的建议,但这些并不容易支持嵌套属性,并且大多数解决方案都依赖于在命名元组中使用字典进行嵌套。

实现这一点的更自然的方法是什么?

data = {'a': 'aval', 'b': {'b1':{'b2a':{'b3a':'b3aval','b3b':'b3bval'},'b2b':'b2bval'}} }

print(data['b']['b1']['b2a']['b3b'])  # dictionary access
# print(data.b.b1.b2a.b3b)  # desired access

import neobunch
data1 = neobunch.bunchify(data)
print(data1.b.b1.b2a.b3b)

【问题讨论】:

    标签: python dictionary nested bunch


    【解决方案1】:

    下面的类可以让你做你想做的事(适用于 Python 2 和 3):

    class AttrDict(dict):
        """ Dictionary subclass whose entries can be accessed by attributes (as well
            as normally).
    
        >>> obj = AttrDict()
        >>> obj['test'] = 'hi'
        >>> print obj.test
        hi
        >>> del obj.test
        >>> obj.test = 'bye'
        >>> print obj['test']
        bye
        >>> print len(obj)
        1
        >>> obj.clear()
        >>> print len(obj)
        0
        """
        def __init__(self, *args, **kwargs):
            super(AttrDict, self).__init__(*args, **kwargs)
            self.__dict__ = self
    
        @classmethod
        def from_nested_dicts(cls, data):
            """ Construct nested AttrDicts from nested dictionaries. """
            if not isinstance(data, dict):
                return data
            else:
                return cls({key: cls.from_nested_dicts(data[key]) for key in data})
    
    
    if __name__ == '__main__':
    
        data = {
            "a": "aval",
            "b": {
                "b1": {
                    "b2b": "b2bval",
                    "b2a": {
                        "b3a": "b3aval",
                        "b3b": "b3bval"
                    }
                }
            }
        }
    
        data1 = AttrDict.from_nested_dicts(data)
        print(data1.b.b1.b2a.b3b)  # -> b3bval
    
    

    【讨论】:

    • 您能否详细说明代码为何/如何工作?谢谢!
    • @BartKleijngeld:你不明白哪些部分?
    • 我不明白字典键如何以某种方式成为data1 对象的属性。我觉得我错过了一些非常简单的东西,但如果你能向我解释那部分,我将不胜感激:)。
    • @BartKleijngeld:当然……这是一只相当奇怪的野兽。 AttrDict 是一个字典子类,它使用自身 作为类'__dict__。后者是通常存储实例属性的地方,通常使用.(点)表示法引用,例如obj.attr,但由于它是一个字典,您仍然可以使用常规[] 表示法来访问它们,例如@987654329 @。这是从Javascript借来的想法。 ActiveState 上有一个不同的配方,我在其他几个地方也看到过。
    【解决方案2】:

    @martineau's 出色答案的基础上,您可以使 AttrDict 类在嵌套字典上工作,而无需显式调用 from_nested_dict() 函数:

    class AttrDict(dict):
    """ Dictionary subclass whose entries can be accessed by attributes
        (as well as normally).
    """
    def __init__(self, *args, **kwargs):
        def from_nested_dict(data):
            """ Construct nested AttrDicts from nested dictionaries. """
            if not isinstance(data, dict):
                return data
            else:
                return AttrDict({key: from_nested_dict(data[key])
                                    for key in data})
    
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self
    
        for key in self.keys():
            self[key] = from_nested_dict(self[key])
    

    【讨论】:

    • 一个绝妙的补充!
    • @saravana-kumar 这太棒了。如果密钥不存在,需要进行哪些修改才能返回 None?覆盖 getitem 方法仅适用于 d[key] 表示法,而不适用于 d.key。谢谢
    • @saravana-kumar 看起来像简单地覆盖 getattr 以返回 None 效果很好,因为只有在“通常的地方”中找不到属性时才会调用该方法
    • @saravana-kumar 不幸的是,仅此一项不适用于嵌套字典。首先调用__getattribute__,如果未找到属性,则调用__getattr__。但是对于嵌套的字典,有一个__getattr__ 调用链。因此,从 __getattr__ 返回 None 会尝试在 NoneType 对象上返回 __getattr__。不确定您是否知道一个好的解决方案。
    【解决方案3】:

    试试DotsiEasyDict。它们都支持嵌套字典的点表示法。

    >>> import dotsi
    >>> data = dotsi.fy({'a': 'aval', 'b': {'b1':{'b2a':{'b3a':'b3aval','b3b':'b3bval'},'b2b':'b2bval'}} })
    >>> print(data.b.b1.b2a.b3b)
    b3bval
    >>> 
    

    除了dicts-within-dicts,Dotsi还支持dicts-within-lists-within-dicts。
    注意:我是 Dotsi 的作者。

    【讨论】:

      【解决方案4】:

      json.loads 有一个有趣的参数,称为 object_hook,如果所有字典值都是 JSON 可序列化的,即可以使用该参数,

      import json
      from types import SimpleNamespace
      
      data = {'a': 'aval', 'b': {'b1':{'b2a':{'b3a':'b3aval','b3b':'b3bval'},'b2b':'b2bval'}}}
      data1= json.loads(
          json.dumps(data), object_hook=lambda d: SimpleNamespace(**d)
      )
      print(data1.b.b1.b2a.b3b)  # -> b3bval
      

      如果 Guido 正在监听,我认为 SimpleNamespace 应该采用 recursive 参数,这样您就可以使用 data1 = SimpleNamespace(recursive=True, **data)

      【讨论】:

      • 这可能有点低效,但到目前为止它是我能找到的最简单的解决方案,不依赖于第三方包。
      【解决方案5】:

      可以使用建立在基本对象上的简单类:

      class afoo1(object):
          def __init__(self, kwargs):
              for name in kwargs:
                  val = kwargs[name]
                  if isinstance(val, dict):
                      val = afoo1(val)
                  setattr(self,name,val)
      

      我借用了 argparse.Namespace 定义,经过调整以允许嵌套。

      它会被用作

      In [172]: dd={'a':'aval','b':{'b1':'bval'}}
      
      In [173]: f=afoo1(dd)
      
      In [174]: f
      Out[174]: <__main__.afoo1 at 0xb3808ccc>
      
      In [175]: f.a
      Out[175]: 'aval'
      
      In [176]: f.b
      Out[176]: <__main__.afoo1 at 0xb380802c>
      
      In [177]: f.b.b1
      Out[177]: 'bval'
      

      它也可以用**kwargs(连同*args)定义。 __repr__ 定义也可能很好。

      与其他简单对象一样,可以添加属性,例如f.c = f(递归定义)。 vars(f) 返回一个字典,虽然它不做任何递归转换)。

      【讨论】:

      • 虽然从功能的角度来看你的类没有什么问题,但它可以有更好的风格。我不会将kwargs 用于常规字典参数,因为该参数名称通常用于关键字参数,在这种情况下根本不适用。我还会使用 for name, value in kwargs.items()(或 Python 2 中的 .iteritems()),而不是遍历键并在下一行查找值。
      • 是的,尽管错误是由于对 Namespace 原始文件的不完整编辑造成的。
      【解决方案6】:

      使用__setattr__方法怎么样?

      >>> class AttrDict(dict):
      ...     def __getattr__(self, name):
      ...         if name in self:
      ...             return self[name]
      ... 
      ...     def __setattr__(self, name, value):
      ...         self[name] = self.from_nested_dict(value)
      ... 
      ...     def __delattr__(self, name):
      ...         if name in self:
      ...             del self[name]
      ... 
      ...     @staticmethod
      ...     def from_nested_dict(data):
      ...         """ Construct nested AttrDicts from nested dictionaries. """
      ...         if not isinstance(data, dict):
      ...             return data
      ...         else:
      ...             return AttrDict({key: AttrDict.from_nested_dict(data[key])
      ...                                 for key in data})
      ...         
      
      >>> ad = AttrDict()
      >>> ad
      {}
      
      >>> data = {'a': 'aval', 'b': {'b1':{'b2a':{'b3a':'b3aval','b3b':'b3bval'},'b2b':'b2bval'}} }
      
      >>> ad.data = data
      >>> ad.data
      {'a': 'aval', 'b': {'b1': {'b2a': {'b3a': 'b3aval', 'b3b': 'b3bval'}, 'b2b': 'b2bval'}}}
      
      >>> print(ad.data.b.b1.b2a.b3b)
          b3bval
      

      【讨论】:

        猜你喜欢
        • 2012-11-12
        • 1970-01-01
        • 2020-06-18
        • 2018-07-14
        • 1970-01-01
        • 2016-07-13
        • 1970-01-01
        • 1970-01-01
        • 2019-03-12
        相关资源
        最近更新 更多