封装、多态、鸭子类型与反射

79 阅读7分钟

派生方法实战演练

# 需求:序列化含有datetime类型value的字典d
d = {
    't1': datetime.datetime.today(),
    't2': datetime.date.today()
}
res = json.dumps(d)
print(res)  # TypeError: Object of type datetime is not JSON serializable
# 序列化操作会报错,原因是datetime类型的数据不能被序列化。查看dumps源码里的 JSONEncoder 类,我们发现只有以下几种类型的数据能被序列化:
    +-------------------+---------------+
    | Python            | JSON          |
    +===================+===============+
    | dict              | object        |
    +-------------------+---------------+
    | list, tuple       | array         |
    +-------------------+---------------+
    | str               | string        |
    +-------------------+---------------+
    | int, float        | number        |
    +-------------------+---------------+
    | True              | true          |
    +-------------------+---------------+
    | False             | false         |
    +-------------------+---------------+
    | None              | null          |
    +-------------------+---------------+
1.解决方案1:将datetime类型手动转成能被序列化的str类型
d = {
    't1': str(datetime.datetime.today()),
    't2': str(datetime.date.today())
}
res = json.dumps(d)
print(res)
上面那种方法太过简单粗暴,不能展现程序员逼格,
2.解决方案2:利用派生方法
查看 JSONEncoder 源码,我们发现序列化报错是由 default 方法触发的:
raise TypeError(f'Object of type {o.__class__.__name__} '
                        f'is not JSON serializable')
我们如果想要避免报错,就肯定要在原来 default 方法的基础上做修改(派生)

class MyJSONEncoder(json.JSONEncoder):
    def default(self, o):
        # o就是json即将要序列化的数据
        if isinstance(o, datetime.datetime):
            return o.strftime('%Y-%m-%d %H:%M:%S')  # 将datetime转成字符串时间
        elif isinstance(o, datetime.date):
            return o.strftime('%Y-%m-%d')  # 将date转成字符串时间
        # 如果是可以序列化的类型 那么不做任何处理 直接让它序列化即可
        return super().default(o)
    
res = json.dumps(d, cls=MyJSONEncoder)
print(res)  # {"t1": "2022-07-28 15:08:53", "t2": "2022-07-28"}
# 这样我们即使要序列化其他类型的数据。也只需在我们重写的default方法中加一条判断即可,灰常耗用。

1.jpg

使程序报错的 default 方法

面向对象三大特性之封装

封装就是将数据或者功能隐藏起来。
隐藏的目的不是让用户无法使用,而是给这些隐藏起来的属性开设特定的接口,只有调用接口才能使用,我们就可以借机会在接口中添加一些额外的操作。

1.在类的定义阶段,使用双下划线开头的名字,都是隐藏属性,后续类和对象都无法直接获取。
2.python不会真正的限制任何代码,所以想要直接访问隐藏的属性也可以,只不过需要把变量名进行特殊的处理:
	__变量名   ---->    _类名__变量名
  君子协定:既然隐藏了,就不要使用变形过后的名字去访问,这样就失去了隐藏的意义。

class Student(object):
    name = 'jason'
    __age = 19  # 年龄属性已被隐藏
    def get_age(self):  # 专门开设一个返回隐藏属性的接口
        return self.__age  # 类的定义阶段能够直接访问到被隐藏的属性

print(Student._Student__age)  # 19  使用经过特殊处理的变量名也能访问到,但不建议这么做
stu = Student()
print(stu.get_age())  # 19  更建议调用接口来访问被隐藏的属性

我们编写python很多时候都是大家墨守成规的东西 不需要真正的限制,一般单下划线开头,别人就知道我们是想把这个属性隐藏起来
class A:
	_school = '清华大学'
	def _choice_course(self):
		pass

propety伪装属性

可以简单理解为:将方法隐藏成数据
    obj.name  # 数据只需要点名字
    obj.func()  # 方法至少还要多加括号调用
    obj.func  # 伪装成数据之后的方法
需求:计算人的BMI指数  
	体质指数(BMI)=体重(kg)÷身高^2(m)
    
class Person(object):
    def __init__(self, name, weight, height):
        self.name = name
        self.weight = weight
        self.height = height

    def BMI(self):
        return self.weight / self.height  

p1 = Person('jason', 70, 1.80)
print(p1.BMI())  # 38.8
# bmi指数需要我们调用相应的方法计算获得,可是bmi更像是人的数据而不是方法
# 在BMI()方法的头上加一个装饰器 @property,我们就将这个方法变成了一个数据
    @property
    def BMI(self):
        return self.weight / self.height  	
    
print(p1.BMI)  # 38.8    

面向对象三大特性之多态

