基本继承(在Python中)

3

用户,

我有一个关于继承(在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

这是否真的是最先进的技术,还是存在其他概念?

Sebastian

非常感谢大家帮助我,包括名称规范方面的帮助 :) 但我仍然不太满意。也许我可以举一个更高级的例子来强调我真正想做的事情。

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继承pvar自类P,因此pvar为C所知。我的天真想法是,如果我创建一个新的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)

这有点繁琐,希望有更简单的方法实现。但它具有一个特性,即只在类P中提到了pvar,而根据我的继承理解,类C不知道pvar的存在。然而,当我创建C的新实例时,可以指定一个现有的P实例,该实例将存储在变量pobject中。当访问变量pvar时,实际上是访问存储在该变量中的P实例的pvar...
输出如下:
3078326816
3078326816
3078326816
3074996544
3074996544
3074996544
3078326816
3074996544
156582944
156583040
156583200
156583232
156583296
156583360

我现在会阅读你最近的评论,祝一切顺利,Sebastian

更新:

我认为最优雅的方式是以下方式(但不起作用)

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的解释。但是,由于这只是一个hack,应该有一个官方版本的相同...
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__的内部方式...


3
请使用大写字母为您的类命名。 - S.Lott
类名使用驼峰命名法,实例名使用下划线命名法。 - Paulo Scardine
假设P1具有pvar = 1,P2具有pvar = 2。为了指定应使用哪个(现有的)P对象 => 那么是否存在多个P类?! - eyquem
@eyquem:我添加了一个解决方案(在我看来不是很好),它具有我想要的行为。但我认为应该能够更好地完成此操作.... - r6d9
有一个明显的方法可以做到这一点——使用适当的方式。 - phooji
你把一个简单的问题搞成了一个难以理解的混乱。请编辑问题,确保它只有一个问题和一个答案。每一个微妙的想法都不应该是问题的一部分。 - S.Lott
8个回答

5
它应该像这样工作,父对象包含多个变量,每当我从子对象访问相应的变量时,我实际上是从父对象访问变量。也就是说,如果我为一个子代更改它,那么对于所有其他子代也会更改它,并且数据仅在内存中存储一次(而不是为每个子代复制...)
那不是继承。
那是完全不同的概念。
您的“共享变量”只是可以被突变并在其他对象中具有引用的对象。没有什么有趣的。
继承与此完全不同。

