面向对象——派生方法、封装、多态、反射
一、派生方法:
子类基于父类某个方法做了扩展
class Person: def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(Person): pass (在继承父类Person的同时还想添加自己特有的类,代码应该如何写呢?) class Teacher(Person): pass (在继承父类Person的同时还想添加自己特有的类,代码应该如何写呢?)
super方法:
class Person: def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(Person): def __init__(self, name, age, gender, sid): super().__init__(name, age, gender) # 子类调用父类的方法 self.sid = sid class Teacher(Person): def __init__(self, name, age, gender, level): super().__init__(name, age, gender) self.level = level stu1 = Student('jason', 18, 'male', 666) print(stu1.__dict__) # {'name': 'jason', 'age': 18, 'gender': 'male', 'sid': 666}
tea1 = Teacher('tony', 28, 'female', 99) print(tea1.__dict__) # {'name': 'tony', 'age': 28, 'gender': 'female', 'level': 99}
派生方法实战:
import json import datetime d = { 't1': datetime.date.today(), 't2': datetime.datetime.today(), 't3': 'jason' } res = json.dumps(d) #把上述字典序列化 print(res) # 报错
上述代码无法执行,会报错,是因为能够被序列化的数据是有限的>>>:里里外外都必须是下列图表中左边的类型:
Python | JSON |
dict | object |
list, tuple | array |
str | string |
int, float | number |
True
|
true |
False | false |
None | null |
转换方式1:手动转类型(简单粗暴)
d = { 't1': str(datetime.date.today()), 't2': str(datetime.datetime.today()) } res = json.dumps(d) print(res) # {"t1": "2022-11-07", "t2": "2022-11-07 16:18:42.207973"}
转换方式2:派生方法(儒雅高端)
查看dumps源码 注意cls参数 默认传JsonEncoder
查看该类的源码 发现default方法是报错的发起者
编写类继承JsonEncoder并重写default方法,之后调用dumps手动传cls=我们自己写的类
import json import datetime class MyJsonEncoder(json.JSONEncoder): def default(self, o): """ :param o: 接收无法被序列化的数据 :return: 返回可以被序列化的数据 """ if isinstance(o, datetime.datetime): # 判断是否是datetime类型,如果是则处理成可以被序列化的类型 return o.strftime('%Y-%m-%d %X') elif isinstance(o, datetime.date): return o.strftime('%Y-%m-%d') return super().default(o) # 最后还是调用原来的方法,防止有一些额外操作没有做 res = json.dumps(d, cls=MyJsonEncoder) print(res) # {"t1": "2022-11-07", "t2": "2022-11-07 16:33:10.078075"}
二、三大特性之封装
封装: 就是将数据和功能'封装'起来
- 隐藏: 将数据和功能隐藏起来不让用户直接调用,而是开发一些接口间接调用从而可以在接口内添加额外的操作
- 伪装:将类里面的方法伪装成类里面的数据
隐藏
类在定义阶段,如果名字前面有两个下划线,那么该名字会被隐藏起来,无法直接访问
class MyClass: school_name = '清华大学' _ = '嘿嘿嘿' _name = 'tony' __age = 18
'''类在定义阶段,名字前面有两个下划线,那么该名字会被隐藏起来,无法直接访问'''
def __choice_course(self):
print('学生正在选课')
print(MyClass.school_name) # 清华大学
print(MyClass._) # 嘿嘿嘿
print(MyClass._name) # tony
print(MyClass.__age) # 报错
在非定义阶段进行添加或修改,是可以访问到的:
class MyClass: school_name = '清华大学' _ = '嘿嘿嘿' _name = 'tony' __age = 18 def __choice_course(self): print('学生正在选课') MyClass.__hobby = 'JDB' print(MyClass.__hobby) # 无法隐藏 JDB obj = MyClass() obj.__addr = '派出所' print(obj.__addr) # 无法隐藏 派出所
在python中其实没有真正意义上的隐藏,仅仅是换了个名字而已 _类名__名字
class MyClass: school_name = '清华大学' _ = '嘿嘿嘿' _name = 'tony' __age = 18 def __choice_course(self): print('学生正在选课') print(MyClass._MyClass__age) # 18
隐藏的作用是什么呢?我们通过举例来说明:
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} """) obj = Person('jason', 18, 'read') obj.get_info() # 姓名:jason # 年龄:18 # 爱好:read
如果想要修改隐藏的名字,同样也可以定义一个新的函数,在新的函数中进行修改,如果要使用新的名字,则直接调用新函数:
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): self.__name = new_name obj = Person('jason', 18, 'read') obj.get_info() """ 姓名:jason 年龄:18 爱好:read """ obj.set_name('tony老师') obj.get_info() """ 姓名:tony老师 年龄:18 爱好:read """
我们还可以在新的函数中自定义很多其他的功能:
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('你好歹写点东西') self.__name = new_name obj.set_name('') # 输入的名字为空,则直接报错
ps: 以后我们在编写面向对象的代码类的定义时,也会看到很多单下划线开关的名字,表达的意思通常也是不要直接访问,而是查找一下下面可能定义的接口
隐藏函数属性
目的的是为了隔离复杂度,例如ATM程序的取款功能,该功能有很多其他功能组成,比如插卡、身份认证、输入金额、打印小票、取钱等,而对使用者来说,只需要开发取款这个功能接口即可,其余功能我们都可以隐藏起来。
class ATM: def __card(self): #插卡 print('插卡') def __auth(self): #身份认证 print('用户认证') def __input(self): #输入金额 print('输入取款金额') def __print_bill(self): #打印小票 print('打印账单') def __take_money(self): #取钱 print('取款') def withdraw(self): #取款功能 self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() obj=ATM() obj.withdraw()
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
身高或体重是不断变化的,因而每次想查看BMI值都需要通过计算才能得到,但很明显BMI听起来更像是一个特征而非功能,为此Python专门提供了一个装饰器property,可以将类中的函数“伪装成”对象的数据属性,对象在访问该特殊属性时会触发功能的执行,然后将返回值作为本次访问的结果,例如:
class Person(object): def __init__(self, name, height, weight): self.name = name self.height = height self.weight = weight def BMI(self): return self.weight / (self.height ** 2) p1 = Person('jason', 1.83, 78) p1.BMI() # BMI应该作为人的基本数据而不是方法
如果想让BMI以数据的形式呈现在人面前,为此Python专门提供了一个装饰器property,可以将类中的函数“伪装成”对象的数据属性,对象在访问该特殊属性时会触发功能的执行,然后将返回值作为本次访问的结果,例如:
class Person(object): def __init__(self, name, height, weight): self.name = name self.height = height self.weight = weight @property def BMI(self): return self.weight / (self.height ** 2) p1 = Person('jason', 1.83, 78) print(p1.BMI) # 利用装饰器伪装成数据
如果隐藏之后涉及到数据修改和删除的情况,还需要再添加两个装饰器:
数据修改:伪装函数名.setter
删除修改:伪装函数名.deleter
class Foo: def __init__(self, val): self.__NAME = val # 将属性隐藏起来 @property def name(self): return self.__NAME @name.setter def name(self, value): if not isinstance(value, str): # 在设定值之前进行类型检查 raise TypeError('%s must be str' % value) self.__NAME = value # 通过类型检查后,将值value存放到真实的位置self.__NAME @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
三、
多态指的是一类事物有多种形态,比如动物有多种形态:猫、狗、猪
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('哼哼哼') # 实例化得到三个对象 cat = Cat() dog = Dog() pig = Pig()
多态性指的是可以在不用考虑对象具体类型的情况下而直接使用对象,这就需要在设计时,把对象的使用方法统一成一种:例如cat、dog、pig都是动物,但凡是动物肯定有talk方法,于是我们可以不用考虑它们三者的具体是什么类型的动物,而直接使用
cat.talk() # 喵喵喵 dog.talk() # 汪汪汪 pig.talk() # 哼哼哼
更进一步,我们可以定义一个统一的接口来使用:
def Talk(animal): animal.talk() Talk(cat) # 喵喵喵 Talk(dog) # 汪汪汪 Talk(pig) # 哼哼哼
面向对象中多态意思是,一种事物可以有多种形态但是针对相同的功能应该定义相同的方法,这样无论我们拿到的是哪个具体的事物 都可以通过相同的方法调用功能。
Python中一切皆对象,本身就支持多态性。
我们可以在不考虑三者类型的情况下直接使用统计三个对象的长度 s.__len__() l.__len__() t.__len__() # Python内置了一个统一的接口 len(s) len(l) len(t)
多态性的好处在于增强了程序的灵活性和可扩展性,比如通过继承Animal类创建了一个新的类,实例化得到的对象obj,可以使用相同的方式使用obj.talk()
class Wolf(Animal): #动物的另外一种形态:狼 def talk(self): print('嗷...') wolf=Wolf() # 实例出一头狼 wolf.talk() # 使用者根本无需关心wolf是什么类型而调用talk 嗷...
综上我们得知,多态性的本质在于不同的类中定义有相同的方法名,这样我们就可以不考虑类而统一用一种方式去使用对象,可以通过在父类引入抽象类的概念来硬性限制子类必须有某些方法名。
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,无法实例化
但其实我们完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象,这正是Python崇尚的“鸭子类型”(duck typing):“如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度,如下:
#二者看起来都像文件,因而就可以当文件一样去用,然而它们并没有直接的关系 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
四、面向对象之反射
在python中,反射指的是通过字符串来操作对象的属性,涉及到四个内置函数的使用(python中一切皆对象,类和对象都可以用下述四个方法)
1.hasattr() 重点
判断对象是否含有某个字符串对应的属性名或方法名
2.getattr() 重点
根据字符串获取对象对应的属性名(值)或方法名(函数体代码)
class C1: school_name = '小姐姐学院' def choice_course(self): print('大宝贝们正在选课') obj = C1()
'''判断某个名字对象是否可以使用(存在)'''
推导思路:
try: obj.xxx except AttributeError: print('你木有这个名字')
'''判断用户随意指定的名字对象是否可以使用(存在)'''
target_name = input('请输入对象可能使用的名字>>>:').strip() try: obj.target_name except AttributeError: print('你木有这个名字')
当我们输入school_name时,仍然会捕获异常:
出现上述情况的原因是input里面输入的school_name是字符串数据值,而我们类中存在的school_name是变量名,两者指代的不是同一个东西。
反射可以利用字符串操作对象的数据和方法。
print(hasattr(obj, 'school_name')) # True print(getattr(obj, 'school_name')) # 小姐姐学院 print(getattr(obj, 'choice_course')) # <bound method C1.choice_course of <__main__.C1 object at 0x00000248C0B65A30>>
基于反射可以十分灵活地操作对象的属性,比如将用户交互的结果反射到具体的功能执行。
while True: target_name = input('请输入您想要操作的名字>>>:') if hasattr(obj, target_name): print('恭喜您 系统中有该名字') # 获取该名字对应的数据(值 函数) data_or_func = getattr(obj, target_name) if callable(data_or_func): print('您本次使用的是系统中的某个方法') data_or_func() else: print('您本次使用的是系统中的某个数据') print(data_or_func) else: print('很抱歉 系统中没有该名字')
3.setattr()
根据字符串给对象设置或者修改数据
4.delattr()
根据字符串删除对象里面的名字
class C1: school_name = '小姐姐学院' def choice_course(self): print('大宝贝们正在选课') obj = C1() print(obj.__dict__) # {}
setattr(obj, 'name', 'jason') # obj.name = 'jason' print(obj.__dict__)
setattr(obj, 'name', 'jason123') # obj.name = 'jason123' print(obj.__dict__) # {'name': 'jason123'} delattr(obj, 'name') print(obj.__dict__) # {}
反射实战案例
1.什么时候应该考虑使用反射,只要需求中出现了关键字>>>
对象....字符串....
案例1:模拟cmd终端
class WinCmd: def tasklist(self): print(""" 1.学习编程 2.学习python 3.学习英语 """) def ipconfig(self): print(""" 地址:127.0.0.1 地址:上海浦东新区 """) def get(self, target_file): print('获取指定文件',target_file) def put(self, target_file): print('上传指定文件',target_file) def server_run(self): print('欢迎进入简易版本cmd终端') while True: target_cmd = input('请输入您的指令>>>:') res = target_cmd.split(' ') if len(res) == 1: if hasattr(self, res[0]): getattr(self, res[0])() else: print(f'{res[0]}不是内部或者外部命令') elif len(res) == 2: if hasattr(self, res[0]): getattr(self, res[0])(res[1]) else: print(f'{res[0]}不是内部或者外部命令') obj = WinCmd() obj.server_run()
案例2:利用反射保留settings.py文件中所有的大写变量名及对应的数据值
settings.py文件中的数据: NAME = 'jason' AGE = 18 desc = '我想干饭行不行' info = '学不好咋办'
代码实现:
import settings print(dir(settings)) # dir列举对象可以使用的名字 userful_dict = {} for name in dir(settings): # {'AGE': 18, 'NAME': 'jason'} if name.isupper(): userful_dict[name] = getattr(settings, name) print(userful_dict) # {'AGE': 18, 'NAME': 'jason'}
案例3:判断某个文件中是否有我们想要的名字
import settings while True: target_name = input('请输入某个名字') if hasattr(settings, target_name): print(getattr(settings, target_name)) else: print('该模块文件中没有该名字')