目录
类和对象
语法
-
类:一个抽象的概念,即生活中的”类别”
例如:学生、水果。 -
对象:类的具体实例,即归属于某个类别的”个体”
例如:张三同学、苹果 -
类是创建对象的”模板”.
- 数据成员:名词性的状态。例如:姓名
- 方法成员:动词性的行为。例如:学习
- 类与类的行为不同,对象与对象的数据不同。
定义类
- 代码:
class 类名:
“”“文档说明”""
def __init__(self,参数列表):
self.实例变量 = 参数
方法成员
2.说明:
- 类名所有单词首字母大写
- __init__叫做构造函数,创建对象时被调用,也可以省略。
- self变量绑定的是被创建的对象,名字通常叫做”self”。
创建对象(实例化)
变量 = 构造函数(参数列表)
实例成员
实例变量
-
语法:
【1】定义:对象.变量名称
【2】调用:对象.变量名称 -
说明:
【1】首次通过对象赋值为创建变量,再次赋值为修改。
【2】通常在构造函数(init)中创建。
【3】每个对象存储一份,通过对象地址访问。 -
作用:描述所有对象的共有数据
-
__dict__: 对象的属性,用于存储自身实例变量的字典
实例方法
-
语法:
【1】定义:def 方法名称(self,参数列表):
方法体
【2】调用:对象.实例方法名称(参数列表)
不建议通过类名访问实例方法 -
说明:
【1】至少有一个形参,用于绑定调用该方法的对象,一般命名为”self”。
【2】无论创建多少对象,方法只有一份,并且被所有对象共享。 -
作用:表示对象的行为。
类成员
类变量
- 语法
【1】定义:在类中,方法外定义变量
class 类名:
类变量名 = 数据
【2】调用:
类名.类变量名
不建议使用对象.类变量名. - 说明
【1】存储在类中。
【2】只有一份,被所有对象共享。 - 描述所有对象的共有数据。
类方法
- 语法
【1】定义:
@classmethod
def 方法名称(cls,参数):
方法体
【2】调用:类名.方法名(参数)
不建议使用对象.类方法名. - 说明
- 至少有一个形参,用于绑定调用该方法的类,一般命名为” cls”。
- 使用@classmethod修饰的目的是调用方法时隐式传递类。
- 类方法中不能访问实例成员,实例方法可以访问类成员。
- 作用:操作类变量
静态方法
-
语法
【1】定义:
@staticmethod
def 方法名称(参数列表):
方法体
【2】调用:类名.方法名称(参数)
不建议通过对象访问静态方法 -
说明
【1】将函数转移到类中,就是静态方法
【2】使用@staticmethod修饰的目的是该方法不需要隐式传递参数。
【3】静态方法不能访问实例成员和类成员 -
作用:统一管理函数(定义在.py文件中的函数)
表达不需要使用实例成员和类成员时,使用静态方法。
class Vector2:
# 实例方法
def fun01(self):
pass
# 类方法
@classmethod
def fun02(cls):
pass
# 静态方法:得不到对象地址/也得不到类名
@staticmethod
def fun03():
pass
v01 = Vector2()
v01.fun01()#隐式传递对象地址
Vector2.fun02()#隐式传递类名
Vector2.fun3()
封装
定义
-
从数据角度,将一些基础变量复合为一个自定义类型。符合人类思考方式,将数据与对数据的操作封装起来。
-
从行为角度讲,向类外提供必要的功能,隐藏实现的细节。
比如:random.randint(1,100)
使用者可以不必操心实现过程。
e.g.私有成员变量
# 注意:只是通过两个方法,读写私有变量
class Wife02:
def __init__(self,name="",age=0):
self.name = name
# 缺点:缺乏对象数据的封装,外界可以随意赋值
# 私有成员:障眼法(解释器会改变双下划线开头的变量名)
self.__age = age
def get_age(self):
return self.__age
def set_age(self,value):
self.__age = value
w01 = Wife02()
print(w01.__age)#找不到双下划线开头的数据
print(w01._Wife02__age)#通过下划线+类名可以访问双下划线开头的数据
- 从设计角度讲:
【1】分而治之
- 一个类中,一个方法,一条语句
- 将一个大的需求分解为许多类,让每个类处理一个独立的功能。
- 优点:便于分工,便于复用,可扩展性强。
【2】封装变化
- 需求可能会变化的功能要单独封装,避免影响其他类。
【3】高内聚
- 类中各个方法都在完成一项任务(单一职责的类)
【4】低耦合
- 类与类的关联性与依赖度要低,让一个类变化,尽少影响其他的类。
[例如:硬件高度集成化,又要可插拔]
最高的内聚莫过于类中仅包含1个方法,将会导致高内聚高耦合。
最低的耦合莫过于类中包含所有方法,将会导致低耦合低内聚。
作用
- 简化编程,使用者不必了解具体的实现细节,只需要调用对外提供的功能。
- 松散耦合,降低了程序各部分之间的依赖性。
- 数据和操作相关联,方法操作的是自己的数据。
- 作用:无需向类外提供的成员,可以通过私有化进行屏蔽。
- 做法:命名使用双下划线开头。
- 本质:障眼法,实际也可以访问。
私有成员
私有成员的名称被修改为:_类名__成员名,可以通过_dict_属性或dir函数查看。
属性@property
公开的实例变量,缺少逻辑验证。私有的实例变量与两个公开的方法相结合,又使调用者的操作略显复杂。而属性可以将两个方法的使用方式像操作变量一样方便。
- 定义
class Wife:
def __init__(self,name="",age=0):
self.name = name #调用@name.setter修饰的方法
self.age = age#调用@age.setter修饰的方法
# 拦截读取操作
# 本质:创建property对象,name存储对象地址
# 注意:创建对象时,需要传递读取方法
# 相当于:name = property(get_name,None)
@property # 拦截读取变量的操作
def name(self):
return self.__name
# 拦截写入操作
@name.setter # 拦截写入变量的操作
def name(self,value):
return self.__name = value
@property
def age(self):
return self.__age
@age.setter
def age(self,value):
if 20<=value<=30:
self.__age = value
else:
self.__age = 0
print("No")
w01=Wife("铁锤",25)
print(w01.name)
print(w01.age)
2.调用:
对象.属性名 = 数据
变量 = 对象.属性名
3.说明:
- 通常两个公开的属性,保护一个私有的变量。
- @property 负责读取,@属性名.setter 负责写入
- 只写:属性名= property(None, 写入方法名)
案例:学生信息管理系统
-
需求
实现对学生信息的增加、删除、修改和查询。 -
分析
界面可能使用控制台,也可能使用Web等等。
识别对象:界面视图类 逻辑控制类 数据模型类 -
分配职责:
- 界面视图类:负责处理界面逻辑,比如显示菜单,获取输入,显示结果等。
- 逻辑控制类:负责存储学生信息,处理业务逻辑。比如添加、删除等。
- 数据模型类:定义需要处理的数据类型。比如学生信息。
-
建立交互:
界面视图对象 <----> 数据模型对象 <----> 逻辑控制对象 -
设计
- 数据模型类:StudentModel
- 数据:编号 id,姓名 name,年龄 age,成绩 score
- 逻辑控制类:StudentManagerController
- 数据:学生列表 __stu_list
- 行为:获取列表 stu_list,添加学生 add_student,删除学生remove_student,修改学生update_student,根据成绩排序order_by_score。
- 界面视图类:StudentManagerView
- 数据:逻辑控制对象__manager
- 行为:显示菜单__display_menu,选择菜单项__select_menu_item,入口逻辑main,输入学生__input_students,输出学生__output_students,删除学生__delete_student,修改学生信息__modify_student
封装总结
- 封装数据:将多个基本类型复合为一个自定义类型
优势:符合人类的思考方式,体现对数据的操作方式 - 封装功能:对外提供必要的功能,隐藏实现细节,模块化的编程思想
- 分而治之(分解),封装变化(变化点),高内聚(类职责单一),低耦合(类与类的关系松散)
继承
语法
- 代码:
class 子类名称(父类名称):
def init(self,父类参数,自身参数):
super().init(父类参数)
self.自身实例变量 =自身参数
- 说明:
- 子类拥有父类所有成员.
- 子类如果没有构造函数,将自动执行父类的,但如果有构造函数将覆盖父类的。
此时必须通过super()函数调用父类的构造函数,以确保父类数据成员被正常创建。
- isisntance(对象,类型)函数:返回对象是否兼容类型。
继承语法–方法:
class Person:
def say(self):
print("说")
class Student(Person):
def study(self):
print("学习")
class Teacher(Person):
def teach(self):
print("教")
s01=Student()
s01.study()
s01.say()
# 判断对象是否“兼容”一个类型
isinstance(s01,Person) # True
# 判断前者(类)是否是后者(类)的子类
print(issubclass(Student,Person))#True
继承语法–数据
class Person:
def __init__(self,name):
self.name = name
class Student(Person):
def __init__(self,name,score):
# 通过super函数,调用父类方法
super().__init__(name)
self.score = score
class Teacher(Person):
def __init__(self,name,salary):
super().__init__(name)
self.salary = salary
s01 = Student("zs",100)
继承–设计思想(面向对象设计原则)
- 开闭原则:对扩展开放(允许增加新功能),对修改关闭(不允许增加/删除/修改以前的代码)。
- 依赖倒置(抽象):使用抽象(父类),而不使用具体(子类)。
class Person:
def __init__(self,name):
self.name = name
def go_to(self,vehicle,str_pos):
# 写代码期间:使用的是交通工具,而不是汽车、飞机等,所以无需判断具体类型
# 运行期间:传递具体的对象(汽车、飞机)
# 如果传入的对象,不是交通工具,则退出
if not isinstance(vehicle,Vehicle):
print("传入的不是交通工具")
return
vehicle.transport(str_pos)
class Vehicle:
"""
交通工具
"""
def transport(self,str_pos):
# 人为创造一个错误,约束子类必须有这个方法
raise NotImplementedError()
class Car(Vehicle):
def transport(self,str_pos):
print("行驶到",str_pos)
class Airplane(Vehicle):
def transport(self,str_pos):
print("飞到",str_pos)
p01 = Person("老张")
p01.goto(Car(),"东北")
多继承
一个子类继承两个或两个以上的基类,父类中的属性和方法同时被子类继承下来。
- 同名方法的解析顺序(MRO,Method Resolution Order):
类自身 --> 父类继承列表(由左至右) --> 再上层父类
class A:
def m(self):
print("a--m")
class B(A):
def m(self):
print("b--m")
class C(A):
def m(self):
print("c--m")
class D(B,C):
def m(self):
# 调用父级同名方法,执行顺序(继承列表顺序)
super().m() # b--m
print("d--m")
d01 = D()
d01.m()
print(D.mro()) # [class D,class B,class C,class A,class Object]
定义
- 重用现有类的功能与概念,并在此基础上进行扩展。
- 说明:
- 子类直接具有父类的成员,还可以具有自己的功能。
- 事物具有一定的层次、渊源,继承可以统一概念。
例如:公司组织架构
优点
- 一种代码复用的方式
- 以层次化的方式管理类
缺点
耦合度高
作用
隔离客户端代码与功能的实现方式
适用性
多个类在概念上是一致的,且需要进行统一的处理。
相关概念
父类(基类、超类)、子类(派生类)。
父类相对于子类更抽象,使用范围更宽泛;子类相对于父类更具体,使用范围更狭小。
单继承:父类只有一个(例如:java,C#)
多继承:父类有多个(例如:python、C++)
Object类:任何类都直接或者间接继承自Object类。
内置函数
isinstance(obj, class_or_tuple)
返回这个对象obj 是否是某个类的对象,或者某些类中的一个类的对象,如果是则返回True,否则返回False
多态
定义
调用父类同一个方法,但在不同子类间,有不同的表现。
作用
- 继承将相关概念的共性进行抽象,多态在共性的基础上,体现类型的个性。
- 增强程序扩展性,体现开闭原则。
重写
子类实现了父类中相同的方法(方法名称、参数),在调用该方法时,实际执行的是子类的方法。
内置可重写函数
在python中,以双下划线开头和结尾的是系统定义的成员。我们可以在自定义类中对其重写,从而改变其行为。
__str__
函数:将对象转换为字符串(对人友好的)
__repr__
函数:将对象转换为字符串(解释器可识别的)
class Wife:
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
# 返回给人看
return "奴家叫:%s,年芳:%d" %(self.name, self.age)
def __repr__(self):
# 返回给解释器看
return 'Wife("%s",%d)'%(self.name, self.age)
w01 = Wife("金莲",25)
print(w01) # 将对象转化为字符串
w02 = eval(w01.__repr__()) # 复制对象
print(w02)
运算符重载
定义:让自定义的类生成的对象(实例)能够使用运算符进行操作。
- 算术运算符
e.g. 对象与整数加法:
class Vector:
'''
向量
'''
def __init__(self,x):
self.x = x
def __str__(self):
return "向量的x变量是:%d" % self.x
# 对象 +
def __add__(self,other):
return Vector(self.x + other)
v01 = Vector(10)
v02 = v01 + 5 # 只支持向量在左边
print(v02)
- 反向算数运算符重载
e.g. 对象与整数加法(反向):
class Vector:
'''
向量
'''
def __init__(self,x):
self.x = x
def __str__(self):
return "向量的x变量是:%d" % self.x
# + 对象
def __radd__(self,other):
return Vector(self.x + other)
v01 = Vector(10)
v02 = 5 + v01 # 只支持向量在右边
print(v02)
e.g. 类与类加法
class Vector:
'''
向量
'''
def __init__(self,x):
self.x = x
# 对象 +
def __add__(self,other):
return Vector(self.x + other)
# + 对象
def __radd__(self,other):
return Vector(self.x + other)
v01 = Vector(10)
v02 = Vector(15)
v03 = v01 + v02
print(v03.x)
- 复合运算符重载
class Vector:
'''
向量
'''
def __init__(self,x):
self.x = x
# 累加:在原有对象基础上进行操作,不创建新对象
def __iadd__(self,other):
self.x += other
return self
v01 = Vector(10)
print(id(v01)) # 同一个对象
v01 += 1
print(v01)
print(id(v01)) # 同一个对象
- 比较运算符重载
class Vector:
'''
向量
'''
def __init__(self,x):
self.x = x
def __lt__(self,other):
return self.x < other
v01 = Vector(10)
print(v01 < 2 ) # False
设计原则
开闭原则(目标、总的指导思想)
对扩展开放,对修改关闭。
允许增加新功能,不改变原有代码。
类的单一职责(一个类的定义)
一个类有且只有一个改变它的原因。
外界一个需求的变化,内部一个改变的类。
高内聚。
依赖倒置(依赖抽象)
客户端代码(调用的类)尽量依赖(使用)抽象(父)的组件。
抽象的是稳定的,实现是多变的。
组合复用原则(复用的最佳实践)
如果仅仅为了代码复用优先选择组合关系,而非继承关系。
组合的耦合度低于继承,灵活度高于继承。
里氏替换(继承后的重写,指导继承的设计)
父类出现的地方可以被子类替换,在替换后依然保持原有功能。
- 子类要拥有父类的所有功能
- 子类在重写父类方法时,尽量选择扩展重写(先调父类同名方法),不要改变原有功能。
迪米特法则(类与类交互的原则)
原话:不要和陌生人说话。
类与类交互时,在满足功能的基础上,传递的数据量越少越好。
低耦合。
类与类的关系
- 泛化(继承):
子类与父类的关系,耦合度最高;
B类泛化A类,意味着B类是A类的子类。
- 做法:B类继承A类。
class Employee(Job)
- 关联(聚合/组合):
部分与这个整体的关系,耦合度要低于泛化。
A类关联B类,意味着B是A的一部分。
- 做法:在A类中包含B类的成员。(类作为一个变量)
self.job = job
- 依赖:
合作关系,一种相对松散的协作,变化影响一个方法。
A类依赖B类,意味着A类的某个功能以依靠B类实现。
- 做法:B类型作为A类中方法的参数,并不是A的成员。
return job.get_salary()
总结
面向对象:
面向对象考虑问题,要从对象的角度出发
主要思想:
识别对象/分配职责/建立交互
封装变化/隔离变化/执行变化
封装、继承、多态
特征:
-
封装:
【1】数据:将多个基本类型,合成一个自定义类型
优势:符合人类的思考方式
比如:学生,向量(x,y)
【2】行为:对外提供简单的必要的功能,隐藏实现的细节。
优势:模块化开发,简化编程
比如:学生管理系统——逻辑控制器,供界面调用
【3】设计:
分而治之:分解需求,让多个类协同完成
封装变化:每个变化点单独做成一个类
高内聚:类的内部处理一个变化点
低耦合:类与类的关系,尽量做到互不影响 -
继承:重用现有类的概念与功能,并在此基础上进行扩展(子类的共性,子类相比父类更加具体)
作用:隔离客户端代码与实现方式(隔离用与做)
比如:交通工具隔离了人与飞机/火车…的变化
图形隔离了图形管理器与圆形/矩形…的变化 -
多态:调用父类一个方法,执行子类方法,不同实现方式不一样,所以表现形态就不一样
作用:通过重写执行不同变化点
比如:人调用的是交通工具的运输方法,执行的是飞机/火车的运输方法
图形管理器调用图形的计算面积方法,执行的是圆形/矩形的计算面积方法
原则:
- 开闭原则:增加新功能,不修改客户端代码
- 单一职责:每个类有且只有一个改变的原因
- 依赖倒置:使用抽象(父类),而不是用具体(子类)
- 组合复用:使用关联关系,代替继承关系
- 里氏替换:父类出现的地方,可以被子类替换。替换后,保持原有功能
- 迪米特法则:低耦合