zoukankan      html  css  js  c++  java
  • 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) # 关键字传参 表明关键字传参可行
  • 相关阅读:
    axios拦截器
    Vue路由守卫
    HTML横向滚动条和文本超出显示三个小圆点
    Vue用户名vuex和localStorage双向存储
    javaScript Es6数组与对象的实例方法
    使用vue实现复选框单选多选
    正则表达式常用字符
    jest函数单元测试
    ts中的类
    ts中接口的用法
  • 原文地址:https://www.cnblogs.com/pupy/p/11271671.html
Copyright © 2011-2022 走看看