一个类似于 dict 的 python 类

Posted

技术标签:

【中文标题】一个类似于 dict 的 python 类【英文标题】:A python class that acts like dict 【发布时间】:2011-04-30 04:49:35 【问题描述】:

我想编写一个行为类似于 dict 的自定义类 - 所以,我继承自 dict

不过,我的问题是:我是否需要在我的 __init__() 方法中创建一个私有 dict 成员?我不明白这一点,因为如果我只是从dict 继承,我已经有了dict 的行为。

谁能指出为什么大多数继承 sn-ps 看起来像下面的那个?

class CustomDictOne(dict):
   def __init__(self):
      self._mydict =  

   # other methods follow

而不是更简单的......

class CustomDictTwo(dict):
   def __init__(self):
      # initialize my other stuff here ...

   # other methods follow

实际上,我想我怀疑这个问题的答案是让用户无法直接访问您的字典(即他们必须使用您提供的访问方法)。

但是,数组访问运算符[] 呢?一个人将如何实施呢?到目前为止,我还没有看到显示如何覆盖 [] 运算符的示例。

那么如果自定义类中没有提供[]访问函数,继承的基方法会在不同的字典上操作?

我尝试了以下sn-p来测试我对Python继承的理解:

class myDict(dict):
    def __init__(self):
        self._dict = 

    def add(self, id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)
print md[id]

我收到以下错误:

KeyError:

上面的代码有什么问题?

如何更正 myDict 类以便我可以编写这样的代码?

md = myDict()
md['id'] = 123

[编辑]

我已经编辑了上面的代码示例,以消除我在离开办公桌之前犯的愚蠢错误。这是一个错字(我应该从错误消息中发现它)。

【问题讨论】:

【参考方案1】:
class Mapping(dict):

    def __setitem__(self, key, item):
        self.__dict__[key] = item

    def __getitem__(self, key):
        return self.__dict__[key]

    def __repr__(self):
        return repr(self.__dict__)

    def __len__(self):
        return len(self.__dict__)

    def __delitem__(self, key):
        del self.__dict__[key]

    def clear(self):
        return self.__dict__.clear()

    def copy(self):
        return self.__dict__.copy()

    def has_key(self, k):
        return k in self.__dict__

    def update(self, *args, **kwargs):
        return self.__dict__.update(*args, **kwargs)

    def keys(self):
        return self.__dict__.keys()

    def values(self):
        return self.__dict__.values()

    def items(self):
        return self.__dict__.items()

    def pop(self, *args):
        return self.__dict__.pop(*args)

    def __cmp__(self, dict_):
        return self.__cmp__(self.__dict__, dict_)

    def __contains__(self, item):
        return item in self.__dict__

    def __iter__(self):
        return iter(self.__dict__)

    def __unicode__(self):
        return unicode(repr(self.__dict__))


o = Mapping()
o.foo = "bar"
o['lumberjack'] = 'foo'
o.update('a': 'b', c=44)
print 'lumberjack' in o
print o

In [187]: run mapping.py
True
'a': 'b', 'lumberjack': 'foo', 'foo': 'bar', 'c': 44

【讨论】:

如果你要继承dict,那么你应该使用对象本身(使用super)而不是简单地委托给实例的__dict__——这实际上意味着你正在创建每个实例有两个字典。 self.__dict__ 与实际字典内容相同。每个 Python 对象,无论其类型如何,都有一个 _dict__,其中包含所有对象属性(方法、字段等)。你确实不想想弄乱这个,除非你想编写修改自身的代码......【参考方案2】:

这样

class CustomDictOne(dict):
   def __init__(self,*arg,**kw):
      super(CustomDictOne, self).__init__(*arg, **kw)

现在您可以使用内置函数,例如 dict.get()self.get()

您不需要包装隐藏的self._dict。您的课程已经一个字典。

【讨论】:

这个。如果不先调用其构造函数,则从 dict 继承是没有意义的。 请注意,您继承的 dict 实际上包含 2 个 dict-instance:第一个是继承的容器,第二个是包含类属性的 dict - 您可以通过使用 slots 来避免这种情况. __dict__实际上是在第一次访问时才创建的,所以只要用户不尝试使用它就可以了。 __slots__ 不过会很好。 在你野蛮的逗号后使用空格! ;-) @andrewtavis 我已经发布了一个演示插槽的答案。【参考方案3】:

查看emulating container types 上的文档。在您的情况下,add 的第一个参数应该是 self

【讨论】:

为了完整起见,这里是@björn-pollex 提到的最新Python 2.x(截至撰写本文时为2.7.7)文档的链接:Emulating Container Types(抱歉没有使用 cmets 函数,*** 不允许我这样做。)【参考方案4】:

这是一个替代解决方案:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__dict__ = self

a = AttrDict()
a.a = 1
a.b = 2

【讨论】:

这很糟糕,因为您没有定义任何自定义方法,并且还有其他答案所说的其他问题。 这正是我所需要的。谢谢!【参考方案5】:

这是我最好的解决方案。这个我用过很多次了。

class DictLikeClass:
    ...
    def __getitem__(self, key):
        return getattr(self, key)

    def __setitem__(self, key, value):
        setattr(self, key, value)
    ...

你可以像这样使用:

>>> d = DictLikeClass()
>>> d["key"] = "value"
>>> print(d["key"])

【讨论】:

【参考方案6】:

一个类似于dict的python类

这是怎么回事?

谁能指出为什么大多数继承 sn-ps 看起来像下面的那个?

class CustomDictOne(dict):
   def __init__(self):
      self._mydict =  

