代码之家  ›  专栏  ›  技术社区  ›  Eric O. Lebigot

子类化dict:应该调用dict.\u init\uuu()吗?

  •  29
  • Eric O. Lebigot  · 技术社区  · 15 年前

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

    对dict进行子类化时:

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

    应该 dict.__init__(self) 被调用,就像一个“安全”度量(例如,如果有一些非常重要的实现细节的话)?如果 dict.__init__() 打电话?我在这里(实际上是,打电话给 D.T.S.InITI^() 是安全的。

    我猜那是什么时候 ImageDB.__init__(self, directory) 被调用时,self已经是一个新的空dict对象,因此不需要调用 dict.__init__ (开始我真的希望听写是空的)。这是正确的吗?

    编辑 :

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

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

    在许多答案中提到了“工厂”。所以我想归根结底是:你是dict的子类,重写 γ-iITI^() 然后添加方法,或者编写返回dict的(工厂)函数,向其添加方法?我倾向于使用第一种解决方案,因为factory函数返回的对象的类型并不表示它具有附加的语义和方法,但是您认为呢?

    编辑2 :

    我从每个人的答案中得出结论,当新课程“不是字典”时,尤其是当新课程 __init__ 方法不能采用与dict相同的参数 爱因斯坦 (以上“实际问题”中的情况)。换句话说,如果我理解正确,共识似乎是:当您进行子类化时,所有方法(包括初始化)都必须具有与基类方法相同的签名。这允许isInstance(subclass_instance,dict)保证 subclass_instance.__init__() 可以像这样使用 D.T.S.InITI^() 例如。

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

    4 回复  |  直到 9 年前
        1
  •  15
  •   ThiefMaster    11 年前

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

    顺便问一下,你没有告诉我们你 希望 如果你想要一个具有dict(mapping)行为的类,并且你不需要一个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
    

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

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

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

        2
  •  10
  •   Anurag Uniyal    9 年前

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

    或者不重写 爱因斯坦 或者如果你需要重写 _初始化__ 调用基类 爱因斯坦 ,如果您担心参数,只需传递*参数、**kwargs或不传递任何参数,如果您需要空的dict,例如。

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

    我们不应该假定基类在做什么或不做什么,不调用基类是错误的 爱因斯坦

        3
  •  3
  •   denis    11 年前

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

    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)
    
        4
  •  2
  •   unutbu    15 年前

    PEP 372 处理将有序dict添加到collections模块。

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

    提议(和接受) patch 到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 那么,我同意艾伦·弗兰佐尼的方法:使用dict工厂而不是子类化:

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