第七周学习笔记总结
周总结
面向对象编程思想
区别 | 相当于 | |
---|---|---|
面向对象 | 核心就是'对象' 二字 ,对象其实就是一个'容器' 将数据与功能整合到一起 |
相当于让你创造出一些事物之后不用你管 |
面向过程 | 将程序的执行流程化 ,分步操作,分步的过程中解决问题 | 相当于让你给出一个问题的具体解决方案 |
共同点: 面向过程和面向对象都是解决实际问题的一种思维方式
二者相辅相成,并不是对立的
解决复杂问题,通过面向对象方式便于我们从宏观上把握事物之间复杂的关系,
方便我们分析整个系统:具体到微观操作,仍然使用面向过程方式来处理
类与对象
类与对象的概念
对象:数据与功能的结合体
类:多个对象相同的数据和功能的结合体
ps:在面向对象编程中 类仅仅是用于节省代码 对象才是核心
类与对象的创建
语法结构
class 类名:
类体代码
1.class是定义类的关键字
2.类名类似于函数名 但是首字母推荐大写 用于区分
3.类体代码就是存放对象公共数据和功能的地方
数据: 变量名 = 变量值
功能: 函数
在代码编程中是先有类才能有对象,所有先定义类,后产生对象
产生对象:类名加括号
对象的实例化
给对象添加自己独有的属性
类中的__ init__方法会在类产生对象的时候自动执行,用来初始化对象的属性
类产生对象的具体步骤
1.先创建一个没有独有数据的空对象 {}
2.将空对象和类括号内传入的数据一并交给__init__执行
__init__的第一个参数就是对象本身
3.将创建好的对象自动返回
ps:针对括号内第一个形参self其实就是一个普通的变量名而已,只不过该变量名将来专门接收对象的,所以给它起了个固定的名字叫self。
对象独有的功能
在类中定义的函数默认都是绑定给对象使用的。即对象来调,会自动将对象当做第一个参数传入。
class Person:
h_type = '人类'
def __init__(self, name): # 让对象拥有独有的数据
self.name = name
# 定义在类中的函数,我们称之为方法
def eat(self):
print('%s正在干饭' % self.name)
def others(self, a, b)
print('from others')
'''
针对对象独有的方法 我们无法真正实现
1.如果在全局则不是独有的
2.如果在类中则是公共的
python解释器针对上述问题添加了一个非常牛的特性
定义在类中的函数默认是绑定给对象的(相当于是对象独有的方法)
'''
动态与静态方法
类体代码中编写的函数 | 对象调用 | 类调用 |
---|---|---|
绑定给对象的方法 | 对象调用会自动将对象当做第一个参数传入 | 类调用则有几个形参就传几个实参 |
绑定给类的方法(@classmethod) | 对象调用会自动将产生该对象的类当做第一个参数传入 | 类调用会自动将类当做第一个参数传入 |
静态方法(普普通通的函数)(@staticmethod) | 按照普普通通的函数传参方式 | 按照普普通通的函数传参方式 |
面向对象三大特性之继承
继承 | 在编程世界 | 在现实生活 |
---|---|---|
含义 | 继承其实就是用来描述类与类之间数据的关系 | 继承其实就是用来描述人与人之间资源的关系 |
目的 | 继承就是为了节省代码编写 | 继承就是想占有别人的财产 |
例子 | 类A继承类B(拥有了类B里面所有的数据和功能),可以继承一个类 ,也可以继承多个类 | 儿子继承父亲的财产(拥有了父亲所有的资源) |
继承的基本使用
class 类名(需要继承的类名):
pass
1.定义类的时候在类名后加括号
2.括号内填写你需要继承的类名
3.括号内可以填写多个父类
逗号隔开即可
"""
我们将被继承的类称之为: 父类或基类或超类
我们将继承类的类称之为: 子类或派生类
ps:平时最常用的就是父类和子类
"""
继承的本质
先抽象再继承
抽象:由下往上抽取相同特征,抽取类似或者说比较像的部分,是一种归类的方法
继承:由上往下直接白嫖资源,子类获取父类的数据和功能
对象:数据和功能的结合体
类:多个对象相同的数据和功能的结合体
父类:多个类相同的数据和功能的结合体
ps:类和父类最主要的功能其实就是节省代码
名字的查找顺序
不继承的情况
名字的查找顺序是:先从对象自己的名称空间里查找,没有的话就去类的名称空间查找,对面>>>类。
单继承的情况
名字的查找顺序是:先从对象自己的名称空间中查找,没有则取产生对象的类中查找,如果还没有并且类有父类则去父类中查找,重复,对象>>>类>>> 父类。
多继承的情况
- 非菱形继承的情况下(最后不会归总到一个我们自定义类上):
父类中名字的查找顺序就是按照继承时从左往右依次查找,如果多个父类还有分类,那么遵循"深度优先"(每个分支都走到底 再切换)。
- 菱形继承的情况下(最后归总到一个我们自定义类上):
父类中名字的查找顺序就是按照继承时从左往右依次查找,如果多个父类还有分类,那么遵循"广度优先"(前面几个分支都不会走最后一个类 最后一个分支才会走)。
ps:也可以使用类点mro()方法查看该类产生的对象名字的查找顺序
名字的查找顺序永远都是,先从当前对象自身开始查找。
经典类与新式类
经典类
不继承object或其子类的类(什么都不继承)
新式类
继承了object或其子类的类
"""
在python3中所有的类默认都会继承object
也就意味着python3里面只有新式类
在python2中有经典类和新式类
由于经典类没有核心的功能 所以到了python3直接砍掉了
以后我们在定义类的时候 如果没有想要继承的父类 一般推荐以下写法
class MyClass(object):
pass
目的是为了兼容python2
"""
以后写代码针对object无需关心 知道它的存在即可
派生方法
如果我们自己写的子类需要使用父类的方法,并且还需要基于父类方法之上做扩展。这样的子类我们称之为派生类(本质还是子类),可以使用super关键字来实现。
子类调用父类的方法 super().父类的方法()
拦截 添加 原路返回
派生方法的实际应用
当前需要序列化一个字典,如下代码:
d1 = {'t1': datetime.datetime.today(), 't2': datetime.date.today()}
res = json.dumps(d1)
print(res)
# 报错
'''
TypeError: Object of type 'datetime' is not JSON serializable
json不能序列化python所有的数据类型 只能是一些基本数据类型
'''
解决方法:
方法1 - 手动将不能序列化的类型先转字符串
d1={'t1': str(datetime.datetime.today()), 't2': str(datetime.date.today())}
res = json.dumps(d1)
print(res) # {"t1": "2022-04-08 20:45:06.122407", "t2": "2022-04-08"}
方法2 - 研究json源码并重写序列化方法
'''
研究源码发现报错的方法叫default
raise TypeError("Object of type '%s' is not JSON serializable" % o.__class__.__name__)
我们可以写一个类继承JSONEncoder然后重写default方法
'''
class MyJsonEncoder(json.JSONEncoder):
def default(self, o):
# 形参o就是即将要被序列化的数据对象
print('重写了', o)
'''将o处理成json能够序列化的类型即可'''
if isinstance(o, datetime.datetime):
return o.strftime('%Y-%m-%d %X')
elif isinstance(o, datetime.date):
return o.strftime('%Y-%m-%d')
return super().default(o) # 调用父类的default(让父类的default方法继续执行 防止有其他额外操作)
d1 = {'t1': datetime.datetime.today(), 't2': datetime.date.today()}
res = json.dumps(d1,cls=MyJsonEncoder)
print(res) # {"t1": "2022-04-08 20:51:41", "t2": "2022-04-08"}
面向对象三大特性之封装
封装其实就是将数据或者功能隐藏起来(包起来 装起来)
隐藏的目的不是让用户无法使用 而是给这些隐藏的数据开设特定的接口,让用户使用接口才可以去使用 我们在接口中添加一些额外的操作
-
1.在类定义阶段使用双下划线开头的名字 都是隐藏的属性
后续类和对象都无法直接获取 -
2.在python中不会真正的限制任何代码
隐藏的属性如果真的需要访问 也可以 只不过需要做变形处理
__ 变量名 _ 类名 __变量名
ps:既然隐藏了 就不该使用变形之后的名字去访问 这样就失去了隐藏的意义
property伪装属性
property就是将方法伪装成数据。
很多时候有些数据需要通过计算才能获取,但这些数据给我们的感觉应该是属于数据而不是功能,这个时候我们就可以用到property来把功能伪装成数据。
property伪装方法
"""
@classmethod
@staticmethod
@property
"""
面向对象三大特性之多态
多态的含义
什么是多态?多态指的是一种食物的多种形态,像水有固态,也太和气态等。
多态性的好处在于增强了程序的灵活性和可扩展性,比如通过继承Animal类创建了一个新的类,实例化得到的对象obj,可以使用相同的方式使用obj.speak()。狗,猫,猪都可以调用speak()发出自己的叫声。
强制多态性
虽然python推崇的是自由,但是也提供了强制性的措施来实现多态性,使用了abc模块,不推荐使用性多态。
衍生多态性
其实我们完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象,这正是Python崇尚的“鸭子类型”(duck typing):“如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。
面向对象之反射
反射的含义
指程序可以访问、检测和修改本身状态或者行为的一种能力。其实就是通过字符串来操作对象的数据和功能。
反射需要掌握的四个方法
主要方法 | 作用 |
---|---|
hasattr() | 判断对象是否含有字符串对应的数据或者功能 |
getattr() | 根据字符串获取对应的变量名或者函数名 |
setattr() | 根据字符串给对象设置键值对(名称空间中的名字) |
delattr() | 根据字符串删除对象对应的键值对(名称空间中的名字) |
反射实战应用
反射其实就可以让用户和代码更好的交互起来
1.加载配置文件纯大写的配置
# 配置文件加载:获取配置文件中所有大写的配置 小写的直接忽略组织成字典
import settings
new_dict = {}
# print(dir(settings)) # dir获取括号中对象可以调用的名字
# ['AGE', 'INFO', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'desc', 'name']
for i in dir(settings):
if i.isupper(): # 如果名字是纯大写 那么获取该大写名字对应的值 'AGE' 'INFO'
v = getattr(settings, i)
new_dict[i] = v
print(new_dict)
2.模拟操作系统cmd终端执行用户命令
class WinCmd(object):
def dir(self):
print('dir获取当前目录下所有的文件名称')
def ls(self):
print('ls获取当前路径下所有的文件名称')
def ipconfig(self):
print('ipconfig获取当前计算机的网卡信息')
obj = WinCmd()
while True:
cmd = input('请输入您的命令>>>:')
if hasattr(obj, cmd): # 根据用户输入的cmd,判断对象obj有无对应的方法属性
cmd_name = getattr(obj, cmd) # 根据字符串cmd,获取对象obj对应的方法属性
cmd_name()
else:
print('%s 不是内部或外部命令,也不是可运行的程序或批处理文件' % cmd)
面向对象之魔法方法(双下方法)
魔法方法其实就是类中定义的双下方法,之所以会叫魔法方法原因是这些方法都是到达某个条件自动触发 无需调用
魔法方法 | 触发条件 |
---|---|
__ init__ | 实例化对象的时候 |
__ str__ | 对象被执行打印操作的时候 |
__ call__ | 对象加括号调用的时候 |
__ getattr__ | 对象获取一个不存在的属性名的时候 |
__ setattr__ | 对象操作属性值的时候 |
__ del__ | 对象在被删除(主动 被动)的时候 |
__ getattribute__ | 对象获取属性的时候自动触发,无论这个属性存不存在 |
__ enter__ | 对象被with语法执行的时候 |
__ exit__ | 对象被with语法执行并运行完with子代码的时候 |
元类
元类简介
所有的对象都是实例化或者说是通过调用类而得到的,python中一切皆对象,通过class关键字定义的类本质也是对象,对象又是通过调用类得到的,因此通过class关键字定义的类肯定也是调用了一个类得到的,这个类就是元类。
产生类的两种表现形式
- class关键字
class C1(object):
pass
print(C1) # <class '__main__.C1'>
2.type元类
type(类名,父类,类的名称空间)
res = type('C1', (), {})
print(res) # <class '__main__.C1'>
元类进阶操作
双下call方法,对象加括号会自动执行产生该对象的类里面的双下call方法,并且该方法返回什么对象加括号就会得到什么。
推导:类加括号会执行元类的里面的双下call该方法返回什么其实类加括号就会得到什么。
'''类里面的__init__方法和元类里面的__call__方法执行的先后顺序'''
class MyTypeClass(type):
def __call__(self, *args, **kwargs):
print('__call__ run')
super().__call__(*args, **kwargs)
"""如果想要切换产生类的元类不能使用继承 必须使用关键字metaclass声明"""
class MyClass(metaclass=MyTypeClass):
def __init__(self, name):
print('__init__ run')
self.name = name
obj = MyClass('jason')
'''
是先执行元类里面的__call__方法,再执行类里面的__init__方法。
'''
定制对象的产生过程
"""强制规定:类在实例化产生对象的时候,对象的独有数据必须采用关键字参数"""
class MyTypeClass(type):
def __call__(self, *args, **kwargs):
# 1.会产生一个空对象
# 2.调用类里面的__init__实例化
# print('__call__ run')
print(args,kwargs)
if args:
raise Exception('必须全部采用关键字参数')
super().__call__(*args, **kwargs)
class MyClass(metaclass=MyTypeClass):
def __init__(self, name):
# print('__init__ run')
self.name = name
# obj1 = MyClass('jason')
obj2 = MyClass(name='jason')
总结:
1.如果你想高度定制类的产生过程,那么编写元类里面的__init__方法
2.如果你想高度定制对象的产生过程,那么编写元类里面的__call__方法
双下new方法
双下new是用于产生空对象用的,它会返回一个空对象。双下init和双下new执行都会产生一个空对象,这个空对象就是由双下new产生的
"""
类产生对象的步骤
1.产生一个空对象
2.自动触发__init__方法实例化对象
3.返回实例化好的对象
"""
__new__方法专门用于产生空对象 骨架
__init__方法专门用于给对象添加属性 血肉
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了