【问题标题】:Use inherited class method within __init__在 __init__ 中使用继承的类方法
【发布时间】:2015-11-10 14:24:24
【问题描述】:

我有一个由几个孩子继承的父类。我想使用父母的 @classmethod 初始化器初始化其中一个孩子。我怎样才能做到这一点?我试过了:

class Point(object):
    def __init__(self,x,y):
        self.x = x
        self.y = y

    @classmethod
    def from_mag_angle(cls,mag,angle):
        x = mag*cos(angle)
        y = mag*sin(angle)
        return cls(x=x,y=y)


class PointOnUnitCircle(Point):
    def __init__(self,angle):
        Point.from_mag_angle(mag=1,angle=angle)


p1 = Point(1,2)
p2 = Point.from_mag_angle(2,pi/2)
p3 = PointOnUnitCircle(pi/4)
p3.x #fail

【问题讨论】:

  • @classmethodself?!这些并没有真正结合在一起。
  • 类方法的第一个参数通常命名为cls,以区别于实例self。您当然可以通过self 访问类方法,但在这种情况下,您不清楚为什么要这样做,因为类方法设置了一个类属性。
  • 您不能在__init__ 中分配给self 并期望它能够工作。如果类方法是备用构造函数,为什么不直接使用A10.from_half_a(5)
  • 类方法调用__init__,而不是相反。继承已经做了你想做的事,因为你在继承方法中使用了cls(...),而不是A(...)
  • @alex 这不是意见问题!当您从类方法内部调用cls(...) 时,它会在cls 代表的任何类上调用__init__(和/或__new__),从而创建一个新实例。 “我想要做的只是使用来自子类的父类的替代构造函数”,所以,再次,只需使用ChildClass.inherited_class_method(...)。参见例如stackoverflow.com/q/1015592/3001761, stackoverflow.com/q/1216356/3001761 为什么你不能分配给self

标签: python inheritance class-method


【解决方案1】:

如果你尝试这样写__init__,你的PointOnUnitCirclePoint 有不同的接口(因为它需要angle 而不是x, y),因此不应该是一个子类其中。像这样的东西怎么样:

class PointOnUnitCircle(Point):

    def __init__(self, x, y):
        if not self._on_unit_circle(x, y):
            raise ValueError('({}, {}) not on unit circle'.format(x, y))
        super(PointOnUnitCircle, self).__init__(x, y)

    @staticmethod
    def _on_unit_circle(x, y):
        """Whether the point x, y lies on the unit circle."""
        raise NotImplementedError

    @classmethod
    def from_angle(cls, angle):
        return cls.from_mag_angle(1, angle)

    @classmethod
    def from_mag_angle(cls, mag, angle):  
        # note that switching these parameters would allow a default mag=1
        if mag != 1:
            raise ValueError('magnitude must be 1 for unit circle')
        return super(PointOnUnitCircle, cls).from_mag_angle(1, angle)

这使接口保持不变,添加了检查子类输入的逻辑(一旦你编写了它!),并提供了一个新的类方法来轻松地从angle 构造一个新的PointOnUnitCircle。而不是

p3 = PointOnUnitCircle(pi/4)

你必须写

p3 = PointOnUnitCircle.from_angle(pi/4)

【讨论】:

    【解决方案2】:

    您可以重写子类的__new__ 方法以从超类的备用构造函数构造实例,如下所示。

    import math
    
    
    class Point(object):
    
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        @classmethod
        def from_polar(cls, radius, angle):
            x = radius * math.cos(angle)
            y = radius * math.sin(angle)
            return cls(x, y)
    
    
    class PointOnUnitCircle(Point):
    
        def __new__(cls, angle):
            point = Point.from_polar(1, angle)
            point.__class__ = cls
            return point
    
        def __init__(self, angle):
            pass
    

    请注意,在__new__ 中,point = Point.from_polar(1, angle) 行不能被 point = super().from_polar(1, angle) 替换,因为 Point 将自身作为备用构造函数的第一个参数发送,super() 将子类 PointOnUnitCircle 发送到备用构造函数构造函数,它循环调用调用它的子类的__new__,依此类推,直到出现RecursionError。另请注意,即使__init__ 在子类中为空,但不覆盖子类中的__init__,超类的__init__ 将在__new__ 之后立即自动调用,从而撤消备用构造函数。

    另外,一些对象设计使用组合比使用继承更简单。例如,您可以替换上面的 PointOnUnitCircle 类而不用以下类覆盖 __new__

    class UnitCircle:
    
        def __init__(self, angle):
            self.set_point(angle)
    
        def set_point(self, angle):
            self.point = Point.from_polar(1, angle)
    

    【讨论】:

    • 这正是我的回答所讨论的问题。您的 "subclass" 现在与父级具有完全不同的签名,如果您在其上调用 from_polar,则将直接发生坏事。您不能在使用父级的任何地方都使用它。实现的尴尬程度表明它是一个坏主意。
    • 好点。我必须将from_polar 变成一个静态方法来解决这个问题,这可能会导致更多问题。放弃与super 的兼容性并接触__new____class__ 似乎是危险信号。我很高兴你指出了这个缺陷。如果子类仅对超类的备用构造函数有意义,则似乎最好使用组合,因为即使使用您提供的无皱纹解决方案,子类的正常构造也不再有用,从这个意义上说,正如您所说,“你不能只在使用父级的地方使用它。”
    猜你喜欢
    • 2011-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-26
    • 1970-01-01
    • 2013-10-18
    • 2015-06-25
    • 2012-02-09
    相关资源
    最近更新 更多