小编典典

为什么小数使用__new__而不是__init__?

python

我正在尝试制作一个新的不可变类型,类似于内置的Fraction但不是从其派生的。分数类是这样创建的:

# We're immutable, so use __new__ not __init__
def __new__(cls, numerator=0, denominator=None):
    ...
    self = super(Fraction, cls).__new__(cls)
    self._numerator = ...
    self._denominator = ...
    return self
但我看不出这与

def __init__(self, numerator=0, denominator=None):
    ...
    self._numerator = ...
    self._denominator = ...

创建2个Fraction具有相同值的对象不会创建2个指向相同对象/内存位置的标签 (实际上,在注释中指出,类型并不常见)。

尽管有源代码注释,但它们实际上并不是一成不变的:

f1 = Fraction(5)

f2 = Fraction(5)

id(f1), id(f2)
Out[35]: (276745136, 276745616)

f1._numerator = 6

f1
Out[41]: Fraction(6, 1)

f2
Out[42]: Fraction(5, 1)

id(f1)
Out[59]: 276745136

那么,这样做的意义何在?

docs说

__new__()主要用于允许不可变类型的子类(例如int,str或tuple)自定义实例创建。为了自定义类的创建,通常也将其覆盖在自定义元类中。

因此,如果我没有将内置类型作为子类,而是从头开始创建一个不可变的类型(的子类object),是否还需要使用它?


阅读 187

收藏
2021-01-20

共1个答案

小编典典

如果要创建真正的不可变类型,则应使用__new__,因为传递给自身的对象在__init__逻辑上已经是不可变的,因此将值分配给其成员将为时已晚。对于那些编写子类的人来说,这更为严厉,因为将禁止添加成员。

由于不可变性实际上不是固有属性,而是一种通常由钩子强制执行的技巧,因此__setattr__人们确实会编写不可变类型,__init__并使用它们进行初始化,然后通过设置某个成员使自己变得不可变,然后使其他成员成为不可能。但是,在这种情况下,逻辑可能会变得相当曲折,并__setattr__可能会充满额外的规则。

拥有某种可变类型,并从该类型继承不可变类型__setattr__,只是引发子类中包含的一个异常,才有意义。这使得使用的逻辑__new__显而易见。由于它可以使可变的超类和对其进行修改,但随后将其作为继承的类型返回,因此它不会造成混乱。

如果Fraction希望是不变的,则实现者要么错过了该步骤,要么后来考虑得更好,却忘记删除他们的评论。

>>> class Pair(object):
...     def __init__(self, key, value):
...         self.key = key
...         self.value = value
...
>>> class ImPair(Pair):
...     def __new__(cls, key, value):
...         self = Pair(key, value)
...         self.__class__ = cls
...     def __setattr__(self, name, value):
...         raise AttributeError(name)
...    
>>> x = Pair(2,3)
>>> x.key
2
>>> x.key = 9
>>> x.key
9
>>> x = ImPair(2,3)
>>> x.key
2
>>> x.key = 9
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __setattr__
AttributeError: key
>>>
2021-01-20