这里有一个两方面的问题,一个是理论问题,另一个是实际问题:
对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这样的类应该如何实现?没有子类化?这需要一些麻烦的样板代码,不是吗?
- 一个工厂的功能就是一条路。如果您需要定制实例行为,那么您可能需要创建一个子类。如果您只想覆盖初始化,就不需要对任何内容进行子类化,因为您的实例与标准实例没有什么不同。记住,init不是实例接口的一部分,而是类的一部分。
- 对于这个问题,我认为最好将__getitem__方法添加到您的imagedb中,而不是将dict子类化,因为它不是dict。这允许您做您想做的事情,而不需要像pop()这样的方法似乎不适合您的类。
- @GS:好的,关于pop;它确实,至少在目前是不相关的(数据库内容仅在初始化时定义)。我认为实现与必要的特性紧密配合确实是最好的。
在进行子类化时,您可能应该调用dict.__init__(self);事实上,您不知道dict中到底发生了什么(因为它是内置的),这可能因版本和实现而异。不调用它可能会导致不正确的行为,因为您不知道dict在哪里保存其内部数据结构。
顺便说一下,你没有告诉我们你想做什么;如果你想要一个具有dict(mapping)行为的类,并且你不需要一个dict(例如,在你的软件中的任何地方都没有执行isinstance(x, dict)的代码,正如它应该的那样),那么你最好使用UserDict.UserDict或UserDict.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_模式
它只是将实例的构造委托给函数的一种方法,而不是重写/更改其构造函数。
- +1:不需要子类的时候进行子类化是个坏主意,工厂更好。
- 即使我不重写dict方法,新类也有其他方法,…(我正在调查工厂,感谢您提供指针!)
- 我不确定userdict:文档中写道"这个模块还定义了一个类userdict,它充当字典对象的包装器。对这个类的需求在很大程度上已经被直接从dict(从python 2.2版开始提供的一个特性)子类的能力所取代。"
- 好的,那么您将使用新方法扩展dict,从dict继承是可以的,但是我建议不要重写init。我将再次编辑我的帖子。
- 抱歉,我确实覆盖了__init__(我在问题的最新版本中详细描述了这个问题),但没有其他内容……
- @艾伦:谢谢你的指点!
- 我同意@alan:i经常将dict()子类化,这样我就可以向对象添加自定义方法,而不是像字典一样访问对象。在大多数情况下,我根本不重写in i t。(如果我这样做了,我肯定会叫超类的init。)
- 有时我对dict进行子类化,这样我的对象就可以在本地JSON序列化,而不必诉诸于恶作剧。
您通常应该称为基类"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__是错误的。
- 打电话给__init__的确是我目前正在做的。因为它看起来像是不带参数地调用它不会做任何事情,所以我只是好奇关于Python的基本事实,这些事实允许不调用它!
- @eol,imo不调用baseclass init显然是错误的,除非有非常强烈的理由否则
- @我明白你的意思。我试图把对python的了解再深入一点,我想知道不调用dict.__init__(self)(没有其他参数)是否存在这样一个"非常强有力的理由"(比如"它永远不会做任何事情")。
- 甚至可以使用super(MyDict, self).__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 |
- 这很有趣。现在,不使用python 3.1调用dict.__init__()是安全的,但是未来会怎样呢?因为我不重写任何方法,所以在imagedb中,子类化是非常安全的;只有初始化是特殊的(它构建dict)。
- 抱歉,伊奥,我没有跟踪你。在我看来,python 3.1是未来…:)
- 考虑到init实际上在做什么。它用所有的参数和关键字更新dict。这是你的课程必须要做的,所以调用dict。"init"(self,*args,**kwds)可能会帮你解决这个问题,或者你必须调用self.update,就像ordereddict一样。
- @ Uunut-LOL:D
- @TorValamo:我已经为我想要的功能添加了细节。基本上,类中包含的唯一数据是字典,我想直接通过db[…]而不是db.contents[…]访问它。对象从不使用类似标准dict的参数创建。