一、派生方法实战演练

举例:时间对象序列化报错

​ 当一个字典类型的数据中有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格式之间互相转换的数据类型,如图

image-20221107150155294
image-20221107150435425

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主要用处

  1. 你可以自由的、动态的修改/增加/删除 类的或者实例中的方法或者属性
  2. 批量的对某些方法使用decorator,而不需要每次都在方法的上面加入@decorator_func
  3. 当引入第三方库的时候,如果该库某些类需要patch的时候可以用metaclass
  4. 可以用于序列化(参见yaml这个库的实现,我没怎么仔细看)
  5. 提供接口注册,接口格式检查等
  6. 自动委托(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,无法实例化

原文地址:http://www.cnblogs.com/DuoDuosg/p/16867140.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性