目录

python中的面向对象中容易忽略的问题

作为一门面向对象的语言,如果在实际的工作中不能很好的使用其面向对象的特性,那始终不能领悟这门语言的精髓,以下这篇文章我会尽量详尽的从基础到深入的介绍面向对象的内容,内容篇幅较长,有些基础的可以选择性的观看。

类和实例

类(Class)和实例(Instance),类是抽象的模板,比如人类,鸟类,而实例则是一个真正的对象。 简单写一个类,类的定义是使用class 关键字

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Persion:
    def __init__(self):
        pass

    def run(self):
        print "Person can run"

p = Persion()
p.run()

# output:Person can run

非常简单的类的定义,定义了一个人类并且定义了一个run方法,之后初始化了一个对象p,并且调用了这个类的run方法。实例类的对象的时候,要在类名后加上(),如果类的__ini__ 方法没有参数,实例对象的时候就不用传参,如果__init__方法中有参数,那么在实例的时候也要有参数。

注意__init__ 方法init前后是两条下划线.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Persion:
    def __init__(self,name,age,sex='M'):
        self.name = name
        self.age = age
        self.sex = sex

    def run(self):
        print "Person can run"

p = Persion("yangyanxing",31)
p.run()

一、 类的访问限制

类中的方法和属性,但是有些方法和属性并不希望外部直接访问到,那么这时候就需要在属性前面加上两个下划线

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#coding:gbk

class Persion:
    def __init__(self,name,age,sex='M'):
        self.__name = name
        self.__age = age
        self.__sex = sex

    def run(self):
        print "Person can run"

    def showInfo(self):
        print("name:%s,age:%s,sex:%s"%(self.__name,self.__age,self.__sex))
p = Persion("yangyanxing",31)
print p.__name

#output AttributeError: Persion instance has no attribute '__name'

得到的结果是Persion实例没有__name 属性。 那如何才能获取到呢,可以写一个方法,在类里进行访问,就像上面的showInfo() 在这个类方法里进行访问。

注意:私有属性是以两个下划线开始但是不是以两个下划线结束的,也就是说__name 是私有属性,但是__name__就是公开属性了,一般以双下划线开始双下划线结束的属性是特殊的变量。

类方法的规则和属性相同,以双下划线开始非双下划线结束的是私有方法,不可以通过实例直接调用。

二、 继承

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。子类从父类继承方法,注意这里并不一定会继承父类的属性,这里的继承说法其实也不准确,这里先挖一个坑,之后再讲self的时候再填上。上面定义了一个Person类,下面定义一个Student类,它继承自Persion

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#coding:gbk
class Persion:
    def __init__(self,name,age,sex='M'):
        self.__name = name
        self.__age = age
        self.__sex = sex
        self.name = name

    def run(self):
        print "Person can run"

    def showInfo(self):
        print("Person class name:%s,age:%s,sex:%s"%(self.__name,self.__age,self.__sex))


class Student(Persion):
    def __init__(self,name,age,grade,sex='M'):
        Persion.__init__(self,name,age,sex)
        self.grade=grade

    def showInfo2(self):
        print("Student class:%s"%self.name)

s = Student("yang",32,'大三')
s.showInfo()
s.showInfo2()

#output 
# Person class name:yang,age:32,sex:M
# Student class:yang

可以看到,子类继承了父类的showInfo()方法,并且有自已的showInfo2() 方法。

2.1 显示的调用父类的构造方法

子类的__ini__方法需要显示的调用父类的__init__方法,当然也可以不调用,只是父类中定义的属性在子类中就没有了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#coding:gbk
class Persion:
    def __init__(self,name,age,sex='M'):
        self.age = age
        self.sex = sex
        self.name = name
    
    def showInfo(self):
        print self.name
       
    def showInfo2(self):
        print 'xxxxx'


class Student(Persion):
    def __init__(self,name,age,grade,sex='M'):
        # Persion.__init__(self,name,age,sex)
        self.grade=grade


s = Student("yang",32,'大三')
s.showInfo2()
s.showInfo()
print s.name

运行结果

1
2
3
4
5
6
7
xxxxx
Traceback (most recent call last):
  File "ooptest.py", line 23, in <module>
    s.showInfo()
  File "ooptest.py", line 9, in showInfo
    print self.name
AttributeError: Student instance has no attribute 'name'

子类中把父类的初始化方法注释掉以后,当调用showInfo2() 方法时由于是继承了父类中的showInfo2,只是打印了’xxxxx’,这里不会报错,但是当调用showInfo()方法时,父类中需要打印self.name,这时由于没有初始化父类,所以根本就没有给self.name赋值,所以这里就会报错了。

2.2 私有变量与方法的继承问题

私有的方法和属性是不能继承的,如上面的Persion类中的__name属性,如果想要在子类中使用的话就会报错.