问:是否可以在不按任何按钮的情况下直接从HTML调用Perl?答:是否可能在成为星期三之前从一幅画中种出蔬菜?[阿比盖尔](摘自:http://www.utdallas.edu/~asimpson/misc/funny/abigailisms)。 - phooji

1

我认为你的解决方法可行;你可以使用属性来更轻松地访问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')

c1c2 共享相同的 pobject

print(c1.pvar)
# 1
c1.pvar=2

注意,通过更改会更改


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的子类来简化这个过程,将违反基本的面向对象设计原则。


@Paulo Scardine:谢谢,Paulo。我倾向于不使用双下划线。请参见https://dev59.com/83VC5IYBdhLWcg3w2k-I#166098底部的原因。 - unutbu
@r6d9:你可以使用__getattr____setattr__来代替定义属性,但是__getattr__有一些令人惊讶的陷阱(http://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html),因此我更喜欢显式属性而不是`__getattr__`,即使它更冗长。 - unutbu
@r6d9:我看了你提出的代码(其中P中有self.pobject)。在我的看法中,让self(即PC的实例)拥有一个名为self.pobject的属性,而该属性本身又是P的一个实例,这似乎非常奇怪。我不认为这是正确的做法。我知道我有偏见,但我更喜欢我的方法 :)。 - unutbu
@r6d9:需要解决的第一件事是:C 是否是 PC 是否是 P 的专门类型?您似乎需要将 P 的实例传递到 C.__init__ 中,这表明 C 实际上不是 P 的子类。相反,C 拥有一个 P。这导致我们需要组合而不是继承。 - unutbu
@r6d9 我对你的问题很感兴趣。它让我想到了类的工作原理,这是一个很好的练习,可以锻炼自己的能力,判断处理问题的方式是否合理。我感觉你的方法不太好,但我无法准确地解释为什么。我尝试了很多想法,但都没有结果。我不知道该怎么做,但我会继续思考这个问题。 - eyquem
显示剩余20条评论

1
另一个答案是正确的,你的问题更多涉及命名空间和引用,而不是继承。
在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,并避免像上面的例子中那样的拼写错误。


@Paulo Scardine:我认为这正是我建议的解决方法。如果这就是现在的最佳方案,那我就会这样做。感谢您的确认! - r6d9
@Paulo Scardine:请看我对unutbu答案的评论,你觉得呢? - r6d9
@r6d9:unutbu的回答是如何在Python中实现它。在我看来,这并不丑陋或是一种变通方法,而且比使用全局变量要好得多。 - Paulo Scardine
@Paulo Scardine 这段代码中没有继承。这是一个带有赋值和底层字典/命名空间的游戏。我稍后会做一个关于这个的回答。 - eyquem
@eyquem:我认为继承并不是实现他想要的目标的好策略。 - Paulo Scardine
@Paulo Scardine 但事实是他确实想要继承,由于我们不知道他的目标是什么,我们不能断言他的意图是错误的,也不能说他不应该使用继承。 - eyquem

0

我被所有这些不同的答案弄得迷失了方向。

但我认为你需要的内容在以下代码中表达:

class P:
    pvar=1                    # <--- class attribute
    def __init__(self,name):
        self.cname  = name

class C(P):
    def __init__(self,name):
        self.cname  = name


c1=C('1')
c2=C('2')

print
print "C.pvar ==",C.pvar,'  id(C.pvar) ==',id(C.pvar)
print "c1.pvar==",c1.pvar,'  id(c1.pvar)==',id(c1.pvar)
print "c2.pvar==",c2.pvar,'  id(c2.pvar)==',id(c2.pvar)

print
C.pvar = [1,2]
print "instruction   C.pvar = [1,2]   executed"
print "C.pvar ==",C.pvar,'  id(C.pvar) ==',id(C.pvar)
print "c1.pvar==",c1.pvar,'  id(c1.pvar)==',id(c1.pvar)
print "c2.pvar==",c2.pvar,'  id(c2.pvar)==',id(c2.pvar)

print
c2.__class__.pvar = 'sun'
print "instruction   c2.__class__.pvar = 'sun'   executed"
print "C.pvar ==",C.pvar,'  id(C.pvar) ==',id(C.pvar)
print "c1.pvar==",c1.pvar,'  id(c1.pvar)==',id(c1.pvar)
print "c2.pvar==",c2.pvar,'  id(c2.pvar)==',id(c2.pvar)

print
c2.pvar = 145
print "instruction   c2.pvar = 145   executed"
print "C.pvar ==",C.pvar,'  id(C.pvar) ==',id(C.pvar)
print "c1.pvar==",c1.pvar,'  id(c1.pvar)==',id(c1.pvar)
print "c2.pvar==",c2.pvar,'  id(c2.pvar)==',id(c2.pvar)

结果

C.pvar == 1   id(C.pvar) == 10021768
c1.pvar== 1   id(c1.pvar)== 10021768
c2.pvar== 1   id(c2.pvar)== 10021768

instruction   C.pvar = [1,2]   executed
C.pvar == [1, 2]   id(C.pvar) == 18729640
c1.pvar== [1, 2]   id(c1.pvar)== 18729640
c2.pvar== [1, 2]   id(c2.pvar)== 18729640

instruction   c2.__class__.pvar = 'sun'   executed
C.pvar == sun   id(C.pvar) == 18579136
c1.pvar== sun   id(c1.pvar)== 18579136
c2.pvar== sun   id(c2.pvar)== 18579136

instruction   c2.pvar = 145   executed
C.pvar == sun   id(C.pvar) == 18579136
c1.pvar== sun   id(c1.pvar)== 18579136
c2.pvar== 145   id(c2.pvar)== 10022024

我的意思是,你必须知道的是,如果要通过直接涉及实例名称(而不是仅涉及父类名称的更改)的指令来更改类属性pvar,同时它仍然被所有P的实例共享,那么你必须编写:

c2.__class__.pvar = something 

而不是

c2.pvar =something

请注意,C是一个有效继承自父类P的类。

@r6d9 我强调这段代码中有继承,与Paulo Scardine的答案相反。 - eyquem

0

作为理解类和实例运行方式的基础,你必须知道一件事情:

类实例有一个命名空间,实现为字典,是属性引用搜索的第一个位置

当在该位置未找到属性时,如果实例的类有该名称的属性,则继续搜索类属性。

http://docs.python.org/reference/datamodel.html#the-standard-type-hierarchy

在第二句话中,“by that name”的意思我不太明白,但我理解全局属性首先在实例的命名空间中搜索,然后在其类型的命名空间中搜索。

在以下代码中:

  • 对于类P和实例c

名字'dataclass'和对象dataclass实际上属于类P的命名空间,而只是表面上属于实例c的命名空间:当调用c.dataclass时,实际上获得的是c.__class__.dataclass,通过上述搜索过程。

  • 但在类PP的实例cc中,属于类P的命名空间的名字'data'被分配(绑定)到__init__()中定义的在实例c的命名空间中创建的新data对象。

因此,获取类的data值的唯一解决方案是通过其真实引用调用它,即PP.datacc.__class__.data

class   P:
    dataclass    = "shareddata"

    def __init__(self,newdata):
        self.data   = newdata

    def printname(self):
        print   self.name

c = P(1)

print 'P.__dict__.keys()==',P.__dict__.keys()
print 'c.__dict__.keys()==',c.__dict__.keys()
print  
print 'c.data==',c.data
print 'c.dataclass==',c.dataclass


print 

class   PP:
    data    = "shareddata"

    def __init__(self,newdata):
        self.data   = newdata

    def printname(self):
        print   self.name

cc = PP(2)
print 'PP.__dict__.keys()==',PP.__dict__.keys()
print 'cc.__dict__.keys()==',cc.__dict__.keys()
print
print 'cc.data==',cc.data
print 'PP.data==',PP.data
print 'cc.__class__.data==',cc.__class__.data

结果

P.__dict__.keys()== ['dataclass', '__module__', 'printname', '__init__', '__doc__']
c.__dict__.keys()== ['data']

c.data== 1
c.dataclass== shareddata

PP.__dict__.keys()== ['__module__', 'data', 'printname', '__init__', '__doc__']
cc.__dict__.keys()== ['data']

cc.data== 2
PP.data== shareddata
cc.__class__.data== shareddata

.

注意:

dir([object])

带有参数时,尝试返回该对象的有效属性列表。

如果对象没有提供dir()函数,则该函数会尽最大努力从对象的dict属性(如果定义了)和其类型对象中收集信息。

默认的dir()机制对不同类型的对象的行为不同,因为它试图生成最相关而不是完整的信息:

• 如果对象是类型或类对象,则列表包含其属性的名称,以及其基类的属性的递归名称。

• 否则,列表包含对象的属性名称、其类的属性名称,以及其类的基类的属性的递归名称。

http://docs.python.org/library/functions.html#dir

.

因此,使用dir(ob)来显示对象ob的属性是一种陷阱,因为它显示了比严格属于该对象的更多属性。
换句话说,__dict__才是真正的东西,而dir()在某种意义上提供了一个仪表板。

感谢您概述实例/对象概念的详细信息。我已将其用于新的实现,请参见上文。 - r6d9

0
r6d9,请在编写更新时,在“UPDATE”一词旁注明日期和时间。跟踪所有这些内容开始变得复杂了。

.

.

关于你的这段代码:

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

它可以被这个替换:

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)
        self.pvar = pobject.pvar
        self.name   = name

