基类的属性通过派生类改变(变得不可写)?

时间:2016-03-05 11:06:42

标签: python inheritance

考虑以下最小例子:

class base:
    def __init__(self, mass):
        self.mass = mass
    def price()
        return self.mass * 10


class deriv(base):
    def __init__(self, payload, *args, **kwargs):
        super().__init__(mass=0, *args, **kwargs)
        #mass of super is shadowed anyways, so setting it to 0
        self.payload = payload

    @property
    def mass(self):
        return self.payload #would be a more complex calculation
    #NO setter, since it's illogical due to it being complex

t = deriv(1000)

基本上,派生类“唯一性”是质量不再是可以设置的简单值。改变。但它基于一个基于其他几个新属性的复杂函数。

现在上面的代码“看起来”对我很好:mass被属性在派生类中隐藏。在基类初始值设定项中,派生类尚未完全存在,因此在那里写入应该写入基类属性(一个带阴影的对象)。然而,对于price函数,它应该查看派生类的属性。

所以我试着执行这个:

line 3, in __init__
    self.mass = mass
AttributeError: can't set attribute

好吧看起来好像在基类python的__init__期间已经知道派生类的属性。这是“奇怪的”,特别是考虑到另一个(愚蠢的)派生类,它确实起作用:

class deriv2(base):
    def __init__(self, mass, *args, **kwargs):
        super().__init__(mass=0, *args, **kwargs)
        self.mass = mass*2

那我该怎么做呢?我宁愿改变对象的界面(仍然想要deriv的质量属性)以及base的少量属性(deriv是“怪异的”一,所以一个人有责任确保他工作。)

3 个答案:

答案 0 :(得分:1)

您提交了设计错误。

Liskov Substitution Principle表示子类应该支持其基类的所有操作。与基类一起使用的代码应该透明地与派生类一起使用;此外,您不应该告诉您正在操作子类。

那么当我编写一些试图设置质量的代码时会发生什么?

def set_mass(item):
    item.mass = 123

如果itembase的实例,则调用set_mass将毫不费力地工作。运行该函数后,项目的质量将为123。期望此函数适用于满足isinstance(item, base)的任何对象是完全合理的。

但是,如果itemderiv的实例,则无法设置质量。此函数将抛出异常。 deriv违反了LSP,结果是代码损坏。

尝试从基类中删除属性是个坏主意。您应该尝试重新设计代码以避免这种继承方式。

答案 1 :(得分:1)

  

在基类初始值设定项中,派生类尚未完全   存在。

这不正确。两个类定义都已由时间控件达到t = deriv(1000)执行,因此在您尝试创建deriv的实例之前,这两个类类型都存在。

deriv.__init__方法super()内部是super(deriv, self)的简写,所以是的,您正在调用base.__init__方法,但是您使用self调用它将新deriv实例作为其第一个参数,因此它的所有操作都应用于deriv实例,而不是base的某个空灵实例。因此,您无法设置.mass,因为它将.mass deriv属性引用到.mass的普通base属性。

顺便说一句,为了符合PEP008风格,你应该使用CamelCase作为类名。

答案 2 :(得分:0)

PM 2Ring explains为什么self.mass = mass会引发错误(当self中的base.__init__引用deriv的实例时)。 Benjamin Hodgson explains为什么一个阻止设置质量的类​​不应该派生出来 从一个确定质量的人。

避免这些问题的一种方法是创建两个子类SettableMass(对于具有可设置质量的对象)和Deriv(对于具有计算质量的对象)并使它们都派生自{{1不承担质量的类是可设置的:

Base
PS:我不清楚为什么你需要class Base: def __init__(self, *args, **kwargs): pass def price(self): return self.mass * 10 class SettableMass(Base): def __init__(self, mass): super().__init__() self.mass = mass class Deriv(Base): def __init__(self, payload, *args, **kwargs): super().__init__(*args, **kwargs) self.payload = payload @property def mass(self): return self.payload t = Deriv(1000, 'foo', bar=3.1) print('mass {}, cost {}'.format(t.mass, t.price())) # mass 1000, cost 10000 s = SettableMass(90) print('mass {}, cost {}'.format(s.mass, s.price())) # mass 90, cost 900 Base课程,如果你计划将它们用于行星或恒星。例如,为什么一个星球会有价格?但是假设你有充分的理由让行星和卫星从公共基础继承方法,上面将提供一个合适的类层次结构。