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

时间:2010-02-28 18:51:42

标签: python dictionary syntax nested

如何通过点“。”来访问Python词典成员?

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

此外,我想以这种方式访问​​嵌套的dicts。例如

mydict.mydict2.val 

会参考

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

27 个答案:

答案 0 :(得分:180)

我总是将它保存在一个util文件中。您也可以在自己的课程中将其用作混音。

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'

答案 1 :(得分:112)

你可以使用我刚刚制作的这个课程。使用此类,您可以使用Map对象,如另一个字典(包括json序列化)或点符号。我希望能帮到你:

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

答案 2 :(得分:91)

通过dotmap

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

答案 3 :(得分:56)

从dict派生并实施__getattr____setattr__

或者您可以使用非常相似的Bunch

我认为monkeypatch内置的dict类是不可能的。

答案 4 :(得分:18)

我试过了:

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

您也可以尝试__getattribute__

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

答案 5 :(得分:14)

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__”的回答。现在你知道了!

答案 6 :(得分:8)

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

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

答案 7 :(得分:7)

别。属性访问和索引是Python中的独立事物,您不应该希望它们执行相同的操作。如果你有一些应该具有可访问属性的东西并使用namedtuple表示法从dict中获取一个项目,那么创建一个类(可能由[]创建)。

答案 8 :(得分:6)

在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

答案 9 :(得分:5)

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

  

import munch

     

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

     

dict_munch = munch.munchify(temp_1)

     

dict_munch.person.fname

答案 10 :(得分:4)

语言本身不支持这一点,但有时这仍然是一个有用的要求。除了束配方,您还可以编写一个可以使用虚线字符串访问字典的方法:

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

答案 11 :(得分:3)

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)

答案 12 :(得分:3)

基于epool的答案,这个版本允许你通过点运算符访问里面的任何字典:

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.iteritems():
                    if isinstance(v, dict):
                        v = Map(v)
                    if isinstance(v, list):
                        self.__convert(v)
                    self[k] = v

        if kwargs:
            for k, v in kwargs.iteritems():
                if isinstance(v, dict):
                    v = Map(v)
                elif isinstance(v, list):
                    self.__convert(v)
                self[k] = v

    def __convert(self, v):
        for elem in xrange(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]

答案 13 :(得分: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)

输出:

10000
StackOverflow

答案 14 :(得分:2)

我最近遇到了“ 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递归错误。

答案 15 :(得分:1)

我最终尝试了AttrDictBunch库,发现它们对我的用途来说是慢的。在我和朋友调查之后,我们发现编写这些库的主要方法导致库通过嵌套对象进行主动递归,并在整个过程中复制字典对象。考虑到这一点,我们做了两个关键的改变。 1)我们使属性延迟加载2)而不是创建字典对象的副本,我们创建轻量级代理对象的副本。这是最终的实施。使用此代码的性能提升令人难以置信。当使用AttrDict或Bunch时,这两个库分别占用了我的请求时间的1/2和1/3(什么!?)。这段代码将时间缩短到几乎没有(在0.5ms的范围内)。这当然取决于您的需求,但如果您在代码中使用此功能,请务必使用这样的简单方法。

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对象上根据需要编写它们。

答案 16 :(得分:1)

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

答案 17 :(得分:0)

这是我的 @derek73 answer 版本。我使用 dict.__getitem__ 作为 __getattr__ 所以它仍然抛出 KeyError,并且我用“”前缀重命名dict公共方法(用“”包围导致特殊方法名称冲突,如 __get__ 将被视为描述符方法)。无论如何,由于关键的 dict 基本方法,您无法获得完全清晰的键作为属性的命名空间,因此解决方案并不完美,但您可以拥有键 - 像 getpop 这样的属性、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

答案 18 :(得分:0)

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

答案 19 :(得分:0)

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

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

答案 20 :(得分:0)

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

答案 21 :(得分:0)

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)

答案 22 :(得分: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__

答案 23 :(得分:0)

一种精致的解决方案

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

答案 24 :(得分:0)

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

答案 25 :(得分:0)

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

答案 26 :(得分:0)

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

https://github.com/skorokithakis/jsane

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