Python 用不同的子签名在uuu new uuu中实例化子项 前言
我希望有两个具有以下属性的类Python 用不同的子签名在uuu new uuu中实例化子项 前言,python,python-3.x,oop,inheritance,Python,Python 3.x,Oop,Inheritance,我希望有两个具有以下属性的类Interval和Segment: Interval可以有start和end点,其中任何点都可以包含/排除(我已经使用了所需的标志参数,如start_inclusive/end_inclusive) 段是一个包含两个端点的间隔,因此用户不需要指定这些标志 如果用户试图创建包含端点的Interval,他会得到一个段 >>> Interval(0, 1, start_inclusive=True, end_inclusive=True) Segment(
Interval
和Segment
:
Interval
可以有start
和end
点,其中任何点都可以包含/排除(我已经使用了所需的标志参数,如start_inclusive
/end_inclusive
)段
是一个包含两个端点的间隔
,因此用户不需要指定这些标志Interval
,他会得到一个段
>>> Interval(0, 1, start_inclusive=True, end_inclusive=True)
Segment(0, 1)
()
间隔
等级:
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)
段
类:
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 ...>
失败,出现以下TypeError
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
TypeError:\uuuu init\uuuuuuuuuuuuuuuuuu()获得意外的关键字参数“end\u inclusive”
所以我的问题是:
在父类的
\uuuuu new\uuuuu
中有没有惯用的实例化子类的方法,使用\uuuu new\uuuuu
和\uuuuuu init\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu>的一些参数,由子类“绑定”
/p>之后调用\uuuu init uuuuuuuuu
时,您可以使用一个要自定义的元类来解决这个问题:
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 ...>
class IntervalMeta(类型):
定义调用(cls、*ARG、**kwargs):
obj=cls.\uuuuu新的\uuuuuu(cls,*args,**kwargs)
#仅当对象的类正是此类时才调用_init
如果类型(obj)为cls:
cls.\uuuuu初始值(obj,*args,**kwargs)
#与违约行为相反:
#如果存在(obj、cls):
#类型(obj)。_初始化(obj,*args,**kwargs)
返回obj
#除了元类之外,下面的代码不会更改
类间隔(元类=IntervalMeta):
定义新(cls,开始:int,结束:int,
*,
起点:布尔,
结束(包括:bool)->“间隔”:
如果cls不是类:
返回super()
如果开始=结束:
raise VALUERROR('找到退化间隔')
如果包括开始和结束:
返回段(开始、结束)
返回super()
定义初始化(自我,
起点:int,
完:int,,
*,
起点:布尔,
结束(包括:bool)->无:
self.start=开始
self.end=结束
self.start_inclusive=开始_inclusive
self.end_inclusive=end_inclusive
班段(间隔):
定义(cls,开始:int,结束:int)->“间隔”:
返回super()。\uuuu new\uuuu(cls,开始,结束,
start_inclusive=True,
结束(包括=真)
def uu init uu(self,start:int,end:int)->无:
super()。\uuuu初始化(开始,结束,
start_inclusive=True,
结束(包括=真)
打印(间隔(0,1,开始=真,结束=真))
#
让我们先看看为什么会出现错误。调用派生自的类时,将调用()的方法。通常是这样的
self = cls.__new__(...)
if isinstance(self, cls):
type(self).__init__(self)
class MyBase:
# put common functionality here
class Interval(MyBase):
# __new__ and __init__ same as before
class Segment(MyBase):
# __new__ and __init__ same as before
这只是一个近似值,但足以说明此处发生的情况:
type.\uuuu调用\uuuu
calls
由于start\u inclusive和end\u inclusive
,Interval.\uu new\uuu
正确返回段的实例
由于issubclass(Segment,Interval)
,type.\uuuu call\uuuu
调用Segment.\uuuu init\uuuuu
,其中包含传递给Interval的调用的所有参数
Segment.\uuuu init\uuuu
不接受任何关键字参数,并引发您看到的错误
对于这种情况,有许多变通办法。显示如何重写type
的行为,以便type.\uuuu call\uuuu
检查type(obj)是否为cls
,而不是使用isinstance
另一种选择是分离间隔
和段
的层次结构。你可以这样做
self = cls.__new__(...)
if isinstance(self, cls):
type(self).__init__(self)
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(段(…),Interval)
将是False
,并且类型。调用
将不会尝试调用Interval.\uu初始化
段
在我看来,最简单的方法是使用工厂模式。具有一个外部函数,该函数根据输入确定要返回的对象类型。这样,您根本不需要实现\uuuuu new\uuuuu
,并且类构造过程将简单得多:
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)
您是否有理由不希望用户选择他们想要的类?还有,在一个区间没有的线段中有什么特殊功能吗?@MadPhysician:对这两个问题都是肯定的,检查点是否在线段中会更快(我已经测量过了,是的,由于检查和区间的数量,这个小的改进很重要)因为我不需要找出哪个操作符
段和区间看起来有很大的不同,即使它们有共同的功能。如果他们可以共享一个共同的抽象基类,而不是在层次结构中保持一致,那么这两个概念都会得到更好的表达,您的实例化也会变得更简单?我觉得这个设计很可疑。如果cls不是代码的一部分,你到底想用做什么?基本上,当\uuuuuu new\uuuuuu
返回一个段时,段仍然是间隔的子类,所以它试图调用类型(obj)。
。但是,obj
在其\uuuu init\uuuu
中缺少两个关键字参数。我认为这个问题本身很有趣,但是是的,我肯定会为此选择一家工厂,称之为