Aza*_*kov 6 python oop inheritance python-3.x
我想要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)
Run Code Online (Sandbox Code Playgroud)
到目前为止,我的MCVE实现是
Interval
类:
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
Run Code Online (Sandbox Code Playgroud)
Segment
类:
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)
Run Code Online (Sandbox Code Playgroud)
创作有点作品
>>> 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 ...>
Run Code Online (Sandbox Code Playgroud)
但
>>> Interval(0, 1, start_inclusive=True, end_inclusive=True)
Run Code Online (Sandbox Code Playgroud)
跟随失败 TypeError
>>> Interval(0, 1, start_inclusive=True, end_inclusive=True)
Segment(0, 1)
Run Code Online (Sandbox Code Playgroud)
所以我的问题是:
是否有父母的孩子实例类的任何惯用的方式__new__
与一些参数__new__
和__init__
一个孩子“约束”?
我们先看看为什么会出现这个错误。当您调用从 派生的类时object
,将调用元类( )__call__
的方法。通常是这样的type
self = cls.__new__(...)
if isinstance(self, cls):
type(self).__init__(self)
Run Code Online (Sandbox Code Playgroud)
这只是近似值,但足以传达这里发生的情况:
type.__call__
来电Interval.__new__
start_inclusive and end_inclusive
,Interval.__new__
正确返回一个实例Segment
issubclass(Segment, Interval)
,使用您传递给调用的所有参数进行type.__call__
调用Segment.__init__
Interval
Segment.__init__
不接受任何关键字参数,并引发您看到的错误。对于这种情况有多种解决方法。@jdehesa 的答案显示了如何覆盖 的行为,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
Run Code Online (Sandbox Code Playgroud)
isinstance(Segment(...), Interval)
有了这样的安排False
,就type.__call__
不会再试图去打电话Interval.__init__
了Segment
。
在我看来,最简单的方法是使用工厂模式。有一个外部函数可以根据输入确定返回什么类型的对象。这样,你根本不需要实现__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)
Run Code Online (Sandbox Code Playgroud)