7.30 反射与元类

一。反射

  在python中,反射通常是指一个对象应该具备,可以检测修复,增加自身属性的能力。

  而反射可以通过字符串对其进行操作。

  其中涉及了4个函数,是四个普通 的函数:

  hasattr(oop,str,none)判断该对象是否有某个属性,如果有返回其值,如果没有,返回第三参数,默认为none

  getattr(oop,str) 获取该对象的某个属性的值

  setattr(oop,str,key)将某个属性添加到该对象中

  delattr(oop,str)将从对象中删除属性

class Test1:
    def __init__(self,name,age):
        self.name=name
        self.age=age

p=Test1('lzx',19)
print(hasattr(p,'lzx'))
print(getattr(p,'name','not this'))
print(setattr(p,'gender','male'))
print(p.__dict__)
print(delattr(p,'age'))
print(p.__dict__)
#False
#lzx
#None
#{'name': 'lzx', 'age': 19, 'gender': 'male'}
#None
#{'name': 'lzx', 'gender': 'male'}

  如果对其对象的字典进行操作也能达到效果,但是语法复杂,不如函数简洁。

  练习:制作一个终端指令:

class Windows:
    def cd(self):
        print('执行cd')

    def ls(self):
        print('执行ls')

    def md(self):
        print('执行md')

win=Windows()
def run(oop):
    while True:
        cmd=input('输入你需要执行的指令:')
        if cmd=='exit':
            print('再见')
            break
        if not hasattr(oop,cmd):
            print('没有该指令')
        else:
            getattr(oop,cmd)()

run(win)

  在程序编写时,不可能知道这个程序以后需要调用什么样的模块,使用什么样的类,在编写后也不易修改,所以,需要使用动态导入模块,对一个字符串中的模块信息进行处理:

import settings
import importlib#动态模块包
def run(oop):
    while True:
        cmd=input('输入你需要执行的指令:')
        if cmd=='exit':
            print('再见')
            break
        if not hasattr(oop,cmd):
            print('没有该指令')
        else:
            getattr(oop,cmd)()
path, class_info = settings.classpath.rsplit('.', 1)#将路径切割成模块路径和类名
mk = importlib.import_module(path)#动态导入模块
cls = getattr(mk, class_info)#通过字符串在模块中寻找该类的名字,获取类
obj = cls()#实例化类
run(obj)#运行

  以上就是动态导入模块,被导入模块的信息放在了settings中,这样即使在编写这段代码时不知道路径,也可以运行。

  注意,对于一个模块来说,也是一个类,可以使用反射getattr方法寻找其中的类(名称空间里的值),倒数第三行可以这样使用。

二。元类metaclass

  在python中,所以的东西都是对象,类可以实例化一个对象,而类也是由元类实例化而来的对象,所以类也是一个对象,默认情况下所有的类的元类都是type,包括object:

class Person:
    pass

p=Person()
print(type(p))
print(type(Person))
print(type(object))
#<class '__main__.Person'>
#<class 'type'>
#<class 'type'>

  所以可以根据type实例化一个类出来,在源码中,元类 的初始化是这样的:

def __init__(cls, what, bases=None, dict=None): 

  其中,what表示类的名字,bases表示类的父类,dict表示类的名称空间。

cls_object=type('animal',(),{'name':'dog'})
class cls_object:
    name='dog'

  这两者都是定义了类。

  元类的作用就是高度的自定义一个类,

  例:在定义类时规定使用大写字母开头:

  1,在源码中修改type,风险太高,不提倡。

  2,新建一个自己的类,继承type,任何继承了type的类都是一个元类,可以用来自定义一个类。

  其中可以使用metaclass=元类将一个类的元类指向它。

class Mytype(type):
    def __init__(self,what,bases,dict):
        super().__init__(what,bases,dict)
        if not what.istitle():
            raise Exception('no')

