Python: float 的子类可以在其构造函数中使用额外的参数吗?

Python: Can a subclass of float take extra arguments in its constructor?

在 Python 3.4 中,我想创建一个 float 的子类——可以像 float 一样用于数学和布尔运算的东西,但还有其他自定义功能,并且可以在初始化时接收控制该功能的参数。 (具体来说,我想要一个自定义 __str__ 和一个在该方法中使用的参数。)

但是,我似乎无法让 float 的子类具有功能性的双参数构造函数。为什么?这仅仅是对扩展内置类型的限制吗?

示例:

class Foo(float):
    def __init__(self, value, extra):
        super().__init__(value)
        self.extra = extra

现在,如果我尝试 Foo(1,2),我会得到:

TypeError: float() takes at most 1 argument (2 given)

令人惊讶的是,我的新 __init__ 参数也被强制执行,所以如果我这样做 Foo(1) 我得到:

TypeError: __init__() missing 1 required positional argument: 'extra'

这是怎么回事?我对 list 的子类型做过类似的事情,但很惊讶它在 float.

上不起作用

由于 float 是不可变的,因此您也必须覆盖 __new__。以下应该做你想做的:

class Foo(float):
    def __new__(self, value, extra):
        return float.__new__(self, value)
    def __init__(self, value, extra):
        float.__init__(value)
        self.extra = extra

foo = Foo(1,2)
print(str(foo))
1.0
print(str(foo.extra))
2

另见 Sub-classing float type in Python, fails to catch exception in __init__()

cgogolin的解法是对的。就像另一个不可变的 类 一样,比如 int,str,......但我会写:

class Foo(float):
    def __new__(cls, value, extra):
       return super().__new__(cls, value)
    def __init__(self, value, extra):
       float.__init__(value)
       self.extra = extra

@cgogolin 和@qvpham 都提供了有效的答案。但是,我认为__init__方法中的float.__init__(value)Foo的初始化无关。也就是说,它不对 Foo 的属性进行初始化。因此,它反而会引起对 float 类型子类化操作的必要性的混淆。

确实,解决方案可以进一步简化如下:

In [1]: class Foo(float):
   ...:     def __new__(cls, value, extra):
   ...:        return super().__new__(cls, value)
   ...:     def __init__(self, value, extra):
   ...:        self.extra = extra

In [2]: foo = Foo(1,2)
   ...: print(str(foo))
1.0

In [3]: print(foo.extra)
2

您可以在根本不实施 __init__ 的情况下执行此操作:

class Foo(float):
    def __new__(cls, value, extra):
        instance = super().__new__(cls, value)
        instance.extra = extra
        return instance

正在使用:

>>> foo = Foo(1, 2)
>>> print(foo)
1.0
>>> print(foo.extra)
2

虽然您可以在 __new__ 方法中处理初始化,因为它总是在 __init__ 之前调用(或者甚至代替,如果 __new__ 返回的对象不是实例在 class 中),最好的做法是在 __init__ 中解耦对象初始化,并让 __new__ 仅用于对象创建。

例如,这样你就可以class Foo。 (此外,将 *args, **kwargs 传递给 __new__ 将允许子 class 具有任意数量的位置或命名参数。)

class Foo(float):
    def __new__(cls, value, *args, **kwargs):
        return super().__new__(cls, value)

    def __init__(self, value, extra):
        self.extra = extra

class SubFoo(Foo):
    def __init__(self, value, extra, more):
        super().__init__(value, extra)
        self.more = more

但是,如果您在 __new__ 中处理初始化,您将继承 object__init__,它没有比实例本身更多的参数。而且您将无法通过常规方式对其进行子class。

class Bar(float):
    def __new__(cls, value, extra):
        self = super().__new__(cls, value)
        self.extra = extra
        return self

class SubBar(Bar):
    def __init__(self, value, extra):
        super().__init__(value, extra)
>>> sub_bar = SubBar(1, 2)
TypeError: object.__init__() takes no parameters