文章目录
CH9. 面向对象程序设计
两大编程思想
- 面向过程
- 功能上的封装
- C Language
- 面向对象
- 属性和行为上的封装
- Python、Java
- 面向对象与面向过程的异同点
- 区别
- 面向过程:事物比较简单,可以用线性的思维去解决
- 面向对象:事物比较复杂,使用简单的线性思维无法解决
- 共同点
- 二者相辅相成,并不是对力的
- 解决复杂问题,通过面对对象方式便于我们从宏观上把握事物之间复杂的关系,方便我们分析整个系统,具体到微观操作,仍然使用面向过程方式来处理
- 区别
类和对象
-
“类” 是怎么来的?
- 由N个对象抽取出“像” 的属性和行为,从而归纳总结出来的一种类别
-
Python中一切皆对象
a=10
b=9.9
c='string'
print(type(a))
print(type(b))
print(type(c))
'''
<class 'int'>
<class 'float'>
<class 'str'>
'''
自定义类
-
自定义数据类型的语法结构:
-
class 类名():
pass
-
-
创建对象的语法格式为:
- 对象名 = 类名()
# 自定义一个类
class Person():
pass
class Cat():
pass
class Student():
pass
zhangsan = Person()
print(type(zhangsan))
cat = Cat()
print(type(cat))
stu = Student()
print(type(stu))
'''
<class '__main__.Person'>
<class '__main__.Cat'>
<class '__main__.Student'>
'''
类的组成
- 类属性:直接定义在类中,方法外的变量
- 实例属性:定义在
__init__
方法中,使用self打点的变量 - 实例方法:定义在类中的函数,而且自带参数
self
- 静态方法:使用修饰器
@staticmethod
修饰的方法 - 类方法:使用修饰器
@classmethod
修饰的方法
class Student:
# 类属性
school = '北京大学'
# 初始方法
def __init__(self, name, age):
# 实例属性
self.name = name
self.age = age
# 实例方法:定义在类中的函数,称为方法,自带一个self参数
def show(self):
print(f'我叫{self.name}, 今年{self.age}岁!')
# 静态方法
@staticmethod
def sm():
print('静态方法中,不能调用实例属性,也不能调用实例方法')
# print(self.name)
@classmethod
def cm(cls): # cls -> class缩写
print('类方法中,不能调用实例属性,也不能调用实例方法')
# 创建对象:__init__方法中,有两个形参,self是自带的参数,无序手动传入
stu = Student(name='张三', age=18)
# 实例属性,使用对象名进行打点调用
print(stu.name, stu.age)
# 类属性
print(stu.school)
# 实例方法
stu.show()
# 类方法:直接使用类名打点使用
Student.sm()
# 静态方法:直接使用类名打点使用
Student.cm()
'''
张三 18
北京大学
我叫张三, 今年18岁!
静态方法中,不能调用实例属性,也不能调用实例方法
类方法中,不能调用实例属性,也不能调用实例方法
'''
动态绑定属性和方法
- 动态绑定属性
stu.gender = '男'
print(stu.name, stu.age, stu.gender)
'''
张三 18 男
'''
- 动态绑定方法
def introduce():
print('--> 此方法为动态绑定')
stu.fun = introduce
stu.fun()
'''
--> 此方法为动态绑定
'''
面向对象的三大特征
封装
- 隐藏内部细节,对外提供操作方式
权限控制
- 是通过对属性或方法添加单下划线、双下划线以及首尾双下划线来实现
- 单下划线开头:以单下划线开头的属性或方法表示protected受保护的成员,这类成员被视为仅供内部使用,允许类本身和子类进行访问,但实际上它可以被外部代码访问
- 双下划线开头:表示private私有的成员,这类成员只允许定义该属性或方法的类本身进行访问
- 首尾双下划线:一般表示特殊的方法
class Student():
# 首尾双下划线,表示特殊的方法
def __init__(self, name, age, gender) -> None:
self._name = name # 表示受保护的,只能本类和子类访问
self.__age = age # 表示私有的,只能类分身去访问
self.gender = gender # 普通的示例属性,类本身,类的内部、外部、以及子类都可以访问
def _fun1(self): # 受保护的
print('受保护的方法,子类及本身可以访问')
def __fun2(self): # 私有的
print('只有定义的类可以访问')
def show(self): # 普通的实例方法
print('show():')
self._fun1() # 类本身访问受保护的方法
self.__fun2() # 类本身访问私有方法
print(self._name) # 受保护的实例属性
print(self.__age) # 私有的实例属性
stu = Student('zhangsan',18, '男')
# 类的外部
print(stu._name)
# print(stu.__age) # AttributeError: 'Student' object has no attribute '__age'.
print('-'*30)
stu._fun1()
# stu.__fun2() # AttributeError: 'Student' object has no attribute '__fun2'.
stu.show()
print('-'*30)
# 私有的实例属性和方法是真的不能访问吗?
print(stu._Student__age)
stu._Student__fun2()
# 为什么可以这样访问? 可以查看私有属性和方法的内部定义名称
print(dir(stu))
'''
zhangsan
------------------------------
受保护的方法,子类及本身可以访问
show():
受保护的方法,子类及本身可以访问
只有定义的类可以访问
zhangsan
18
------------------------------
18
只有定义的类可以访问
['_Student__age', '_Student__fun2', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_fun1', '_name', 'gender', 'show']
'''
属性的设置
- 使用
@property
修改方法,将方法转成属性使用 - 使用
funtion object
.setter,可以设置私有属性值
class Student():
# 首尾双下划线,表示特殊的方法
def __init__(self, name, age, gender) -> None:
self._name = name # 表示受保护的,只能本类和子类访问
self.__age = age # 表示私有的,只能类分身去访问
self.__gender = gender # 普通的示例属性,类本身,类的内部、外部、以及子类都可以访问
# 使用@property修改方法,将方法转成属性使用
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
if value > 0:
self.__age = value
else:
print('年龄设置错误!默认设置为18')
self.__age = 18
@property
def gender(self):
return self.__gender
@gender.setter
def gender(self, value):
if value != '男' and value != '女':
print('性别有误,已将性别默认设置为男')
self.__gender = '男'
else:
self.__gender = value
stu = Student('zhangsan',18, '男')
print(f'{stu._name}的年龄是:{stu.age}, 性别是{stu.gender}')
stu.age = 19
stu.gender = '女'
print(f'{stu._name}的年龄是:{stu.age}, 性别是{stu.gender}')
'''
zhangsan的年龄是:18, 性别是男
zhangsan的年龄是:19, 性别是女
'''
继承
-
是在函数调用时,使用 “形参名称=值” 的方式进行传参,传递参数顺序可以与定义时参数的顺序不同
-
概念:
- 在Python中一个子类可以继承N多个父类 (基类)
- 一个父类也可以拥有N多个子类(派生类)
- 如果一个类没有继承任何类,那么这个类默认继承的是object类
-
语法结构:
-
class 类名(父类1,父类2,…, 父类N):
pass
-
class Person: # 默认继承Object类
def __init__(self, name, age) -> None:
self.name = name
self.age = age
def show(self):
print(f'大家好,我叫{self.name},今年{self.age}岁!')
class Student(Person):
def __init__(self, name, age, stuno) -> None:
super().__init__(name, age) # 调用父类的初始化方法,为name和age赋值
self.stuno = stuno
class Doctor(Person):
def __init__(self, name, age, department) -> None:
super().__init__(name, age)
self.department = department
stu = Student('张三', 18, '1000')
stu.show()
doctor = Doctor('李四', 25, '外科')
doctor.show()
'''
大家好,我叫张三,今年18岁!
大家好,我叫李四,今年25岁!
'''
# 一个子类可以继承多个父类
class FatherA():
def __init__(self, name) -> None:
self.name = name
def showA(self):
print('父类A')
class FatherB():
def __init__(self, age) -> None:
self.age = age
def showB(self):
print('父类B')
# 多继承
class Son(FatherA, FatherB):
def __init__(self, name, age, gender) -> None:
FatherA.__init__(self, name)
FatherB.__init__(self, age)
self.gender = gender
son = Son('张三', 20, '男')
son.showA()
son.showB()
'''
父类A
父类B
'''
方法重写
- 子类继承父类就拥有了父类中共有成员和受保护的成员
- 父类的方法并不能完全适合子类的需求,这个时候子类就可以重写父类的方法
- 子类在重写弗雷德方法时,要求方法的名称必须与父类方法的名称相同,在子类重写后的方法中可以通过super().xxx()调用父类中的方法
class Person: # 默认继承Object类
def __init__(self, name, age) -> None:
self.name = name
self.age = age
def show(self):
print(f'大家好,我叫{self.name},今年{self.age}岁!')
class Student(Person):
def __init__(self, name, age, stuno) -> None:
super().__init__(name, age) # 调用父类的初始化方法,为name和age赋值
self.stuno = stuno
# 方法重写
def show(self):
# 调用父类中的方法
super().show()
print(f'我来自xxx大学,我的学号是:{self.stuno}.')
class Doctor(Person):
def __init__(self, name, age, department) -> None:
super().__init__(name, age)
self.department = department
def show(self):
# super().show()
print(f'大家好,我叫{self.name},今年{self.age}岁, 我的工作科室是:{self.department}')
stu = Student('张三', 18, '1000')
stu.show() # 调用子类的show()方法
doctor = Doctor('李四', 25, '外科')
doctor.show()
'''
大家好,我叫张三,今年18岁!
我来自xxx大学,我的学号是1000.
大家好,我叫李四,今年25岁, 我的工作科室是外科
'''
多态
-
是在函数定义时,直接对形式参数进行赋值,在调用时如果该参数不传值,将使用默认值,如果该参数传值,则使用传递的值
-
概念:
- 指的就是“多种形态”,即便不知道一个变量所引用的对象到底是什么类型,仍然可以通过这个变量调用对象的方法
- 在程序运行过程中,根据变量所引用对象的数据类型,动态决定调用哪个对象中的方法。
- Python语言中的多态,根本不关心对象的数据类型,也不关心类之间是否存在继承关系,只关心对象的行为(方法)。只要不同的类中有同名的方法,即可实现多态
class Person():
def eat(self):
print('人,吃五谷杂粮')
class Cat():
def eat(self):
print('猫,喜欢吃鱼')
class Dog():
def eat(self):
print('狗,喜欢啃骨头')
# 这三个类都有一个同名的方法:eat()
# 编写函数
def fun(obj): # obj是函数的形式参数,在定义处不知道形参的数据类型
obj.eat() # 通过obj(对象)调用eat方法
# 创建三个类的对象
per = Person()
cat = Cat()
dog = Dog()
# 调用fun函数,Python中的多态,不关心对象的数据类型,只关心对象是否具有同名方法
fun(per)
fun(cat)
fun(dog)
'''
人,吃五谷杂粮
猫,喜欢吃鱼
狗,喜欢啃骨头
'''
object类
- 所有类直接或间接的父类
- 所有类都拥有object类的属性和方法
object类中特殊的方法 | 功能描述 |
---|---|
__new__() | 由系统调用,用于创建对象 |
__init__() | 创建对象时手动调用,用于初始化对象属性值 |
__str__() | 对象的描述,返回值是str类型,默认输出对象的内存地址 |
class Person(object):
def __init__(self, name, age) -> None:
self.name = name
self.age = age
def show(self):
print(f'我叫{self.name},我今年{self.age}岁')
bro = Person('张三', 20)
print(dir(bro))
'''
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name', 'show']
'''
# 除了'age', 'name','show',其它属性和方法都继承自object父类
# __str__()方法
print(bro.__str__())
# <__main__.Person object at 0x00000250B609A5A0>
- 重写
__str__()
方法
class Person(object):
def __init__(self, name, age) -> None:
self.name = name
self.age = age
# 重写__str__方法
def __str__(self):
return '这是一个人类,具有name和age两个实例属性'
bro = Person('张三', 20)
# __str__()方法
print(bro.__str__())
# 默认调用__str__方法
print(bro)
'''
这是一个人类,具有name和age两个实例属性
这是一个人类,具有name和age两个实例属性
'''
对象的特殊方法
- 首尾双下划线 的方法
运算符 | 特殊方法 | 功能描述 |
---|---|---|
+ | __add__() | 加法运算 |
- | __sub__() | 减法运算 |
<, <=, == | __lt__() , __le__() , __eq__() | 比较运算 |
>, >=, != | __gt__() , __ge__() , __ne__() | 比较运算 |
*, / | __mul__() , __truediv__() | 乘法运算,非整除运算 |
%, // | __mod__() , __floordiv__() | 取余运算,整除运算 |
** | __pow__() | 幂运算 |
a = 1
b = 2
print(dir(a))
print('a+b=', a+b)
print('a.__add__(b) =', a.__add__(b))
'''
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']
a+b= 3
a.__add__(b) = 3
'''
对象的特殊属性
特殊属性 | 功能描述 |
---|---|
obj.__dict__ | 对象的属性字典 |
obj.__class__ | 对象所属的类 |
class.__bases__ | 类的父类元组 |
class.__base__ | 类的父类 |
class.__mro__ | 类的层次结构 |
class.__subclasses__() | 类的子类列表 |
class A:
pass
class B:
pass
class C(A, B):
def __init__(self, name, age) -> None:
self.name = name
self.age = age
a = A()
b = B()
c = C('Jack', 20)
print('对象a的属性字典:', a.__dict__)
print('对象b的属性字典:', b.__dict__)
print('对象c的属性字典:', c.__dict__)
print('对象a的所属的类:', a.__class__)
print('对象b的所属的类:', b.__class__)
print('对象c的所属的类:', c.__class__)
print('A类的父类元组:', A.__bases__)
print('B类的父类元组:', B.__bases__)
print('C类的父类元组:', C.__bases__)
print('A类的父类:', A.__base__)
print('B类的父类:', B.__base__)
print('C类的父类:', C.__base__) # 只显示第一个父类
print('A类的层次结构:', A.__mro__)
print('B类的层次结构:', B.__mro__)
print('C类的层次结构:', C.__mro__)
print('A类的子类列表:', A.__subclasses__())
print('B类的子类列表:', B.__subclasses__())
print('C类的子类列表:', C.__subclasses__())
'''
对象a的属性字典: {}
对象b的属性字典: {}
对象c的属性字典: {'name': 'Jack', 'age': 20}
对象a的所属的类: <class '__main__.A'>
对象b的所属的类: <class '__main__.B'>
对象c的所属的类: <class '__main__.C'>
A类的父类元组: (<class 'object'>,)
B类的父类元组: (<class 'object'>,)
C类的父类元组: (<class '__main__.A'>, <class '__main__.B'>)
A类的父类: <class 'object'>
B类的父类: <class 'object'>
C类的父类: <class '__main__.A'>
A类的层次结构: (<class '__main__.A'>, <class 'object'>)
B类的层次结构: (<class '__main__.B'>, <class 'object'>)
C类的层次结构: (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
A类的子类列表: [<class '__main__.C'>]
B类的子类列表: [<class '__main__.C'>]
C类的子类列表: []
'''
类的深拷贝和浅拷贝
- 变量的赋值:只是形成两个变量,实际上还是指向同一个对象
class CPU():
pass
class Disk():
pass
class Computer():
def __init__(self, cpu, disk) -> None:
self.cpu = cpu
self.disk = disk
cpu = CPU()
disk = Disk()
# 创建一个Computer对象
com = Computer(cpu, disk)
com1 = com
print('com1:',com1,'子对象的内存地址:',com.cpu,com.disk)
print('com :',com,'子对象的内存地址:',com.cpu,com.disk)
# com1和com的地址相同
# com1: <__main__.Computer object at 0x000001CBAF82AD50> 子对象的内存地址: <__main__.CPU object at 0x000001CBAF82AD20> <__main__.Disk object at 0x000001CBAF82AC90>
# com : <__main__.Computer object at 0x000001CBAF82AD50> 子对象的内存地址: <__main__.CPU object at 0x000001CBAF82AD20> <__main__.Disk object at 0x000001CBAF82AC90>
- 浅拷贝:拷贝时,对象包含的子对象内容不拷贝,因此,源对象与拷贝对象会引用同一个子对象
import copy
# 浅拷贝
com2 = copy.copy(com)
# com2是新产生的对象,com2的子对象 cpu, disk不变
print('com2:',com2,'子对象的内存地址:',com2.cpu,com2.disk)
print('com :',com,'子对象的内存地址:',com.cpu,com.disk)
# com2: <__main__.Computer object at 0x000001CBAF82AE10> 子对象的内存地址: <__main__.CPU object at 0x000001CBAF82AD20> <__main__.Disk object at 0x000001CBAF82AC90>
- 深拷贝:使用copy模块的deepcopy函数,递归拷贝对象中包含的子对象,源对象和拷贝对象所有的子对象也不相同
# 深拷贝
com3 = copy.deepcopy(com) # com3是新产生的对象,com3的子对象cpu,disk也会新产生
print('com3:',com3,'子对象的内存地址:',com3.cpu,com3.disk)
# print('com :',com,'子对象的内存地址:',com.cpu,com.disk)
# com3: <__main__.Computer object at 0x000001CBAF85EF60> 子对象的内存地址: <__main__.CPU object at 0x000001CBAF85F020> <__main__.Disk object at 0x000001CBAF85F080>
实战
实战1
- 定义一个圆的类计算面积和周长
- 定义一个圆类——Circle,提供一个属性r(半径),提供两个方法:计算圆的面积get_area(self),和计算圆的周长get_perimeter(self),通过两个方法计算圆的周长和面积并且对计算结果进行输出,最后从键盘录入半径,创建圆类的对象,并调用计算面积和周长的方法输出面积和周长。
class Circle():
def __init__(self, radius) -> None:
self.radius = radius
def get_area(self):
return 3.14 * (self.radius**2)
def get_perimeter(self):
return 2 * 3.14 * self.radius
r = eval(input("请输入圆的半径:"))
c = Circle(r)
print('圆的面积为:', c.get_area())
print('圆的周长为:', c.get_perimeter())
# 请输入圆的半径:4
# 圆的面积为: 50.24
# 圆的周长为: 25.12
实战2
- 定义学生类录入5个学生信息存储到列表中
- 定义学生类Student,包含姓名,年龄,性别,分数四个属性,提供一个用于学员信息输出的方法info(self)。编写测试代码,使用循环录入5位学生的信息,由于录入的学生信息中间使用“#”进行分隔,所以需要使用字符串的split()的方法,进行劈分,使用劈分的信息创建学生对象,使用列表存储学生信息,最后使用循环遍历列表,调用对象的info()方法输出学员信息
class Student():
def __init__(self, name, age, gender, score) -> None:
self.name = name
self.age = age
self.gender = gender
self.score = score
def info(self):
print(self.name, self.age, self.gender, self.score)
print('请输入5为学生信息:(姓名#年龄#性别#成绩)')
lst = []
for i in range(1, 6):
s = input(f'请输入第{i}位学生信息及成绩:')
s_lst = s.split('#') # 字符串分割,结果为列表
stu = Student(s_lst[0], s_lst[1], s_lst[2], s_lst[3])
lst.append(stu) # 存储学生信息
for item in lst:
item.info()
'''
请输入5为学生信息:(姓名#年龄#性别#成绩)
请输入第1位学生信息及成绩:张三#20#男#30
请输入第2位学生信息及成绩:李四#21#男#86
请输入第3位学生信息及成绩:王五#19#男#95
请输入第4位学生信息及成绩:程丽#19#女#93
请输入第5位学生信息及成绩:许芳#20#女#98
张三 20 男 30
李四 21 男 86
王五 19 男 95
程丽 19 女 93
许芳 20 女 98
'''
实战3
- 使用面向对象思想实现乐器弹奏
- 乐手可以弹奏不同的乐器从而发出不同的声音。可以弹奏的乐器包括二胡、钢琴和琵琶。定义乐器类Instrument,包括方法make_sound()。定义乐器类的子类:二胡Erhu、钢琴Piano和小提琴Violin,定义一个函数可以弹奏各种乐器,play(instrument),测试给乐手不同的乐器让他弹奏
class Instrument():
def make_sound(self):
pass
class Erhu(Instrument):
def make_sound(self):
print('二胡在弹奏')
class Piano(Instrument):
def make_sound(self):
print('钢琴在弹奏')
class Violin(Instrument):
def make_sound(self):
print('小提琴在弹奏')
def play(instrument):
instrument.make_sound()
erhu = Erhu()
piano = Piano()
violin = Violin()
# 弹奏乐器
play(erhu)
play(piano)
play(violin)
'''
二胡在弹奏
钢琴在弹奏
小提琴在弹奏
'''
实战4
- 使用面向对象的思想,设计自定义类,描述出租车和家用轿车的信息
- 出租车类:
- 属性包括:车型,车牌,所属出租公司;
- 方法包括:启动,停止
- 家用轿车类:
- 属性包括:车型,车牌,车主姓名;
- 方法包括:启动,停止
- 出租车类:
class Car:
def __init__(self, type, number) -> None:
self.type = type
self.number = number
def start(self):
pass
def stop(self):
pass
class Taxi(Car):
def __init__(self, type, number, company) -> None:
super().__init__(type, number)
self.company = company
def start(self):
print('乘客您好!')
print(f'我是{self.company}的,我的车牌是:{self.number},您要去哪里?')
def stop(self):
print('目的地到了,请您付款下车,欢迎下次乘坐')
class Familycar(Car):
def __init__(self, type, number, owner) -> None:
super().__init__(type, number)
self.owner = owner
def start(self):
print(f'我是{self.owner},我的宝马我做主')
def stop(self):
print('目的地到了,我们去玩儿吧')
taxi = Taxi('长城', '京A12345,', '长城出租车公司')
taxi.start()
taxi.stop()
print('-'*60)
family_car = Familycar('宝马', '京A88888', '张三')
family_car.start()
family_car.stop()
'''
乘客您好!
我是长城出租车公司的,我的车牌是:京A12345,,您要去哪里?
目的地到了,请您付款下车,欢迎下次乘坐
------------------------------------------------------------
我是张三,我的宝马我做主
目的地到了,我们去玩儿吧
'''