考虑以下最小例子:
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
是“怪异的”一,所以一个人有责任确保他工作。)
答案 0 :(得分:1)
您提交了设计错误。
Liskov Substitution Principle表示子类应该支持其基类的所有操作。与基类一起使用的代码应该透明地与派生类一起使用;此外,您不应该告诉您正在操作子类。
那么当我编写一些试图设置质量的代码时会发生什么?
def set_mass(item):
item.mass = 123
如果item
是base
的实例,则调用set_mass
将毫不费力地工作。运行该函数后,项目的质量将为123
。期望此函数适用于满足isinstance(item, base)
的任何对象是完全合理的。
但是,如果item
是deriv
的实例,则无法设置质量。此函数将抛出异常。 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
课程,如果你计划将它们用于行星或恒星。例如,为什么一个星球会有价格?但是假设你有充分的理由让行星和卫星从公共基础继承方法,上面将提供一个合适的类层次结构。