面向对象三大特性之封装、多态
一、派生方法实战演练
举例:时间对象序列化报错
当一个字典类型的数据中有datetime产生的时间对象的时候,想要把此类对象通过json模块进行序列化,则会报错。
因为,json模块并不支持所有数据类型的对象都进行序列化,在python中,只有如下对应的数据类型才能被json模块序列化:
python数据类型 | JSON数据类型 |
---|---|
int | int |
float | float |
bool(True,False) | bool(true,false) |
None | null |
str | str(必须双引号) |
list([])、tuple(()) | Array([]) |
dict({}) | Object({})(键必须是双引号) |
import json import datetime d = { 't1': datetime.date.today(), 't2': datetime.datetime.today() } res = json.dumps(d) print(res) --------------打印res的结果会报错------------- raise TypeError(f'Object of type {o.__class__.__name__} ' TypeError: Object of type date is not JSON serializable
当我们点开dumps
方法的源码的时候,会看到JSONEncoder
相关json编码的cls类,再点开JSONEncoder
的源码,则会看到python提供的与json格式之间互相转换的数据类型,如图


dumps()
方法中的ensure_ascii=True
参数的含义:这个是因为
json
在进行序列化时,默认使用的是编码是ASCII,而中文为Unicode编码,ASCII中不包含中文,所以出现了乱码。
想要json.dumps()
能正常显示中文,只要加入参数ensure_ascii=False
即可,这样json在序列化的时候,就不会使用默认的ASCII编码。
遇到序列化时报错,可以有两种方式将datetime的时间对象进行序列化:
1 转换方式一:手动转类型
先将时间对象都转换成str字符串格式,由于在python中万物都可转换成str,这样再转换为json格式,就不会报错
d2 = { 't1':str(datetime.date.today()), 't2':str(datetime.datetime.today()) } # str()方法转换为字符串格式 res = json.dumps(d2) # 再进行序列化转成json格式数据 print(res) ------结果------- {"t1": "2022-11-07", "t2": "2022-11-07 15:09:59.268513"}
2 转换方式二:派生方法
当我们查看dumps()
源码,注意看cls
参数,默认传JsonEncoder
;查看该类的源码,发现default
方法是报错的发起者。
那么我们可以,编写类继承JsonEncoder
并重写default
方法,之后掉用dumps()
手动传cls=我们自己写的类
,使datetime类型处理成可以被序列化的类型从而成功序列化
class MyJsonEncoder(json.JSONEncoder): def default(self, o): """ :param o: 接收无法被序列化的数据 :return: 返回可以被序列化的数据 """ if isinstance(o, datetime.datetime): # 判断是否是datetime类型,如果是则处理成可以被序列化的类型:strftime格式化时间字符串 return o.strftime('%Y-%m-%d %X') elif isinstance(o, datetime.date): return o.strftime('%Y-%m-%d ') return super().default(o) # 将json中默认的的default()方法,通过派生方法super()改写成了将datetime产生的时间对象转换成strftime格式化时间字符串,这样就可以成功序列化了 rst = json.dumps(d, cls=MyJsonEncoder) print(rst) -------------------- {"t1": "2022-11-07 ", "t2": "2022-11-07 15:19:53"}
这样通过对将json中默认的的default()
方法,通过派生方法super()
改写成了将datetime
产生的时间对象转换成strftime
格式化时间字符串,这样就可以成功序列化了
当我们使用python中一些内置的模块或者函数,会报错的时候,我们可以通过派生方法,再原有基础扩展出新的功能,满足我们的需求。
二、面向对象三大特性之封装
0.封装简介
封装:就是将数据和功能‘封装’起来
封装的两种使用方法:
隐藏:将数据和功能隐藏起来,不让数据直接调用,而开发一些接口间接调用,从而可以在接口内添加额外的操作
伪装:将类里面的方法伪装成类里面的数据
1.隐藏
类在定义阶段,名字前面有两个下划线,那么该名字会被应藏起来,无法直接访问
class MyClass: name = 'duoduo' _ = 'hhh' __age = 18 def choice_name(self): # 方法名前面加__也可以隐藏方法 print('nihao')
当我们使用未隐藏的属性时,正常显示
print(MyClass.name) ----------------- 'duoduo'
当我们去使用隐藏的名字时,会直接报错
print(MyClass.__age) # AttributeError: type object 'MyClass' has no attribute '__age' """ 在python中没有真正的隐藏,仅仅是换了个名字而已,换成了_类名__名字, 隐藏真的功能是不让用户直接调用,而去开放接口 """ print(MyClass.__dict__) # '_MyClass__age' print(MyClass._MyClass__age) # 18
__
俩下划线隐藏名字的功能,仅可以再类定义阶段使用才有效
MyClass.__hobby = 'dbj' print(MyClass.__hobby) # dbj无法隐藏,不在定义阶段
ps:在python中没有真正的隐藏,仅仅是换了个名字而已,换成了_类名__名字
,隐藏真的功能是不让用户直接调用,而去开放接口
当我们去调用 __dict__方法,查看类中的名字的时候,可以看到其中被我们隐藏的名字,现在变成了_MyClass__age print(MyClass.__dict__) # '_MyClass__age' # 通过这个名字,还是可以得到我们隐藏的属性 print(MyClass._MyClass__age) # 18
案例:隐藏真的功能是不让用户直接调用,而是去开放接口
# 1 定义类的阶段,隐藏一些名字 class Person: def __init__(self, name, age, hobby): self.__name = name # __变量名让对象也可以拥有隐藏的属性 self.__age = age self.__hobby = hobby def get_info(self): # 类体代码中,可以直接只用隐藏的名字 print(f""" 姓名:{self.__name} 年龄:{self.__age} 爱好:{self.__hobby} """) # 隐藏的属性开放修改的接口,可以自定义很多功能 def set_name(self, new_name): # 类体代码中,可以直接只用隐藏的名字 if len(new_name) == 0: raise ValueError('名字太短') if new_name.isdigit(): raise ValueError('名字是数字') self.__name = new_name obj = Person('json', 18, 'read') obj.get_info() obj.set_name('13d') obj.get_info()
以后我在编写面向对象代码 类的定义的时候,也会看到很多单下划线开头的名字,这也是提醒我们,不要直接使用这些名字
2.伪装属性
(1)装饰器@property:将方法伪装成属性
案例:将计算BMI指数的方法,伪装成属性
BMI指数:衡量一个人的体重与身高对健康影响的一个指标
体脂指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
class Person(object): def __init__(self, name, height, weight): self.name = name self.height = height self.weight = weight @property # 利用装饰器property来将方法伪装成数据 def BMI(self): return self.weight / self.height ** 2 p1 = Person('jason', 1.83, 78) print(p1.BMI)
装饰器@property可以将一个方法伪装成数据,通过句点符来直接调用该方法的结果
@property 可以将python定义的函数“当做”属性访问,从而提供更加友好访问方式,但是有时候 setter/deleter 也是需要的。
(2)修改伪装的属性:@function.setter
和 删除提醒伪装的属性:@function.deleter
(1)只有 @property
表示 只读 。
(2)同时有 @property
和@*.setter
表示 可读可写 。
(3)同时有 @property
和 @*.setter
和@*.deleter
表示可读可写可删除。
class Foo: def __init__(self, val): self.__NAME = val # 将属性隐藏起来 @property def name(self): return self.__NAME # @*.setter 装饰,表示类的属性可修改 @name.setter def name(self, value): if not isinstance(value, str): # 在设定值之前进行类型检查 raise TypeError('%s must be str' % value) self.__NAME = value # 通过类型检查后,将值value存放到真实的位置self.__NAME # @*.deleter 装饰,表示类的属性可删除,删除后,该类无此属性 @name.deleter def name(self): raise PermissionError('Can not delete') f = Foo('jason') print(f.name) # jason f.name = 'jason123' print(f.name) # jason123 del f.name # f.name = 'jason' # 触发name.setter装饰器对应的函数name(f,’jason') # f.name = 123 # 触发name.setter对应的的函数name(f,123),抛出异常TypeError # del f.name # 触发name.deleter对应的函数name(f),抛出异常PermissionError
三、面向对象三大特性之多态
1.多态的含义
多态:一种事物的多种形态
面向对象中多态的意思是,一种事物可以有多种形态,但是针对相同的功能应该定义相同的方法
python提倡自由简洁大方,不约束程序员的行为,但是多态提供了约束的方法
2.多态的案例
(1) 引入案例
比如用代码模仿动物届,叫声spark对应不同的动物都应该有spark方法,都用来表示叫声,尽管不同动物的叫声不同,也就是print的值不同,但是方法是一致的,可以表示面向对象中多态的含义。
class Animal: def spark(self): pass class Cat(Animal): # def miao(self): # print('miaomiao') def spark(self): print('miaomiao') class Dog(Animal): # def wang(self): # print('wangwang') def spark(self): print('wangwang') """ 面向对象中多态的意思是,一种事物可以有多种形态,但是针对相同的功能应该定义相同的方法 这样无论我们拿到的是哪个具体的事物,都可以通过相同的方法调用功能 """
(2)len()
函数中多态的体现
其实我们常用的len()
函数也用到了多态的思想
# len方法也体现了多态性 s1 = 'jsong' l1 = [11, 33, 334, 4] d = {'name': 'duoduo', 'pwd': 123} print(s1.__len__()) print(l1.__len__()) print(d.__len__()) # linux系统 """ 文件 能读取数据也能保存数据 内存 能读取数据也能保存数据 硬盘 能读取数据也能保存数据 。。。。 一切皆文件 """
综上,我们可以得出了经典的鸭子理论:
鸭子类型:只要你看上去像鸭子,走路像鸭子,说话像鸭子,那么你就是鸭子。
也就是只要事物的种类是一致的,那么他们就应该有相同的属性和方法
(3)Linux
中多态的体现
文件 : 能读取数据也能保存数据
内存 : 能读取数据也能保存数据
硬盘 : 能读取数据也能保存数据
按照鸭子理论得出结论==>>一切皆文件
# 比如 文件 内存 硬盘都应该有读取和写入的功能 class File: def read(self): pass def write(self): pass class Memory: def read(self): pass def write(self): pass class Disk: def read(self): pass def write(self): pass # 所以可以将他们认为是同一种类型的对象
(4)abc模块和metaclass
python永远提倡自由简介大方 不约束程序员行为 但是多态提供了约束的方法
ABC(抽象基类),主要定义了基本类和最基本的抽象方法,可以为子类定义共有的API,不需要具体实现。
abc模块,Python 对于ABC的支持模块,定义了一个特殊的metaclass—— ABCMeta 还有一些装饰器—— @abstractmethod 和 @abstarctproperty 。
abc.ABCMeta 是一个metaclass,用于在Python程序中创建抽象基类。
metaclass是“类的类”,秉承Python“一切皆对象”的理念,Python中的类也是一类对象,metaclass的实例就是类(class),自己写metaclass时需要让其继承自type对象。也就是说metaclass的实例化结果是类,而class实例化的结果是instance。
可以这么理解的: metaclass是创建类的模板,所有的类都是通过他来create的(调用__new__),这使得你可以自由的控制 创建类的那个过程,实现你所需要的功能。
metaclass主要用处:
- 你可以自由的、动态的修改/增加/删除 类的或者实例中的方法或者属性
- 批量的对某些方法使用decorator,而不需要每次都在方法的上面加入@decorator_func
- 当引入第三方库的时候,如果该库某些类需要patch的时候可以用metaclass
- 可以用于序列化(参见yaml这个库的实现,我没怎么仔细看)
- 提供接口注册,接口格式检查等
- 自动委托(auto delegate)
import abc # 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化 class Animal(metaclass=abc.ABCMeta): @abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法 def talk(self): # 抽象方法中无需实现具体的功能 pass class Cat(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准 def talk(self): pass cat = Cat() # 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY