zoukankan      html  css  js  c++  java
  • 反射reflect(框架的基石),动态导入小技巧 | 元类 | 单例设计模式

    今日内容

    反射,元类,项目生命周期,选课系统分析


    反射reflect(框架的基石)

    什么是反射?

    • 其实是反省,自省的意思;反射指的是一个对象应该具备可以检测修改增加自身属性的能力
    • 反射就是通过字符串操作属性

    涉及的四个函数:

    • 这四个函数就是普通的内置函数,没有双下划线,与print等函数没有区别
    hasattr getattr setattr delattr
    # hasattr getattr setattr delattr的应用
    class Person:
        def __init__(self,name,age,gender):
            self.name = name
            self.age = age
            self.gender = gender
           
    p = Person('jack',20,'man')
    print(hasattr(p.'names'))  # False
    if hasattr(p.'name'):  # 判断某个对象是否存在某个属性
        print(p.name)  # jack
        # 从对象中取出属性
        print(getattr(p, 'name'))  # jack
        # 从对象中取出属性,第三个值为默认值,当默认值为None时,返回None
        print(getattr(p, 'name',None))  # None
    # 为对象添加新的属性 
    setattr(p, 'id', '123')
    print(p.id)  # 123
    ​
    delattr(p, 'id')
    print(p.id)  # 报错,因为已经被删掉了

    使用场景:

    • 反射其实就是对属性的增删改查,但是如果直接使用内置的__ dict __来操作,语法繁琐,不好理解
    • 另外一个最主要的问题,如果对象不是我自己写的,而是另一方提供的,那么我就必须要判断这个对象是否具备我需要的属性和方法

    为什么反射被称为框架的基石?

    • 因为框架的设计者,不可能提前知道你的对象是怎么设计的,所以你提供给框架的对象,必须通过判断验证之后才能正常使用
    • 判断验证就是反射要做的事,当然通过__ dict __也是可以实现的,其实这些方法也就是对双下dict的操作进行了封装

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

    • 框架就是已经实现了最基础的构架,就是所有项目都一样的部分
    # 创建一个plugins插件对象,存放指令并调用框架使用它
    class WinCMD:
        def cd(self):
            print('wincmd 切换目录...')
        def delete(self):
            print('wincmd 要不要删库跑路...')
        def dir(self):
            print('mycmd 切换目录...')
            
    class LinuxCMD:
        def cd(self):
            print('Linuxcmd 切换目录...')
        def rm(self):
            print('Linuxcmd 要不要删库跑路...')
        def ls(self):
            print('Linuxcmd 列出所有文件...') 
    ​
    # 调用操作
    def run(plugin):
        while True:
            cmd = input('请输入指令:').strip()
            if cmd == 'exit':
                break
            # 判断对象是否具备处理指令的方法
            if hasattr(plugin,cmd):
                # 取出指令对应方法
                func = getattr(plugin,cmd)
                func() # 执行方法处理指令
            else:
                print('该指令不受支持...')
        print('see you la la!')
        
    wincmd = plugins.WinCMD()
    linux = plugins.LinuxCMD()
    run(linux)

    动态导入:

    在框架设计中,我们不可能提前知道,用框架的用户要提供的相关的信息

    plugins.py

    class WinCMD:
        def cd(self):
            print('wincmd 切换目录...')
        def delete(self):
            print('wincmd 要不要删库跑路...')
        def dir(self):
            print('mycmd 切换目录...')
            
    class LinuxCMD:
        def cd(self):
            print('Linuxcmd 切换目录...')
        def rm(self):
            print('Linuxcmd 要不要删库跑路...')
        def ls(self):
            print('Linuxcmd 列出所有文件...') 

    myframework.py

    import importlib
    import settings
    ​
    def run(plugin):
        while True:
            cmd = input('请输入指令:').strip()
            if cmd == 'exit':
                break
            # 判断对象是否具备处理指令的方法
            if hasattr(plugin,cmd):
                # 取出指令对应方法
                func = getattr(plugin,cmd)
                func() # 执行方法处理指令
            else:
                print('该指令不受支持...')
        print('see you la la!')
        
    pl = importlib.import_module('libs.plugins')
    # 框架之外的对象由自定义完成
    # 框架得根据配置文件拿到需要得类
    path = settings.CLASS_PATH
    # 从配置中单独拿出来,模块路径和类名称
    moudule_path, class_name = path_rsplit('.',1)
    print(moudle_path)  # libs.plugins
    print(class_name)  # WinCMD
    # 拿到模块
    mk = importlib.import_module(moudule_path)
    # 拿到类
    cls = getattr(mk,class_name)
    # 实例化对象
    obj.cls()
    # 调用框架
    run(obj)

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

    但是上述框架代码中,写死了必须使用某个类,这是不合理的,因为无法提前知道对方的类在什么地方,以及类叫什么名字,所以我们应该为框架的使用者提供一个配置文件,要求对方将类的信息写入配置文件中,然后框架自己加载需要的模块

    settings.py

    # 该文件作为框架的配置文件
    # 作为框架使用者,在配置文件中指定你配合框架的类是哪个
    CLASS_PATH = 'libs.plugins.WinCMD'
    CLASS_PATH = 'libs.plugins.LinuxCMD'

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


    元类metaclass

    元类是什么?

    • 用于创建类的类
    • 万物皆对象,类当然也是对象
    • 对象是通过类实例化产生的,如果类也是对象的话,那么类对象必然也是由另一个类实例化产生的
    • 默认情况下,所有类的元类都是type

    验证:类的类是谁?且Person类是通过type类实例化产生的

    # 类的类是谁?
    class Person(object):
        name = '123'
        pass
    ​
    p = Person()
    print(type(p))
    ​
    # Person类是通过type类实例化产生的
    print(type(Person))
    ​
    class Student:
        pass
    print(type(Student))

    直接调用type类来产生类对象

    一个类的三个基本组成部分:

    • 1.类的名字(字符类型)
    • 2.类的父亲们(是一个元祖或者列表)
    • 3,类的名称空间(是一个字典类型)
    cls_obj = type('dog',(),{})
    print(cls_obj)  # <class '__main__.dog'>

    学习元类的目的:

    • 高度的自定义一个类
    • 类也是对象,也有自己的类
    • 我们的需求是创建类对象时做一些限制,所以我们可以用初始化方法,我们只要找到类对象的类(元类),覆盖其中的init方法,就能实现需求
    • 当然我们不能修改源代码,所以应该继承type来编写自己的元类,同时覆盖init方法来完成需求

    例如控制类的名字必须以大驼峰的方式来书写

    # 只要继承了type,那么这个类就变成了一个元类
    class MyType(type):  # 此时MyType是一个元类
        def __init__(self,class_name,bases,dict):
            super().__init__(class_name,bases,dict)
            if not class_name.istitle():
                raise Exception('你丫的,类名不会写吗')
    ​
    MyType('pig',(),{})  # pig  ()  {}  
    # 为Pig类指定了MyType元类
    class Pig(metaclass=MyType):
        pass

    元类中的call方法:

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

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

    class MyMeta(type):
        def __init__(self,name,bases,dict):
            super().__init__(name,bases,dict)
            print('init run')
            
        def __call__(self, *args,  **kwargs):
            print('元类 call run')
            return super().__call__(*args,**kwargs)
    ​
    class Dog(metaclass=MyMeta):
        def __init__(self,name):
            self.name = name
            
        def __call__(self, *args,  **kwargs):
            print('call run')
             
    # 未调用Dog类时,返回init run
    # 调用Dog类
    d = Dog()  # call run
    d = Dog('大黄',age=1)  
    print(d)  # init call run   ('大黄')  {'age':1}

    使用场景:

    • 当你想要控制对象的创建过程时,就可以覆盖call方法
    • 当你想要控制类的创建过程时,就覆盖init方法

    案例:想把对象的所有属性变成大写

    class MyMeta(type):
        def __call__(self, *args,  **kwargs):
            new_args = []
            for a in args:
                new_args.append(a.upper())
            print(new_args)  # ('JACK')
            print(kwargs)  # {}
            return super().__call__(*new_args,**kwargs)
    ​
    class Person(metaclass=MyMeta):
        def __init__(self,name,gender):
            self.name = name
            self.gender = gender
            
    p = Person(name='jack',gender='woman')
    print(p.name)  # JACK
    print(p.gender)  # WOMAN

    案例:要求创建对象时必须以关键字参数形式来传参

    • 覆盖元类的双下call方法
    • 判断你有没有传非关键字参数,不能有位置参数,如果有就报错
    # 第一种方法
    class Meta(type):
        def __call__(self, *args,  **kwargs)
            if args:
                raise Exception('不好意思,不允许使用位置参数')
                return super().__call__(*args,**kwargs)
                
    class A(metaclass=Meta):
        def __init__(self,name):
            self.name = name
            
    a = A(name='jack')
    print(a.name)  # jack
    ​
    ​
    # 第二种方式
    class Meta(type):
        def __call__(self, *args,  **kwargs)
            obj = object.__new__(self)  # 创建一个空对象
            self.__init__(obj,*args,**kwargs) # 让对象去初始化
                return obj
                
    class A(metaclass=Meta):
        def __init__(self,name):
            self.name = name
            
    a = A(name='jack')
    print(a.name)  # jack

    注意:

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

    补充:元类中的new方法

    当你要创建类对象时,会首先执行元类中的双下new方法,拿到一个空对象,然后会自动调用双下init方法来自动对这个类进行初始化操作

    注意:

    • 如果你覆盖了该new方法,new方法必须有返回值,且必须是对应的类对象
    # 情况1
    class Meta(type):
        def __new__(cls, *args, **kwargs):
            print(cls)  # 元类自己
            print(args)  # 创建类需要的几个参数,类名,基类,名称空间
            print(kwargs)  # 空的
            print('new run')
            super().__new__(cls,*args,**kwargs)
            
        def __init__(self,a,b,c):
            super().__init__(a,b,c)
            print('init run')
            
    # new run
    # init run
    # 情况2
    class Meta(type):
        def __new__(cls, *args, **kwargs):
            print(cls)  # 元类自己
            print(args)  # 创建类需要的几个参数,类名,基类,名称空间
            print(kwargs)  # 空的
            print('new run')
            obj = type.__new__(cls,*args,**kwargs)
            return obj
            
        def __init__(self,a,b,c):
            super().__init__(a,b,c)
            print('init run')
            
    # new run
    # init run 

    总结:

    • new方法和init方法,都可以实现控制类的创建过程,init更加简单

    单例设计模式

    什么是设计模式?

    • 用于解决某种固定问题的套路,如MVC,MTV

    单例:

    • 指的是一个类产生一个对象

    单例的目的:

    • 单例是为了节省空间资源,当一个类的所有对象属性全部相同时,则没有必要创建多个对象
    # 未使用单例模式,却出现需要单例的情况
    class Person:
        def __init__(self,name,age):
            self.name = name
            self.age = age
            
        def say_hi(self):
            print('hello im %s' %self.name)
            
    p1 = Person('jack',18)
    p1.say_hi()  # hello im jack
    ​
    p2 = Person('jack',18)
    p2.say_hi()  # hello im jack
    ​
    ​
    # 伪单例模式
    class Single(type):
        def __call__(self, *args, **kwargs):
            if hasattr(self,'obj'):
                return getattr(self,'obj')
            obj = super().__call__(*args,**kwargs)
            self.obj = obj
            print(obj)
    ​
    class Person(metaclass=Single):
        def __init__(self,name,age):
            self.name = name
            self.age = age
    ​
        def say_hi(self):
            print('hello im %s' %self.name)
            
        # 用于获取对象的方法
        @staticmethod
        def get_instance():
            # 判断是否已经有了对象
            if hasattr(Person,'obj'):
                return getattr(Person,'obj')
            obj = Person('jack',18)
            Person.obj = obj
            return obj
    ​
    p1 = Person('jack',18)
    p1.say_hi()  # hello im jack
    ​
    p2 = Person('jack',18)
    p2.say_hi()  # hello im jack
    ​
    ​
    # 单例模式,单例元类
    class Single(type):
        def __call__(self, *args, **kwargs):
            if hasattr(self,'obj'):  # 判断是否存在已有的对象
                return getattr(self,'obj')  # 有就返回
            obj = super().__call__(*args,**kwargs)  # 没有则创建
            print('new run')
            self.obj = obj  # 并存入类中
            print(obj)class Student(metaclass=Single):
        def __init__(self,name):
            self.name = name
            
    class Person(metaclass=Single):
        pass# 只会创建一个对象
    Person()
    Person()
    Person()
    Person()
    Person()
    # new run
  • 相关阅读:
    自动化生成测试报告
    测试用例设计的常见几种方法
    python的七种数据类型
    python读写文件的几种方法
    测试工具之fiddler
    自动化前置用例和后置用例
    python的几种数据类型以及举例
    Selenium请求库
    第一篇帖子,上火了
    汉诺塔算法
  • 原文地址:https://www.cnblogs.com/zhukaijian/p/11271639.html
Copyright © 2011-2022 走看看