我想拥有两个具有以下属性的类Interval
和Segment
:
Interval
可以具有start
和end
点,可以包含/排除其中的任何一个点(我已经使用start_inclusive
/ {{ 1}})。end_inclusive
是包含两个端点的Segment
,因此用户无需指定这些标志。如果用户尝试创建包含端点的Interval
,他将得到一个Interval
,如
Segment
到目前为止,我的MCVE实现是
>>> Interval(0, 1, start_inclusive=True, end_inclusive=True)
Segment(0, 1)
类:
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
类:
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)
但是
>>> 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__
被孩子“绑定”? >
答案 0 :(得分:3)
让我们看看为什么首先出现错误。当您调用从object
派生的类时,将调用__call__
(metaclass)的type
方法。通常是这样
self = cls.__new__(...)
if isinstance(self, cls):
type(self).__init__(self)
这只是一个近似值,但足以传达这里发生的事情:
type.__call__
呼叫Interval.__new__
start_inclusive and end_inclusive
起,Interval.__new__
会正确返回Segment
的实例issubclass(Segment, Interval)
起,type.__call__
用您传递给Segment.__init__
的调用中的所有参数调用Interval
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__
将不尝试在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)
答案 1 :(得分:2)
您可以使用元类来解决此问题,以在__init__
之后调用__new__
时进行自定义:
class IntervalMeta(type):
def __call__(cls, *args, **kwargs):
obj = cls.__new__(cls, *args, **kwargs)
# Only call __init__ if class of object is exactly this class
if type(obj) is cls:
cls.__init__(obj, *args, **kwargs)
# As opposed to default behaviour:
# if isinstance(obj, cls):
# type(obj).__init__(obj, *args, **kwargs)
return obj
# Code below does not change except for metaclass
class Interval(metaclass=IntervalMeta):
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)
print(Interval(0, 1, start_inclusive=True, end_inclusive=True))
# <__main__.Segment object at ...>