我正在尝试制作一个新的不可变类型,类似于内置的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)自定义实例创建。为了自定义类的创建,通常也将其覆盖在自定义元类中。
__new__()
因此,如果我没有将内置类型作为子类,而是从头开始创建一个不可变的类型(的子类object),是否还需要使用它?
如果要创建真正的不可变类型,则应使用__new__,因为传递给自身的对象在__init__逻辑上已经是不可变的,因此将值分配给其成员将为时已晚。对于那些编写子类的人来说,这更为严厉,因为将禁止添加成员。
__new__
__init__
由于不可变性实际上不是固有属性,而是一种通常由钩子强制执行的技巧,因此__setattr__人们确实会编写不可变类型,__init__并使用它们进行初始化,然后通过设置某个成员使自己变得不可变,然后使其他成员成为不可能。但是,在这种情况下,逻辑可能会变得相当曲折,并__setattr__可能会充满额外的规则。
__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 >>>