反射、元类,和项目生命周期
- 去幕布看>>
- 反射
- 定义
其实是反省,自省的意思
反射指的是一个对象应该具备,可以检测,修改,增加自身属性的能力 - 怎么用
反射就是通过字符串操作属性涉及的四个函数:hasattr getattr setattr delattr 这四个函数就是普通的内置函数 没有双下划綫,与print等等没有区别 - 案例
# hasattr getattr setattr delattr p = Person("jack",18,"man") # 1.判断某个对象是否存在某个属性 if hasattr(p,"name"): print(getattr(p,"names",None)) # 2.从对象中取出属性,第三个值位默认值 当属性不存在是返回默认值 # 3.为对象添加新的属性 setattr(p,"id","123") print(p.id) # 4.从对象中删除属性 delattr(p,"id") print(p.id)
- 使用场景
反射其实就是对属性的增删改查,但是如果直接使用内置的dict来操作,语法繁琐,不好理解
另外一个最主要的问题是,如果对象不是我自己写的是另一方提供的,我就必须判断这个对象是否满足的要求,也就是是否有需要的属性和方法 - 框架设计方式
- 分析
反射被称为框架的基石,为什么?
因为框架的设计者,不可能提前知道你的对象到底是怎么设计的
所以你提供给框架的对象 必须通过判断验证之后才能正常使用,判断验证就是反射要做的事情,
当然通过__dict__也是可以实现的, 其实这些方法也就是对__dict__的操作进行了封装
框架是什么:就是已经实现了最基础的构架,就是所有项目都一样的部分 - 需求(要实现一个用于处理用户的终端指令)
# 框架已经实现的部分 import plugins def run(plugin): while True: cmd = input("请输入指令:") 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) # 插件部分 class WinCMD: def cd(self): print("wincmd 切换目录....") def delete(self): print("wincmd 要不要删库跑路?") def dir(self): print("wincmd 列出所有文件....") class LinuxCMD: def cd(self): print("Linuxcmd 切换目录....") def rm(self): print("Linuxcmd 要不要删库跑路?") def ls(self): print("Linuxcmd 列出所有文件....")
上述框架代码中 写死了必须使用某个类,这是不合理的,因为无法提前知道对方的类在什么地方 以及类叫什么
所以我们应该为框架的使用者提供一个配置文件,要求对方将累的信息写入配置文件
然后框架自己去加载需要的模块,
-
最后的框架代码:
import importlib import settings # 框架已经实现的部分 def run(plugin): while True: cmd = input("请输入指令:") if cmd == "exit": break # 因为无法确定框架使用者是否传入正确的对象所以需要使用反射来检测 # 判断对象是否具备处理这个指令的方法 if hasattr(plugin,cmd): # 取出对应方法方法 func = getattr(plugin,cmd) func() # 执行方法处理指令 else: print("该指令不受支持...") print("see you la la!") # 创建一个插件对象 调用框架来使用它 # wincmd = plugins.WinCMD() # 框架之外的部分就有自定义对象来完成 # 框架 得根据配置文件拿到需要的类 path = settings.CLASS_PATH # 从配置中单独拿出来 模块路径和 类名称 module_path,class_name = path.rsplit(".",1) #拿到模块 mk = importlib.import_module(module_path) # 拿到类 cls = getattr(mk,class_name) # 实例化对象 obj = cls() #调用框架 run(obj)
- 分析
- 定义
元类是什么:用于创建类的类 - 特点
万物皆对象,类当然也是对象
对象是通过类实例化产生的,如果类也是对象的话,必然类对象也是有另一个类实例化产生的
默认情况下所有类的元类都是type - 验证(默认情况下所有类的元类都是type )
# Person类是通过type类实例化产生的 class Person: pass p = Person() print(type(p)) print(type(Person)) # 执行结果 <class '__main__.Person'> <class 'type'>
- 为什么要使用元类
自定义一个元类,并继承type:
当你想要控制对象的创建过程时,就覆盖type的call方法
当你想要控制类的创建过程时,就覆盖type的init方法 - 案例 1
- 需求:要求新建的类的类名全为大写,否则抛出异常;
- 实现:自定义元类,并覆盖type的init方法,加判断条件
# 只要继承了type 那么这个类就变成了一个元类 # 自定义了一个元类 class MyType(type): def __init__(self,clss_name,bases,dict): super().__init__(clss_name,bases,dict) print(clss_name,bases,dict) if not clss_name.istitle(): raise Exception("你丫的 类名不会写...") # 为pig类指定了元类为MyType class Pig(metaclass=MyType): pass class Duck(metaclass=MyType): pass
- 需求:实现将对象的所有属性名称转为大写
- 实现:自定义元类,覆盖call方法,call里边写代码 将属性名转为大写即可
- 注意:由于自己无法写出创建对象方法,所以call的返回值,还是要调用type的对象创建方法的:super().__all__
class MyType(type): def __call__(self, *args, **kwargs): new_args = [] for a in args: new_args.append(a.upper()) print(new_args) print(kwargs) return super().__call__(*new_args,**kwargs) class Person(metaclass=MyType): def __init__(self,name,gender): self.name = name self.gender = gender p = Person(name="jack",gender="woman") print(p.name) print(p.gender)
- 定义
当你要创建类对象时,会首先执行元类中的__new__方法,拿到一个空对象,
然后会自动调用__init__来对这个类进行初始化操作 - 注意:
如果你覆盖了该方法则必须保证,new方法必须有返回值且必须是,对应的类对象
new方法和init 都可以实现控制类的创建过程,init更简单 - 测试(new方法和init 都可以实现控制类的创建过程,init更简单)
class Meta(type): def __new__(cls, *args, **kwargs): print(cls) # 元类自己 print(args) # 创建类需要的几个参数 类名,基类,名称空间 print(kwargs) #空的 print("new run") # return super().__new__(cls,*args,**kwargs) obj = type.__new__(cls,*args,**kwargs) return obj def __init__(self,a,b,c): super().__init__(a,b,c) print("init run") class A(metaclass=Meta): pass print(A) # 执行结果 <class '__main__.Meta'> ('A', (), {'__module__': '__main__', '__qualname__': 'A'}) {} new run init run <class '__main__.A'>
- 定义
单例:指的是一个类只产生一个对象
模式:用于解决某种固定问题的套路 - 为什么要使用单例
单例是为了节省 资源,
当一个类的所有对象属性全部相同时,则没有必要创建多个对象 - 通过元类实现单例
- 原理
创建对象时走的是元类中call方法,那就自定义一个元类;
且在call方法中通过getattr判断:创建过对象的话,就返回这个已创建的对象,没创建过的话就新建对象 - 代码实现
# 单例n元类 class Single(type): def __call__(self, *args, **kwargs): if hasattr(self,"obj"): #判断是否存在已经有的对象 return getattr(self,"obj") # 有就返回 obj = super().__call__(*args,**kwargs) # 没有则创建 print("new 了") self.obj = obj # 并存入类中 return obj class Student(metaclass=Single): def __init__(self,name): self.name = name class Person(metaclass=Single): pass # 只会创建一个对象,这里拿Person举例,Student一样 Person() Person() Person() # 执行结果,只有一行,也就是说,只执行了一次新建对象的操作 new 了
- 原理
- 流程:
需求分析-->技术选型-->项目设计-->项目开发-->项目测试-->上线运行-->维护更新 - 各阶段的主要内容
1.明确用户需求,用户到底需要什么样的程序,要实现什么样的功能,很多时候,用户都是在意淫,逻辑上是不正确的,所以需要工程师,与用户当面沟通以确定用户的真实需求,以及需求的可实现性,并根据最终的需求,产生项目需求分析书 2.我们需要根据公司的实际情况考虑采用的框架技术,通常要做的业务业界用主流的实现方案,例如各种框架的版本,要考虑兼容性,流行程度,以及工程师的熟练程度 3.由于项目不可能一次开发完就完事了,后期需要维护扩展,所以良好的架构设计对后续的维护扩展有重要意义 另外如果你的思路从一开始就不正确,那后期很有可能把整个项目推翻重写 项目的设计当然是越早越好,但是学习阶段,直接按照某种架构来编写,你会觉得非常抽象,为什么要这么设计,好处是什么?会造成一种感觉是,还没开始写代码就已经懵逼了 所以要先明确不进行设计前存在的问题,然后在找相应的解决方案 4.项目开发其实是耗时相对较短的一个阶段,前提是需求已经明确,项目设计没有问题,然后根据需求分析书进行代码编写,公司一般都有多个工程师,项目经理负责分配任务,每个工程师开发完自己的模块后提交自己的代码,最后所有代码合并到一起,项目开发完成 5.开发完成并不意味这,工作结束了,需要将完整的项目交个测试工程师,一些小公司可能没有这个岗位,那就需要开发工程师自己完成测试 6.在测试通过后项目就可以上线运行了,上线运行指的是是你的程序能够真正的运行在互联网上,在开发项目阶段一般使用的都是局域网环境,普通用户是无法访问的,需要将项目部署到服务器上,再为服务器获取一个公网ip,并给这个ip绑定域名,至此项目即正常运行了 7.后续都需要增加新功能或是修改各种bug,不断地完善项目,进行版本的更新迭代,当然如果公司不幸倒闭了,那么这个项目的生命周期也就结束了