Python 重写字典访问方法

3 投票
3 回答
3466 浏览
提问于 2025-04-18 02:28

我有一个叫 devices 的类,它是从字典类(dict)继承来的。这个 devices 就是一个字典里面又包含字典的结构。我想通过方法调用的方式来访问它的键和值,同时也希望能用普通的方式来访问。也就是说,像这样:

class Devices(dict):
    def __init__(self):
        self['device'] = {'name':'device_name'}
    def __getattr__(self, name):
        value = self[name]
        return value
...

mydevices = Devices()
mydevice = mydevices.device #works and mydevices['device'] also works

#But what code do I need for the following?
name = mydevices.device.name 
mydevices.device.name = 'another_name'

我知道如果我重写 __getattr____setattr__ 这两个方法,我就可以做到这一点。但是从我的代码来看,我不太确定怎么去访问嵌套的字典。有没有人知道怎么实现这个?

谢谢,labjunky

3 个回答

1

一般来说,要访问字典中的值,应该使用 dictionary[key] 而不是 dictionary.key。在第一种情况下,使用属性的方式之所以有效,是因为你重写了 __getattr__,而不是 __getitem__。这就是为什么它只对你的类有效,而对内部字典无效。

现在,要在 __getitem__ 中访问字典(__getattr__ 是用来访问属性的,比如 dict.keys,其中 keys 是属性)时,你需要小心一点。你需要调用字典类的 __getitem__ 方法(否则会导致无限递归)。有两种方法可以做到这一点。第一种方法在你选择改变对象的继承时会比较持久:

def __getitem__(self, name):
    value = super(Devices,self).__getitem__(name)
    return value

另一种方法是:

def __getitem__(self, name):
    value = dict.__getitem__(self, name)
    return value

如果你仍然想用属性来访问值,那么每次使用字典时,你都需要用你的类来替换字典(所以 self['device'] = {'name':'device_name'} 将变成 self['device'] = MyDictClass({'name':'device_name'}))。


顺便提一下:注意你在 __init__ 中有个错误。self = {'device':{'name':'device_name'}} 这一行实际上没有任何作用。(它把字典放在了局部变量 self 中,但这个变量在你退出函数后就消失了。)

你应该使用能改变对象本身的操作。也就是说:

def __init__(self):
    self['device'] = {'name':'device_name'}
1

你可以使用这个有点特别的类,它是 dict 的一个子类,你可以通过两种方式来访问它的键:一种是像平常那样用 d[k],另一种是用属性的方式 d.k

class Struct(dict):
     def __init__(self,*args, **kwargs):
        dict.__init__(self,*args, **kwargs)
        self.__dict__ = self  # yes, it is tricky

这里的关键是,解释器在调用 __[gs]etattr__ 之前会先检查 __dict__,所以你甚至不需要去重写这些方法。

现在你可以这样做:

class Devices(Struct):
    pass

devices = Devices()
devices.device = Struct(name='devicename')
devices.device.name = 'anothername'
2

所以你的回答基本上已经很完整了。你可以用下面的方式来定义你想要的结构:

class Devices(dict):
    def __init__(self,inpt={}):
        super(Devices,self).__init__(inpt)

    def __getattr__(self, name):
        return self.__getitem__(name)

    def __setattr__(self,name,value):
        self.__setitem__(name,value)

然后一个使用案例可以是:

In [6]: x = Devices()

In [7]: x.devices = Devices({"name":"thom"})

In [8]: x.devices.name
Out[8]: 'thom'

基本上就是把你的属性查找字典嵌套在一起。

撰写回答