How to (or why not) call unicode.__init__ from subclass
我遇到过这样一种情况:子类化Unicode会导致3.3之前的python警告和3.3之前的python错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| # prove that unicode.__init__ accepts parameters
s = unicode('foo')
s.__init__('foo')
unicode.__init__(s, 'foo')
class unicode2(unicode):
def __init__(self, other):
super(unicode2, self).__init__(other)
s = unicode2('foo')
class unicode3(unicode):
def __init__(self, other):
unicode.__init__(self, other)
s = unicode3('foo') |
奇怪的是,警告/错误不会出现在前三行,而是出现在第8行和第14行。下面是python 2.7的输出。
1 2 3 4 5
| > python -Wd .\init.py
.\init.py:8: DeprecationWarning: object.__init__() takes no parameters
super(unicode2, self).__init__(other)
.\init.py:14: DeprecationWarning: object.__init__() takes no parameters
unicode.__init__(self, other) |
代码被简化以举例说明这个问题。在实际应用程序中,我执行的不仅仅是调用super __init__。
从前三行可以看出,unicode类实现了__init__,并且该方法至少接受一个参数。但是,如果我想从子类中调用该方法,那么无论我是否调用super(),似乎都无法这样做。
为什么可以在Unicode实例上调用unicode.__init__,而不是在Unicode子类上调用unicode.__init__?如果对unicode类进行子类化,作者应该怎么做?
- 我不认为你应该对字符串进行子类化…为什么不能在内部使用Unicode字符串创建自定义类呢?你可以很容易地让一个对象像Unicode字符串一样行走和说话。
- 我不知道为什么会出现这些问题,但是子类化Unicode看起来确实很不寻常。
- 在一个完美的世界中,应该能够对任何对象进行子类划分。当子类作为字符串时,子类化字符串尤其有用。尝试模拟字符串的所有接口比简单的子类化要困难得多,而且容易出错。例如,在不进行子类化的情况下,如何实现一个类来为isinstance(my_subclass_instance, basestring)返回true?有关有用的示例,请参见bitback.org/yougov/pmxbot/src/6415472739/pmxbot/core.py cl‌&8203;-48和github.com/jaraco/path.py/blob/ba38fc205e/path.py l106。
- 我还应该提到,datetime.datetime也存在同样的问题,所以它不是字符串特有的。
- 不知道为什么要明确地调用unicode.__init__。我认为如果您想替换底层字符串,使用collections.UserString会更容易。
我怀疑这个问题是因为unicode是不变的。
创建unicode实例后,不能修改。因此,任何初始化逻辑都将位于__new__方法(用于创建实例)中,而不是__init__方法(仅在实例存在后调用)。
不可变类型的子类没有相同的严格要求,因此如果需要,可以在unicode2.__init__中执行操作,但调用unicode.__init__是不必要的(而且可能不会执行您认为它会执行的操作)。
更好的解决方案可能是使用您自己的__new__方法进行定制逻辑:
1 2 3 4 5 6
| class unicode2(unicode):
def __new__(cls, value):
# optionally do stuff to value here
self = super(unicode2, cls).__new__(cls, value)
# optionally do stuff to self here
return self |
如果您愿意,也可以通过给类一个总是引发异常的__setattr__方法使其不可变(您还可以通过省略每个实例的__dict__给类一个__slots__属性来保存内存)。
- 回答不错,但YOOR示例应该用unicode2替换str2。或者直接打电话给unicode.__new__(cls, ...。
- 哎呀,这是从我尝试的一个python 3实现中复制而来的一个错误(这里没有unicode类)。我会更新的。
- 谢谢你的回答,但我不认为这能解释为什么我能在Unicode实例上调用__init__,而不是在Unicode子类实例上调用__init__。虽然unicode是不变的,但仍然可以想象在__init__期间发生了什么。在不了解底层实现的情况下,我运行了前三行代码,并确认unicode.__init__似乎确实存在并接受参数,因此我希望避免在子类中抑制该方法。问题不是问如何对unicode进行子类化,而是为什么unicode.__init__不只在子类中工作。
- 我很肯定unicode.__init__是从object继承的(这就是为什么警告来自object.__init__)。DepreciationWarning是因为在python 3中,用任何参数(self除外)调用__init__都是一个错误。我不知道python是如何使用传递给构造函数的参数来调用重写的__init__,而不是object.__init__。
- 啊,我找到了。通过这个问题的答案,我查了来源。顶部的一个长评论解释了当object的__new__和__init__会抱怨(或提出例外)过度争论的时候。
- @很好的发现。事实上,评论和消息来源确实澄清了这一点。在如何处理重写的__init__中,可变类型的处理方式不同于不可变类型。尤其是对于"unicode"类型,tp_new != object_new但是tp_init == object_new,所以在对象init中不会发生任何事情,但是对于unicode2和unicode3,tp_init != object_new,所以会出现警告/错误。事实上,"突变性"似乎比存在/不存在__new__和__init__方法更不重要。谢谢!