但这似乎太简单了


抱歉没有写日期,现在有点乱了……我认为你的代码也违反了oop原则,即类C不应该知道类P有一个变量pvar。你怎么看? - r6d9
@r6d9 我认为我不知道OO的原则,除了一个实例将数据聚集在字段和方法中以处理数据之外。此外:_方法可以像普通函数一样引用全局名称。与方法相关联的全局范围是包含类定义的模块。(类本身永远不会用作全局范围。)虽然很少遇到在方法中使用全局数据的好理由,但有许多合法使用全局范围的情况_(http://docs.python.org/tutorial/classes.html#random-remarks) - eyquem
顺便说一下,@r6d9,P.init(self,None) 要求 C 知道 P 有一个 **__init__ 函数。 - eyquem
感谢您在这个问题上的辛勤工作。我不确定是否正确,但我认为OO也意味着继承自另一个类的类只需了解有关父类的最少信息。特别是内部结构(如私有于父类的变量)不应该被“知道”到子类。至少这是我认为良好设计的一部分...我认为子类应该仅知道和使用与父类通信的明确定义接口,如果必要的话,我会认为__init__是其中的一部分。 - r6d9

0

最终,我找到了一种方法来实现它。

关键是放弃获取具有真实 pvar 字段的实例c的目标,因为这是不可能的:

由于处理创建对象pvar的是相同的_init_()函数(位于类P中),因此无法在实例c中创建pvar,该实例将指向实例p中的pvar以反映其值,并且还将提供每次更改cpvar的值时更改ppvar值的可能性。这使得要验证的条件太多矛盾了。

因此,由于实例c不能有真正的pvar字段,最好设置一种机制来控制(使用_setattr_)创建和访问(使用_getattr_)这些c的表面上的pvar对象,以给人一种它们存在的错觉。
class P(object):
    def __init__(self,pvar_arg,foo="^^^ "):
        self.pvar = pvar_arg
        self.cat  = foo
    def printname(self):
        print self.name

class C(P):

    def __init__(self,name,pobject,foo=''):
        self.__dict__['name'] = name
        P.__init__(self,None,pobject.cat+foo)
        C.dic[name] = pobject

    def __setattr__(self,xn,val):
        if xn!='pvar':
            self.__dict__[xn] = val
        elif self.name in C.dic:
            # During the creation of an instance c,
            # this condition is False because the instruction
            # C.dic[name] = pobject is  written after 
            # P.__init__(self,None,pobject.cat+foo).
            # Hence the value of pobject.pvar is preserved,
            # not changed with the value val being None
            # due to P.__init__(self,None,pobject.cat+foo)
            # that provokes self.pvar = pvar_arg and
            # consequently a call __setattr__(self,'pvar',None)
            C.dic[self.name].pvar = val

    def __getattribute__(self,xn):
        if xn=='pvar':
            return object.__getattribute__(C.dic[self.name],'pvar')
        else:
            return object.__getattribute__(self,xn)

    dic = {}


p1  = P("1")
p2  = P("2","QZX ")
print '--- p1  = P("1")  and  p2  = P("2","QZX ")  executed ---'
print "p1.__dict__  ==",p1.__dict__
print "p2.__dict__  ==",p2.__dict__
print 'p1.pvar==',p1.pvar
print 'p2.pvar==',p2.pvar

c1a = C("c1a",p1,'sea')
c1b = C("c1b",p1,'mountain')
c1c = C("c1c",p1,'desert')
c2a = C("c2a",p2,'banana')
c2b = C("c2b",p2)
c2c = C("c2c",p2,'pear')
print '\n--- creations of c1a, c1b, c1c, c2a, c2b, c2c executed ---'
print "p1.__dict__  ==",p1.__dict__
print "p2.__dict__  ==",p2.__dict__
print "c1a.__dict__ ==",c1a.__dict__
print "c1b.__dict__ ==",c1b.__dict__
print "c1c.__dict__ ==",c1c.__dict__
print "c2a.__dict__ ==",c2a.__dict__
print "c2b.__dict__ ==",c2b.__dict__
print "c2c.__dict__ ==",c2c.__dict__
print 'p1.pvar==',p1.pvar
print 'p2.pvar==',p2.pvar
print '(c1a.pvar, c1b.pvar, c1c.pvar)==',(c1a.pvar,c1b.pvar,c1c.pvar)
print '(c2a.pvar, c2b.pvar, c2c.pvar)==',(c2a.pvar,c2b.pvar,c2c.pvar)

c1a.pvar = "newpvar1"
print '\n--- c1a.pvar = "newpvar1"  executed ---'
print 'p1.pvar==',p1.pvar
print 'p2.pvar==',p2.pvar
print '(c1a.pvar, c1b.pvar, c1c.pvar)==',(c1a.pvar,c1b.pvar,c1c.pvar)
print '(c2a.pvar, c2b.pvar, c2c.pvar)==',(c2a.pvar,c2b.pvar,c2c.pvar)

c2c.pvar = 45789
print '\n--- c2c.pvar = 45789  executed ---'
print 'p1.pvar==',p1.pvar
print 'p2.pvar==',p2.pvar
print '(c1a.pvar, c1b.pvar, c1c.pvar)==',(c1a.pvar,c1b.pvar,c1c.pvar)
print '(c2a.pvar, c2b.pvar, c2c.pvar)==',(c2a.pvar,c2b.pvar,c2c.pvar)

结果

--- p1  = P("1")  and  p2  = P("2","QZX ")  executed ---
p1.__dict__  == {'cat': '^^^ ', 'pvar': '1'}
p2.__dict__  == {'cat': 'QZX ', 'pvar': '2'}
p1.pvar== 1
p2.pvar== 2

--- creations of c1a, c1b, c1c, c2a, c2b, c2c executed ---
p1.__dict__  == {'cat': '^^^ ', 'pvar': '1'}
p2.__dict__  == {'cat': 'QZX ', 'pvar': '2'}
c1a.__dict__ == {'name': 'c1a', 'cat': '^^^ sea'}
c1b.__dict__ == {'name': 'c1b', 'cat': '^^^ mountain'}
c1c.__dict__ == {'name': 'c1c', 'cat': '^^^ desert'}
c2a.__dict__ == {'name': 'c2a', 'cat': 'QZX banana'}
c2b.__dict__ == {'name': 'c2b', 'cat': 'QZX '}
c2c.__dict__ == {'name': 'c2c', 'cat': 'QZX pear'}
p1.pvar== 1
p2.pvar== 2
(c1a.pvar, c1b.pvar, c1c.pvar)== ('1', '1', '1')
(c2a.pvar, c2b.pvar, c2c.pvar)== ('2', '2', '2')

--- c1a.pvar = "newpvar1"  executed ---
p1.pvar== newpvar1
p2.pvar== 2
(c1a.pvar, c1b.pvar, c1c.pvar)== ('newpvar1', 'newpvar1', 'newpvar1')
(c2a.pvar, c2b.pvar, c2c.pvar)== ('2', '2', '2')

--- c2c.pvar = 45789  executed ---
p1.pvar== newpvar1
p2.pvar== 45789
(c1a.pvar, c1b.pvar, c1c.pvar)== ('newpvar1', 'newpvar1', 'newpvar1')
(c2a.pvar, c2b.pvar, c2c.pvar)== (45789, 45789, 45789)

备注:

  1. 指令

    P.init(self,None,pobject.cat+foo)

    中必须先定义属性 name,因为执行该指令会调用 __setattr__(self,'pvar',"1"),该指令本身又会执行指令 C.dic[self.name].pvar = "1"。例如,当执行 c1a = C("c1a",p1,'sea') 时,就需要将 self.name 作为字典的键。

  2. 我引入了 foocat 来说明需要编写指令

    P.init(self,None,pobject.cat+foo)

    的原因。否则,由于实例 c 中实际上没有定义 pvar,该指令将毫无用处。

  3. __setattr__ 被调用有两种情况:在创建实例时和修改现有实例的属性时。在创建实例时,实例 ppvar 值必须不受指令 C.dic[self.name].pvar = None 的影响。因此,条件 elif self.name in C.dic: 必须得到正确的结果,指令 C.dic[name] = pobject 必须跟随调用 P.__init__(self,None,pobject.cat+foo)

.

编辑 1

我认为最好写成:

def __setattr__(self,xn,val):
    if xn=='pvar':
        self.__class__.dic[self.name].pvar = val

def __setattr__(self,xn,val):
    if xn=='pvar':
        C.dic[self.name].pvar = val

在第一种情况下,解释器必须在self的命名空间中搜索指向self所属类C(即以“_class_”为名称)的引用。

在第二种情况下,解释器必须在定义类P和C的级别的命名空间中搜索相同的引用(但以名称“C”表示)。

与实例的有限命名空间相比,第二个命名空间可能更大。在第一种情况下,“_class_”这个名称作为self命名空间实现字典中的键进行查找。在第二种情况下,“C”是在包含类P和C的字典中寻找的关键词。

这两个对象的身份可以通过函数id()进行验证。

.

.

编辑2

dic 对象还有另一种可能性:可以将它定义在 C 类的外部范围,而不是将它作为类属性。如果这个外部级别是一个模块,那么 dic 就是一个全局对象。

class P(object):
    def __init__(self,pvar,foo="^^^ "):
        self.pvar   = pvar
        self.cat = foo
    def printname(self):
        print self.name

class C(P):

    def __init__(self,name,pobject,foo=''):
        self.__dict__['name'] = name
        P.__init__(self,None,pobject.cat+foo)
        dic[name] = pobject

    def __setattr__(self,xn,val):
        if xn!='pvar':
            self.__dict__[xn] = val
        elif self.name in dic:
            # During the creation of an instance c,
            # this condition is False because the instruction
            # dic[name] = pobject is  written after 
            # P.__init__(self,None,pobject.cat+foo).
            # Hence the value of pobject.pvar is preserved,
            # not changed with the value val being None
            # due to P.__init__(self,None,pobject.cat+foo)
            # that provokes self.pvar = pvar_arg and
            # consequently a call __setattr__(self,'pvar',None)
            dic[self.name].pvar = val

    def __getattribute__(self,xn):
        if xn=='pvar':
            return object.__getattribute__(dic[self.name],'pvar')
        else:
            return object.__getattribute__(self,xn)


dic = {}

结果完全相同

这样做,dic 就失去了面向对象的特性。

.

.

编辑3

最后,还有另一种方法:不是通过使用函数__setattr____getattribute__为每个实例c创建虚假属性pvar,而是更好的方法是使用一个带有字典dic作为默认参数的函数来替换它们。

class P(object):
    def __init__(self,pvar,foo="^^^ "):
        self.pvar   = pvar
        self.cat = foo
    def printname(self):
        print self.name

class C(P):
    def __init__(self,name,pobject,foo=''):
        P.__init__(self,None,pobject.cat+foo)
        self.__dict__['name'] = name
        del self.pvar
        self.pvar(pobject)

    def pvar(self,x = None,dic = {}):
        if x.__class__==P: # a pobject
            dic[self.name] = x
        elif x: # a value
            dic[self.name].pvar = x
        else: # to return value
            return dic[self.name].pvar

p1  = P("1")
p2  = P("2","QZX ")
print '--- p1  = P("1")  and  p2  = P("2","QZX ")  executed ---'
print "p1.__dict__  ==",p1.__dict__
print "p2.__dict__  ==",p2.__dict__
print 'p1.pvar==',p1.pvar
print 'p2.pvar==',p2.pvar

c1a = C("c1a",p1,'sea')
c1b = C("c1b",p1,'mountain')
c1c = C("c1c",p1,'desert')
c2a = C("c2a",p2,'banana')
c2b = C("c2b",p2)
c2c = C("c2c",p2,'pear')
print '\n--- creations of c1a, c1b, c1c, c2a, c2b, c2c executed ---'
print "p1.__dict__  ==",p1.__dict__
print "p2.__dict__  ==",p2.__dict__
print "c1a.__dict__ ==",c1a.__dict__
print "c1b.__dict__ ==",c1b.__dict__
print "c1c.__dict__ ==",c1c.__dict__
print "c2a.__dict__ ==",c2a.__dict__
print "c2b.__dict__ ==",c2b.__dict__
print "c2c.__dict__ ==",c2c.__dict__
print 'p1.pvar==',p1.pvar
print 'p2.pvar==',p2.pvar
print   '(c1a.pvar(),c1b.pvar(),c1c.pvar())==',(c1a.pvar(),c1b.pvar(),c1c.pvar())
print   '(c2a.pvar(),c2b.pvar(),c2c.pvar())==',(c2a.pvar(),c2b.pvar(),c2c.pvar())

c1a.pvar("newpvar1")
print '\n--- c1a.pvar("newpvar1")  executed ---'
print 'p1.pvar==',p1.pvar
print 'p2.pvar==',p2.pvar
print   '(c1a.pvar(),c1b.pvar(),c1c.pvar())==',(c1a.pvar(),c1b.pvar(),c1c.pvar())
print   '(c2a.pvar(),c2b.pvar(),c2c.pvar())==',(c2a.pvar(),c2b.pvar(),c2c.pvar())

c2c.pvar(45789)
print '\n--- c2c.pvar(45789) ---'
print 'p1.pvar==',p1.pvar
print 'p2.pvar==',p2.pvar
print   '(c1a.pvar(),c1b.pvar(),c1c.pvar())==',(c1a.pvar(),c1b.pvar(),c1c.pvar())
print   '(c2a.pvar(),c2b.pvar(),c2c.pvar())==',(c2a.pvar(),c2b.pvar(),c2c.pvar())

结果是一样的,只是使用c.pvar()的方式略有不同:

--- p1  = P("1")  and  p2  = P("2","QZX ")  executed ---
p1.__dict__  == {'cat': '^^^ ', 'pvar': '1'}
p2.__dict__  == {'cat': 'QZX ', 'pvar': '2'}
p1.pvar== 1
p2.pvar== 2

--- creations of c1a, c1b, c1c, c2a, c2b, c2c executed ---
p1.__dict__  == {'cat': '^^^ ', 'pvar': '1'}
p2.__dict__  == {'cat': 'QZX ', 'pvar': '2'}
c1a.__dict__ == {'cat': '^^^ sea', 'name': 'c1a'}
c1b.__dict__ == {'cat': '^^^ mountain', 'name': 'c1b'}
c1c.__dict__ == {'cat': '^^^ desert', 'name': 'c1c'}
c2a.__dict__ == {'cat': 'QZX banana', 'name': 'c2a'}
c2b.__dict__ == {'cat': 'QZX ', 'name': 'c2b'}
c2c.__dict__ == {'cat': 'QZX pear', 'name': 'c2c'}
p1.pvar== 1
p2.pvar== 2
(c1a.pvar(),c1b.pvar(),c1c.pvar())== ('1', '1', '1')
(c2a.pvar(),c2b.pvar(),c2c.pvar())== ('2', '2', '2')

--- c1a.pvar("newpvar1")  executed ---
p1.pvar== newpvar1
p2.pvar== 2
(c1a.pvar(),c1b.pvar(),c1c.pvar())== ('newpvar1', 'newpvar1', 'newpvar1')
(c2a.pvar(),c2b.pvar(),c2c.pvar())== ('2', '2', '2')

--- c2c.pvar(45789) ---
p1.pvar== newpvar1
p2.pvar== 45789
(c1a.pvar(),c1b.pvar(),c1c.pvar())== ('newpvar1', 'newpvar1', 'newpvar1')
(c2a.pvar(),c2b.pvar(),c2c.pvar())== (45789, 45789, 45789)

请注意,在这段代码中,P的实例不能是C的实例值,因为传递给c.pvar()方法的pobject永远不会被视为一个值。

0

好的,我认为您可能需要重新表达您的问题:

如何扩展Python的面向对象编程,使继承在对象级别而不是类级别上工作?

首先 - 不要搞乱字典: 如果您只是将父字典的条目复制到子字典中,这对于实例化来说是有效的,但是任何字典中的更改都不会自动更新所有其他字典中的条目。将新值分配给属性将仅创建一个新引用,因此该属性将不再指向同一对象。

解决方案是使用一些Python魔法告诉Python在正确的位置查找属性...

class ProxyMixin (object):

    def __init__(self, parent):
        self.parent = parent

    def __getattribute__(self, name):
        if name != 'parent' and hasattr(self.parent, name):
            return getattr(self.parent, name)
        else:
            return object.__getattribute__(self, name)

    def __setattr__(self, name, val):
        if name != 'parent' and hasattr(self.parent, name):
            setattr(self.parent, name)
        else:
            object.__setattr__(self, name, val)

(请参阅Python属性访问的参考
只需将ProxyMixin添加到您的子类中,您就可以了。
class P:
    data = "shared data"
    def __init__(self, name):
        self.name = name
    def printname(self):
        print self.name

class C(P, ProxyMixin):
    def __init__(self, parent=None):
        if parent:
            ProxyMixin.__init__(self, parent)

现在你可以使用简单的赋值语句随时动态重连父对象:
c.parent = newparent

非常感谢您的帖子,我会阅读关于__getattribute__和__setattr__的内容,并尝试您的代码... - r6d9
请您举一个带有实际数值的例子,谢谢。 - eyquem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接