曲水的博客

曲水的博客

面向对象编程的三大特征——封装、继承、多态

405
2022-03-10

总所周知,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 objreturn 的对象其实是 self,所以 selfobj 的 id 相同。

    • 最后执行等式的赋值操作,将 self 赋值给了 person,所以 self 的 id 和 person 的 id 相同。

  • 0