【问题标题】:How to use a dot "." to access members of dictionary?如何使用点“。”访问字典的成员?
【发布时间】:2011-01-22 01:42:11
【问题描述】:

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

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

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

mydict.mydict2.val 

指的是

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

【问题讨论】:

  • 在人们使用嵌套字典的许多情况下,使用元组作为键的字典同样或更好地服务,其中d[a][b][c]d[a, b, c] 替换。
  • 这不是魔法:foo={}; foo[1,2,3] = "一,二,三!"; foo.keys() => [(1,2,3)]
  • 哇。又是哇。我不知道元组可能是 dict 的键。哇第三次了。
  • 任何“可散列”的对象都可以用作字典的键。大多数不可变对象也是可散列的,但前提是它们的所有内容都是可散列的。代码 d[1, 2, 3] 有效,因为“,”是“创建元组运算符”;它与 d[(1, 2, 3)] 相同。元组声明周围的括号通常是可选的。
  • 您是否考虑过键本身有一个点的情况 - {"my.key":"value"}?或者当键是关键字时,例如“from”?我考虑过几次,发现问题和故障排除比感知的好处更多。

标签: python dictionary syntax nested


【解决方案1】:

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

它还支持酸洗。

这是this answer的扩展。

class DotDict(dict):
    # https://stackoverflow.com/a/70665030/913098
    """
    Example:
    m = DotDict({'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)
        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)

        if kwargs:
            for k, v in kwargs.items():
                self[k] = v
                if isinstance(v, dict):
                    self[k] = DotDict(v)

    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]

if __name__ == "__main__":
    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"}]
                        }
                    }
                ],
        }
        map_d = DotDict(d)
        w = map_d.l[2].q.t[2].v
        assert w == "w"


    test_map()

【讨论】:

    【解决方案2】:

    您可以使用 SimpleNamespace 实现此目的

    from types import SimpleNamespace
    # Assign values
    args = SimpleNamespace()
    args.username = 'admin'
    
    # Retrive values
    print(args.username)  # output: admin
    

    【讨论】:

      【解决方案3】:

      我的 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'}})])
      

      它比addict 更进一步,通过向/从json yamlpickle 添加转储/加载方法,并且在MinyDict.update() 中还具有strict 模式以防止创建新密钥(此有助于防止命令行中的拼写错误)

      【讨论】:

        【解决方案4】:

        我只是从很久以前从事的一个项目中挖掘出来的。它可能会被优化一点,但它就是这样。

        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'
        

        【讨论】:

          【解决方案5】:

          这是我的@derek73 answer 版本。我使用dict.__getitem__ 作为__getattr__ 所以它仍然抛出KeyError,并且我用“”前缀重命名dict公共方法(用“”包围会导致特殊方法名称冲突,如__get__这将被视为描述符方法)。无论如何,由于关键的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  
          
                                                                              
                                                                                        
          

          您可以从 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
          

          【讨论】:

            【解决方案6】:

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

            【讨论】:

              【解决方案7】:

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

              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
              

              【讨论】:

                【解决方案8】:

                基于 epool 的回答,这个版本允许您通过点运算符访问内部的任何 dict:

                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]
                

                【讨论】:

                • Python 3:将iteritems() 替换为items(),将xrange() 替换为range()
                • 这是对epool 的解决方案和deepak 的更新的一个很好的更新。没有外部依赖并且运行良好。希望它冒出顶峰,为人们节省一些侦探。
                【解决方案9】:

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

                  我最近遇到了做同样事情的“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/

                  【讨论】:

                    【解决方案11】:

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

                    【讨论】:

                    • 这种方法效果更好。 (从文件中加载 json)
                    • 这是否考虑了嵌套字典?
                    • 我认为这是最好的答案。无需定义自己的类或任何东西。
                    • 很好,我有一个从 Argparse.parse_args() 获取输出的函数,它返回命名空间而不是字典。我正在寻找一种测试该功能的好方法。这是完美的。
                    【解决方案12】:

                    The answer of @derek73 非常简洁,但它不能被腌制或(深度)复制,它会返回 None 丢失键。下面的代码解决了这个问题。

                    编辑:我没有看到the answer above 解决了完全相同的问题(赞成)。我将答案留在这里以供参考。

                    class dotdict(dict):
                        __setattr__ = dict.__setitem__
                        __delattr__ = dict.__delitem__
                    
                        def __getattr__(self, name):
                            try:
                                return self[name]
                            except KeyError:
                                raise AttributeError(name)
                    

                    【讨论】:

                    • 或者你可以设置__getattr__ = dict.__getitem__
                    【解决方案13】:

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

                    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__
                    

                    【讨论】:

                      【解决方案14】:

                      一种微妙的解决方案

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

                      【讨论】:

                        【解决方案15】:

                        我一直把它保存在一个 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'
                        

                        【讨论】:

                        • 非常简单的答案,太好了!你碰巧知道我需要做什么才能在 IPython 工作中完成制表符吗?该类需要实现 __dir__(self),但不知何故我无法让它工作。
                        • +1 为简单起见。但似乎不适用于嵌套的字典。 d = {'foo': {'bar': 'baz'}}; d = dotdict(d); d.foo.bar 引发属性错误,但 d.foo 工作正常。
                        • 是的,这不适用于复杂的嵌套结构。
                        • @tmthyjames 您可以简单地在 getter 方法中返回 dotdict 类型对象,以递归访问带有点符号的属性,例如:python class DotDict(dict): """dot.notation access to dictionary attributes""" def __getattr__(*args): val = dict.get(*args) return DotDict(val) if type(val) is dict else val __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__
                        • 经过试验,似乎get 确实是个坏主意,因为它会返回None 而不是因为丢失项目而引发错误...
                        【解决方案16】:

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

                        【讨论】:

                          【解决方案17】:

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

                          有了这个类,现在可以执行如下操作:A.B.C.D

                          【讨论】:

                          • 对 python3 使用 .items()
                          【解决方案18】:

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

                          【讨论】:

                            【解决方案19】:

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

                            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
                            

                            【讨论】:

                            • 感谢您对酸洗的评论。我被这个错误逼疯了,才意识到是因为这个问题!
                            • 使用 copy.deepcopy 时也会发生。这个添加是必要的。
                            • 简化:__getattr__ = dict.get
                            【解决方案20】:

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

                            进口蒙克

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

                            dict_munch = munch.munchify(temp_1)

                            dict_munch.person.fname

                            【讨论】:

                              【解决方案21】:

                              我试过了:

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

                              你也可以试试__getattribute__

                              让每个 dict 成为一种 dotdict 就足够了,如果你想从多层 dict 中初始化它,也可以尝试实现 __init__

                              【讨论】:

                              • 我发现通过将 def docdict(name): 放在它前面然后 ` if isinstance(name, dict): return DotDict(name) return name ` 将它嵌入到函数中很有用
                              • 很棒的简单示例.. 我对此进行了一点扩展,以便嵌套 dict 很容易链接,类似于@DanielMoskovich,但也为 int、string 等正确返回叶节点......或 null如果没有找到class dotdict(dict): def __getattr__(self, name): if name not in self: return None elif type(self[name]) is dict: return JsonDot(self[name]) else: return self[name]
                              【解决方案22】:

                              Fabric 有一个非常好的、最小的implementation。扩展它以允许嵌套访问,我们可以使用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__”的回答。现在你知道怎么做了!

                              【讨论】:

                              • 很高兴包含一个 defaultdict - 但是这似乎只在从头开始一个 dict 时才有效。如果我们需要递归地将现有的字典转换为“dotdict”。这是一个替代的dotdict,它允许递归地转换现有的dict对象:gist.github.com/miku/…
                              • 我非常害怕使用这个类,因为它真的不符合 Liskov 替换原则。但这不是主要问题,可能会出现许多问题,主要是围绕意外的属性创建,其中一些可以通过更复杂的实现来解决,而另一些则不能。您的 repo 中的更新实现完全不同,但在为天真者布置雷区方面并非如此。
                              【解决方案23】:

                              不是对 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"
                              

                              【讨论】:

                                【解决方案24】:

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

                                【讨论】:

                                • 为了使用 Python 3,我将 .iteritems() 更新为 .items()
                                • 请注意,这将与常见的预期不同,因为如果属性不存在,它不会引发AttributeError。相反,它将返回None
                                • 推荐添加getstatesetstate,以便深拷贝等系统支持。
                                • 您可以将构造函数简化为self.update(*args,**kwargs)。另外,您可以添加__missing__(self,key): value=self[key]= type(self)(); return value。然后,您可以使用点表示法添加缺失的条目。如果您希望它可以选择,您可以添加__getstate____setstate__
                                • 这将使hasattr(Map, 'anystring') is true. which means the hasattr would always return True due to overriding __getattr__`
                                【解决方案25】:

                                我想把我自己的解决方案扔进戒指:

                                https://github.com/skorokithakis/jsane

                                它允许您将 JSON 解析为可以访问 with.attribute.lookups.like.this.r() 的内容,主要是因为我在开始研究它之前没有看到这个答案。

                                【讨论】:

                                • Python 犯了一些令人讨厌的简单设计错误,提高KeyError 就是其中之一,当访问不存在的密钥时,它所要做的就是返回None,类似于JS行为。我非常喜欢阅读和写作的自动生存。您的图书馆最接近理想。
                                【解决方案26】:
                                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 attr(**kwargs): o = lambda: None o.__dict__.update(**kwargs) return o
                                【解决方案27】:

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

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

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

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

                                与束比较

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

                                • 记住订单项已添加并按该顺序迭代
                                • 自动创建子DotMap,当你有很多层次结构时,这可以节省时间并让代码更简洁
                                • dict 构造并递归地将所有子dict 实例转换为DotMap

                                【讨论】:

                                • :-) 你可以让它与名字中已经有点的键一起工作吗? {"test.foo": "bar"} 可以通过mymap.test.foo 访问,那太棒了。将平面图转换为深度图然后应用 DotMap 需要一些回归,但这是值得的!
                                • 整洁。有什么方法可以使用 Jupyter 笔记本中的键使选项卡列表/完成工作?点式访问对于交互式使用最有价值。
                                • @Dmitri 酷产品。以前从未听说过它,所以我不确定如何使其自动完成工作。我同意使用带有自动完成功能的 DotMap 效果最好。我使用 Sublime Text,它会自动完成之前输入的关键字。
                                • 我发现它缺少像**kwargsc = {**a, **b} 这样的字典提取。事实上,它悄悄地失败了,它在提取时表现得像一个空字典。
                                • @SimonStreicher 我用m = DotMap(); m.a = 2; m.b = 3; print('{a} {b}'.format(**m)); 对此进行了测试,得到了预期的2 3。如果您有一个经过验证的损坏案例适用于 dict() 但不适用于 DotMap(),请将您的代码提交到 GitHub 中的问题选项卡。
                                【解决方案28】:

                                我最终同时尝试了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
                                

                                查看here 的原始实现https://stackoverflow.com/users/704327/michael-merickel

                                另外需要注意的是,这个实现非常简单,并没有实现您可能需要的所有方法。您需要根据需要在 DictProxy 或 ListProxy 对象上编写这些内容。

                                【讨论】:

                                  【解决方案29】:

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

                                  【讨论】:

                                    【解决方案30】:

                                    语言本身不支持这一点,但有时这仍然是一个有用的要求。除了 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'
                                    >>
                                    

                                    【讨论】:

                                      猜你喜欢
                                      • 1970-01-01
                                      • 2020-06-01
                                      • 2021-08-12
                                      • 2021-12-24
                                      • 2018-12-02
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 2013-02-02
                                      相关资源
                                      最近更新 更多