Day24 面向对象之反射、元类

1.反射

反射指的是一个对象应该具备,可以检测,修改,增加自身属性的能力

反射就是通过字符串操作属性

涉及四个函数,这四个函数就是普通的内置函数,没有双下划线,与print等在使用方法上没有区别

hasattr,getattr,setattr,delattr(他们的具体使用方法和作用见下列代码)

 1 class Person:
 2     def __init__(self,name,age):
 3         self.name = name
 4         self.age = age
 5 
 6 p = Person('sxc',18)
 7 
 8 if hasattr(p,'name'):  # 判断某个对象是否存在某个属性
 9     print(getattr(p,'name'))  # 把这个值当做返回值返回出来
10 
11 print(getattr(p,'gender',None))  # 如果没有这个值后面还可以赋予他一个默认值
12 
13 setattr(p,'id',177)  # 可以为该对象赋予新的属性
14 print(p.id)
15 
16 delattr(p,'id')  # 也可以删除的对象的属性
17 print(p.id)

使用场景:

反射其实就是对属性的增删查改,但是如果直接使用内置的__dict__来操作,语法繁琐,不好理解

如果是另一方提供的,无法判断是否是我需要的属性和方法

 

简单的框架设计

框架就是已经实现了最基础的构架,就是所有项目都一样的部分已经完成了。反射被称为框架的基石,框架的设计者不可能提前知道使用者的对象到底是怎么设计的,所以使用者提供给框架的对象必须通过判断验证之后才能正常使用,判断验证就是反射要做的事

需求:实现一个用于处理用户的终端指令的小框架

 1 # 框架部分
 2 def run(plugin):
 3     while True:
 4         cmd = input('请输入您的指令>>>:').strip()
 5         if cmd == 'exit':
 6             break
 7         #  无法判断传入的参数,需要加入反射加以判断
 8         if hasattr(plugin,cmd):  # 取出对应的指令
 9             func = getattr(plugin,cmd)
10             func()  # 执行指令处理
11         else:
12             print('请输入正确的指令')
13     print('退出程序')
14 
15 # 从系统配置文件中截取模块路径和类名
16 module_path, class_name = settings.CLASS_PATH.rsplit('.',1)
17 # 拿到模块
18 mk = importlib.import_module(module_path)
19 # 拿到类
20 cls = getattr(mk,class_name)
21 # 实例化对象
22 obj = cls()
23 # 调用框架
24 run(obj)
项目框架以及运行代码
# 配置文件路径

CLASS_PATH = 'lib.plugin.WinCmd'
 1 class WinCmd:
 2     def cd(self):
 3         print('进入文件')
 4 
 5     def delate(self):
 6         print('删库跑路')
 7 
 8     def dir(self):
 9         print('文件列表')
10 
11 
12 class LinuxCmd:
13     def cd(self):
14         print('进入文件')
15 
16     def rm(self):
17         print('删库跑路')
18 
19     def ls(self):
20         print('文件列表')
插件代码

2.元类 metaclass

用于创建类的类就叫做元类

在python中万物皆对象,类也是对象

对象是通过类实例化产生的,如果类也是对象的话,必然类对象也是由另一个类实例化产生的

默认情况下所有类的元类都是type

元类的验证:

1 # 元类的验证
2 class Person:
3     pass
4 
5 p = Person()
6 
7 # type方法可以查看对象的类型
8 print(type(p))
9 print(type(Person))

学习元类的目的:

可以高度自定义一个类,具体到可以控制类的名字必须以固定的格式书写

我们可以通过初始化方法,只要继承元类并覆盖其中的__init__方法就能实现需求

案例,通过元类限制类的名字必须以大驼峰的方式命名

 1 # 继承元类可以高度定义一个子类
 2 class MyType(type):
 3     def __init__(self,cls,bases,dict):
 4         super().__init__(cls,bases,dict)
 5         if not cls.istitle():
 6             raise Exception('请小写')
 7 # 生成两个子类
 8 class Pig(metaclass= MyType):
 9     pass
10 
11 class duck(metaclass= MyType):
12     pass

Pig类可以通过,duck类并未大写,报错

 

元类中call方法

当调用类对象时会自动执行元类中的__call__方法,并将这个类本身作为第一个参数传入,以及后面的参数

覆盖元类中的call之后,就无法产生对象.必须调用super().__call__来完成对象的创建,并返回其返回值

 1 # __call__是用来实例化对象返回值的
 2 class MyType(type):
 3     def __call__(self, *args, **kwargs):
 4         print('元类call')
 5         print(args)
 6         print(kwargs)
 7 
 8 class Person(metaclass=MyType):
 9     def __init__(self,name):
