第七章:面向对象之OOP(第二部分,封装&继承)
2 面向对象的三大特性
2.1 封装
- 作用:对对象成员进行有限制的访问
- 3个级别
- 公有成员,public
- 受保护成员,protected
- 私有成员,private
注:public,protected,private不是关键字,但是变量命名等尽量避开
- Python中下划线的使用(参考:https://blog.csdn.net/g11d111/article/details/71367649)
- object #public(公有成员)
- _object #obey python coding convention, consider it as private(将其狭义的看做保护成员)
- __object #private(私有成员)
- __object__ #special, python system use, user should not define like it
(特殊成员,与公私有性质无关,如__init__表示构造函数,__doc__等)
其实在Python中并不存在保护成员的概念,如_object这样狭义上的保护成员在类、子类以及外部都可以进行访问。尽管可以在外部访问,但是需要把他看做私有成员,尽量不要在外部访问。
以双下划线开头是Python中最高级别的封装,即私有成员,只有类对象本身可以访问该成员。但是Python中private的私有并不是真的私有,而是一种name mangling的改名策略,使用【对象名._类名__私有成员名】即可访问
封装案例:
class Student():
name = "xiaoming"
_age = 18
__school = "NCU"
s = Student()
print(s.name) #访问公有成员
print(s._age) #此处虽然可以访问,但是要将其看做私有成员
#print(s.__school) #在外部不能访问私有成员,此处会报错
print("*" * 20)
print(Student.__dict__) #查看类Student中的成员
s._Student__school = 19 #使用name mangling可以访问、修改私有成员,但是最好不要这样用
print(s._Student__school)
结果:
xiaoming
18
********************
{'__module__': '__main__', 'name': 'xiaoming', '_age': 18, '_Student__school': 'NCU', '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
19
2.2 继承
- 概念:一个类可以获得另一个类的成员属性和成员方法
- 作用:可以减少重复性的代码,增加类代码的服用功能,增强类与类之间的联系
- 继承与被继承的概念:
- 被继承的类称为父类,也叫基类或者超类
- 继承的类称为子类,也叫派生类
- 继承和被继承之间存在着is-a的关系,即
继承的语法:参见下列代码
#在Python中所有的类都有一个父类,即object
class Person():
name = "xiaoming"
age = 18
_petname = "mingming" #
__score = 0
def sleep(self):
print("sleeping")
class Student(Person): #类Student继承父类Person,父类名Person写在子类括号中
school_id = "9527"
def homework(self):
print("I must do homework")
s = Student()
print(s.name) #用子类访问父类中的成员
print(s._petname) #用子类访问父类中的保护成员,可以访问但最好不要使用,将其看做私有成员
#print(s.__score) #用子类访问父类中的私有成员,此处会报错
print(s.sleep()) #用子类访问父类中的方法
print("*" * 20) #分割线
print(s.school_id) #子类访问自己的成员属性
print(s.homework()) #子类访问自己的成员方法
结果:
xiaoming
mingming
sleeping
None
********************
9527
I must do homework
None
- 继承的特征
- 所有的类都继承自类object,即类object是所有类的父类
- 子类可以使用父类除私有成员之外的所有内容
- 子类在继承父类后并没有将父类成员全部传入子类当中,即没有另外开辟存储空间,而是通过引用关系访问(代码示例1)
- 子类可以定义子类独有的属性和方法,也即增加代码的功能性(代码示例1)
- 子类和父类中的成员相同,则优先使用子类中的成员(代码示例1)
- 子类如果想扩充父类的方法,可以在定义新方法的同时访问父类成员进行代码重用。有下列两种方法:(代码示例2)
- 父类名.父类成员
- super().父类成员
代码实例1:
#在Python中所有的类都有一个父类,即object
class Person():
name = "xiaoming"
age = 18
_petname = "mingming" #
__score = 0
def sleep(self):
print("sleeping")
class Student(Person): #类Student继承父类Person,父类名Person写在子类括号中
school_id = 9527 #此school_id为子类Student独有的属性
name = "DaNa"
print(Person.__dict__)
print(Student.__dict__)
print("*" * 20)
s = Student()
print(s.name) #子类和父类拥有相同的成员,则优先使用子类中的成员
结果:
{'__module__': '__main__', 'name': 'xiaoming', 'age': 18, '_petname': 'mingming', '_Person__score': 0, 'sleep': <function Person.sleep at 0x0000000002813488>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
{'__module__': '__main__', 'school_id': 9527, 'name': 'DaNa', '__doc__': None}
******************** #由此处可以看出,父类并没有把父类中的成员全部到子类中,而是通过子类引用的方法来访问父类中的成员
DaNa
代码实例2:
class Person():
name = "xiaona"
age = 19
def sleep(self):
print("sleeping……………")
def work(self):
print("make some money")
class Teacher(Person):
name = "dana"
age = 21
def test(self):
print("make the test")
def work(self):
Person.work(self) #扩充父类功能的方法1
#super().work() #扩充父类功能的方法2,super表示得到父类
self.test()
t = Teacher()
t.work()
结果:
make some money
make the test
- 关于super:
- super - super不是关键字, 而是一个类
- super的作用是获取MRO(MethodResolustionOrder)列表中的第一个类
- super于父类直接没任何实质性关系,但通过super可以调用到父类
- super使用两个方,参见在构造函数中调用父类的构造函数
继承变量函数的查找顺序问题:
- 优先查找自己的变量,没有则往上查找父类
- 构造函数如果子类中没有定义,则往上查找父类;如果子类中有定义,则不再往上查找父类
2.3 构造函数
2.4 单继承与多继承
- 单继承(上述关于继承的案例都是单继承)
- 每个类只能继承一个类
- 优点:传承有序,语法简单,逻辑清晰,隐患少
- 缺点:功能不能无限扩展,只能局限于当前继承链中的功能
- 多继承
- 每个类允许继承多个类
- 优点:类的功能扩展方便
- 缺点:继承关系复杂,容易出错
代码实例3:
#多继承代码
class Person():
def __init__(self,name):
self.name = name
def work(self):
print("Working")
class Bird():
def __init__(self,name):
self.name = name
def fly(self):
print("Flying")
class Fish():
def __init__(self,name):
self.name = name
def swim(self):
print("Swimming")
#单继承
class Student(Person):
def __init__(self,name):
self.name = name
stu = Student("yueyue")
stu.work()
#多继承
class SwimMan(Person,Fish):
def __init__(self,name):
self.name = name
swi = SwimMan("yueyue")
print("*" * 20)
swi.work()
swi.swim()
#多继承
class SuperMan(Person,Bird,Fish):
def __init__(self,name):
self.name = name
sup = SuperMan("yueyue")
print("*" * 20)
sup.work()
sup.fly()
sup.swim()
结果:
Working
********************
Working
Swimming
********************
Working
Flying
Swimming
2.4.1 菱形继承/钻石继承问题
- 多个子类继承来自同一个父类,这些子类又被同一个类继承,形成一个菱形(钻石)的形状,如下图
#菱形问题代码
class A():
pass
class B(A):
pass
class C(A):
pass
class A(B,C):
pass
- 对于解决菱形问题,一般采用MRO方法(Method Resolution Order,MRO)
- MRO具体参考:https://www.cnblogs.com/whatisfantasy/p/6046991.html
- Python 3中采用MRO C3的方法来解决菱形问题(上面链接有详解)
- MRO列表计算原则:
- 子类永远在父类前面
- 如果有多个父类,则根据继承语法中括号内类的书写顺序存放
- 如果多个类继承了同一个父类,则孙子类只会选取继承语法括号中的第一个父亲的父类?