今天在测试的时候发现一个很诡异的问题,语言描述不清楚,直接看代码吧。为了测试各种可能性,我写了两种类继承的代码如下:
#!/usr/bin/python
#-*- coding: UTF-8 -*-
import re
import sys
import os
import json
import simplejson
class Foo(object):
_data = 1
def __init__(self,data):
self._data = data
def ShowFoo(self):
print 'Foo',self._data
class Bar(Foo):
def __init__(self,data):
super(Bar,self).__init__(data)
def ShowBar(self):
#会报错
#super(Bar,self)._data = 3
#Foo._data = 3
self.ShowFoo()
print 'Bar',self._data
print 'Bar',super(Bar,self)._data
t = Bar(2)
t.ShowBar()
运行结果如下:
Foo 2
Bar 2
Bar 1
#!/usr/bin/python
#-*- coding: UTF-8 -*-
import re
import sys
import os
import json
import simplejson
class Foo:
_data = 1
def __init__(self,data):
self._data = data
def ShowFoo(self):
print 'Foo',self._data
class Bar(Foo):
def __init__(self,data):
Foo.__init__(self,data)
def ShowBar(self):
#会更改父亲的数据
#Foo._data = 3
self.ShowFoo()
print 'Bar',self._data
print 'Bar',Foo._data
t = Bar(2)
t.ShowBar()
运行结果如下:
Foo 2
Bar 2
Bar 1
不管是调用super(Bar,self),或者直接用Foo._data,获取到的父类的_data字段都是没有经过改变的,即初始化的1,反而是直接通过self._data获取的数据是经过改变的。由此可以推测出:
super(Bar,self).__init__(data)
或者
Foo.__init__(self,data)
改变的实际上是子类的数据,而并不是父类的数据。 但是到这里还不够,我们再把代码中注释掉的部分打开,即:
#super(Bar,self)._data = 3
和
#Foo._data = 3
则两份代码的运行结果:
Foo 2
Traceback (most recent call last):
test.py|29| AttributeError: 'super' object has no attribute '_data'
和
Foo 2
Bar 2
Bar 3
可见没有问题。于是我们可以有如下结论: 1.父类里面的self._data 和 子类里面的 self._data是同一份数据
2.父类里面的self._data 和 子类的super(Bar,self)._data及Foo._data是不一样的
3.用super拿到的父类的数据是不可写的,倒是直接用父类名来更改数据。Foo._data的修改可以直接影响super(Bar,self)._data的值。
好吧,到这里我还是不想结束,我们用C++代码来测试一下:
#include <iostream>
#include <string>
#include <vector>
#include <map>
using namespace std;
class Foo
{
public:
Foo()
{
m_A = 1;
}
Foo(int a)
{
m_A = a;
}
int m_A;
void ShowFoo()
{
cout<<m_A<<endl;
}
};
class Bar : public Foo
{
public:
Bar(int a) : Foo(a) {
}
void ShowBar()
{
cout<<m_A<<endl;
cout<<Foo::m_A<<endl;
m_A = 100;
ShowFoo();
cout<<m_A<<endl;
cout<<Foo::m_A<<endl;
Foo::m_A = 200;
cout<<m_A<<endl;
cout<<Foo::m_A<<endl;
}
};
int main(int argc, const char *argv[])
{
Bar f(10);
f.ShowBar();
return 0;
}
运行结果如下:
10
10
100
100
100
200
200
可见,对C++来说父类的m_A,子类的m_A,还有Foo::m_A都是一样的。 试验数据,不对之处还请大家不吝赐教~
附测试代码如下:下载
双木成林 on #
我觉得Python中的那个问题可以这么理解。Python中类也是一种对象。Foo._data拿到的是Foo这个class的_data属性。而self._data拿到的则是self指向的实例的_data属性。按照python的属性搜索顺序,肯定是先访问实例的属性。而如果搞一个类似这样的方法:
@classmethod
def show_in_class(cls):
print cls._data
这里肯定是会返回1,因为传入的参数是cls,类对象。
至于cpp,我个人感觉应该是父子共用了一块内存空间。而类和实例之间没有python那样的类属性和实例属性的差别。
Reply
Dante on #
确实,如果加上
@classmethod
之后,就是一直返回类属性的数据了,看来还是收到C++的误导了,以为python中
Foo._data 或者 super(Bar,self)._data 是访问的父类实例的数据。。。
多谢指教~
Reply
grissiom on #
实验了实验,发现 suer class 有 __thisclass__ 和__self_class__ 属性。
[code]
su = super(Foo,self)
print repr(su.__thisclass__)
print repr(su.__self_class__)
su.__thisclass__._data = 3
[/code]
[code]
Foo 2
Bar 2
Bar 3
[/code]
这样或许更好一点~
Reply
Dante on #
打印了一下__thisclass__和__self_class__
|| <class '__main__.Foo'>
|| <class '__main__.Bar'>
就是父类和本身?不过这个属性的命名好让人困惑。。
Reply
Dante on #
汗。。没转义。。
<class '__main__.Foo'>
<class '__main__.Bar'>
Reply
grissiom on #
另外,我发现子类不能继承父类的私有变量( __* ):
[code]
- 6 class Foo(object):
| 7 _data = 1
| 8 __p = 0
...
- 15 class Bar(Foo):
- 16 def __init__(self,data):
| 17 super(Bar,self).__init__(data)
...
| 28 print self.__p
[/code]
[code]
Traceback (most recent call last):
File "/home/grissiom/test/t.py", line 31, in
t.ShowBar()
File "/home/grissiom/test/t.py", line 28, in ShowBar
print self.__p
AttributeError: 'Bar' object has no attribute '_Bar__p'
[/code]
这个问题怎么解决?……
Reply
Dante on #
可以在子类里用:
self._Foo__p
来进行访问父类私有变量。
-------------------------------------------------------------
Python对私有类成员有部分支持。任何象__spam这样形式的标识符(至少有两个前导下划线,至多有一个结尾下划线)目前被替换成_classname__spam,其中classname是所属类名去掉前导下划线的结果。这种搅乱不管标识符的语法位置,所以可以用来定义类私有的实例、变量、方法,以及全局变量,甚至于保存对于此类是私有的其它类的实例。如果搅乱的名字超过255个字符可能会发生截断。在类外面或类名只有下划线时不进行搅乱。
名字搅乱的目的是给类一种定义“私有”实例变量和方法的简单方法,不需担心它的其它类会定义同名变量,也不怕类外的代码弄乱实例的变量。注意搅乱规则主要是为了避免偶然的错误,如果你一定想做的话仍然可以访问或修改私有变量。这甚至是有用的,比如调试程序要用到私有变量,这也是为什么这个漏洞没有堵上的一个原因。(小错误:导出类和基类取相同的名字就可以使用基类的私有变量)。
Reply
grissiom on #
这个方法太丑了……(但是貌似没有更好的方法?) 我想要的是类似于 C++ 里 protected 变量。
Reply
Dante on #
呃,其实我觉得也不是那么丑吧。。。好像我也没发现过有protect的方法。。
python的一个设计理念就是:假定程序员知道自己在干什么,可能是没有设计protect的原因吧。
Reply
grissiom on #
嗯,但是因为这个,就要把以前写的私有变量都变成公开变量,不爽……
Reply
Dante on #
呃,其实我觉得那种强制访问的方式不错的。。。还可以避免子类的变量覆盖父类变量的情况。
Reply
k on #
你说的父类_data属性没变,都是因为返回的是类型,用类型._data,得到的是类属性,而变的是实例属性
Reply
k on #
应该就是实例属性与类属性的区别
Reply
Dante on #
呵呵,言简意赅~~ 被C++的实现误导了,以为python中
Foo._data 或者 super(Bar,self)._data 是访问的父类实例的数据。。。
Reply
依云 on #
你看看Python的官方文档就什么都明白了。Python和C++虽然都是面向对象的,但它们是很不一样的。用C来理解Python反而更适合。
super() 返回的并不是父类,而是“Return a “super” object that acts like the superclass of type.”,3.1的文档中进一步说明了它“Return a proxy object that delegates method calls to a parent or sibling class of type.”,只用于方法,访问不了数据,所以那行注释掉的代码才会出错。
Python中不仅是不能“访问的父类实例的数据”,而且上例根本没有父类的实例的存在。Python的每个实例都是独立的。继承主要是在查找属性和方法时会按规则查找到父类的并将其作为自己的。
Reply
Dante on #
对于super确实如依云所说,我特意打印了一下:
print super(Bar,self)
print dir(super(Bar,self))
结果是:
<super: <class 'Bar'>, <Bar object>>
['__class__', '__delattr__', '__doc__', '__format__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__self_class__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__thisclass__', '_data']
确实得好好看一下python的官方文档了,很多东西和已有的只是不太一样。。
Reply
grissiom on #
汗,在那个错误信息里本来就有提示啊……
AttributeError: *'super' object* has no attribute '_data'
Reply
Dante on #
dir的结果里面是有_data这个属性的。。。
Reply
grissiom on #
嗯,但是它提示那是个 super object 而不是 Foo...
Reply
grissiom on #
很怀疑 Foo 的构造函数写得有问题。那个 self._data = data 并不是修改了类变量,而是给这个实例创建了一个实例变量~ 换成 self.__class__._data = data 会有惊喜~;) 打算写一篇文章分析分析这个……
Reply
Dante on #
哈哈,期待你的文章~~
self._data = data 修改的就是实例变量哦
如果担心是创建了实例变量,可以在赋值之前判断一下是否hasattr('_data')的~
Reply
Jare on #
很喜欢你的博客
在py里类变量和类实例化后的变量是不同的
类变量就相当于static变量,所有对象公用的
Foo._data和Foo()._data取值是不同的
Reply
Dante on #
呵呵,谢谢~~多交流~~
确实类变量和实例变量不一样~~,之前理解没这么深刻~~
Reply
4399游戏 on #
从搜狗找到博主的blog,真厉害
Reply
很嗨音乐 on #
评论好多
Reply
妞妞 on #
谢谢博主的分享
Reply