1
2
3
4
def showInfo2(self):
    print("Student class:%s"%self.__name)

#output AttributeError: Student instance has no attribute '_Student__name'

2.3 多继承

python 是支持多继承的,也就是一个类可以继承自多个类,但是这样就会造成一个问题,比如有一个A类,它有一个test方法,B继承自A但是重写的test()方法,C也继承自A,也重写的test方法,D继承自B和C,但是它没有自己的test方法,现在调用D对象的test方法,那它该调用谁的test方法呢?这个要区别在python2和python3,它们的运行结果不同

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class A:
    def test(self):
        print("A test")

class B(A):
    pass

class C(A):
    def test(self):
        print("C test")

class D(B,C):
    pass

d = D()
d.test()

上面的代码运行结果

1
2
python2 A test
python3 C test

但是如果将A类继承自object类,则python2 和python3的运行结果是一样的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class A(object):
    def test(self):
        print("A test")

class B(A):
    pass

class C(A):
    def test(self):
        print("C test")

class D(B,C):
    pass

d = D()
d.test()

#output
#python2 C test
#python3 C test

是不是有点乱,不用担心,这个和新式类与经典类有关,之后会详细说明。不过多继承用的比较少,所以大家了解即可。

2.4 多继承中的方法重复问题

如果一个类(如C类)继承自多个类(如A,B),在A和B类中都有test方法,那么C类该如何继承test方法呢?别着急,和上面的问题一样,在之后讲过经典类与新式类以后大家就明白了。

三、 多态

由于python是一种弱类型语言,并不能很好的实现像java中的父类引用指向子类对象,但是python中也可以模拟实现类似于java中的多态。

3.1 实例的类型

判断某一个对象是否是一个类的实例时可以使用isinstance()函数

1
2
3
4
5
6
s = Student("yang",32,'大三')
p = Persion("yangyanxing",32)
print isinstance(s,Student)
print isinstance(s,Persion)
print isinstance(p,Persion)
print isinstance(p,Student)

结果

1
2
3
4
True
True
True
False

可以看出,s既是Student实例,也是Persion实例,但是p只是Persion实例,并不是Student实例,这个也很好理解,说某个人是一个学生,那么他肯定也是个人,但是如果说某个人是一个人,那么他并不一定是个学生.

3.2 方法的重载

子类可以重写父类的方法,这样可以根据子类的行为做出一些调整

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#coding:gbk
class Persion:
    def __init__(self,name,age,sex='M'):
        self.__name = name
        self.__age = age
        self.__sex = sex
        self.name = name

    def run(self):
        print "Person can run"

    def showInfo(self):
        print("Person class name:%s,age:%s,sex:%s"%(self.__name,self.__age,self.__sex))

    def __showInfo(self):
        print("Person class Private name:%s,age:%s,sex:%s"%(self.__name,self.__age,self.__sex))


class Student(Persion):
    def __init__(self,name,age,grade,sex='M'):
        Persion.__init__(self,name,age,sex)
        self.grade=grade

    def showInfo(self):
        print("Student class:%s"%self.name)

s = Student("yang",32,'大三')
p = Persion("yangyanxing",32)
s.showInfo()
p.showInfo()

运行结果

1
2
Student class:yang
Person class name:yangyanxing,age:32,sex:M

3.3 实现多态

上面介绍过,如果一个对象的类型既是它所属的类,同时也属于该类的父类,那么可以写一个函数,它的参数是某一个父类,那么在传参数的时候,既可以传这个父类对象,也可以传子类对象

1
2
3
4
5
6
7
8
s = Student("yang",32,'大三')
p = Persion("yangyanxing",32)

def showDetail(Persion):
    Persion.showInfo()

showDetail(s)
showDetail(p)

这么看也没有多大的优势嘛,你的s和p的对象本身就有showInfo() 方法,可以调用也不稀奇呀,但是如果现在我再写一个Teacher类,但是我不实现他的showInfo方法,这时如果再调用showDetail函数会不会报错呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Teacher(Persion):
    def __init__(self,name,age,tech,sex='F'):
        Persion.__init__(self,name,age,sex)
        self.tech = tech

s = Student("yang",32,'大三')
p = Persion("yangyanxing",32)
t = Teacher("fan",24,'PE')

def showDetail(Persion):
    Persion.showInfo()

showDetail(s)
showDetail(p)
showDetail(t)

运行结果:

1
2
3
Student class:yang
Person class name:yangyanxing,age:32,sex:M
Person class name:fan,age:24,sex:F

结果并不会报错,当调用showDetail()函数传入的是一个Teacher对象时,由于这个对象并没有自已的showInfo方法,但是他从父类中继承了showInfo方法,所以这里也就会调用父类的showInfo函数。

四、 经典类和新式类

