• 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()

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

      

  • 相关阅读:
    4:4 自定义拦截器
    DDD学习笔记一
    Winform/WPF国际化处理
    NPOI 操作Excel
    将输入的字符串进行大写格式化
    将输入的字符串分2个字符添加空格并大写格式化
    VS使用技巧
    NotifyIcon用法
    C#Winfrom系统打印机调用/设置默认打印机
    TextBox(只允许输入字母或者数字)——重写控件
  • 原文地址:https://www.cnblogs.com/LZXlzmmddtm/p/11272778.html
Copyright © 2020-2023  润新知