面向对象的三大特性
- 封装
- 继承
- 多态
封装
封装就是对对象的成员进行访问限制
封装的三个级别:
- 公开:public
- 受保护的:protected
- 私有的:private
- 这三个不是关键字
判别对象的位置
- 对象内部
- 对象外部
- 子类中
私有成员
私有成员是最高级的封装,只能在当前类或对象中访问
-
在成员前面添加两个下划线即“__a”
``` class Person (): # 公有成员 name = "Tom" # 私有成员, __age = 29 p = Person() # 访问公有成员 print(p.name) # 不能访问私有成员 # print(p.__age) ```
在Python中,私有成员只是一个障眼法,可以通过“_dict_”属性访问到私有成员。
通过“_dict_”访问私有成员:
print(Person.__dict__)
print(p.__dict__)
结果:
{'__module__': '__main__', 'name': 'Tom', '_Person__age': 25, '__dict__': , '__weakref__': , '__doc__': None}
{}
可以看到“Person__age:25”,说明已经访问到私有成员了。我们知道,实例化对象调用“_dict”得到的是空对象,只有实例对象才会返回所有的属性和方法。
如果我们添加以下代码,还是可以访问到私有成员的:
p._Person__age = 20
print(p._Person__age)
结果:
20
受保护的封装
- 将对象或成员进行一定级别的封装,然后,在类中或者子类中可以访问,而在外部不能访问。
- 封装方法:在成员前面添加一个 下划线 即可。
公共 public
- 对成员不进行任何操作,任何地方均可访问
继承
格式
子类与父类的交互方式
多重继承
包含
概念:一个类获得另一个类的所有成员属性和方法叫做继承
作用:减少代码冗余,增加代码的复用功能,同时可以设置类与类之间的关系
-
被继承者与继承之间的关系:
- 被继承者叫做父类,也叫基类,或叫超类
- 继承者叫做子类,或叫派生类
- 继承者与被继承者之间存在“is——a”的关系,也就是继承者是被继承者的子集、子类
继承的一般格式:
# 这是一个父类
class Parent (object):
name = "Tom"
def func1 (self):
print(self.name)
return
# 这是一个子类,它继承了父类的属性和方法
class Chind (Parent):
pass
# 类的实例化
dad = Parent()
son = Chind()
# 调用属性
print(dad.name)
print(son.name)
print(Child.name)
# 调用方法
dad.func1()
son.func1()
Child.func1()
结果如下:
Tom
Tom
Tom
Tom
Tom
Tom
其中,“Child(Parent)”表示子类Child继承了父类Parent所有的属性和方法。“Parent(Object)”的意思是所有类都是继承了Object对象的属性和方法,在Python3.0以后可以省略掉Object,程序自动默认继承。
当子类继承了父类的属性和方法后,不仅子类可以调用父类的所有属性和方法,子类的实例化对象也可以调用父类的属性和方法。
从上例可以得知,如果类Child不使用继承功能的话,那么它就必须重新定义和书写成员属性和方法,如果它的成员属性和方法与类Parent的相同,那么就会使代码冗余,代码重复。继承就省去了这样的麻烦,还充分发挥了重复使用代码的功能。
特点注意:
我们知道,Python中的所有数据均可以看作是对象,类也是同理。Parent和Child仅仅是两个名称,是指向类的 引用。Child继承Parent并不是将Parent中的成员赋值给了Child,而是通过Child这个引用访问Paren中的成员而已。
如,Parent中的name属性和func1函数存放在一个内存空间,可以通过Parent这个引用访问、调用,当Child继承后,也可以通过Child这个引用访问它们。
子类与父类的交互方式
- 子类隐性继承父类
- 子类重写父类方法
- 重写方法后,使用super调用父类同名的方法
子类继承父类,是为了减少代码冗余度。子类不仅可以拥有父类的属性和方法,还可以拥有自己独有的属性和方法。
也就是说,继承是让通用的功能放在父类中,而自己独有的属性和方法就需要子类自己定义,这也叫子类与父类的交互方式。
子类与父类的交互方式有三种:
子类隐性继承父类
这一种就是最常见的普通继承,子类继承父类的所有属性和方法,它本身没有独自的属性和方法。
# 定义一个Teacher父类
class Teacher ():
name = "Jon"
age = 25
duty = "育人"
def doWork (self):
print("你好,我叫{},我今年{}岁了,我的职责是:{}。".format(self.name, self.age, self.duty))
return
# 定义一个子类
class Teacher1 (Teacher):
pass
# 实例化对象
teacher1 = Teacher1()
# 调用继承的属性和方法
print(teacher1.name)
teacher1.doWork()
结果:
Jon
你好,我叫Jon,我今年25岁了,我的职责是:育人。
这种情况是子类没有父类中的属性和方法,只有继承父类。
子类中重写方法
- 所谓重写,一是改变继承父类后的属性和方法的功能,即方法名相同,但功能却不相同。二是在继承的基础上再添加新的功能方法。
- 调用重写后的方法,它的功能是子类中定义的功能,不是父类中的功能。
- 重写方法后,子类重写的方法实现自己的功能,父类的方法还是实现原来的功能,父类方法不受影响。
在上面的例子的基础上,我们为子类Teacher1新添加属性和重写doWork方法,并再新添加doLover方法:
# 重写属性和方法
# 定义一个Teacher父类
class Teacher ():
name = "Jon"
age = 25
duty = "教师"
def doWork (self):
print("你好,我叫{},我今年{}岁了,我的职责是:{}".format(self.name, self.age, self.duty))
return
# 定义一个子类
class Teacher1 (Teacher):
#重写部分属性
name = "God"
lover = "打乒乓球"
# 重写doWork方法
def doWork (self):
print("大家都叫我{},我今年已有{}岁了,现在我的职业是一名{}。".format(self.name, self.age, self.duty))
return
# 添加新的功能方法
def doLover (self):
print("我的爱好是{}".format(self.lover))
return
# 实例化对象
teacher = Teacher()
teacher1 = Teacher1()
# 调用属性
print(teacher1.name)
print(teacher.name)
teacher.doWork()
# 调用重写后的方法
teacher1.doWork()
# 调用新添加的方法
teacher1.doLover()
结果:
Jon
God
你好,我叫Jon,我今年25岁了,我的职责是:教师
大家都叫我God,我今年已有25岁了,现在我的职业是一名教师。
我的爱好是打乒乓球
我们可以看出,当子类的实例化对象调用doWork方法时,它实现的功能不是父类Teacher中定义的功能,而下子类Teacher1中重写的功能。
然后,我们又将父类中的属性name重写了一遍,将它赋值God,实例化对象调用后的结果也是God。更加有意思的是,我们为子类Teacher1新添加的方法也实现了它的功能,这个功能是父类没有的。
使用super()方法重新调用父类方法
- 使用这个方法的前提:子类已经重写父类的方法。
- 格式:super(子类名, self).父类方法
- 在Python3中,super()中的参数可以省略,程序自动识别继承。
- super不是关键字,它是一个可以调用到父类的类
还是以上面的例子为例,使用super()方法让子类可以重新调用父类中的方法:我们在子类Teacher1中修改doWork方法的代码:
# 重写doWork方法
# 使用super()方法重新调用父类的方法
def doWork (self):
# 与super()方法作对比
print("大家都叫我{},我今年已有{}岁了,现在我的职业是一名{}。".format(self.name, self.age, self.duty))
#子类重新调用父类的方法
print("子类重新调用父类的方法:")
super(Teacher1, self).doWork()
return
结果:
Jon
God
你好,我叫Jon,我今年25岁了,我的职责是:教师
大家都叫我God,我今年已有25岁了,现在我的职业是一名教师。
子类重新调用父类的方法:
你好,我叫God,我今年25岁了,我的职责是:教师
我的爱好是打乒乓球
可以看到,子类又可以重新调用父类的方法了。
super是一个可以调用父类的类,它不是关键字:
print(type(super))
help(super)
结果:
class super(Object)
关于私有成员和受保护成员的访问权限
- 私有成员:在属性前面加两个下划线“__”
- 受保护成员:在属性前面加一个下划线“_”
- 子类中可以访问私有成员和受保护成员,外部不能访问这两种成员
现在,我们在父类中新添加一个私有成员和一个受保护成员,在子类中可以访问到这两种成员,但在外部即直接打印这两种成员是不允许的。
class Person ():
name = "刘三"
# 定义私有成员
__age = 20
# 定义受保护的数据
_corde = 90
loverN = "网上冲浪"
def doG (self):
print("大家好,我叫{},我今年{}岁了,我的爱好是{},我的成绩是{}分。". format(self.name, self.__age, self.loverN, self._corde))
return
class Per (Person):
pass
person = Per()
print(person.name)
# 外部不能访问私有成员
#print(person.__age)
# 外部不能访问受保护的数据,打印为空
print(person._corde)
# 子类可以访问父类中私有的成员和受保护的成员
person.doG()
结果:
刘三
90
大家好,我叫刘三,我今年20岁了,我的爱好是网上冲浪,我的成绩是90分。
可以看到,通过外部访问受保护的成员即“print(person._corde)”打印出来的是空白。
构造函数(_init_)
- 构造函数是特殊的函数,在类实例化之前调用。
- 如果定义了构造函数,在实例化对象时会自动调用。
- 构造函数的查找顺序:优先级别
- 先从子类中查找,如果子类中定义了构造函数,就调用子类的构造函数
- 如果子类中没有定义构造函数,父类中定义了构造函数,那么就会自动向上查找调用父类的构造函数
- 如果父类也没有定义构造函数,那么就会继续向上查找父类的父类中是否有构造函数,如果有就调用,如果没有就不调用构造函数
- 构造函数中除了了self这个默认的参数外,还可以定义其它的参数:
- 如果构造函数有其它参数,在实例化对象时,需要传入实参
- 实例化对象时传入的实参数量需要与构造函数中的形参数量相同
现在,我们定义三个类并依次继承,先在子类中定义构造函数。
# 构造函数的使用
# 父类中没有构造函数
class Animal ():
pass
# 父类没有构造函数
class PaxingAni (Animal):
pass
# 子类中有构造函数
class Dog (PaxingAni):
# 定义构造函数
def __init__ (self):
print("这是子类上的构造函数__init__")
# 实例化对象
dog = Dog()
结果:
这是子类上的构造函数__init__
可以看到,我们只是实例化了对象,并没有使用如“dog._init_()”的格式调用构造函数,程序就自动为实例化对象调用了构造函数。
然后,我们取消子类中的构造函数,在PaxingAni这个父类中定义构造函数:
# 父类中没有构造函数
class Animal ():
pass
# 父类中定义构造函数
class PaxingAni (Animal):
def __init__ (self):
print("这是PaxingAni中的构造函数__init__")
# 子类中有构造函数
class Dog (PaxingAni):
# 没有定义构造函数
pass
# 实例化对象
dog = Dog()
结果:
这是PaxingAni中的构造函数__init__
子类中没有定义构造函数,在PaxingAni这个父类中定义了构造函数,程序会自动向上查找,自动调用父类的构造函数。
同理,如果只在Animal这个父类中定义构造函数,程序也会自动查找到它并调用。
当然,如果子类和父类同时定义了构造函数,实例化对象会按构造函数查找顺序调用子类本身的构造函数:
# 父类中没有构造函数
class Animal ():
pass
# 父类中定义构造函数
class PaxingAni (Animal):
def __init__ (self):
print("这是PaxingAni中的构造函数__init__")
# 子类中有构造函数
class Dog (PaxingAni):
# 没有定义构造函数
pass
# 子类中定义了构造函数
class Cat (PaxingAni):
def __init__ (self):
print("子类中的构造函数__init__")
# 实例化对象
dog = Dog()
cat = Cat()
结果:
这是PaxingAni中的构造函数__init__
子类中的构造函数__init__
即使PaxingAni父类中有构造函数,实例化对象cat也会调用子类Cat中的构造函数。
当构造函数中有形参时,实例化对象时也需要传入实参:
# 父类中没有构造函数
class Animal ():
pass
# 父类中定义构造函数
class PaxingAni (Animal):
def __init__ (self, name):
self.name = name
print("这是PaxingAni中的构造函数__init__,名字:{}".format(self.name))
# 子类中有构造函数
class Dog (PaxingAni):
# 没有定义构造函数
pass
# 子类中定义了构造函数
class Cat (PaxingAni):
def __init__ (self, name):
self.name = name
print("子类中的构造函数__init__,名字:{}".format(self.name))
# 实例化对象
dog = Dog("Tom")
cat = Cat("Jon")
结果:
这是PaxingAni中的构造函数__init__,名字:Tom
子类中的构造函数__init__,名字:Jon
如果在实例化对象时不传入实参,或者定义构造函数时不定义形参,使实参数量与形参数量不相等,程序出错。
单继承和多继承
- 单继承:每个类只能继承一个父类。
- 多继承:每个类可以继承多个父类,不推荐。
- 两者的优缺点
- 单继承:
- 优点:逻辑清晰,语法简单明了。
- 缺点:功能无法无限扩展性,只能在当前继承链中扩展。
- 多继承:
- 优点:功能可以无限扩展。
- 缺点:继承关系混乱。如菱形继承/钻石继承问题:关系错综复杂
- 单继承:
- mro:表示继承的顺序列表,返回父类的列表。它的顺序是继承时括号内书写父类的顺序。
- 查找顺序:从子类本身出发,依次查找父类。
举例:我们实现单个继承
# 依次单个继承
class A ():
name = "Tom"
def doA (self):
print("我的名字:{}".format(self.name))
return
class B (A):
age = 21
def doB (self):
print("年龄:{}".format(self.age))
return
class C (B):
lover = "网上音浪"
def doC (self):
print("爱好:{}".format(self.lover))
return
c = C()
print(c.name)
print(c.age)
print(c.lover)
# 返回父类链表
print(C.__mro__)
结果:
Tom
21
网上音浪
(, , , )
可以看出,B继承了A,然后C继承了B。C的实例化对象可以访问它的父类中的所有非私有成员,查找顺序为子类本身,然后向上查找父类。
其中,“C.mro”表示C类的所有父类链表,它表示继承的顺序列表,也就是它继承的所有父类的列表。A是默认继承Object对象的。
然后我们使用多继承:
# 依次单个继承
class A ():
name = "Tom"
def doA (self):
print("我的名字:{}".format(self.name))
return
class B ():
age = 21
def doB (self):
print("年龄:{}".format(self.age))
return
class C (A,B):
lover = "网上音浪"
def doC (self):
print("爱好:{}".format(self.lover))
return
c = C()
print(c.name)
print(c.age)
print(c.lover)
print(C.__mro__)
结果:
Tom
21
网上音浪
(, , , )
结果还是和依次单个继承一样。我们将C类的继承方式修改成了“class C(A, B)”表示同时继承A和B。最后返回的结果一样。
我们可以注意到,“mro”返回的是继承的所有父类的列表,它表示继承的顺序列表。
如果我们写成这样“class C (B, A)”,那么父类B就在A的前面。它的顺序就是继承括号内父类的书写顺序。
多态
- 同一对象在不同情况下以不同状态出现
- 多态不是语法,是一种设计思想
- 多态性:一种调用方式,多种执行结果
Mixin设计模式
- 采用多继承对类进行功能扩展
- 使用多继承语法实现Mixin
- 条件:
- 首先表示某一单一功能,如果有多个功能,需要写多个Mixin
- 子类如果没有继承这个Mixin,它照样工作,只是少了一个功能
Mixin举例:我们知道超人可以在天上飞,也可以在海里自由自在地游泳且不用任何装备。这里超人拥有两个功能:飞和游泳。这里就需要写两个Mixin,分别代表飞和游泳。如果超人没有继承飞这个Mixin,他照样可以游泳,只是不能飞而已。
在这里,超人有三个父类,分别是人、鸟、鱼。
Mixin例子:
# A的功能,单一功能
class A ():
name = "Tom"
def doA (self):
print("我的名字:{}".format(self.name))
return
# B的功能,单一功能
class B ():
age = 21
def doB (self):
print("年龄:{}".format(self.age))
return
class C (A,B):
lover = "网上音浪"
def doC (self):
print("爱好:{}".format(self.lover))
return
c = C()
print(c.name)
print(c.age)
print(c.lover)
print(C.__mro__)
以上,A和B分别表示一种功能,这种功能均是单一的功能,然后通过多继承全部继承给C,让C同时拥有两种功能。
A和B都是Mixin,各自表示各自的功能。
继承相关的函数
- issubclass(B, A):判断B是否是A的子类,如果是返回True。
- isinstance(a, A):判断a是否为A的实例化对象,如果是返回True。
- hasattr(a, attr):检查类中是否有某成员,如果有返回True。
- getattr
- setattr
- dir:获取对象的成员列表
- delatrr:删除成员
举例:
class A ():
pass
class B (A):
name = "Tom"
a = A()
b = B()
print(issubclass(B, A))
print(isinstance(a, A))
print(hasattr(b, "name"))
print(hasattr(b, "age"))
结果:
True
True
True
False
继承的特点:
在Python中,所有类都继承Object对象,也就是说,Object是所有类的父类。Python3.0以后,所有类默认继承,可以不书写Object。
子类继承父类后,可以调用除了私有成员外的所有成员(属性和方法)。
-
子类继承父类,不是将父类的所有成员赋值给子类,而是通过引用方式访问父类成员。
- 父类名也只是指向类的引用而已,成员在一个内存空间,通过父类名访问它们,子类继承后也是通过子类名这个引用访问这些成员的。
子类除了继承父类的属性和方法外,还可以拥有自己独有的属性和方法,这也是子类的作用之一。
子类中定义的属性和方法与父类的属性和方法相同,优先使用子类中定义的属性和方法,这称为 重写。
子类可以扩展父类的属性和方法。即在子类中定义方法的同时,可以访问父类的成员,一般使用“super.父类成员”的格式访问。
继承后,属性和方法的查找顺序:先从子类自身查找,如果没有,再从其继承的父类开始查找。
-
super
- super不是关键字,而是一个类。
- super是一个获取父类的类。
- super与父类没有直接性的关系,但可以调用到父类。