反射,元类,项目生命周期,选课系统分析
反射reflect(框架的基石)
什么是反射?
- 其实是反省,自省的意思;反射指的是一个对象应该具备可以检测修改增加自身属性的能力
- 反射就是通过字符串操作属性
涉及的四个函数:
- 这四个函数就是普通的内置函数,没有双下划线,与print等函数没有区别
hasattr getattr setattr delattr
# hasattr getattr setattr delattr的应用 class Person: def __init__(self,name,age,gender): self.name = name self.age = age self.gender = gender p = Person('jack',20,'man') print(hasattr(p.'names')) # False if hasattr(p.'name'): # 判断某个对象是否存在某个属性 print(p.name) # jack # 从对象中取出属性 print(getattr(p, 'name')) # jack # 从对象中取出属性,第三个值为默认值,当默认值为None时,返回None print(getattr(p, 'name',None)) # None # 为对象添加新的属性 setattr(p, 'id', '123') print(p.id) # 123 delattr(p, 'id') print(p.id) # 报错,因为已经被删掉了
使用场景:
- 反射其实就是对属性的增删改查,但是如果直接使用内置的__ dict __来操作,语法繁琐,不好理解
- 另外一个最主要的问题,如果对象不是我自己写的,而是另一方提供的,那么我就必须要判断这个对象是否具备我需要的属性和方法
为什么反射被称为框架的基石?
- 因为框架的设计者,不可能提前知道你的对象是怎么设计的,所以你提供给框架的对象,必须通过判断验证之后才能正常使用
- 判断验证就是反射要做的事,当然通过__ dict __也是可以实现的,其实这些方法也就是对双下dict的操作进行了封装
需求:要实现一个用于处理用户的终端指令的小框架
# 创建一个plugins插件对象,存放指令并调用框架使用它 class WinCMD: def cd(self): print('wincmd 切换目录...') def delete(self): print('wincmd 要不要删库跑路...') def dir(self): print('mycmd 切换目录...') class LinuxCMD: def cd(self): print('Linuxcmd 切换目录...') def rm(self): print('Linuxcmd 要不要删库跑路...') def ls(self): print('Linuxcmd 列出所有文件...') # 调用操作 def run(plugin): while True: cmd = input('请输入指令:').strip() if cmd == 'exit': break # 判断对象是否具备处理指令的方法 if hasattr(plugin,cmd): # 取出指令对应方法 func = getattr(plugin,cmd) func() # 执行方法处理指令 else: print('该指令不受支持...') print('see you la la!') wincmd = plugins.WinCMD() linux = plugins.LinuxCMD() run(linux)
动态导入:
在框架设计中,我们不可能提前知道,用框架的用户要提供的相关的信息
plugins.py
class WinCMD: def cd(self): print('wincmd 切换目录...') def delete(self): print('wincmd 要不要删库跑路...') def dir(self): print('mycmd 切换目录...') class LinuxCMD: def cd(self): print('Linuxcmd 切换目录...') def rm(self): print('Linuxcmd 要不要删库跑路...') def ls(self): print('Linuxcmd 列出所有文件...')
myframework.py
import importlib import settings def run(plugin): while True: cmd = input('请输入指令:').strip() if cmd == 'exit': break # 判断对象是否具备处理指令的方法 if hasattr(plugin,cmd): # 取出指令对应方法 func = getattr(plugin,cmd) func() # 执行方法处理指令 else: print('该指令不受支持...') print('see you la la!') pl = importlib.import_module('libs.plugins') # 框架之外的对象由自定义完成 # 框架得根据配置文件拿到需要得类 path = settings.CLASS_PATH # 从配置中单独拿出来,模块路径和类名称 moudule_path, class_name = path_rsplit('.',1) print(moudle_path) # libs.plugins print(class_name) # WinCMD # 拿到模块 mk = importlib.import_module(moudule_path) # 拿到类 cls = getattr(mk,class_name) # 实例化对象 obj.cls() # 调用框架 run(obj)
如此一来,框架就与实现代码彻底解耦了,只剩下配置文件
但是上述框架代码中,写死了必须使用某个类,这是不合理的,因为无法提前知道对方的类在什么地方,以及类叫什么名字,所以我们应该为框架的使用者提供一个配置文件,要求对方将类的信息写入配置文件中,然后框架自己加载需要的模块
settings.py
# 该文件作为框架的配置文件 # 作为框架使用者,在配置文件中指定你配合框架的类是哪个 CLASS_PATH = 'libs.plugins.WinCMD' CLASS_PATH = 'libs.plugins.LinuxCMD'
如此一来,框架就与实现代码彻底解耦了,只剩下配置文件
元类metaclass
元类是什么?
- 用于创建类的类
- 万物皆对象,类当然也是对象
- 对象是通过类实例化产生的,如果类也是对象的话,那么类对象必然也是由另一个类实例化产生的
- 默认情况下,所有类的元类都是type
验证:类的类是谁?且Person类是通过type类实例化产生的
# 类的类是谁? class Person(object): name = '123' pass p = Person() print(type(p)) # Person类是通过type类实例化产生的 print(type(Person)) class Student: pass print(type(Student))
直接调用type类来产生类对象
一个类的三个基本组成部分:
- 1.类的名字(字符类型)
- 2.类的父亲们(是一个元祖或者列表)
- 3,类的名称空间(是一个字典类型)
cls_obj = type('dog',(),{}) print(cls_obj) # <class '__main__.dog'>
学习元类的目的:
- 高度的自定义一个类
- 类也是对象,也有自己的类
- 我们的需求是创建类对象时做一些限制,所以我们可以用初始化方法,我们只要找到类对象的类(元类),覆盖其中的init方法,就能实现需求
- 当然我们不能修改源代码,所以应该继承type来编写自己的元类,同时覆盖init方法来完成需求
例如控制类的名字必须以大驼峰的方式来书写
# 只要继承了type,那么这个类就变成了一个元类 class MyType(type): # 此时MyType是一个元类 def __init__(self,class_name,bases,dict): super().__init__(class_name,bases,dict) if not class_name.istitle(): raise Exception('你丫的,类名不会写吗') MyType('pig',(),{}) # pig () {} # 为Pig类指定了MyType元类 class Pig(metaclass=MyType): pass
元类中的call方法:
当你调用类对象时会自动执行元类中的双下call方法,并将这个类本身作为第一个参数传入,以及后面的一堆参数
覆盖元类中的call后,这个类就无法产生对象,必须调用super().双下call来完成对象的创建,并返回其返回值
class MyMeta(type): def __init__(self,name,bases,dict): super().__init__(name,bases,dict) print('init run') def __call__(self, *args, **kwargs): print('元类 call run') return super().__call__(*args,**kwargs) class Dog(metaclass=MyMeta): def __init__(self,name): self.name = name def __call__(self, *args, **kwargs): print('call run') # 未调用Dog类时,返回init run # 调用Dog类 d = Dog() # call run d = Dog('大黄',age=1) print(d) # init call run ('大黄') {'age':1}
使用场景:
- 当你想要控制对象的创建过程时,就可以覆盖call方法
- 当你想要控制类的创建过程时,就覆盖init方法
案例:想把对象的所有属性变成大写
class MyMeta(type): def __call__(self, *args, **kwargs): new_args = [] for a in args: new_args.append(a.upper()) print(new_args) # ('JACK') print(kwargs) # {} return super().__call__(*new_args,**kwargs) class Person(metaclass=MyMeta): def __init__(self,name,gender): self.name = name self.gender = gender p = Person(name='jack',gender='woman') print(p.name) # JACK print(p.gender) # WOMAN
案例:要求创建对象时必须以关键字参数形式来传参
- 覆盖元类的双下call方法
- 判断你有没有传非关键字参数,不能有位置参数,如果有就报错
# 第一种方法 class Meta(type): def __call__(self, *args, **kwargs) if args: raise Exception('不好意思,不允许使用位置参数') return super().__call__(*args,**kwargs) class A(metaclass=Meta): def __init__(self,name): self.name = name a = A(name='jack') print(a.name) # jack # 第二种方式 class Meta(type): def __call__(self, *args, **kwargs) obj = object.__new__(self) # 创建一个空对象 self.__init__(obj,*args,**kwargs) # 让对象去初始化 return obj class A(metaclass=Meta): def __init__(self,name): self.name = name a = A(name='jack') print(a.name) # jack
注意:
- 一旦覆盖了call,就必须调用父类的call方法来产生对象并返回这个对象
补充:元类中的new方法
当你要创建类对象时,会首先执行元类中的双下new方法,拿到一个空对象,然后会自动调用双下init方法来自动对这个类进行初始化操作
注意:
- 如果你覆盖了该new方法,new方法必须有返回值,且必须是对应的类对象
# 情况1 class Meta(type): def __new__(cls, *args, **kwargs): print(cls) # 元类自己 print(args) # 创建类需要的几个参数,类名,基类,名称空间 print(kwargs) # 空的 print('new run') super().__new__(cls,*args,**kwargs) def __init__(self,a,b,c): super().__init__(a,b,c) print('init run') # new run # init run # 情况2 class Meta(type): def __new__(cls, *args, **kwargs): print(cls) # 元类自己 print(args) # 创建类需要的几个参数,类名,基类,名称空间 print(kwargs) # 空的 print('new run') obj = type.__new__(cls,*args,**kwargs) return obj def __init__(self,a,b,c): super().__init__(a,b,c) print('init run') # new run # init run
总结:
- new方法和init方法,都可以实现控制类的创建过程,init更加简单
单例设计模式
什么是设计模式?
- 用于解决某种固定问题的套路,如MVC,MTV
单例:
- 指的是一个类产生一个对象
单例的目的:
- 单例是为了节省空间资源,当一个类的所有对象属性全部相同时,则没有必要创建多个对象
# 未使用单例模式,却出现需要单例的情况 class Person: def __init__(self,name,age): self.name = name self.age = age def say_hi(self): print('hello im %s' %self.name) p1 = Person('jack',18) p1.say_hi() # hello im jack p2 = Person('jack',18) p2.say_hi() # hello im jack # 伪单例模式 class Single(type): def __call__(self, *args, **kwargs): if hasattr(self,'obj'): return getattr(self,'obj') obj = super().__call__(*args,**kwargs) self.obj = obj print(obj) class Person(metaclass=Single): def __init__(self,name,age): self.name = name self.age = age def say_hi(self): print('hello im %s' %self.name) # 用于获取对象的方法 @staticmethod def get_instance(): # 判断是否已经有了对象 if hasattr(Person,'obj'): return getattr(Person,'obj') obj = Person('jack',18) Person.obj = obj return obj p1 = Person('jack',18) p1.say_hi() # hello im jack p2 = Person('jack',18) p2.say_hi() # hello im jack # 单例模式,单例元类 class Single(type): def __call__(self, *args, **kwargs): if hasattr(self,'obj'): # 判断是否存在已有的对象 return getattr(self,'obj') # 有就返回 obj = super().__call__(*args,**kwargs) # 没有则创建 print('new run') self.obj = obj # 并存入类中 print(obj) class Student(metaclass=Single): def __init__(self,name): self.name = name class Person(metaclass=Single): pass # 只会创建一个对象 Person() Person() Person() Person() Person() # new run