关于python:subclassing dict:应该dict .__ init __()被调用?

Subclassing dict: should dict.__init__() be called?

这里有一个两方面的问题,一个是理论问题,另一个是实际问题:

对dict进行子类化时:

1
2
3
4
class ImageDB(dict):
    def __init__(self, directory):
        dict.__init__(self)  # Necessary??
        ...

是否应该调用dict.__init__(self),就像一个"安全"措施(例如,如果有一些重要的非常重要的实现细节)?如果不调用dict.__init__(),代码是否有可能与未来版本的python中断?我在这里寻找做一件或另一件事情的根本原因(实际上,打电话给EDOCX1[1]是安全的)。

我的猜测是,当调用ImageDB.__init__(self, directory)时,self已经是一个新的空dict对象,因此不需要调用dict.__init__(我首先希望dict是空的)。这是正确的吗?

编辑:

上述基本问题背后更实际的问题是:我在考虑对dict进行子类化,因为我经常使用db[…]语法(而不是一直使用db.contents[…]);对象的唯一数据(属性)实际上是一个dict。我想向数据库添加一些方法(例如get_image_by_name()get_image_by_code()),并且只重写__init__(),因为年龄数据库由包含它的目录定义。

总之,(实际的)问题可能是:对于行为类似于字典的东西,什么是一个好的实现,除了它的初始化是不同的(它只使用一个目录名),并且它有其他的方法?

在许多答案中提到了"工厂"。所以我想这一切都归结为:您是子类dict,重写__init__()并添加方法,还是编写一个(factory)函数来返回dict,并向其添加方法?我倾向于使用第一种解决方案,因为factory函数返回的对象的类型并不表示它具有附加的语义和方法,但是您认为呢?

编辑2:

我从每个人的回答中得出结论,当新的类"不是字典"时,尤其是当它的__init__方法不能采用与dict的__init__相同的论点时(在上面的"实际问题"中就是这样),对dict进行子类划分是不好的。换句话说,如果我理解正确,共识似乎是:当您进行子类化时,所有方法(包括初始化)都必须具有与基类方法相同的签名。这允许isinstance(subclass_instance,dict)保证subclass_instance.__init__()可以像dict.__init__()那样使用。

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


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

顺便说一下,你没有告诉我们你想做什么;如果你想要一个具有dict(mapping)行为的类,并且你不需要一个dict(例如,在你的软件中的任何地方都没有执行isinstance(x, dict)的代码,正如它应该的那样),那么你最好使用UserDict.UserDictUserDict.DictMixin,如果你使用python<=2.5,或者collections.MutableMapping,如果你使用python=2.6。这将为你的班级提供良好的口述行为。

编辑:我在另一条评论中看到你没有重写dict的任何方法!这样子类化就没有意义了,不要这样做。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
class MyContainer(dict):
    def newmethod1(self, args):
        pass

    def newmethod2(self, args2):
        pass


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

顺便问一下:你在添加什么方法?你确定你正在创建一个好的抽象吗?也许你最好使用一个类来定义你需要的方法,并在内部使用一个"普通"的dict。

工厂功能:http://en.wikipedia.org/wiki/factory_method_模式

它只是将实例的构造委托给函数的一种方法,而不是重写/更改其构造函数。


您通常应该称为基类"EDOCX1"(0),那么为什么在这里例外呢?

或者不重写__init__,或者如果需要重写__init__调用基类__init__,如果担心参数,只需传递*参数,**kwargs,如果需要空dict,则不执行任何操作,例如。

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

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


在子类化dict时要小心酸洗;例如,这需要在2.7中使用"getnewargs",可能在旧版本中设置了状态。(我不知道为什么。)

1
2
3
4
5
6
7
8
9
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)

PEP372处理向Collections模块添加有序的dict。

它警告说,"对dict进行子类化是一项非常重要的任务,许多实现没有正确地重写所有方法,这可能导致意外的结果。"

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

1
2
3
4
5
6
7
+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的方法,那么,我同意Alan Franzoni:使用dict工厂而不是子类化:

1
2
3
4
def makeImageDB(*args,**kwargs):
   d = {}
   # modify d
   return d