大概有充分的理由从 dict 继承(也许你已经在传递一个并且你想要一种更具体的 dict)并且你有充分的理由实例化另一个 dict 来委托to (因为这将为此类的每个实例实例化 两个 dicts。)但这听起来不正确吗?

我自己从来没有遇到过这个用例。我确实喜欢在您使用可输入的 dicts 的地方输入 dicts 的想法。但在那种情况下,我更喜欢类型化类属性的想法 - 字典的全部意义在于你可以给它 any hashable 类型的键,以及 any 的值> 输入。

那么为什么我们会看到这样的 sn-ps 呢?我个人认为这是一个容易犯的错误,没有得到纠正,因此随着时间的推移而延续。

我宁愿在这些 sn-ps 中看到这个,来演示通过继承来重用代码:

class AlternativeOne(dict):
    __slots__ = ()
    def __init__(self):
        super().__init__()
        # other init code here
    # new methods implemented here

或者,为了演示重新实现dicts的行为,这个:

from collections.abc import MutableMapping 

class AlternativeTwo(MutableMapping):
    __slots__ = '_mydict'
    def __init__(self):
        self._mydict = 
        # other init code here
    # dict methods reimplemented and new methods implemented here

按要求 - 将插槽添加到 dict 子类。

为什么要添加插槽?内置的 dict 实例没有任意属性:

>>> d = dict()
>>> d.foo = 'bar'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'foo'

如果我们按照大多数人在这个答案中所做的方式创建一个子类,我们会看到我们没有得到相同的行为,因为我们将有一个 __dict__ 属性,导致我们的 dicts 可能占用两倍空间:

my_dict(dict):
    """my subclass of dict""" 

md = my_dict()
md.foo = 'bar'

由于上面没有产生错误,上面的类实际上并没有起作用,“就像dict。”

我们可以通过给它空槽使它像dict一样:

class my_dict(dict):
    __slots__ = ()

md = my_dict()

所以现在尝试使用任意属性将会失败:

>>> md.foo = 'bar'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'my_dict' object has no attribute 'foo'

而且这个 Python 类的行为更像是 dict

有关如何以及为何使用插槽的更多信息,请参阅此问答:Usage of __slots__?

【讨论】:

【参考方案7】:

我真的没有在任何地方看到正确的答案

class MyClass(dict):
    
    def __init__(self, a_property):
        self[a_property] = a_property

您真正需要做的就是定义自己的__init__ - 这就是它的全部内容。

另一个例子(稍微复杂一点):

class MyClass(dict):

    def __init__(self, planet):
        self[planet] = planet
        info = self.do_something_that_returns_a_dict()
        if info:
            for k, v in info.items():
                self[k] = v

    def do_something_that_returns_a_dict(self):
        return "mercury": "venus", "mars": "jupiter"

当您想要嵌入某种逻辑时,最后一个示例很方便。

无论如何...简而言之,class GiveYourClassAName(dict) 足以让您的班级表现得像听话一样。您在self 上执行的任何 dict 操作都将与常规 dict 一样。

【讨论】:

【参考方案8】:

这段代码的问题:

class myDict(dict):
    def __init__(self):
        self._dict = 

    def add(id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)

...是您的“添加”方法(...以及您希望成为类成员的任何方法)需要将明确的“自我”声明为其第一个参数,例如:

def add(self, 'id', 23):

要实现运算符重载以按键访问项目,请在 docs 中查找魔术方法 __getitem____setitem__

请注意,由于 Python 使用 Duck Typing,实际上可能没有理由从该语言的 dict 类派生您的自定义 dict 类——在不了解您正在尝试做什么的情况下(例如,如果您需要传递将此类的实例放入某些除非isinstance(MyDict(), dict) == True 否则会中断的代码中,您最好只实现使您的类足够像dict 并停在那里的API。

【讨论】:

【参考方案9】:

Python 标准库中的UserDict 就是为此目的而设计的。

【讨论】:

【参考方案10】:

永远不要从 Python 内置 dict 继承!例如update 方法不会使用__setitem__,它们在优化方面做了很多工作。使用 UserDict。

from collections import UserDict

class MyDict(UserDict):
    def __delitem__(self, key):
        pass
    def __setitem__(self, key, value):
        pass

【讨论】:

基于什么人永远不应该从内置字典继承?来自文档 (docs.python.org/3/library/collections.html#collections.UserDict):“对此类的需求已被直接从 dict 子类化的能力部分取代;但是,此类可以更容易使用,因为底层字典可作为属性访问。”同样在同一页面上:集合模块是“自 3.3 版以来已弃用,将在 3.9 版中删除:将集合抽象基类移至 collections.abc 模块。”...没有 UserDict。 我怀疑这是受到treyhunner.com/2019/04/…的启发或至少引起了共鸣 @NumesSanguis 关于第二点:弃用整个模块并将其置于更深的命名空间级别几乎没有意义。弃用警告适用于过去位于 collections 和现在位于 collections.abc 中的抽象基类 (ABC)。这适用于collections.abc 中的每个班级。 UserDict 不是 ABC。 Counterdefaultdict 也不是。完整的模块弃用在文档中会有非常不同的说明。

以上是关于一个类似于 dict 的 python 类的主要内容,如果未能解决你的问题,请参考以下文章

python 递归地将嵌套的dicts转换为嵌套的namedtuples,为您提供类似于不可变对象文字的东西

[python]Python 字典(Dictionary) update()方法

python3 与dict相关的魔法方法。使用于二叉搜索树的类中

一入python深似海--对象的属性

R语言list与Python中的dict

Python进阶3