反射,元类

Posted panshao51km-cn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了反射,元类相关的知识,希望对你有一定的参考价值。

一、反射

  1.什么是反射?

  反射就是反省的意思,自省的意思。反射就是指一个对象应该具备,可以检测,修改,增加自身属性的能力,它通过字符串操作属性,涉及的四个函数,这四个函数就是普通内置函数,无双划线,与print无异。

python中通过以下四个函数来实现反射

hasattr(object,name) # 判断对象是否拥有某个属性
setattr(object,name,value) #为对象增加新的属性
getattr(object,name,default) #从对象中获取某个属性
delattr(object,name) #从对象中删除某个属性
class Person:
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender

p = Person("jack",18,"man")
# print(p.name)

# print(hasattr(p,"name"))
# if hasattr(p,"name"): # 判断某个对象是否存在某个属性
#     print(getattr(p,"names",None)) # 从对象中取出属性,第三个值位默认值 当属性不存在是返回默认值

# 为对象添加新的属性
setattr(p,"id","123")
print(p.id)

# 从对象中删除属性
delattr(p,"id")
print(p.id)

  2.为什么需要反射?

  一个类在定义的时候,可能一些属性的设计并不是很完美,而后期需要作出修改,或是增加新属性时,,使用反射的方式可以不需要修改源代码

  反射的另一个优势:可插拔设计。不仅限于修改已有的属性,通过反省也能够发现已经存在的属性,只要你给我一个对象,我就能检查其拥有的属性,从而使用这些属性,而不需要提前了解这些对象,大大提高了程序的扩展性。

  3.使用场景:

  反射其实就是对属性的增删改查,但是如果直接使用内置的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("se 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 setting


# 框架已经实现的部分
def run(plugin):
    while True:
        cmd = "exit"
        if cmd == "exit":
            break
        # 因为无法确定框架使用者是否传入正确的对象所以需要反来检测
        # 判断对象是否具备处理这个指令的方法
        if hasattr(plugin,cmd):
            # 取出对应方法
            func = getattr(plugin,cmd)
            func()
        else:
            print("该指令不受支持")
    print("se you la la")


# 创建一个插件对象,调用框架来使用它
wincmd = plugins.WinCMD()
# 框架之外的部分就有自定义对象来完成


# 框架得根据配置文件拿到需要的类

path = settings.CLASS_PATH
# 从配置中单独拿出来的 模块路径和类名称
module_path,class_name = path.resplit(".",1)
# 拿到模块
mk = importlib.import_module(module_path)
# 拿到类
cls = getattr(mk,class_name)
# 实例化对象
obj = cls()
# 调用框架
run(obj)

  如此一来 ,框架就与实现代码彻底解耦了,只剩下配置文件

二、元类

  1.元类是类的类,是类的模板

  元类是用来控制如何创建类的,正如类是创建对象的模板一样

  元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例Foo类是 type 类的一个实例)

  type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象。

  typer----->类------>对

  2.创建类的两种方式

  方式一:

class Foo:
     def func(self):
         print(‘from func‘)

  方式二:type成为元类,是所有类的类,利用type模拟class关键字的创建类的过程

def func(self):
    print(‘from func‘)
x = 1 # 类的数据属性
class_name = ‘Foo‘  # 类的名字
bases = (object,)   # 类的继承,可以是多继承
class_dict = 
    "x" : 1,
    "func" : func
   # 类的名称字典
Foo = type(class_name,bases,class_dict)   # 注意顺序一定不能错
print(Foo)



# 输出为:<class ‘__main__.Foo‘>

  3.一个类没有声明自己的元类,默认他的元类是type,除了使用元类type,用户也可以通过继承type来自定义元类。

  验证:

class Person:
    pass


p = Person()
print(type(p))
print(type(Person))

  Person类是通过type类实例化产生的。

  4.学习元类的目的

  高度的自定义一个类,例如控制类的名字必须以大驼峰的方式来书写,类也是对象,也有自己的类,我们的需求是创建类对象做一些限制想到了初始化方法 我们只要找到类对象的类(元类),覆盖其中 init方法就能实现需求,当然我们不能修改源代码,所以应该继承type来编写自己的元类,同时覆盖init来完成需求。

class MyType(type):
    def __init__(self,class_name,bases,dict):
        super().__init__(class_name,bases,dict)
        print(class_name,bases,dict)
        if not class_name.istitle():
            raise Exception(‘NIYADE‘)

class Pig(metaclass = MyType):
    pass
class Duck(metaclass = MyType):
    pass

  总结:

  我们需要记住一点:名字加括号的本质(即,任何name()的形式)都是先找到name的爹,然后执行爹.__call__

  而爹.__call__一般做两件事:

  1.调用name.__new__方法并返回一个对象

  2.进而调用name.__init__方法对儿子name进行初始化

  

  class定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行Foo=Mymeta(‘foo‘,(...),...)

  因此我们可以看到,只定义class就会有如下执行效果

===>Mymeta.__new__
===>Mymeta.__init__
实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta(‘Foo‘,(...),...)操作,
遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的爹type,然后执行type.__call__(...)方法
于是触发Mymeta.__new__方法得到一个具体的对象,然后触发Mymeta.__init__方法对对象进行初始化
obj=Foo(‘egon‘)
的原理同上
 
总结:元类的难点在于执行顺序很绕,其实我们只需要记住两点就可以了
1.谁后面跟括号,就从谁的爹中找__call__方法执行
type->Mymeta->Foo->obj
Mymeta()触发type.__call__
Foo()触发Mymeta.__call__
obj()触发Foo.__call__
2.__call__内按先后顺序依次调用儿子的__new__和__init__方法
 

  

以上是关于反射,元类的主要内容,如果未能解决你的问题,请参考以下文章

反射,元类

反射 元类

反射与元类

反射 动态导入 元类

OOP 反射&元类

反射元类 练习