class pdd(metaclass=Mytype):
    pass

  覆盖了type的初始化方法后就可以当作元类使用,再编写其限制。

  在元类中,也有__call__方法,这个方法是在该类创建的对象被调用时,执行,而元类的对象是类,类被调用就是将类实例化的过程

class Mytype1(type):
    def __init__(self,what,bases,dict):
        super().__init__(what,bases,dict)

    def __call__(self, *args, **kwargs):
        print(self)
        print(args)
        print(kwargs)
     #return super().__call__(*args, **kwargs)
class Per(metaclass=Mytype1): def __init__(self,name,age): self.name=name self.age=age e=Per('lzx',age=10) # print(e.name)

<class '__main__.Per'>
('lzx',)
{'age': 10}

  如上,将元类终端__call__覆盖,输出其参数self,*args,**kwargs,可以发现self是生成的类对象,args是类实例化中 的任意参数,而kwargs是指定参数。

  由于覆盖__call__方法后没有对其进行返回值,所以并没有实例化成功,需要调用父类方法。

  例:在实例化对象时一定要使用指定参数,不能使用位置参数:

class Mytype1(type):
    def __init__(self,what,bases,dict):
        super().__init__(what,bases,dict)

    def __call__(self, *args, **kwargs):
        if args:
            raise Exception('not this')
        return super().__call__(*args,**kwargs)

class Per(metaclass=Mytype1):
    def __init__(self,name,age):
        self.name=name
        self.age=age

e=Per(name='lzx',age=10)

  在__call__方法中可以以使用__new__方法new一个新的空对象,让对象使用类的初始化方法。这就是call的内部原理:

    def __call__(self, *args, **kwargs):
        obj=object.__new__(self)
        self.__init__(obj,*args, **kwargs)
        return obj

  注意,一定要返回obj值。

  补充new。

  在元类中,也可以使用new方法创建类对象,

当你要创建类对象时,会首先执行元类中的__new__方法,拿到一个空对象,然后会自动调用__init__来对这个类进行初始化操作 。
注意:,如果你覆盖了该方法则必须保证,new方法必须有返回值且必须是,对应的类对象。(如果传入了其他类,就不会运行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  #返回对应 的类后会执行init方法
    def __init__(self,a,b,c):
        super().__init__(a,b,c)
        print("init run")
class A(metaclass=Meta):
    pass
print(A)

三。单例设计模式

  在类中,可以创建很多对象,而当一个类产生的值是一模一样时,就不需要创建那么多的类,所以就有单例设计模式。

  单例就是一个类只产式一个对象。

  这样的设计可以节省资源,当一个类的所有对象属性全部相同时,就没必要创建多个对象。

class Single:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    
    def say_hi(self):
        print('hi')
        
s1=Single('lzx',24)
s1.say_hi()

s2=Single('lzx',24)
s2.say_hi()

  可以看到,由single创建的s1,s2都是一样的属性,但是却创建了两个不同的对象,浪费空间,可以使用单例设计模式,将两次实例化的对象变成一个。

class Single1:
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def say_hi(self):
        print('hi')

    @classmethod
    def get_single(cls):
        if hasattr(cls,'obj'):
            return getattr(cls,'obj')
        obj=cls('lzx',18)
        cls.obj=obj
        return obj
s3=Single1.get_single()
s3.say_hi()

s4=Single1.get_single()

  虽然实现了单例,但是不能使用原来的方法调用该类,所以,可以使用该类的元类中 的call完成功能:

class Single2(type):
    def __call__(self, *args, **kwargs):
        if hasattr(self,'obj'):
            return getattr(self,'obj')
        obj=super().__call__( *args, **kwargs)
        self.obj=obj
        print('new')
        return obj

class test_sin(metaclass=Single2):
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def say_hi(self):
        print('hi')

s5=test_sin('lzx',12)
s5.say_hi()

s6=test_sin('lzx',12)
s6.say_hi()

  这样就只会创建一个对象了。

  

原文地址:https://www.cnblogs.com/LZXlzmmddtm/p/11272778.html