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
    中缺少两个关键字参数。我认为这个问题本身很有趣,但是是的,我肯定会为此选择一家工厂,称之为