第七周 内容回顾
文章目录
1、面向对象
1.1、面向对象的概念
我们将数据与功能绑定到一起的操作起名为:面向对象编程
将特定的数据与特定的功能绑定到一起,将来只能彼此调用
1.2、编程思想
面向过程编程
- 我们之前所学缩写的代码都是面向过程编程
- 过程其实就是流程,面向过程编程其实就是执行一系列的流程
- 按照指定的步骤依次执行,最终就可以得到想要的结果
面向对象编程
- 核心就是对象二字
- 对象其实就是一个容器,里面将数据和功能绑定到一起
- 例如 游戏人物
- 只负责创造出该人物以及该人物 具备的功能 至于 后续战绩如何 无人知晓
总结:
面向过程 编程相当于让你给出一个问题的具体解决方案
面向对象 编程 相当于让你创造出一些事物之后 不用你管两种编程思想没有优劣之分 仅仅是使用的场景不同,甚至很多时候两者 混合使用
1.3、对象与类的概念
对象: 数据与功能的结合体
类: 多个对象相同的数据 和功能的结合体
类主要用于记录多个对象 相同的数据 和 功能
对象则用于记录多个对象 不同的数据 和 功能
1.4、对象与类的创建
面向对象 编程 本质是将数据和功能绑定到一起 但是 为了突出 面向对象 编程形式
python 特地开发了一套语法 专门用于面向对象编程的操作
创建类的完整语法
class Myclass:
该对象的公共数据
该对象的公共数据
...
class
是定义类的关键字
MyClass
是类的名字
类名的命名 跟变量名一致 并且 推荐首字母大写(为了更好的区分)
类体代码: 分为公共的数据 和公共的方法
类体代码 在类定义阶段 就会执行
1.5、查看名称空间的方法
关键字: __dict__
print(Myclass.__dict__) #查看名称空间 返回的是一个字典
print(Myclass.__dict__['name']) #使用字典 的取值方式 获取名字
print(Myclass.__dict__.get('name')) #使用字典取值方式 获取名字
在面向对象 编程中 想要 获取名称空间中的名字 可以直接采用句点符 比通过__dict__
字典方式取值更方便
print(Myclass.name)
print(Myclass,age)
1.6、类实例化产生对象
实例化产生对象:类名加括号
s1=Myclass()
2、对象的公共属性与独有属性
2.1、对象的公共属性
在创建类 时在类体代码中直接编写的数据 和指定定义的函数 为对象的公共属性
类体代码 在类定义阶段 就会执行
2.2、对象独有属性
2.2.1对象独有数据
关键字: __init__
双下init函数 是专门赋给对象 创建独有数据的 其他对象 不能调用>>>面向对象思想 将数据和功能整合到一起
class Myclass:
# 独有数据
def __init__(self,name,age,gender):
self.name=name
self.age=age
# 公共数据
gender='male'
实例化产生对象步骤
- 产生一个空对象
- 自动触发
__init__
方法实例化对象 - 返回实例化好的ui想
2.2.2、独有方法
针对 对象独有方法 我们无法 真正的实现
- 如果在全局则 不是独有的
- 如果 在类中 则时公共的
python 解释器针对上述问题添加了一个非常牛的特性
- 定义在类中的函数 默认为是绑定给对象(相当于是对象独有的方法)
3、动静态方法
动静态方法算是定义在类体代码中的函数方式
动态方法
- 绑定给对象的方法
- 直接在类体代码中编写即可
- 对象调用会自动将对象当作第一个参数传入
- 类调用则有几个参数 就需要传入几个canshu
- 绑定给类的方法
- 类调用会自动将类 当作第一个参数 传入
- 对象调用 会自动将 对象的类当作第一个参数 传入
静态方法
- 就是普通的函数
- 对象或 类来调用 都必须传固定的参数个数
class Myclass:
#绑定给对象的方法
def eat(self):
print('绑定给对象的方法',self)
#绑定 给类的方法
@classmethod
def run(cls):
print('绑定给类的方法',cls)
#静态方法
@staticmethod
def sleep(a,b):
print('静态方法 谁来调用 都必须自己传参',a,b)
4、面向对象 三大特性之继承
面对想的三大特性
继承
、封装
、多态
继承的含义
在编程世界里 继承其实就是用来描述 类与类 之间的数据关系
例如: 类A 继承 类B(拥有了 类B 里面所有的数据与功能)
继承的目的
编程世界里 继承就是为了节省代码的编写
例如:可以继承一个类 也可以继承多个类
继承的操作
- 定义类时候 需要类名 后面加括号
- 括号内填写你需要继承的类名
- 括号内可以填写 多个父类,逗号隔开即可(可以继承多个类)
class 类名(要继承的类名):
pass
===========================
#继承多个类
class Myclass(要继承的类名):
pass
4.1、继承的本质
抽象: 将多个类共同的数据 或功能抽取出来形成一个基类
继承: 从上往下白嫖 各自基类里面的资源
对象、类、父类的概念规律
对象: 数据功能的结合体
类: 多个对象相同的数据和功能的结合体
父类: 多个类相同的数据和功能结合体
类 与 父类 最主要目的 都是为了节省代码
4.2、名字的查找顺序
不继承的情况下 名字的查找顺序
先从对象自身查找,如果没有的话,再去产生该对象的类中查找
对象>>>类
单继承的情况下 名字的查找顺序
先从对象自身查找,然后是产生该对象的类,然后是一个个父类
对象>>>类>>>父类
多继承的情况下 名字的查找顺序
-
非菱形继承
深度优先(每个分支都走到底 再切换)
-
菱形继承(最后归总到一个自定义类上)
广度优先(前几个分支都不会走到最后一个类 最后一个分支才会走到底)
可以通过类.mro()
方法查看该类产生的对象名字的朝朝顺序
返回的是一个列表
只要涉及到对象的查找顺序 那么几乎都是:
对象自身 >>>类>>>父类
4.3、经典类与新式类
经典类
- 不继承
object
或object
子类的类(什么都不继承)
新式类
- 继承了
object
或object
的子类
在oython3 中所有的类都会默认继承object
意味着python3 里面只有新式类
由于我们在定义类的时候 如果没有想要继承的父类 一般推荐直接继承object
class Myclass(object):
pass
- 这样可以让 类代码兼容 python2
4.4、派生方法
派生:
子类中 定义了 与父类一摸一样的方法,比且扩展了该功能
主要关键字: super().
# 定义一个父类
class Person:
def __init__(self,name,age,gender):
self.name=name
self.age=age
self.gender=gender
#通过括号内填写父类名 继承父类
class Student(person):
def __init__(self,name,age,gender,student_id):
#通过 super 调用父类的方法
super().__init__(name,age,gender)
self.student_if=student_id
5、面向对像的三大特性特性之封装
封装其实就是将数据 或 功能隐藏起来(包起来,装起来)
隐藏的目的 不是让用户 无法使用,而是给这些隐藏的数据开设特定的接口,让用户使用接口才可以去使用 我们在接口中添加一些额外的操作
在类定义阶段使用双下划线开头的名字 都是隐藏属性
后续类和对象都无法直接获取
在python中不会真正的限制任何代码
隐藏属性 如果真的需要访问,也可以 只不过需要做变形处理
__变量名>>>_类名__变量名
寂然隐藏了 就不该 使用 变形之后的名字 去访问 不然就失去了其原本的隐藏意义了
class Student(object):
__school = '北京大学'
def __init__(self,name,age):
self.__name=name
self.__age=age
#查找封装后的数据的通道,也叫接口
def check_info(self):
print("""
学生姓名:%s
学生年龄:%s
学校:%s
""" % (self.__name, self.__age, self.__school))
#创建一个 修改数据通道的 通道(接口)
#参数中要有 要修改数据的参数
def set_info(self,name,age,school):
if len(name)==0:
print('用户不能为空')
return
if not isinstance(age,int):
print('年龄,必须是数字')
return
self.__name= name
self.__age = age
self.__school = school
5.1、property伪装属性
可以简单的理解为 将方法伪装成 数据
数据与方法的使用方式
obj.name
数据只需要点名字
obj,func()
方法使用 还需要加括号
伪装之后 可以将func
方法伪装成数据obj.func
#伪装属性
class Person:
def __init__(self,name,weight,height):
self.name=name
self.weight=weight
self.height = height
#伪装 装饰器语法
@property
def BMI(self):
return self.weigt / (self.heigt**2)
# 正常情况下 我们使用的方法 都是需要加括号
p1=Person('kk',58,1.7)
#因为加了伪装 装饰器 所以可以像数据一样直接使用
print(p1.BMI)
5.2、匿名方法
我们以查看或和修改 一个封装数据为例
class Person:
def __init__(self,name.weight,height):
self.__name = name
self.weight = weight
self.heigt = height
# 查看封装 __name的接口
# 将接口伪装一下,那么 在通过接口查看name数据时 可以和查看普通数据一样
@property
def name(self):
return self.__name
#将 修改的封装接口 变为 像普通 数据一样 可以直接通过=号 直接修改
@name。setter
def name(self,value):
self.__name = value
6、面向对象三大特性 之多态
多态: 一种事物的多种形态
水:液态、气态、固态
动物:人、狗、猫、猪
一种事物有多种形态,但是相同的功能应该有相同的名字
这样的话,以后我们无论 拿到哪个具体的动物,都不需要管它到底是谁 直接调用相同的功能即可
多态的概念 其实我们早就以及接触过了
例如:列表 元组 字典 在查找元素 个数的时候 我们用len
方法,但他们都是不同的数据类型和结构
这显然就是将方法名称统一为了len
鸭子类型:
只要你长得像鸭子,走路像鸭子,说话像鸭子,那么你就是鸭子
你不需要继承共同的父类 当你们的阿方法 类似时,也要讲方法名称统一
操作系统
Linux(一切皆文件)
只要你能读数据,能写数据,那么你就是文件
那么不管硬盘、内存、文件、的读和取 的方法名都要相同
一切皆对象
在python世界里 所有东西都可以看成是对象
例如:
文件名、文件对象
模块名:模块对象
变量名:变量对象
7、面向对象 反射
反射: 通过字符串来操作对象的数据或方法
主要四个反射方法:
关键字 | 功能 |
---|---|
hasattr | 判断对象是否含有某个字符串对应的属性 |
getattr | 获取对象字符串对应的属性 |
setattr | 根据字符串给对象设置属性 |
delattr | 根据字符串给对象删除属性 |
class Student:
school = '清华大学'
def choice_course(self):
print('选课')
s1=Student()
while True:
#获取用户想要查找的名字
user_name=input('请输入您要查找的名字>>>').strip()
#判断 对象中是否有该属性
if hasattr(s1,user_name):
#获取属性值
res=getattr(s1,user_name)
#判断是否能调用
if callable(res):
#能调用 就是方法
print('是函数',res())
#没有则 为数据
else:
print('',res)
else:
print('不好意思 您想要的查找的名字 对象没有')
8、面向对象魔法方法
魔法方法其实就是类中定义双下方法
之所以会叫魔法方法 原因是 这些方法都是达到某个条件 自然触发的,无需调用
例如:__init__
方法在给对象设置独有数据时自动触发(实例化)
魔法方法 | 功能及触发条件 |
---|---|
__init__ | 实例化对象时自动触发 |
__str__ | 对象在被执行打印操作时会自动触发(该方法必须返回一个字符串,返回什么字符串,打印对象之后就展示什么字符串) |
__call__ | 对象加括号调用时 自动触发该方法 |
__getattr__ | 对象获取一个不存在的属性名 自动触发(该方法返回什么,对象获取不存在属性名就会得到什么) |
__setattr__ | 当对象操作属性值得时候自动触发(对象···属性名=属性值) |
__del__ | 当对象 在被删除(主动或被动)得时候自动触发 |
__getattribute__ | 对象获取属性得时候自动触发 无论这个属性存不存在 |
__enter__ | 对象在被with执行上下文操作的时候自动触发,该方法返回什么,as关键字后面的变量名就会得到什么 |
__exit__ | 对象被with语法执行并运行完with子代码后 自动触发 |
__new__ | 产生对象时自动触发,在双下init之前触发 |
8.1、魔法方法笔试题
题目要求:补全以下diamagnetic,执行之后不会报错
class Context;
# 对象被 with 语法执行上下问操作的时候 需要有双下enter方法 和双下exit方法
def __enter__(self):
return self
def __exit__(self)
pass
def do_something(self):
pass
with Context()as f:
f.do_something
9、元类
9.1、元类的简介
基础阶段 我们使用type
来查看数据类型
但是学了面向对象之后发现查看的不是数据类型 而是该数据所属的类
我们定义的数据类型其实本质还是通过各个类产生的对象
我们也可以理解为type
用于查看产生当前对象的类是谁
9.2、产生类的两种方式
通过class
关键字
class Myclass:
pass
利用元类type
type(类名,类的父类,类的名称空间)
- 学习元类其实就是掌握类的产生过程,我们就可以在类的产生过程种 高度定制化的行为
9.3、元类的基本使用
只有继承了 type
的类才能称之为元类
class Mymetaclass(type):
pass
#如果想要切换产生类的元类 则不能使用继承,必须使用关键字 metaclass 声明
class Myclass(metaclass=Mymetaclass):
pass
例题:规定定义类时 类名必须首字母大写
推导:
类中的__init__
用于实例化对象
元类中__init__
用于实例化类
#定义一个元类
class Mymetclass(type):
#重新 制定 元类 给类名 添加一个条件判断
def __init__(self,what,bases=Nonne,dict=None):
if not what.istitle():
#当定义类时 首字母不为大写
raise Exception('类名要大写')
super().__init__(what,bases.dict)
#定义一个类 以我们的元类为其元类
class My_class(metaclass=Mymetclass):
pass
#应为类名没有 每个首字母大写 所以报错
9.4、元类的进阶
元类不单单可以控制类的产生过程 其实也可以控制对象
例题
制定产生对象,实例化对象时,对象传参必须要以关键字传参的方式
思路:
- 我们知道 对象加括号执行产生该对象的 类里面的
__call__
- 那么类加括号调用 也就会执行产生该类的元类里面的
call
- 那么在生成对象时 类就进行了一个加括号调用的操作,所以只要在元类
__call__
中添加限制即可
class Mymetaclass(type):
def __call__(self,*args,**lwargs):
if args:
raise Exception('只能进行关键字传参')
return
super().__call__(args,kwargs)
class Myclass(metaclass=Mymetaclass):
def __init__(self,name,age):
self.name=name
self.age=age
s1=Myclass('kk',age=18)
#直接报错
总结:
如果 我们想高度定制对象的产生过程
可以操作元类 里面的 call
如果 我们想高度定制类的产生过程
可以操作元类里面的__init__
9.5、双下new方法
类产生对象的步骤
1. 产生一个空对象
2. 自动触发``__init__``方法实例化对象
3. 返回实例化好的对象