OOP 反射 & 元类
反射:reflect,可以理解为自省的意思
反射是指一个对象应该具有自我检测、修改、增加自身属性的能力
反射就是通过字符串操作属性
涉及到的函数:hasattr & getattr & setattr & delattr
hasattr(对象,'属性名'):判断某个对象是否存在某个属性
getattr(对象,'属性名',None):从对象中取出属性 第三个值位默认值,当属性不存在是返回默认值
setattr(对象,'属性名','属性对应的值'):为对象设置添加新的属性
delattr(对象,'属性名'):从对象中删除属性
class Person: def __init__(self, name): self.name = name p = Person('rose') if hasattr(p, 'name'): # 判断是否具有某个属性 print(getattr(p, 'name')) # 获取属性 rose setattr(p, 'age', 18) # 设置添加属性 print(getattr(p, 'age')) # 18 delattr(p, 'name') # 删除属性 print(p.__dict__) # {'age': 18} 可以看到name属性被成功删除
使用场景:
反射其实就是对属性的增删改查,但是如果直接使用内置的__dict__来操作,语法繁琐,不好理解
# 以框架设计方式为例: # 需求:要实现一个用于处理用户的终端指令的小框架 import kkkkk def run(a): while True: cmd = input('请输入指令>>> ') if cmd == 'exit': break if hasattr(a, cmd): res = getattr(a, cmd) res() else: print('不支持该指令!!!') print('拜拜了您嘞!') # 生成模块的实例化对象 wincmd = kkkkk.Windows() linuxcmd = kkkkk.Linux() # 调用框架来使用 run(wincmd) # 括号里传入模块的实例化对象,这样就可以使用到模块实例化对象中的方法
# kkkkk模块内容 class Windows: def cd(self): print('Windows 切换目录...') def dir(self): print('Windows 列出所有目录') def delete(self): print('Windows 删除文件') class Linux: def cd(self): print('Linux 切换目录') def rm(self): print('Linux 删除文件') def ls(self): print('Linux 列出所有目录')
上述框架代码中,写死了必须使用某个类,这是不合理的;因为无法提前知道对方的类在什么地方以及类叫什么
所以我们应该为框架的使用者提供一个配置文件,要求对方将类的信息写入配置文件;如将模块路径和类名写到settings中
然后框架自己去加载需要的模块(从settings中导入模块路径及类名,拿到类后实例化对象,最后调用框架)
import kkkkk def run(a): while True: cmd = input('请输入指令>>> ') if cmd == 'exit': break if hasattr(a, cmd): res = getattr(a, cmd) res() else: print('不支持该指令!!!') print('拜拜了您嘞!') # 在settings拿到你需要的类 path = settings.CLASS_PATH # 从settings单独拿出模块路径和类名 module_path, class_name = path.rsplit('.', 1) # 拿到模块 get_class = importlib.import_module(module_path) # 拿到类 cls = getattr(get_class, class_name) # 实例化对象 obj = cls() # 调用框架 run(obj)
# kkkkk class Windows: def cd(self): print('Windows 切换目录...') def dir(self): print('Windows 列出所有目录') def delete(self): print('Windows 删除文件') class Linux: def cd(self): print('Linux 切换目录') def rm(self): print('Linux 删除文件') def ls(self):
print('Linux 列出所有目录')
# settings # 作为框架使用者 在配置文件中指定你配合框架的类是哪个 这里面要求格式书写———— 模块名与类 CLASS_PATH = 'kkkkk.Windows' # ---模块名与类----字符串形式
元类:metaclass 创建类的类
作用:用于创建类
解释:对象是通过类实例化产生的,万物皆对象,类也是对象,所以也有产生类对象的类,这个类就叫元类
PS:通常默认所有的类的元类都是type
扩展:如果只是为了创建类就不需要使用元类,直接定义就可以了。
所以我们使用元类,一般是为了在类创建的过程时候进行一些限制
如何创建??
1.定义一个类,使其继承type,这样这个类就变成了一个元类,并且这个元类就有了type所有的属性方法
type(类名,基类,类的名称空间)
type(class_name, class_base, class_dict)
type(字符串,元祖,字典) type('', (), {})
2.对定义的元类进行初始化,覆盖type中的init方法
# 定义一个元类,使其限制新创建的类的类名必须使用大驼峰体 class MyType(type): def __init__(self, name, base, dict): super().__init__(name, base, dict) if name.istitle(): pass else: raise Exception('每个单词首字母要大写!') class person(metaclass=MyType): # 报错,因为没有使用大驼峰体 pass
(补充)元类中 __new__ 的使用:
当你要创建类对象时,会首先执行元类中的__new__方法,拿到一个空对象,然后会自动调用__init__来对这个类进行初始化操作
如果你覆盖了该方法则必须保证,new方法必须有返回值且必须是对应的类对象
例:
class Meta(type):
def __new__(cls, *args, **kwargs):
'''
这中间可以增加你想要的定义操作。增加以后会先执行__new__方法,有了一个返回值以后才会执行__init__方法
不增加的话可以不写,默认返回对应的类对象
'''
# return super().__new__(cls,*args,**kwargs) # 这里面的super就像相当于type
# 可以写成下面的形式,两者等价
obj = type.__new__(cls,*args,**kwargs)
return obj
def __init__(self,a,b,c):
super().__init__(a,b,c)
所以__new__和__init__都可以控制类对象的创建,但是__init__更为简单
元类中 __call__ 的使用:
我们知道,类实例化的过程就是调用__call__的过程,所以如果我们想要控制对象创建过程(实例化过程),据需要覆盖call方法;但是,覆盖元类中的call之后,这个类就无法产生对象,必须调用super().__call__来完成对象的创建,并返回其返回值。如下例:
# 将对象的所有属性名称转为大写 class M(type): def __call__(self, *args, **kwargs): new_args = [] for i in args: new_args.append(i.upper()) return super().__call__(*new_args,**kwargs) # 注意:一旦覆盖了call必须调用父类的call方法来产生对象并返回这个对象 class K(metaclass=M): def __init__(self,name): self.name = name k = K('www') print(k.name) # WWW 成功实现
例子:
# 要求创建对象时必须以关键字参数形式来传参
# 覆盖元类的__call__
# 判断你有没有传非关键字参数 == 不能有位置参数
# 有就炸
# 第一步,创建一个元类: class K(type): # 第二步,定义实例化时候的格式 def __call__(self, *args, **kwargs): if args: raise Exception('不能使用位置传参...') # 如果是位置参数,则报错 return super().__call__(*args, **kwargs) # 注意:一旦覆盖了call必须调用父类的call方法来产生对象并返回这个对象 # 第三步,创建一个类,并制定其元类 class O(metaclass=K): def __init__(self, name): self.name = name # 第四步,实例化,创建对象的过程 # o = O('位置参数') # Exception: 不能使用位置传参... 表明位置传参不行 o = O(name = '关键字传参') print(o.name) # 关键字传参 表明关键字传参可行