基本继承(在Python中)
大家好,
我有个关于继承的基本问题(在Python中)。我有两个类,其中一个是从另一个类继承的,像这样:
class p:
def __init__(self,name):
self.pname = name
class c(p):
def __init__(self,name):
self.cname = name
我能否创建一个父对象和几个子对象,这些子对象都指向同一个父对象?也就是说,父对象里有几个变量,每当我从子对象访问这些变量时,实际上是访问父对象里的变量。换句话说,如果我在一个子对象中修改了某个变量,其他子对象也会受到影响,并且这些数据在内存中只存储一次(而不是为每个子对象都复制一份...)
谢谢大家的帮助。
这里有一个可能的解决办法,但我觉得不是很好:
class P:
def __init__(self, name):
self.pname = name
class C:
def __init__(self, name,pobject):
self.pobject = pobject
self.cname = name
这真的是目前的最佳方案吗?还是说还有其他的概念?
塞巴斯蒂安
感谢大家的帮助,也谢谢你们对命名规范的建议 :) 但我还是不太满意。也许我可以给一个更复杂的例子,来强调我真正想做的事情。
class P:
data = "shareddata"
def __init__(self,newdata):
self.data = newdata
def printname(self):
print self.name
class C(P):
def __init__(self,name):
self.name = name
现在我可以这样做:
In [33]: c1 = test.C("name1")
In [34]: c2 = test.C("name2")
In [35]: c1.printname()
name1
In [36]: c2.printname()
name2
In [37]: c1.data
Out[37]: 'shareddata'
In [38]: c2.data
Out[38]: 'shareddata'
这正是我想要的。有一个变量名,对每个子对象来说都是不同的,而父类则访问这些各自的变量。这是正常的继承。然后还有一个变量数据,它来自父类,所有子对象都可以访问它。然而,现在以下的做法就不再有效了:
In [39]: c1.data = "tst"
In [40]: c2.data
Out[40]: 'shareddata'
In [41]: c1.data
Out[41]: 'tst'
我希望在c1.data中的变化也能影响到c2.data,因为我想让这个变量是共享的,某种意义上是父类的一个全局变量。
不仅如此,我还想创建不同的P实例,每个实例都有自己的数据变量。当我创建一个新的C对象时,我希望能够指定从哪个P对象中继承数据,也就是共享数据……
更新:
关于@eyquem的评论:谢谢你,这朝着我想要的方向发展。然而,现在的__class__.pvar
在所有类的对象之间是共享的。我希望多个P的实例可以有不同的pvar。假设P1的pvar=1,P2的pvar=2。那么我想创建与P1相关的子对象C1a、C1b、C1c,也就是说,如果我说C1a.pvar,它应该访问P1的pvar。然后我创建C2a、C2b、C2c,如果我访问C2b.pvar,我希望访问P2的pvar。由于类C从类P继承了pvar,C是知道pvar的。我的简单想法是,如果我创建一个新的C实例,我应该能够指定哪个(现有的)P对象应该用作父对象,而不是像在C的__init__
中调用P.__init__
时那样创建一个全新的P对象……这对我来说听起来很简单,也许我忘记了什么……
更新:
我找到了这个讨论,这基本上就是我的问题。
有什么建议吗?
更新:
方法.class._subclasses_似乎不再存在了……
更新:
这里还有一个链接:
那里是通过复制来解决的。但我不想复制父类,因为我希望它只存在一次……
更新:
抱歉昨天离开讨论,我有点生病……谢谢你们的帖子!我现在会仔细阅读它们。我再想了一下,这里是我找到的一个可能的解决方案:
class P(object):
def __init__(self,pvar):
self.pobject = None
self._pvar = pvar
@property
def pvar(self):
if self.pobject != None:
return self.pobject.pvar
else:
return self._pvar
@pvar.setter
def pvar(self,val):
if self.pobject != None:
self.pobject.pvar = val
else:
self._pvar=val
def printname(self):
print self.name
class C(P):
def __init__(self,name,pobject): #<-- The same default `P()` is
# used for all instances of `C`,
# unless pobject is explicitly defined.
P.__init__(self,None)
self.name = name
self.pobject = pobject
p1 = P("1")
p2 = P("2")
c1a = C("c1a",p1)
c1b = C("c1b",p1)
c1c = C("c1c",p1)
c2a = C("c2a",p2)
c2b = C("c2b",p2)
c2c = C("c2c",p2)
print id(c1a.pvar)
print id(c1b.pvar)
print id(c1c.pvar)
print id(c2a.pvar)
print id(c2b.pvar)
print id(c2c.pvar)
print id(p1.pvar)
print id(p2.pvar)
print id(c1a.name)
print id(c1b.name)
print id(c1c.name)
print id(c2a.name)
print id(c2b.name)
print id(c2c.name)
这有点麻烦,我希望有更简单的方法来实现这个。但它的特点是pvar只在类P中提到,而类C并不知道pvar,这正是我对继承的理解。然而,当我创建一个新的C实例时,我可以指定一个现有的P实例,这个实例会存储在变量pobject中。当访问变量pvar时,实际上是访问存储在这个变量中的P实例的pvar……
输出是:
3078326816
3078326816
3078326816
3074996544
3074996544
3074996544
3078326816
3074996544
156582944
156583040
156583200
156583232
156583296
156583360
我现在会阅读你们最后的评论,
祝一切顺利,塞巴斯蒂安
更新:
我认为最优雅的方法是以下这种(但这并不工作):
class P(object):
def __init__(self,pvar):
self.pvar = pvar
def printname(self):
print self.name
class C(P):
def __init__(self,name,pobject):
P = pobject
self.name = name
我认为Python应该允许这样做……
更新:
好吧,我现在找到了一种方法来实现这一点,感谢eyquem的解释。但由于这确实是一个技巧,所以应该有一个官方的版本来实现同样的功能……
def replaceinstance(parent,child):
for item in parent.__dict__.items():
child.__dict__.__setitem__(item[0],item[1])
print item
class P(object):
def __init__(self,pvar):
self.pvar = pvar
def printname(self):
print self.name
class C(P):
def __init__(self,name,pobject):
P.__init__(self,None)
replaceinstance(pobject,self)
self.name = name
p1 = P("1")
p2 = P("2")
c1a = C("c1a",p1)
c1b = C("c1b",p1)
c1c = C("c1c",p1)
c2a = C("c2a",p2)
c2b = C("c2b",p2)
c2c = C("c2c",p2)
print id(c1a.pvar)
print id(c1b.pvar)
print id(c1c.pvar)
print id(c2a.pvar)
print id(c2b.pvar)
print id(c2c.pvar)
print id(p1.pvar)
print id(p2.pvar)
print id(c1a.name)
print id(c1b.name)
print id(c1c.name)
print id(c2a.name)
print id(c2b.name)
print id(c2c.name)
输出与上面相同
3077745184
3077745184
3077745184
3074414912
3074414912
3074414912
3077745184
3074414912
144028416
144028448
144028480
144028512
144028544
144028576
更新:即使ID看起来是正确的,最后的代码也不工作,这从这个测试中可以看出:
c1a.pvar = "newpvar1"
print c1a.pvar
print c1b.pvar
print c1c.pvar
print c2a.pvar
print c2b.pvar
print c2c.pvar
print p1.pvar
print p2.pvar
输出是:
newpvar1
1
1
2
2
2
1
2
然而,我最初发布的版本是有效的:
class P(object):
def __init__(self,pvar):
self.pobject = None
self._pvar = pvar
@property
def pvar(self):
if self.pobject != None:
return self.pobject.pvar
else:
return self._pvar
@pvar.setter
def pvar(self,val):
if self.pobject != None:
self.pobject.pvar = val
else:
self._pvar=val
def printname(self):
print self.name
class C(P):
def __init__(self,name,pobject): #<-- The same default `P()` is
# used for all instances of `C`,
# unless pobject is explicitly defined.
P.__init__(self,None)
self.name = name
self.pobject = pobject
p1 = P("1")
p2 = P("2")
c1a = C("c1a",p1)
c1b = C("c1b",p1)
c1c = C("c1c",p1)
c2a = C("c2a",p2)
c2b = C("c2b",p2)
c2c = C("c2c",p2)
print id(c1a.pvar)
print id(c1b.pvar)
print id(c1c.pvar)
print id(c2a.pvar)
print id(c2b.pvar)
print id(c2c.pvar)
print id(p1.pvar)
print id(p2.pvar)
print id(c1a.name)
print id(c1b.name)
print id(c1c.name)
print id(c2a.name)
print id(c2b.name)
print id(c2c.name)
print "testing\n"
c1a.printname()
c1b.printname()
c1c.printname()
c2a.printname()
c2b.printname()
c2c.printname()
print "\n"
c1a.name = "c1anewname"
c2b.name = "c2bnewname"
c1a.printname()
c1b.printname()
c1c.printname()
c2a.printname()
c2b.printname()
c2c.printname()
print "pvar\n"
print c1a.pvar
print c1b.pvar
print c1c.pvar
print c2a.pvar
print c2b.pvar
print c2c.pvar
print p1.pvar
print p2.pvar
print "\n"
c1a.pvar = "newpvar1"
print c1a.pvar
print c1b.pvar
print c1c.pvar
print c2a.pvar
print c2b.pvar
print c2c.pvar
print p1.pvar
print p2.pvar
print "\n"
c2c.pvar = "newpvar2"
print c1a.pvar
print c1b.pvar
print c1c.pvar
print c2a.pvar
print c2b.pvar
print c2c.pvar
print p1.pvar
print p2.pvar
输出是:
3077745184
3077745184
3077745184
3074414912
3074414912
3074414912
3077745184
3074414912
144028416
144028448
144028480
144028512
144028544
144028576
testing
c1a
c1b
c1c
c2a
c2b
c2c
c1anewname
c1b
c1c
c2a
c2bnewname
c2c
pvar
1
1
1
2
2
2
1
2
newpvar1
newpvar1
newpvar1
2
2
2
newpvar1
2
newpvar1
newpvar1
newpvar1
newpvar2
newpvar2
newpvar2
newpvar1
newpvar2
有没有人知道为什么会这样?我可能对Python如何处理这个__dict__
的内部机制理解得不够透彻……
8 个回答
另一个回答是对的,你的问题其实更关乎命名空间和引用,而不是继承。
在Python中,所有的变量都是引用,而每个对象实例都是一个命名空间。所以你可以这样做:
class C():
def __init__(self, x):
self.x = x
class Shared(object):
def __init__(self, value):
self.value = value
# instances:
>>> shared1 = Shared(1)
>>> shared2 = Shared(2)
>>> c1 = C(shared1)
>>> c2 = C(shared1)
>>> c3 = C(shared2)
>>> c4 = C(shared2)
# c1 and c2 sharing a reference to shared1
>>> c1.x.value
1
>>> c2.x.value
1
# change c2.x will reflect on c1
>>> c2.x.value = 3
>>> c1.x.value
3
# but not on c3, because shared1 and shared2 are distinct namespaces
>>> c3.x.value
2
更新:
不过要小心,容易出错:
>>> c4.x = 4
>>> c3.x.value
2
>>> c4.x.value
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
AttributeError: 'int' object has no attribute 'value'
>>>
我觉得最好的做法是使用属性来隐藏__shared_instance
,把它放在一个私有实例变量里,这样你可以用c1.x而不是c1.x.value,避免像上面那样的拼写错误。
我觉得你的解决办法是可行的;你可以使用属性来让访问 P
的属性变得更简单:
class P(object):
def __init__(self,name='default',pvar=1):
self.pname = name
self.pvar=pvar
class C(object):
def __init__(self,name,pobject=P()): #<-- The same default `P()` is
# used for all instances of `C`,
# unless pobject is explicitly defined.
self.cname = name
self.pobject=pobject
@property
def pvar(self):
return self.pobject.pvar
@pvar.setter
def pvar(self,val):
self.pobject.pvar=val
c1=C('1')
c2=C('2')
c1
和 c2
共享同一个 pobject
:
print(c1.pvar)
# 1
c1.pvar=2
注意,通过 c1
改变 pvar
会影响到 c2.pvar
:
print(c2.pvar)
# 2
c3
有一个不同的 pobject
:
c3=C('3',P())
print(c3.pvar)
# 1
关于心理实验的面向对象设计(在评论中提到的):
import Image
class Picture(object):
def __init__(self,filename):
self.filename = filename
self.image=Image.open(filename)
class Person(object):
def __init__(self,name):
self.name=name
# other vital statistics associated with people as individuals here
class Trial(object):
# A trial is composed of one person, one picture, and the places they look
def __init__(self,person,picture,locations):
self.person=person
self.picture=picture
self.locations = locations
# put methods for analyzing where the person looked here
Picture
绝对不是 Person
,反之亦然。Trial
也是如此。所以这些类之间不应该相互继承。
每个类都有一个公共(可能还有一个私有)接口。公共的方法和属性应该可以被其他类自由访问。所以给定一个 Trial
实例 t
,图像应该可以通过 t.picture.image
访问。只要你访问的是公共属性和方法,一切都应该没问题。
为了方便,你可以使用 properties
将属性链接到组件属性。例如:
class Trial(object):
...
@property
def image(self):
return self.picture.image
但是,如果你为了简化操作,让 Trial
成为 Picture
的子类,那就违背了基本的面向对象设计原则。
应该是这样的:父对象里有几个变量,每当我从子对象访问这些变量时,其实是访问父对象里的变量。也就是说,如果我在一个子对象里修改了这个变量,其他所有子对象里的这个变量也会跟着改变,而且这些数据在内存中只存储一次(而不是为每个子对象都复制一份...)
这不是继承。
这是一个完全不同的概念。
你说的“共享变量”其实就是可以被修改的对象,并且在其他对象中有引用。没什么特别的。
继承和这个完全不一样。