32

这是一个双重问题,一个理论部分,一个实践部分:

子类化dict时:

class ImageDB(dict):
    def __init__(self, directory):
        dict.__init__(self)  # Necessary?? 
        ...

应该dict.__init__(self)被称为“安全”措施(例如,如果有一些重要的实现细节很重要)?如果不调用,代码是否存在与 Python 的未来版本中断dict.__init__()的风险我在这里寻找做一件事或另一件事的根本原因(实际上,打电话dict.__init__()是安全的)。

我的猜测是,当ImageDB.__init__(self, directory)被调用时, self 已经是一个新的空 dict 对象,因此不需要调用dict.__init__(我确实希望 dict 一开始是空的)。它是否正确?

编辑

上述基本问题背后更实际的问题如下。我正在考虑将 dict 子类化,因为我会经常使用 db[...] 语法(而不是一直使用 db.contents[...]);对象的唯一数据(属性)确实是一个字典。我想向数据库添加一些方法(例如get_image_by_name(),或get_image_by_code(),例如),并且只覆盖__init__(), 因为图像数据库是由包含它的目录定义的。

总而言之,(实际)问题可能是:对于表现得像字典的东西,除了它的初始化不同(它只需要一个目录名称)并且它有其他方法之外,什么是一个好的实现?

许多答案中都提到了“工厂”。所以我想这一切都归结为:你是继承dict,覆盖__init__()和添加方法,还是你编写一个返回dict的(工厂)函数,你添加方法?我倾向于第一种解决方案,因为工厂函数返回一个对象,其类型并不表明它具有额外的语义和方法,但你怎么看?

编辑 2

我从每个人的回答中得知,当新类“不是字典”时,将 dict 子类化不是一个好主意,特别是当它的__init__方法不能采用与 dict 相同的参数时__init__(在“实际问题”中就是这种情况)多于)。换句话说,如果我理解正确的话,共识似乎是:当您子类化时,所有方法(包括初始化)必须具有与基类方法相同的签名。例如,这允许 isinstance(subclass_instance, dict) 保证subclass_instance.__init__()可以像 一样使用dict.__init__()

另一个实际的问题随之而来:除了初始化方法之外,和dict一样的类应该如何实现?没有子类化?这需要一些麻烦的样板代码,不是吗?

4

5 回答 5

17

您可能应该dict.__init__(self)在子类化时调用;实际上,您不知道 dict 中究竟发生了什么(因为它是内置的),并且可能因版本和实现而异。不调用它可能会导致不当行为,因为您不知道 dict 在哪里保存其内部数据结构。

顺便说一句,你没有告诉我们你想做什么;如果您想要一个具有 dict(映射)行为的类,并且您并不真正需要 dict(例如isinstance(x, dict),您的软件中没有任何代码在做任何事情,因为它应该是),您可能最好使用UserDict.UserDict或者UserDict.DictMixin如果您重新在 python <= 2.5 上,或者collections.MutableMapping如果你在 python >= 2.6 上。这些将为您的班级提供出色的听写行为。

编辑:我在另一条评论中读到你没有覆盖任何 dict 的方法!那么子类化根本没有意义,不要这样做。

def createImageDb(directory):
    d = {}
    # do something to fill in the dict
    return d

编辑 2:您想从 dict 继承以添加新方法,但您不需要覆盖任何方法。一个好的选择可能是:

class MyContainer(dict):
    def newmethod1(self, args):
        pass

    def newmethod2(self, args2):
        pass


def createImageDb(directory):
    d = MyContainer()
    # fill the container
    return d

顺便说一句:您要添加哪些方法?你确定你正在创建一个好的抽象吗?也许你最好使用一个定义你需要的方法的类,并在它内部使用一个“正常”的字典。

工厂功能: http ://en.wikipedia.org/wiki/Factory_method_pattern

它只是将实例的构造委托给函数而不是覆盖/更改其构造函数的一种方式。

于 2010-01-09T12:55:56.643 回答
12

您通常应该调用基类,__init__那么为什么要在这里例外呢?

要么不覆盖__init__,要么如果你需要覆盖__init__ 调用基类__init__,如果你担心参数只需传递 *args,**kwargs 或者如果你想要空字典,则什么都不传递,例如

class MyDict(dict):
    def __init__(self, *args, **kwargs ):
        myparam = kwargs.pop('myparam', '')
        dict.__init__(self, *args, **kwargs )

我们不应该假设基类在做什么或不做什么,不调用基类是错误的__init__

于 2010-01-09T13:56:43.183 回答
3

子类化dict时要小心酸洗;例如,在 2.7 中需要 __getnewargs__,在旧版本中可能需要 __getstate__ __setstate__。(我不知道为什么。)

class Dotdict( dict ):
    """ d.key == d["key"] """

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

    def __getnewargs__(self):  # for cPickle.dump( d, file, protocol=-1)
        return tuple(self)
于 2010-01-27T15:51:22.363 回答
2

PEP 372处理向集合模块添加有序字典。

它警告说“将 dict 子类化是一项不平凡的任务,许多实现没有正确覆盖所有方法,这可能导致意外结果。”

python3.1的提议(和接受)补丁使用__init__如下所示:

+class OrderedDict(dict, MutableMapping):
+    def __init__(self, *args, **kwds):
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        if not hasattr(self, '_keys'):
+            self._keys = []
+        self.update(*args, **kwds)

基于此,看起来dict.__init__()不需要调用。

编辑:如果您没有覆盖或扩展任何dict's 方法,那么,我同意 Alan Franzoni:使用 dict 工厂而不是子类化:

def makeImageDB(*args,**kwargs):
   d = {}
   # modify d
   return d
于 2010-01-09T12:06:19.840 回答
0

如果您打算将诸如dict基本类型之类的子类化,您也可以考虑使用UserDictfrom 集合。UserDict被设计为子类。

于 2019-06-09T21:31:07.560 回答