类的补充
一、派生的实际操作
之前我们所讲的派生有很强大的功能
可以继承父类的同时添加新功能
现在有个案例:
将获取的时间按照字典的形式实例化到json格式
import json import datetime d = { '1': datetime.datetime.today(), '2': datetime.date.today() } # print(d) # {'1': datetime.datetime(2022, 7, 28, 14, 38, 11, 200242), '2': datetime.date(2022, 7, 28)} # 现在要实例化的操作的话是不是只需要dump即可 print(json.dumps(d)) # 但是会报错 ''' 我们之前知道想要转json格式是有特定数据类型的: +-------------------+---------------+ | Python | JSON | +===================+===============+ | dict | object | +-------------------+---------------+ | list, tuple | array | +-------------------+---------------+ | str | string | +-------------------+---------------+ | int, float | number | +-------------------+---------------+ | True | true | +-------------------+---------------+ | False | false | +-------------------+---------------+ | None | null | +-------------------+---------------+ 只有这上面表格内的数据才能转成json格式 而想要转的话一个数据里的里里外外都要是上方表格里的数据 而datetime.datetime.today()是时间类型的所以会报错 '''
1.解决方案一
# 我们可以把datetime.datetime.today()转成上方表格中的任意数据类型即可 d = { '1': str(datetime.datetime.today()), '2': str(datetime.date.today()) } print(json.dumps(d)) # {"1": "2022-07-28 14:46:14.246448", "2": "2022-07-28"} # 这样操作就能够转换成json格式
2.解决方案二
''' 第二种我们可以尝试更改源码解决问题 报错信息: TypeError: Object of type 'datetime' is not JSON serializable ''' d = { 't1': datetime.datetime.today(), 't2': datetime.date.today() } # res = json.dumps(d) # print(res) ''' 我们可以点击dumps查看源码我们可以看到dumps有很多默认形参 我们可以找到cls可以看到 cls=None 因为cls默认等于None的 if cls is None: cls = JSONEncoder return cls(...) 所以cls会一直等于JSONEncoder 然后我们可以进入JSONEncoder查看源码 我们可以看到: def default(self, o): """...""" raise TypeError("Object of type '%s' is not JSON serializable" % o.__class__.__name__) 我们就可以知道报错是由 default执行的 那么我们就可以自己编写一个类继承JSONEncoder ''' class MYJSONEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, datetime.datetime): return o.strftime('%Y-%m-%d %H:%M:%S') # 转换成字符串格式 elif isinstance(o, datetime.date): return o.strftime('%Y-%m-%d ') # 转换成字符串格式 else: return super().default(o) print(json.dumps(d, cls=MYJSONEncoder)) # {"t1": "2022-07-28 15:09:35", "t2": "2022-07-28 "}
# 就是使用的时候加一个参数即可
''' 当cls有值时 就不会执行JSONEncoder 而是会执行我们自己定义的类(MYJSONEncoder) 然后会先执行我们自己的default方法 然后在通过super方法继续使用原来的父类(JSONEncoder)里的default方法 '''
二、面对对象三大特征之封装
封装其实就是把数据和功能封装(隐藏)起来
隐藏的目的不是让用户无法使用 而是给这些数据和功能开设特定的接口
让用户使用接口才可以使用 我们在接口添加额外操作
2.1 封装的定义
就是在定义阶段在变量名前面加上双下划线
这样就能把数据或功能封装起来了
class Student: school = '清华大学' def course(self): print('正在选课') stu1 = Student() # 我们可以使用对象点的方法使用类中的数据 print(stu1.school) # 清华大学 stu1.course() # 正在选课 # 但是我们如果封装之后这样对象就不能直接使用类中的数据 class Student: __school = '清华大学' def __course(self): print('正在选课') stu2 = Student() print(stu2.__school) # 就会报错 stu2.__course() # 报错
2.2封装如何取值
在python中是没有真正意义上的隐藏数据的
因为python提倡自由所以
python的隐藏其实只是把变量名稍微做了修改
# 其实就是把变量名改成了 _类名__变量名 class Student: __school = '清华大学' def __course(self): print('正在选课') stu2 = Student() print(stu2._Student__school) # 清华大学 print(stu2._Student__course()) # 正在选课 # 这样就能够得到类中的数据和功能了
2.3 封装的意义
1.在定义阶段使用双下划线开头的名字 都是隐藏的属性
后续类和对象都不能直接获取
2.在python中不会真正意义上去限制任何代码
隐藏的属性如果想要访问 也可以 只不过需要做出改变处理
__变量名 >>> _类名__变量名
ps:既然隐藏了 就不该使用变形后的名字去访问 这样就失去了隐藏的意义
我们可以开一个接口让用于去查看数据
# 其实就是把变量名改成了 _类名__变量名 class Student: def __init__(self, name, age): self.__name = name self.__age = age # 专门开一个接口查看信息 def get_info(self): print(f""" 学生姓名:{self.__name} 学生年龄:{self.__age} """) # 开一个接口修改数据 def update_info(self, name, age): if len(name) == 0: print('名字不能为空') return if not isinstance(age, int): print('年龄必须是数字') return self.__name = name self.__age = age
stu2 = Student('jason', 18) stu2.__name = 'jerry' print(stu2.__name) # 在使用阶段自己编写的双下划线 还是可以访问的 stu2 = Student('jason', 18) print(stu2.get_info()) ''' 学生姓名:jason 学生年龄:18 ''' stu2.update_info('tony', 18) print(stu2.get_info()) stu2.update_info('', '中文') # 名字不能为空 ''' 学生姓名:tony 学生年龄:18 ''' # 我们在后续遇到类中在定义阶段看到下划线的一般都是隐藏属性 一般不用直接访问 class A: _school = '清华大学' def _course(self): pass # 一个下滑线也可以看成隐藏属性 我们要自觉遵守
2.4property伪装属性
我们可以理解为把方法伪装成数据
我们在调用类中的数据和方法:
obj.name # 只需要句点符和名字
obj.func() # 方法还需要加括号
所以我们可以把一个方法伪装成数据
就比如:人类的BMI指数:
BMI = 体重/(身高*身高)
class Person: def __init__(self, name, weight, height): self.name = name self.weight = weight self.height = height def BMI(self): return self.weight / (self.height ** 2) p1 = Person('jason', 75, 1.83) print(p1.BMI()) # 22.395413419331717
# BMI指数虽然是计算出来的 但是还是像是人的数据 所以我们可以在BMI上加上语法糖 class Person: def __init__(self, name, weight, height): self.name = name self.weight = weight self.height = height @property def BMI(self): return self.weight / (self.height ** 2) p1 = Person('jason', 75, 1.83) print(p1.BMI) # 22.395413419331717 # 这样我们就可以直接调用名字使用即可 使用的时候就会更方便一些
三、面对对象三大特征之多态
多态就是一个事物有多个形态
eg:
水:固态、液态、气态、class Animal: #同一类事物:动物
def talk(self): pass class Cat(Animal): #动物的形态之一:猫 def miao(self): print('喵喵喵') class Dog(Animal): #动物的形态之二:狗 def wang(self): print('汪汪汪') class Pig(Animal): #动物的形态之三:猪 def heng(self): print('哼哼哼') # 每一种动物的叫声都一样 但是如果只要是动物都会叫的话我们完全可以使用同样的名字 c = Cat() d = Dog() p = Pig() c.miao() d.wang() p.heng() ''' 喵喵喵 汪汪汪 哼哼哼 ''' class Animal: #同一类事物:动物 def talk(self): pass class Cat(Animal): #动物的形态之一:猫 def talk(self): print('喵喵喵') class Dog(Animal): #动物的形态之二:狗 def talk(self): print('汪汪汪') class Pig(Animal): #动物的形态之三:猪 def talk(self): print('哼哼哼') ''' 多态性就是可以在不考虑对象具体类型的情况下而直接使用对象这就需要在设计时,把对象的使用方法统一成一种 eg:cat、dog、pig 都有talk功能 只要想要使用
那么我们就可以不用考虑是什么动物类型直接使用talk即可 ''' c = Cat() d = Dog() p = Pig() c.talk() d.talk() p.talk() ''' 喵喵喵 汪汪汪 哼哼哼 '''
其实上述多态的概念 我们之前就已经使用过
# 就比如我们计算一个容器里的数据数量时我们自己使用len方法即可 l1 = [1, 2, 3, 4, 5] d1 = {'name': 'jason', 'pwd': 123, 'gender': 'run'} t1 = (11, 22, 33, 44, 55) print(len(l1)) # 5 print(len(d1)) # 3 print(len(t1)) # 5 # 就是我们在使用len功能的时候只要是容器类型那么完全不用看调用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() # 这样是不会报错的 import abc # 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化 class Animal(metaclass=abc.ABCMeta): @abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法 def talk(self): # 抽象方法中无需实现具体的功能 pass class Person(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准 def talk11(self): pass def run(self): pass obj = Person() # 但是一旦子类中没有没有talk方法就会直接报错
3.1 鸭子类型
''' 鸭子类型: 其实就是 只要你长的像鸭子 走的像鸭子 说话像鸭子 那么你就是鸭子 ''' class Teacher: def run(self): pass def eat(self): pass class Student: def run(self): pass def eat(self): pass ''' 现在有两个类 老师类和学生类 没有继承任何父类 但是他们肯定有共同的功能 那么我们在命名的时候就要名字成一样 '''
3.2一切皆对象
""" 操作系统 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:一切皆对象 只要你有数据 有功能 那么你就是对象 文件名 文件对象 模块名 模块对象 """
四、面向对象之反射
反射其实就是 通过字符串来操作对象的数据和方法
class Student: school = '清华大学' # 现在我们可以直接通过对象调用school的数据 stu1 = Student() print(stu1.school) # 清华大学 # 但是现在我们想通过用户输入来调用者个school的数据 target_name = input('请输入数据名>>>:').strip() print(stu1.target_anme) ''' 这样是使用不了的 因为input输入是字符串类型直接输入school 最后出来的是'school' 那么stu1.target_anme 其实就是等于 stu1.'school' 那么显然类中是没有'school'这个数据的 那么我们该如何跟用户交互起来呢? '''
那么我们就可以使用到反射的方法了
而反射又有四种方法
4.1 hasattr()
hasattr:判断对象是否含有某个字符串对应的属性
class Student: school = '清华大学' def course(self): print('正在选课') stu1 = Student() # hasattr():判断对象是否含有某个字符串对应的属性 # 那么我们就可以使用用户输入来判断对象中是否有我们想要的属性 print(hasattr(stu1, 'school')) # True 有返回True print(hasattr(stu1, 'get_school')) # False 没有返回False target_name = input('请输入想要查找的属性名>>>:').strip() if hasattr(stu1, target_name): print(target_name) else: print(f'对象中没有{target_name}属性') ''' 请输入想要查找的属性名>>>:school school 请输入想要查找的属性名>>>:xxx 对象中没有xxx属性 '''
4.2 getattr()
getattr:获取对象字符串对应的属性
class Student: school = '清华大学' def course(self): print('正在选课') # 现在我们可以直接通过对象调用school的数据 stu1 = Student() print(getattr(stu1, 'school')) # 清华大学 print(getattr(stu1, 'course')) # <bound method Student.course of <__main__.Student object at 0x000001E9AD8792E8>> print(getattr(stu1, 'get_school')) # 没有就会报错 # 通常hasattr和getattr是一起使用的 while True: target_name = input('请输入想要查找的属性名>>>:').strip() if hasattr(stu1, target_name): res = (getattr(stu1, target_name)) if callable(res): print('拿到的是一个函数', res()) else: print('拿到的是一个数据', res) else: print('没有该属性') ''' 请输入想要查找的属性名>>>:school 拿到的是一个数据 清华大学 请输入想要查找的属性名>>>:course 正在选课 拿到的是一个函数 None 请输入想要查找的属性名>>>:get_course 没有该属性 '''
4.3 setattr()
setattr:根据字符串给对象设置属性
class Student: school = '清华大学' def course(self): print('正在选课') stu1 = Student() # 一般我们给对象添加新的属性的时候都是用点的形式添加: # stu1.name = 'jason' # stu1.age = 18 # print(stu1.__dict__) # {'name': 'jason', 'age': 18} # setattr: 是根据字符串给对象设置属性 # 所以我们可以通过setattr添加新的属性 setattr(stu1, 'name', 'jason') # {'name': 'jason'} setattr(stu1, 'gender', 'male') # {'name': 'jason', 'gender': 'male'} print(stu1.__dict__)
4.4delattr()
delattr:根据字符串给对象删除属性
class Student: school = '清华大学' def course(self): print('正在选课') stu1 = Student() stu1.name = 'jason' stu1.age = 18 # 我们之前删除对象的属性可以使用del # print(stu1.__dict__) # {'name': 'jason', 'age': 18} # del stu1.name # print(stu1.__dict__) # {'age': 18} # delattr:根据字符串给对象删除属性 # 我们就可以通过delattr删除对象的属性 stu1.gender = 'male' print(stu1.__dict__) # {'name': 'jason', 'age': 18, 'gender': 'male'} delattr(stu1, 'name') print(stu1.__dict__) # {'age': 18, 'gender': 'male'}
这样我们就可以通过字符串来访问和修改对象中的数据
既然是字符串那么我们就可以人类进行交互了
因为input的输出是 字符串类型
所以以后只要在需求看见关键字
...对象名...字符串
我们就可以使用反射方法
4.5 小案例
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() ''' 就是根据对象输入方法 来后通过hasattr判断对象是否有输入方法 如果有那么就通过getattr方法来执行 '''