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

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

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

mydict.mydict2.val 

会提到

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

当前回答

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

其他回答

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)

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

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

派生自dict和并实现__getattr__和__setattr__。

或者你也可以用Bunch,非常相似。

我不认为这是可能的monkeypatch内置字典类。

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

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