目录:
10.面向对象
一种编程思维,原来接触到的都是面向过程、 面向函数。
面向过程/面向函数/面向对象 编程模式:
面向过程:
按照需求分析步骤,一步一步的将内容建设完成进而完成需求
面向函数:
把使用的步骤逻辑封装成函数功能,在按照逻辑调用函数完成最终需求
面向对象:
按照需求分析各种功能,按照功能职责分配给对应的对象,由相应的对象相互配合调用完成最终的需求
注重的是职责的分配
魔法方法:
魔法方法是python内置方法,不需要主动调用,存在的目的是为了给python的解释器进行调用,几乎每个魔法方法都有一个对应的内置函数,或者运算符,当我们对这个对象使用这些函数或者运算符时就会调用类中的对应魔法方法,可以理解为重写这些python的内置函数。
格式:"__ 方法名 __"
10.1类和对象
面向对象编程思想中两个核心概念是类和对象。
类---泛称抽象的概念对具有共同特征或者共同行为的事物进行归类,对这类事务的抽象描述
对象--- 这类事物中具体存在的某个实体
类的分类:系统类和自定义类
10.1.1自定义类的创建
语法:
类是对对象的共同特征或者行为的描述
特征--- 属性--- 变量
行为--- 表示能做这件事---方法 (函数)
一个类是有多个对象的,到底是哪个对象来设置特征值或者调用行为的, 就是self来接受这个对象的
在类中声明的对象的行为都必须有 一个参数self
我们以前使用的某些函数也是这样, 例如:
字符串对象.upper()
class 类名():
#描述对象的属性
def __init__(self, )
# self表示对象
def __init__(self, 形参, 形参1): # 形参接受对应对象的属性值
# self表示进行对应操作的对象
self.属性名=形参 # 给对象添加属性 并赋予对应的属性值
#描述行为
def 行为名(se1f):
# self来接受的执行这个行为的对象
注意: 类名命名规范: 采用的是大驼峰命名制 每个单词首字母大写(包括首字母)
10.2成员变量
全局变量---作用于全局的跟着程序同寿命
局部变量---跟着函数的调用加载栈中 只作用于当前函数体内
成员变量---对象的属性 跟随这对象加载到堆中
私有变量---在名称前加一个前导"_"表示API中非公开部分.
注意:
类变量和对象变量查找顺序:
1.魔法函数__init__中self是实例化对象,中的参数是对象变量,在实例化后调用变量是向上查找(即先查找对象变量,后查找类变量),类变量可以直接通过类访问;
2.类变量是所有实例共享的
类属性和实例属性以及查找顺序:
1.向上查找,即先查找对象变量(实例属性),后查找类属性:
2.多继承采用MRO[Method Resolution Order]:方法解析顺序)算法:
10.3构造方法和析构方法
构造方法---新建对象的方法 __new__
析构方法---被释放的时候调用的 __del__
堆内存的管理机制:
数据都有自己的地址,使用地址这个操作称为引用
堆内存管理机制称为引用计数管理机制
当有一个变量使用这个对象的地址这个对象的引用计数器就会+1
堆中的对象当对象的引用计数器为0的时候对象就会被释放
10.4名词提炼对象法
根据语句中的主谓宾将句子中的信息提取出来,创建抽象类并将语句中的对象实例化!
'''
练习:
一只5岁名叫tom的猫抓住了一只名叫jerry的老鼠
'''
class cat():
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls)
return obj
def __init__(self, name, age):
self.name = name
self.age = age
def grab(self, obj):
action = '抓'
print(f'一只{self.age}岁名叫{self.name}的猫抓住了一只名叫{obj.name}的老鼠')
class mouse():
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls)
return obj
def __init__(self, name):
self.name = name
if __name__ == '__main__':
cat1 = cat('tom', 5)
mouse1 = mouse('jerry')
cat1.grab(mouse1)
print(cat1.__dict__)
后记:这种方法还能用来表示对象跟对象之间的联系
10.5面向对象编程的特征
所有面向对象编程的三个共同特征特征:封装、继承、多态
10.5.1封装
把不需要对外开放或者不能让外界随意修改的属性或者行为给隐藏起来,对外提供使用的接口(方法),这个就是面向对象的封装
怎么把对象的特征或者行为隐藏? ? ?
----在特征名或者行为名前面添加两个下划线
隐藏的内容外界就获取不到了,
外界想获取或者修改的话,类中就得提供对应的接口
获取的话---提供的是get方法
get方法书写的规范
def get_特征名(se1f):
return se1f.__特征名:
修改的话---提供的是set方法
set方法书写的规范
def set_特征名(se1f, 形参):
self.__特征名 = 形参
注意:Python中并没有真正的私有化, 所谓的私有化只是 解释器在解释的时候把 __特征名 解释成了"_类名__特征名"
限制对象动态增加属性:
对象的属性是可以是动态增加的,但是生成类的时候,该类的对象的特征已经声明完成,不需要动态增加了,那么该如何限制对象动态增加属性? ? ? ? I
方法:在类中添加元组字段口__slots__, 在这个字段中把对象的特征列出来
__slots__ = ('name', '__age')
注意: __slots__和__dict__ 只能存在一个,不能共同使用
方法属性化
调用方法就像使用属性一样取值或者赋值
属性的取值与赋值的形式
class Person():
def __init__(self, name, age):
self.name = name
self.set_age(age)
def get_age(self):
return self.__age
def set_age(self, age):
if age < 0:
raise ValueError("年龄不能为负")
self.__age = age
# 创建对象
p = Person('小明', 10)
# 获取属性值 对象.特征名
print(p.name)
# 给属性赋值 对象.特征名 = 特征值
p.name = '小小明'
没有方法属性化的时候
调用方法取值 对象.get方法名()
print(p.get_age())
调用方法赋值 对象.set方法名(数据)
p.set_age(11)
方法属性化 --- 调用方法就像操作属性一样的格式
调用方法取值 对象.get方法名
print(p.get_age)
调用方法赋值 对象.set方法名 = 值
p.set_age = 11
怎么将一个方法向属性一样调用呢??
get方法属性化的方式
给get方法添加一个装饰器 @property
set方法属性化的方法
装饰器是在get方法的基础上衍生来的
装饰器是 @get方法名.setter
封装代码示例:
- 设计一个Circle(圆)类,
包括圆心点位置、半径等属性。方法有计算周长和面积,
创建一个圆的对象,获取该圆的周长和面积,
以及其他点和圆的关系 【点的坐标可以使用元组表示(0,0)】
from math import pi
class Circle():
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls)
return obj
def __init__(self, circle_center, radius):
self.circle_center = circle_center
self.radius = radius
@property
def circle_center(self):
return self.__circle_center
@property
def radius(self):
return self.__radius
@circle_center.setter
def circle_center(self, circle_cen):
if not (len(circle_cen) == 2 and type(circle_cen) == tuple):
raise ValueError('圆心数据格式错误')
self.__circle_center = circle_cen
@radius.setter
def radius(self, rad):
if rad > 0:
self.__radius = rad
return
raise ValueError('输入的半径格式有误')
def perimeter(self):
return pi * 2 * self.__radius
def area(self):
return pi * (self.__radius ** 2)
def point_circle(self, *spot):
model = ((self.__circle_center[0] - spot[0]) ** 2 + (self.__circle_center[1] - spot[1]) ** 2) ** 0.5
if model == self.__radius:
print(f'点{spot}在以{self.__circle_center}为圆心, {self.__radius}为半径的圆上')
elif model > self.radius:
print(f'点{spot}在以{self.__circle_center}为圆心, {self.__radius}为半径的圆外')
elif model < self.radius:
print(f'点{spot}在以{self.__circle_center}为圆心, {self.__radius}为半径的圆内')
else:
print('出错!!!')
if __name__ == '__main__':
circle = Circle(circle_center=(1, 2), radius=3)
print(circle.__dict__)
print(circle.area())
print(circle.perimeter())
circle.point_circle(5, 6)
抛出异常点
raise ValueError(‘年龄不能为负数’)
10.5.2继承
类和类之间的关系:
-
has a 谁拥有谁 是特征的形式的展现
拥有 -> 特征 -> 属性
但这个属性可以映射到另外一个类学生类
特征:学号姓名年龄成绩
班级类
特征:班级名称这个班的所有的学生[这个就需要一 个容器来存放 这个容器是什么类型的? ? ?列表]
如果一个类型的某个特征是一个列表的话 赋予默认值的时候不能直接赋予成空列表,这样会造成该类型所有的对象
公用一个容器,应该赋予None默认值 在函数调 用的时候判断是不是None是None就新建一 个空列表, 这样的话
该类的每个对象的容器就相对独立的
class Grade() :
def init(self,name,students=None) :
#一开始是有初始值 或者默认值的
if students is None:
students = []
se1f.name = name
self. students = studentsI -
is a 谁是谁 准是继承的体现
从两个或者两个以上的相关的普通类中,提取共同的特征和属性,放在一个共通类中 ,然后以继承的形式,普通类中从共通类中获取这些特征和行为,这种模式就是继承
继承的作用:简化代码, 提供代码的复用性
共同类: --> 在继承体系中称之为:父类 超类 基类
相关的普通类--> 在继承体系中称之为:子类
继承 --> 子类继承自父类
派生上 --> 父类派生出子类
例子:
学生类:
特征:学号 姓名 年龄 性别 成绩
行为:学习 吃饭 睡觉
工人类:
特征:工号 姓名 年龄 性别 薪资 工龄
行为:工作 吃饭 睡觉
人类:
特征:姓名 年龄 性别
行为:吃饭 睡觉
python中的继承: 将代码进一步抽象, 简化代码,
继承只是把代码提到父类中子类创建对象的时候还是需要给这个特征进行赋值的
这些赋值的操作在父类中, 需要调用父类中的给特征赋值的方法
继承的格式:
class 父类1名():
pass
class 父类2名():
pass
class 子类名(父类1名, 父类2名):
pass
调用父中的方法调用方式有3种:
1.父类类型.__init__(self, 其他的特征值)
这种方式使用通过父类类型进行调用的在init方法中是要给对象添加特征赋予初始值的
但是此时在父类init方法中不知道给哪个对象赋予特征值
所以需要咱们手动传递要赋予特征值的对象的这个对象就是Student的init 方法中的self
2. super ---表示的是父类
super(子类类型,self).__init.__(其他特征值)
因为一个父类是有很多的子类的要告诉父类是哪个子类要调用这个初始化init方法, 又由于一个子类是有很多的对象的, 是哪个对象要添加特征需要传递告知一下
#在多继承体系中不能再使用super调用父类的方法, 因为这里只默认调用第一个
3.第二种的简化
super().__init__(特征值)
#在多继承体系中不能再使用super调用父类的方法, 因为这里只默认调用第一个
继承的种类
分为单继承和多继承。Python是支持多继承的 一个子类可以直接继承自多个父类
class子类(父类,父类1,父类2, ..):
如何查看类的继承体系 类名.mro()
查看一个类继承的直接父类 类名.__bases__
一般是使用的是单继承[一个类只设置一个父类] 通过间接继承获取对应的操作
练习:
宠物狗:
特征:品种 昵称 年龄 性别
行为:睡觉 拆家
宠物猫:
特征:品种 昵称 年龄 性别 眼睛的颜色
行为:睡觉 撒娇粘人
class Pet():
def __init__(self, breed, nickname, age, gender):
self.breed = breed
self.nickname = nickname
self.age = age
self.gender = gender
@property
def gender(self):
return self.__gender
@gender.setter
def gender(self, gd):
if gd in ('公', '母'):
self.__gender = gd
return
raise ValueError('The attribute gender of an object can only be 公 or 母')
def sleep(self):
print(f'宠物{self.nickname}在睡觉')
class PetDog(Pet):
def __init__(self, breed, nickname, age, gender):
super().__init__(breed, nickname, age, gender)
def demolition(self):
print(f'宠物{self.nickname}正在拆家')
class PetCat(Pet):
def __init__(self, breed, nickname, age, gender, eye_color):
super().__init__(breed, nickname, age, gender)
self.eye_color = eye_color
def spoiled(self):
print(f'宠物{self.nickname}正在撒娇粘人呢')
if __name__ == '__main__':
petDog1 = PetDog('拉布拉多', '大拉', 3, '公')
print(dir(petDog1))
petDog1.sleep()
petDog1.demolition()
petCat1 = PetCat('布偶猫', '小布', 3, '母', '黄色')
print(dir(petCat1))
petCat1.sleep()
petCat1.spoiled()
import time
time.sleep(1)
# 错误实验
petCat2 = PetCat('布偶猫', '小布', 3, '雌性', '黄色')
print(dir(petCat2))
petCat2.sleep()
petCat2.spoiled()
继承中方法的重写
把子类中共同的属性和行为提升到父类中。但是有些场景子类对应共同的行为的实现是有差异性的,子类对这个方法进行重写
场景:
1.与父类中的实现完全不一样
2.与父类中的实现部分一样但是又有自己的独特的地方
重写的时候可以使用super() .方法名()实现父类的内容 再此基础上再增加自己的
子类重写之后相对于子类对象来说父类的原本的方法就被重写给覆盖了,子类对象 只能调用重写之后的
如果和父类的实现是一模一样的就可以直接使用父类的
练习:
公司员工发工资
销售:
特征:工号 姓名 年龄 性别 底薪 绩效
行为:计算薪水
行政:
特征:工号 姓名 年龄 性别 底薪
行为:计算薪水
教学部:
特征:工号 姓名 年龄 性别 底薪 课时费 绩效
行为:计算薪水
class Staff(object):
def __init__(self, job_number, name, age, gender, base_salary):
self.job_number = job_number
self.name = name
self.age = age
self.gender = gender
self.base_salary = base_salary
self.wages = 0
def calculate_salary(self):
self.wages += self.base_salary
return self.wages
class Sale(Staff):
def __init__(self, job_number, name, age, gender, base_salary, achievements):
self.achievements = achievements
super().__init__(job_number, name, age, gender, base_salary)
def calculate_salary(self):
# 默认绩效已经是工资了
# return self.base_salary + self.achievements
self.wages += self.achievements
super().calculate_salary()
return self.wages
class Administration(Staff):
pass
class TeachingDepartment(Staff):
def __init__(self, job_number, name, age, gender, base_salary, achievements, teaching_time_subsidy):
self.achievements = achievements
self.teaching_time_subsidy = teaching_time_subsidy
super().__init__(job_number, name, age, gender, base_salary)
def calculate_salary(self):
self.wages += self.achievements
self.wages += self.teaching_time_subsidy
super().calculate_salary()
return self.wages
if __name__ == '__main__':
teach1 = TeachingDepartment('102', 'abc', 20, '男', 5000, 2000, 1000)
print(teach1.calculate_salary())
admin = Administration('102', 'abc', 20, '男', 5000)
print(admin.calculate_salary())
sale = Sale('102', 'abc', 20, '男', 5000, 2000)
print(sale.calculate_salary())
继承中私有化属性如何在子类中使用
私有化的内容只能对当前类是可见的,外界是无法直接使用的,如果想使用通过人家类中提供的操作接口来使用
在继承体系中子类使用父类中私有化的内容也是需要通过接口来使用的
继承中限制对象动态增加属性
在对应的类中__slots__添加, 这个添加仅很于对当前类有限制, 对于其他类(包含子类)没有限制的
在继承体系中子类想继承父类的__slots__的限制, 需要在类中进行重写, 重写之后,括号里面没数据是承父类的限制,如果有数据的话 表示的是在父类的基础上额外增加的限制
如何用类做装饰器
10.5.3多态
多态是指一类事物有多种形态,比如动物类,可以有猫,狗,猪等等。(一个抽象类有多个子类,因而多态的概念依赖于继承)
多态性
注意:多态与多态性是两种概念
多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
综上可以说,多态性是 : 一个接口,多种实现
多态性的好处:
增加了程序的灵活性,以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(obj)
增加了程序额可扩展性,通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(obj)去调用