上面当说到多继承的时候说到有两个问题在看完经典类与新式类以后就会明白了,这里详细说明一下,别被名字给唬了,没什么特殊的,就是在python2的时候,当一个类没有继承任务类的时候就上上面的Person类,它就是经典类,如果一个类显示的继承自「object」则它是一个新式类,在python3中默认都是新式类,也就是都是继承自object,一般的使用情况下差异不大,只是在多继承的时候在搜索类属性与方法的时候会有些差异,请看以下代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class A():
    def __init__(self):
        pass
    def save(self):
        print("This is from A")

class B(A):
    def __init__(self):
        pass

class C(A):
    def __init__(self):
        pass
    def save(self):
        print("This is from C")

class D(B,C):
    def __init__(self):
        pass
fun =  D()
fun.save()

上面的代码在python2和python3上运行的结果是 /image/p2p3.png

也就是说在经典类(老式类或者称之为旧类)上,搜索类的属性或者方法是按照深度优先的,即从下往上搜索,在经典类中,当调用fun.save() 时,先从D这个类里查找,发现它没有save方法,之后再从它继承的B类中查找,B类中也没有,那么再从B的父类A中查找,A类中有save方法,则停止搜索,调用A类的save方法。 新式类的查找顺序,新式类有一个mro()方法,显示查找顺序 调用D.mro() 显示

1
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

它的搜索顺序是D,B,C,A,这里由于C类有save方法,则直接调用C类的save方法,停止搜索。

五、 super 类

super 是一个类,当子类中想要调用父类的方法时需要使用super类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# coding:gbk
class Persion(object):
    def __init__(self, name, age, sex='M'):
        self.age = age
        self.sex = sex
        self.name = name

    def showInfo(self):
        print(self.name)

    def showInfo2(self):
        print('parent fun')


class Student(Persion):
    def __init__(self, name, age, grade, sex='M'):
        Persion.__init__(self,name,age,sex)
        self.grade = grade

    def showInfo2(self):
        super(Student,self).showInfo2()
        super(Student,self).showInfo()
        print('child fun')


s = Student("yang",32,'大三')
s.showInfo2()

#output
parent fun
yang
child fun

注意想要使用super类,它一般有两个参数,第一个参数是一个类名,第二个参数是一个实例(也可以是个类),如果第二个参数是个实例的话(比如这里的self,你可以理解为sefl就是一个实例)需要满足isinstance(obj, type) == True,如果第二个参数也是个类名的话,那么需要满足issubclass(type2, type) == True,也就是第二个类要是第一个类的子类,super 以后做了些什么呢?super 包含了两个非常重要的信息: 一个 MRO(Method Resolution Order) 列表以及 MRO 中的一个类。上面有介绍新式类有一个mro()方法,调用某个类的mro方法会显示方法和属性的查找顺序,如 调用D.mro() 显示

1
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

当使用super初始化一个类以后,则会在该类以后的类中去查找对应的方法,如某一个类的mro列表是 [A, B, C, D, E, object],当使用super(C,self)初始化super类以后,这个mro则只会从C以后的类中去查找,如调用super(C,self).test() 方法,则只会从D,E,object类中去查找是否有test() 方法,这样也就实现了调用父类的方法。看以下的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class A(object):
    def __init__(self):
        self.n = 2

    def test(self):
        print("A test")

class B(A):
    def test(self):
        print('B test')

class C(B):

    def test(self):
        print("C test")


class D(C):

    def test(self):
        super(D,self).test()
        super(C,self).test()
        super(B,self).test()
        print("D test")

print(D.mro())
d = D()
d.test()

#output
[<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <type 'object'>]
C test
B test
A test
D test

D.mro() 方法显示列表为[D,C,B,A,object] (简写了),当使用super(D,self).test()时就是在查询D类之后的第一个有test()方法的类,C类中就有test方法,所以这里调用了C类的test方法,之后的类似。 注意,这里最上面的类要显示的继承object类,也就是要新式类

六、self 到底是什么?

上面有提到挖了一个坑,这里填一下,self究竟是什么?看以下代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class A(object):
    def __init__(self):
        self.n = 2

    def test(self):
        print("A test")

class B(A):
    def __init__(self,n):
        self.n = n
        A.__init__(self)

b = B(10)
print b.n

它的结果是 2 并不是10,这里self其实就是一个对象实例,虽然在父类中的self,但是这里的self其实是子类的一个对象。

这里啰里啰唆的说了很多关于面向对象的东西,有些东西在编码的时候不一定用的上,但是会有一些莫名的bug当更加理解面向对象后可能会更好的找到原因。

参考文章 python新式类和经典类的区别? Python新式类与经典类的区别 Python: super 没那么简单

  • 文章标题: python中的面向对象中容易忽略的问题
  • 本文作者: 杨彦星
  • 本文链接: https://www.yangyanxing.com/article/oop_in_python.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。