面向对象编程的三大特征——封装、继承、多态
总所周知,Python是面向对象的编程语言,面向对象的语言有三大特征,分别是:封装、继承、多态。那么这些特征的含义以及在Python中又是如何实现的,且看本文。
面向对象的三大特征
-
封装:提高程序的安全性。 封装是将数据(属性)和行为(方法)包装到类对象中。在方法内部对属性进行操作,在类对象的外部调用方法。这样就无须关心内部的具体实现细节,从而隔离了复杂度。
-
继承:提高代码的复用性。 继承是指子类的属性和方法可以通过继承父类的属性和方法,这样同样的代码不需要写两遍,提高了代码重复使用的能力,减少了无意义的代码。
-
多态:提高程序的可拓展性和可维护性。 多态就是“具有多种形态”,指的是即便不知道一个变量所引用的对象到底是什么类型,仍然可以通过这个变量调用方法,在运行的过程中根据变量所引用对象的类型,动态决定调用哪个对象中的方法。
封装
- 在Python中封装的过程就是创建类的过程。通过定义类中的属性和方法来实现封装,然后在类外调用。例如:
class Student: # 封装的过程
def __init__(self,name,age):
self.name = name
self.age = age
def show():
print(self.name,self.age)
stu = Student('张三',20) # 类的实例化
print(stu.name) # 封装属性的调用
print(stu.age) # 封装属性的调用
stu.show() # 封装方法的调用
- 在Python中没有专门的修饰符用于属性的私有,如果该属性不希望在类之外被访问,可以在属性定义前使用两个"_"。例如,如果不希望学生的年龄在类外被获取:
class Student: # 封装的过程
def __init__(self,name,age):
self.name = name
self.__age = age # 不希望age在类外被获取,加两个_
def show():
print(self.name,self.__age) # 在类中可以正常使用__age
stu = Student('张三',30)
print(student.name) # 输出为'张三'
print(student.__age) # 报错,显示不存在该属性
- 虽然是不希望该属性在类外被访问,但是如果想要硬来也不是不行。所以加双下划线的方法是“防君子不防小人”,主要是靠程序员的自觉。当然,想要访问的路线也会比较曲折。
# 先用dir函数查看stu所包含的所有属性
print(dir(stu))
# 得到的输出结果
['_Student__age', '__class__',…… 'name', 'show']
# 其中的 '_Student__age' 就可以用来调用类中的 '__age'
# 也就是说,可以利用 对象名._类名__属性名 的方式调用
print(stu._Student__age) # 输出为30
- 当然,封装时定义结构函数时也可以使用类方法,例如:
class Student:
def __init__(self,age):
self.set_age(age)
def get_age(self):
return self.__age
def set_age(self,age)
if 0<=age<=120:
self.__age = age
else:
self.__age = 18
stu1 = Student(150)
stu2 = Student(30)
print(stu1.get_age()) # 输出为18
print(stu2.get_age()) # 输出为30
print(stu1.__age) # 会报错,因为__age不能在类外被调用
继承
-
继承,是一种类和类之间的关系,说白了就是子类可以继承父类的属性和方法,减少没有必要的代码数量。
-
举个例子:鳄鱼和蛇都属于爬行动物,老虎、猴子和狼都属于哺乳动物,所以爬行动物的子类是鳄鱼和蛇,哺乳动物的子类是老虎、猴子和狼。但是爬行动物和哺乳动物又同属于动物,所以动物的子类为爬行动物和哺乳动物。
-
如果一个类没有继承任何类,则默认继承的是Object类(例如上面定义的Student类)。
-
Python支持多继承,也就是一个子类可以有多个父类。当然,一个父类肯定可以有多个子类。
继承语法
- 基本语法格式:
class 子类类名(父类1,父类2……):
pass
-
在定义子类的时候,必须在其构造函数
__init__
中调用父类的构造函数,调用的方式为:super().__init__(属性1,属性2,...)
或者父类名.__init__(self,属性1,属性2,...)
。 -
继承代码的实现:
# 定义父类Person
class Person(object): # 这里的object可填可不填
def __init__(self,name,age):
self.name = name
self.age = age
def info(self):
print('姓名:{0},年龄:{1}'.format(self.name,self.age))
# 定义子类Student
class Student(Person): # Student的父类是Person
def __init__(self,name,age,score):
super().__init__(name,age) # 调用父类的属性
self.score = score
stu = Student('张三',20,100)
stu.info() # 输出为 '张三' 20
多继承
- Python 支持多继承,也就是一个子类可以有多个父类。例如:
class A(object):
pass
class B(object):
pass
class C(A,B): # C有两个父类,分别为A和B
pass
- 需要注意的是,在多继承的时候,在写子类的构造函数的时候,继承父类的属性不采用super的写法,而是采用
父类名.__init__(self,属性1,属性2,...)
,例如:
class C(A,B):
def __init__(self,a,b,c):
A.__init__(self,a)
B.__init__(self,b)
self.c = c
c = C(1,2,3)
print(c.a) # 输出为1
print(c.b) # 输出为2
print(c.c) # 输出为3
方法重写
-
如果子类对继承自父类的某个属性或者方法不满意,可以在子类中对其(方法体)进行重新编写,也就是方法重写。
-
子类重写后的方法中可以通过
super().fuction_name()
调用父类中被重写的方法。 -
代码实现:
# 定义父类Person
class Person(object): # 这里的object可填可不填
def __init__(self,name,age):
self.name = name
self.age = age
def info(self):
print('姓名:{0},年龄:{1}'.format(self.name,self.age))
# 定义子类Student
class Student(Person): # Student的父类是Person
def __init__(self,name,age,score):
super().__init__(name,age) # 调用父类的属性
self.score = score
# 重写 info 方法
def info(self):
super().info() # 调用父类中的info方法
print('成绩:{0}'.format(self.score)) # 重写部分
stu = Student('张三',20,100)
stu.info() # 输出为 '张三' 20 100
- 多继承:和多继承调用父类的属性一样,多继承中调用父类的方法格式大差不差:
class A:
def __init__(self,a):
self.a = a
def info(self):
print(self.a)
class B:
def __init__(self,b):
self.b =b
def info(self):
print(self.b)
class C(A,B):
def __init__(self,a,b,c):
A.__init__(self,a)
B.__init__(self,b)
self.c = c
def info(self):
A.info(self)
B.info(self)
print(self.c)
c = C(1,2,3)
c.info() # 输出为 1 2 3
Object类
-
Object类是所有类的父类,因此所有类都有Object类的属性和方法。
-
使用内置函数
dir()
可以查看指定对象所有的属性。 -
Object有一个
__str__
方法,用于返回一个对于“对象的描述”,对应于内置函数str()
经常用于print()
方法,帮助我们查看对象的信息,所以经常会对__str()__
进行重写。如下:
class Student:
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return '这是一个保存学生的名字和年龄的类'
stu = Student('张三',20)
print(stu) # 输出 '这是一个保存学生的名字和年龄的类'
- 从上面的例子可以看出,当我们
print(对象名)
时,系统会默认调用__str()__
方法,返回该方法return
的内容。
多态
-
多态能够让具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容(功能)的函数。
-
Python中多态的特点:
-
只关心对象的实例方法是否同名,不关心对象所属的类型;
-
对象所属的类之间,继承关系可有可无;
-
多态的好处可以增加代码的外部调用灵活度,让代码更加通用,兼容性比较强;
-
多态是调用方法的技巧,不会影响到类的内部设计。
-
-
比如考虑如下继承关系
object[Person,Animal[Dog,Cat]]
,创建类:
class Animal(object):
def eat(self):
print('动物要吃东西')
class Dog(Animal):
def eat(self):
pritn('狗吃骨头')
class Cat(Animal):
def eat(self):
print('猫吃鱼')
class Person(object):
def eat(self):
print('人吃五谷杂粮')
# 定义函数
def fun(animal)
animal.eat()
fun(Animal()) # 输出为 '动物要吃东西'
fun(Dog()) # 输出为 '狗吃骨头'
fun(Cat()) # 输出为 '猫吃鱼'
fun(Person()) # 输出为 '人吃五谷杂粮'
- 所以在调用方法的时候,不需要关心对象的类型,不需要关心对象之间的继承关系,只关心该对象是否存在这个实例方法就行。
特殊方法和特殊属性
- 通俗来讲,用两个
_
开始,并以两个_
结束的就是特殊的属性或者特殊的方法。
特殊属性
class Student:
def __init__(self,name,age):
self.name = name
self.age = age
def info(self):
print(self.name,self.age)
stu = Student('张三', 20)
__dict__
:获得类对象或者实例对象所绑定的所有方法和属性的字典。
# 实例调用__dict__特殊属性,返回实例的所有属性
print(stu.__dict__) # 输出 {'name': '张三', 'age': 20}
# 类调用__dict__特殊属性,返回类的所有方法
print(Student.__dict__) # 输出{'__module__': '__main__',…… '__doc__': None}
__class__
:返回对象所属的类。
print(stu.__class__) # 输出 <class '__main__.Student'>
__bases__
:返回类的父类的元组,如果是多继承,元组会有多个元素。
print(Student.__bases__) # 输出 (<class 'object'>,)
__mro__
:返回类的层次结构。
print(Student.__mro__) # 输出 (<class '__main__.Student'>, <class 'object'>)
特殊方法
__len__()
:通过重写__len__()
方法,让内置函数len()
的参数可以是自定义类型。
class Student:
def __init__(self,name):
self.name = name
def __len__(self):
return len(self.name)
stu = Student('Jarvix')
print(len(stu)) # 输出为 6
__add__()
:通过重写__add__()
方法,可以让自定义对象具有"+"的功能。
class Student:
def __init__(self,name):
self.name = name
def __add__(self,other): # 重写 __add__()方法
return self.name + other.name
stu1 = Student('张三')
stu2 = Student('李四')
print(stu1 + stu2) # 输出 '张三李四'
-
__new__()
:用于创建对象。 -
__init__()
:对创建的对象进行初始化。 -
下面用一段代码对上面两个方法的使用进行展示和说明:
class Person:
def __new__(cls, *args, **kwargs):
print('__new__被调用执行了,cls的id值为:{0}'.format(id(cls)))
obj = super().__new__(cls)
print('创建的对象的id为:{0}'.format(id(obj)))
return obj
def __init__(self, name, age):
print('__init__被调用执行了,self的id值为:{0}'.format((id(self))))
self.name = name
self.age = age
print('object这个类对象的id为:{0}'.format(id(object)))
print('Person这个类对象的id为:{0}'.format(id(Person)))
print('——————分割线——————')
# 创建Person类的实例对象
person = Person('张三', 20)
print('preson这个实例对象的id为:{0}'.format(id(person)))
# 输出
object这个类对象的id为:4514360240
Person这个类对象的id为:140669666498240
——————分割线——————
__new__被调用执行了,cls的id值为:140669666498240
创建的对象的id为:140669709590192
__init__被调用执行了,self的id值为:140669709590192
preson这个实例对象的id为:140669709590192
-
可以看到
__new__
和__init__
只有在创建实例的时候才会被调用,并且Person
对象和cls
对象的 id 相同,说明两者是同一个东西;__new__
创建的对象obj
的 id 、self
的 id 和person
的 id 都相同,说明三者也是同一个东西。运行的逻辑如下:-
在运行
person = Person('张三',20)
这一行代码的时候,先执行的是等号右端的代码,将Person('张三',20)
这个类对象作为参数cls
传入__new__(cls, *args, **kwargs)
方法中,所以两者是同一个东西。 -
再将
cls
参数传入到super().__new__(cls)
中,创建了对象obj
,此时obj
的 id 就已经确定下来为 140669709590192,然后return obj
中return
的对象其实是self
,所以self
和obj
的 id 相同。 -
最后执行等式的赋值操作,将
self
赋值给了person
,所以self
的 id 和person
的 id 相同。
-
- 0
-
分享