多态:一种事物的多种形态
	eg:水:液态,气态,固态		动物:猪,狗,猫
class Animal(object):
    def speak(self):  # 每个动物都会叫,所以在父类Animal中定义speak方法
        pass
        
class Cat(Animal):
    def speak(self):
        print('喵喵喵')

class Dog(Animal):
    def speak(self):
        print('汪汪汪')

class Pig(Animal):
    def speak(self):
        print('哼哼哼')	
# c1 = Cat()
# d1 = Dog()
# p1 = Pig()
按理说,每个动物的叫声都不一样,所以应该调用不同的叫声的方法。
# c1.miao()
# d1.wang()
# p1.heng()    
但是反过来想:叫声不一样,方法也不一样,那如果有许多种动物,那岂不是每个动物都要专门为其定义叫声函数。所以:'一种事物有多种形态 但是相同的功能应该有相同的名字'
这样的话 以后我无论拿到哪个具体的动物 都不需要管到底是谁 直接调用相同的功能即可
无论你是鸡 鸭 猫 狗 猪 只要你想叫 你就调固定的叫的功能:speak()
# c1.speak()
# d1.speak()
# p1.speak()

其实上述思想,我们在很早之前就已经接触过
比如:统计数据类型长度的len()方法
# l1 = [11, 22, 33, 44]
# d1 = {'name': 'jason', 'pwd': 123, 'hobby': 'raed'}
# t1 = (11, 22, 33, 44)
# print(len(l1))
# print(len(d1))
# print(len(t1))
无论是列表、字典还是字符串,只要是统计长度,统一都来调用len()方法,不必每个数据类型都记一个专门对应的方法了

"""
python也提供了一种强制性的操作(了解即可)  应该是自觉遵守
"""
# import abc
# # 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
# class Animal(metaclass=abc.ABCMeta):
#     @abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法
#     def talk(self): # 抽象方法中无需实现具体的功能
#         pass
# class Person(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
#     def talk(self):
#         pass
#     def run(self):
#         pass
# obj = Person()

鸭子类型

概念:只要你长得像鸭子 走路像鸭子 说话像鸭子 那么你就是鸭子
'''
eg:操作系统
    linux系统:一切皆文件
        只要你能读数据 能写数据 那么你就是文件
            内存 硬盘
        class Txt: #Txt类有两个与文件类型同名的方法,即read和write
            def read(self):
                pass
            def write(self):
                pass
        
        class Disk: #Disk类也有两个与文件类型同名的方法:read和write
            def read(self):
                pass
            def write(self):
                pass
        
        class Memory: #Memory类也有两个与文件类型同名的方法:read和write
            def read(self):
                pass
            def write(self):
                pass
'''        
python:一切皆对象
    只要你有数据 有功能 那么你就是对象
    文件   --->   文件对象
    模块   --->   模块对象

反射(重要)

概念:通过字符串来操作对象的数据或方法
使用场景 : 根据用户输入操作对应的方法等(用户输入是字符串格式,所以不能直接加括号调用,这时候就需要用到反射)

反射主要就四个方法
	hasattr():判断对象是否含有某个字符串对应的属性
	getattr():获取对象字符串对应的属性
 	setattr():根据字符串给对象设置属性
	delattr():根据字符串给对象删除属性
# 需求:判断用户提供的名字在不在对象可以使用的范围内      
class Student:
    school = '清华大学'

    def choice_course(self):
        print('选课')
stu = Student()

while True:
    inp = input('请输入您要查找的属性')
    if hasattr(Student, inp):  # 判断Student类中有没有属性inp
        res = getattr(Student, inp)  # 获取字符串inp对应的属性
        if callable(res):  # 判断res是
            print(f'{inp}是可以被调用的方法名')
        else:
            print(f'{inp}是一个变量名')
    else:
        print(f'没有该属性:{inp}')
# 输入:name  打印:没有该属性:name
# 输入:school  打印:school是一个变量名
# 输入:choice_course  打印:choice_course是可以被调用的方法名
"""
以后只要在需求中看到了关键字
	....对象....字符串 
那么肯定需要使用反射
"""

反射实战案例

class FtpServer:
    def serve_forever(self):
        while True:
            inp = input('input your cmd>>: ').strip()
            cmd, file = inp.split()
            if hasattr(self, cmd):  # 根据用户输入的cmd,判断对象self有无对应的方法属性
                func = getattr(self, cmd)  # 根据字符串cmd,获取对象self对应的方法属性
                func(file)
    def get(self, file):
        print('Downloading %s...' % file)

    def put(self, file):
        print('Uploading %s...' % file)
obj = FtpServer()
obj.serve_forever()