10         self.name = name
11 
12 p = Person('sxc',age = 18)
13 print(p)

当我们覆盖了元类的__call__方法,他的返回值就变成了None,说明__call__是用来实例化对象传值的

案例1:把子类中的所有属性都改为大写

 1 # 把子类的所有属性都改成大写
 2 class MyType(type):
 3     def __call__(self, *args, **kwargs):
 4         new_args = []  # 定义一个新列表
 5         for a in args:  # 循环输出旧列表中的内容
 6             new_args.append(a.upper())  # 添加到新列表中
 7         return super().__call__(*new_args,**kwargs)
 8 
 9 
10 class Person(metaclass=MyType):
11     def __init__(self,name):
12         self.name = name
13 
14 
15 p = Person('sxc')
16 p1 = Person('aabbcceeff')
17 print(p.name)
18 print(p1.name)

案例2:要求创建对象时只能关键字传参

 1 # 要求:只允许关键字传参
 2 class MyType(type):
 3     def __call__(self, *args, **kwargs):  # 覆盖元类的__call__方法
 4         if args:
 5             raise Exception('不允许直接传参')  # 当直接传参时报错
 6         return super().__call__(*args,**kwargs)
 7 
 8 class Person(metaclass= MyType):
 9     def __init__(self,name,age):
10         self.name = name
11         self.age = age
12 
13 p1 = Person(name = 'sxc',age = 18)
14 print(p1.name,p1.age)
15 p = Person('sxc',18)

注意:一旦覆盖了call必须调用父类的call方法来产生对象并返回这个对象

__init__方法和__call__方法的使用场景:

  当想要控制对象的创建过程时,就覆盖call方法

  当想要控制类的创建过程时,就覆盖init方法

补充new方法

当创建类对象时,会首先窒息感元类中的__new__方法,拿到一个空对象,然后自动调用__init__来初始化

 1 # new方法
 2 class Mytype(type):
 3     def __new__(cls, *args, **kwargs):
 4         print(cls)  # 元类自己
 5         print(args)  # 创建类需要的几个参数  类名,基类,名称空间
 6         print(kwargs)  # 空的值
 7         print('new run')
 8         return super().__new__(cls, *args, **kwargs)
 9 
10     def __init__(self,a,b,c):
11         super().__init__(a,b,c)
12         print('init run')
13 
14 class A(metaclass= Mytype):  # new方法和call方法在类生成时自动执行
15     pass
16 
17 print(A)  # 类对象本身

注意:当我们覆盖了__call__方法必须保证new方法有返回值并且是对应的类对象

 

单例设计模式

单例是为了节省资源,当一个类的所有对象属性全部相同时,没有必要创建多个对象

单例实现

 1 # 单例的实现
 2 class Single(type):
 3     def __call__(self, *args, **kwargs):
 4         if hasattr(self,'obj'):  # 判断是否有已存在的对象
 5             return getattr(self,'obj')  # 有就返回
 6         obj = super().__call__(*args,**kwargs)  # 没有就创建
 7         self.obj = obj  # 存到类中,方便下次查找
 8         return obj
 9 
10 class Person(metaclass=Single):
11     def __init__(self,name):
12         self.name = name
13 
14 # 只会创建初始的对象
15 p = Person('sxc')
16 print(p.name)
17 p1 = Person('zzj')
18 print(p1.name)
19 p2 = Person('zzp')
20 print(p2.name)
21 p3 = Person('lzx')
22 print(p3.name)
23 p4 = Person('zkj')
24 print(p4.name)

元类练习:

 1 '''
 2 1.使用元类实现检测类中的所有属性 包括类属性和方法,将名字存储到类的名称空间中
 3 例如:有student类  有eat函数 和school属性  ,获取这两个名字 存储到一个叫做attrs的列表中,并将列表作为student类的属性
 4 使得student类可以通过访问attrs来获取自身所有的属性名字
 5 '''
 6 class MyType(type):
 7     def __init__(self,cls,bases,dict):
 8         attr = []
 9         for i in dict:  # 遍历字典中的类属性
10             if i == 'school' or i == 'eat':
11                 attr.append(i)  # 把他添加到新的列表中去
12         self.attr = attr  # 添加到类属性中
13         super().__init__(cls,bases,dict)
14 
15 class Student(metaclass=MyType):
16     school = 'oldboy'
17 
18     def eat(self):
19         print('吃东西')
20 
21 stu = Student()
22 print(stu.attr)
练习

猜你喜欢

转载自www.cnblogs.com/sxchen/p/11272885.html