子类化字典:是否应该调用dict.__init__()?

34 投票
5 回答
24681 浏览
提问于 2025-04-15 17:49

这里有一个双重问题,分为理论部分和实际部分:

当你在继承字典(dict)时:

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

是否应该调用 dict.__init__(self),作为一种“安全”措施(比如,万一有一些重要的实现细节需要考虑)?如果不调用 dict.__init__(),会不会有风险在未来的Python版本中代码会出问题?我想要的是做这件事的根本原因(实际上,调用 dict.__init__() 是安全的)。

我的猜测是,当调用 ImageDB.__init__(self, directory) 时,self已经是一个新的空字典对象,因此不需要再调用 dict.__init__(我确实希望字典一开始是空的)。这样理解对吗?

编辑

在上述根本问题背后,更实际的问题是:我在考虑继承字典,因为我会经常使用 db[…] 这种语法(而不是每次都写 db.contents[…]);这个对象唯一的数据(属性)确实是一个字典。我想给这个数据库添加一些方法(比如 get_image_by_name()get_image_by_code()),并且只重写 __init__(),因为图像数据库是由包含它的目录定义的。

总结一下,这个(实际的)问题可以是:对于一个行为像字典的东西,除了初始化方式不同(它只接受一个目录名),并且有额外的方法,什么样的实现比较好?

在很多回答中提到了“工厂”。所以我想这归结为:你是选择继承字典,重写 __init__() 并添加方法,还是写一个(工厂)函数返回一个字典,并给它添加方法?我倾向于选择第一个方案,因为工厂函数返回的对象类型并没有表明它有额外的语义和方法,但你怎么看?

编辑 2

从大家的回答中,我了解到当新类“不是字典”时,继承字典并不是一个好主意,特别是当它的 __init__ 方法不能接受与字典的 __init__ 相同的参数时(这正是上面“实际问题”的情况)。换句话说,如果我理解正确,大家的共识似乎是:当你继承时,所有方法(包括初始化)必须与基类的方法有相同的参数。这使得 isinstance(subclass_instance, dict) 能够保证 subclass_instance.__init__() 可以像 dict.__init__() 一样使用。

接下来又出现了一个实际问题:一个与字典几乎相同的类,除了它的初始化方法,应该如何实现?不继承的话?这会需要一些麻烦的模板代码,不是吗?

5 个回答

3

当你在创建一个字典的子类时,要小心使用“序列化”(也就是把对象转换成可以保存或传输的格式)。比如在Python 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)
14

一般来说,你应该调用父类的 __init__ 方法,那为什么在这里要例外呢?

要么就不要重写 __init__ 方法,要么如果你确实需要重写,就一定要调用父类的 __init__ 方法。如果你担心参数的问题,可以使用 *args 和 **kwargs,或者如果你想传空字典,就什么都不传,比如:

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

我们不能假设父类在做什么或者不做什么,不调用父类的 __init__ 方法是错误的。

17

当你在创建一个新的类来继承字典(dict)时,最好调用一下 dict.__init__(self)。其实你并不知道字典内部具体是怎么运作的,因为它是内置的,可能在不同的版本和实现中会有所不同。如果不调用这个方法,可能会导致一些奇怪的问题,因为你无法知道字典是如何存储它内部的数据结构的。

顺便说一下,你没有告诉我们你想要做什么。如果你想要一个具有字典(映射)行为的类,但实际上并不需要一个字典(比如在你的代码中没有地方使用 isinstance(x, dict)),那么你可能更适合使用 UserDict.UserDictUserDict.DictMixin(如果你使用的是 Python 2.5 及以下版本),或者使用 collections.MutableMapping(如果你使用的是 Python 2.6 及以上版本)。这些都会给你的类提供很好的字典行为。

编辑:我在另一个评论中看到你并没有重写字典的任何方法!那么继承就没有意义了,不要这样做。

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

编辑 2:你想从字典继承以添加新方法,但并不需要重写任何方法。那么一个好的选择可能是:

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

这只是将实例的构建委托给一个函数,而不是重写或改变它的构造函数的一种方式。

撰写回答