反射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
posted @ 2019-07-30 17:45  泡泡茶壶i  阅读(261)  评论(0编辑  收藏  举报