python - 用不同的 __new__ 签名为 child 实例化 __new__ 中的 child

标签 python python-3.x oop inheritance

前言

我想要具有以下属性的 2 个类 IntervalSegment:

  1. Interval 可以有 startend 点,它们中的任何一个都可以被包含/排除(我已经使用必需的标志参数实现了这个像 start_inclusive/end_inclusive)。
  2. Segment 是一个包含两个端点的Interval,因此用户不需要指定这些标志。
  3. 如果用户尝试创建包含端点的Interval,他会得到一个Segment,如

    >>> Interval(0, 1, start_inclusive=True, end_inclusive=True)
    Segment(0, 1)
    

    ( this doesn't look impossible )

问题

我的 MCVE到目前为止的实现是

区间类:

class Interval:
    def __new__(cls, start: int, end: int,
                *,
                start_inclusive: bool,
                end_inclusive: bool) -> 'Interval':
        if cls is not __class__:
            return super().__new__(cls)
        if start == end:
            raise ValueError('Degenerate interval found.')
        if start_inclusive and end_inclusive:
            return Segment(start, end)
        return super().__new__(cls)

    def __init__(self,
                 start: int,
                 end: int,
                 *,
                 start_inclusive: bool,
                 end_inclusive: bool) -> None:
        self.start = start
        self.end = end
        self.start_inclusive = start_inclusive
        self.end_inclusive = end_inclusive

类:

class Segment(Interval):
    def __new__(cls, start: int, end: int) -> 'Interval':
        return super().__new__(cls, start, end,
                               start_inclusive=True,
                               end_inclusive=True)

    def __init__(self, start: int, end: int) -> None:
        super().__init__(start, end,
                         start_inclusive=True,
                         end_inclusive=True)

创作还挺有效

>>> Interval(0, 1, start_inclusive=False, end_inclusive=True)
<__main__.Interval object at ...>
>>> Interval(0, 1, start_inclusive=False, end_inclusive=False)
<__main__.Interval object at ...>
>>> Segment(0, 1)
<__main__.Segment object at ...>

但是

>>> Interval(0, 1, start_inclusive=True, end_inclusive=True)

失败并出现以下 TypeError

Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'end_inclusive'

所以我的问题是:

在父类的 __new__ 中,是否有任何惯用的方法使用 __new____init__ “绑定(bind)”的一些参数来实例化子类一个 child ?

最佳答案

让我们先看看为什么会出现错误。当您调用派生自 object 的类时, __call__ metaclass 的方法( type ) 被调用。这通常是这样的

self = cls.__new__(...)
if isinstance(self, cls):
    type(self).__init__(self)

这只是近似值,但足以传达这里发生的事情:

  1. type.__call__ 调用 Interval.__new__
  2. 由于 start_inclusive 和 end_inclusiveInterval.__new__ 正确返回了 Segment
  3. 的实例
  4. 由于 issubclass(Segment, Interval)type.__call__ 使用您传递给调用的所有参数调用 Segment.__init__间隔
  5. Segment.__init__ 不接受任何关键字参数,并引发您看到的错误。

对于这种情况,有许多解决方法。 @jdehesa's answer显示如何覆盖 type 的行为,以便 type.__call__ 检查 type(obj) is cls 而不是使用 isinstance.

另一种选择是分离IntervalSegment 的层次结构。你可以做类似的事情

class MyBase:
    # put common functionality here

class Interval(MyBase):
    # __new__ and __init__ same as before

class Segment(MyBase):
    # __new__ and __init__ same as before

通过这种安排,isinstance(Segment(...), Interval) 将为 False,而 type.__call__ 将为 不要尝试在Segment 上调用Interval.__init__

在我看来,最简单的方法是使用工厂模式。有一个外部函数,可以根据输入确定返回什么类型的对象。这样一来,你根本不需要实现__new__,你的类构建过程也会简单很多:

def factory(start, end, *, start_inclusive, end_inclusive):
    if start_inclusive and end_inclusive:
        return Segment(start, end)
    return Interval(start, end, start_inclusive=start_inclusive, end_inclusive=end_inclusive)

关于python - 用不同的 __new__ 签名为 child 实例化 __new__ 中的 child ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57078848/

相关文章:

python - 如何生成Python虚拟环境的environments.yml文件?

python - 拦截对对象属性的 __getitem__ 调用

python - Project Euler 的性能差异 B/n C++ 和 Python

python - 在没有 SSL 证书验证的情况下使用 urllib 发布数据

c++ - 将设置/获取请求映射到 C++ 类/结构更改中

python - IPython 脚本 - 带有状态代码的退出脚本

python-3.x - fastai 学习者要求和批量预测

python - 如何摆脱错误 "' float' 对象没有属性 'exp' ”?

c# - 为特定用例扩展基础模型类

actionscript-3 - 何时子类化以及何时使用现有类?