前言
我想要具有以下属性的 2 个类 Interval
和 Segment
:
Interval
可以有start
和end
点,它们中的任何一个都可以被包含/排除(我已经使用必需的标志参数实现了这个像start_inclusive
/end_inclusive
)。Segment
是一个包含两个端点的Interval
,因此用户不需要指定这些标志。如果用户尝试创建包含端点的
Interval
,他会得到一个Segment
,如>>> Interval(0, 1, start_inclusive=True, end_inclusive=True) Segment(0, 1)
问题
我的 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)
这只是近似值,但足以传达这里发生的事情:
type.__call__
调用Interval.__new__
- 由于
start_inclusive 和 end_inclusive
,Interval.__new__
正确返回了Segment
的实例
- 由于
issubclass(Segment, Interval)
,type.__call__
使用您传递给调用的所有参数调用Segment.__init__
到间隔
Segment.__init__
不接受任何关键字参数,并引发您看到的错误。
对于这种情况,有许多解决方法。 @jdehesa's answer显示如何覆盖 type
的行为,以便 type.__call__
检查 type(obj) is cls
而不是使用 isinstance
.
另一种选择是分离Interval
和Segment
的层次结构。你可以做类似的事情
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/