首页 > 其他分享 >面向对象三大特性之封装、多态

面向对象三大特性之封装、多态

时间:2022-11-07 19:34:55浏览次数:58  
标签:__ .__ name self 多态 面向对象 def print 三大

目录

一、派生方法实战演练

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

​ 当一个字典类型的数据中有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,无法实例化

标签:__,.__,name,self,多态,面向对象,def,print,三大
From: https://www.cnblogs.com/DuoDuosg/p/16867140.html

相关文章

  • 面向对象之反射
    目录一、面向对象之反射1.hasattr(object,name)2.getattr()3.setattr(object,name,value)4.delattr(object,name)二、反射实战案例1.使用反射的场景:2.案例(1)模拟终端(2......
  • 面向对象三大特性封装,多态,反射
    派生方法实战演练importjsonimportdatetimed={'ti':datetime.date.today()'t2':datetime.datetime.today()'t3':'jason'}res=json.dumps(d)pr......
  • 面向对象之封装、多态、反射
    面向对象之封装、多态、反射面向对象之封装封装:将数据和功能‘封装’起来隐藏:将数据和功能隐藏起来不让用户直接调用,并开发一些接口间接调用,而且可以在接口内添加一些......
  • Python基础之面向对象:5、三大特征-多态
    面对对象之多态目录面对对象之多态一、多态1、多态的概念2、多态的实际应用1、自我约束2、abc模块一、多态1、多态的概念​ 多态在实际应用时较为抽象,指事物的多种形态......
  • Python基础之面向对象:6、三大特征-封装
    面向对象之封装目录面向对象之封装一、封装1、封装的概念2、为什么要封装3、封装的两个层面二、隐藏与调用的方法1、隐藏的方法2、调用与修改的方法三、伪装1、伪装的概念......
  • Python基础之面向对象:7、反射
    面向对象之反射目录面向对象之反射一、反射1、反射的定义2、使用的场景3、常用方法1.hasattr()2.getattr()3.setattr()4.delattr()4、反射的实际应用一、反射1、反射的......
  • 11月7日内容总结——派生方法实例、面向对象三大特性之封装、多态和反射
    目录一、派生方法实战二、面向对象三大特性之封装隐藏属性如何调用隐藏属性隐藏属性的作用和使用方式三、伪装伪装的操作两个小功能:删除和修改四、面向对象三大特性之多态......
  • 面向对象三
    目录派生方法实战演练面向对象三大特性之封装伪装三大特性之多态面向对象之反射反射实战案例派生方法实战演练importjsonimportdatetimed={'t1':datetime.dat......
  • Python基础之面向对象:4、super方法实战
    派生方法实战​ 以上我们学习了通过super()的方法可以重写父类、额外添加父类中的数据,下面将通过实战案例来讲述super()方法来重写、添加父类中的功能代码需求:1、使用js......
  • 面向对象封装/多态/反射
    内容概要派生方法实战面向对象三大特性之封装三大特性之多态面向对象之反射反射实战案例练习题派生方法实战派生方法实战案例:importjsonimportdatetime#......