代码改变世界

上海 day24----面向对象之 反射、元类

2019-07-30 20:38  在上海的日子里  阅读(193)  评论(0编辑  收藏  举报

目  录

 

 

一、反射

什么是反射?

反射指的是一个对象应该具备,可以检测,修改,增加自身属性的能力

反射就是通过字符串操作属性

涉及到4个函数:getattr   setattr   delattr

这四个函数就是普通的内置函数 没有双下划綫,与print等等没有区别

案例:

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

p = Person("jack",18,"man")

# if hasattr(p,"name"): # 1.判断某个对象是否存在某个属性
#     print(getattr(p,"names",None)) # 2.从对象中取出属性,第三个值位默认值 当属性不存在是返回默认值

# 3.为对象添加新的属性
setattr(p,"id","123")
print(p.id)

# 4.从对象中删除属性
delattr(p,"id")
print(p.id)

使用场景:

反射其实就是对属性的增删改查,但是如果直接使用内置的dict来操作,语法繁琐,不好理解

另外一个最主要的问题是,如果对象不是我自己写的是另一方提供的,我就必须判断这个对象是否满足的要求,也就是是否我需要的属性和方法

框架设计方式:

框架代码:

"""
反射被称为框架的基石,为什么
因为框架的设计者,不可能提前知道你的对象到底是怎么设计的
所以你提供给框架的对象 必须通过判断验证之后才能正常使用
判断验证就是反射要做的事情,
当然通过__dict__也是可以实现的, 其实这些方法也就是对__dict__的操作进行了封装
需求:要实现一个用于处理用户的终端指令的小框架
框架就是已经实现了最基础的构架,就是所有项目都一样的部分
"""

import plugins

# 框架已经实现的部分
def run(plugin):
    while True:
        cmd = input("请输入指令:")
        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)

插件部分:

class WinCMD:

    def cd(self):
        print("wincmd 切换目录....")

    def delete(self):
        print("wincmd 要不要删库跑路?")

    def dir(self):
        print("wincmd 列出所有文件....")

class LinuxCMD:

    def cd(self):
        print("Linuxcmd 切换目录....")

    def rm(self):
        print("Linuxcmd 要不要删库跑路?")

    def ls(self):
        print("Linuxcmd 列出所有文件....")

上述框架代码中 写死了必须使用某个类,这是不合理的,因为无法提前知道对方的类在什么地方 以及类叫什么

所以我们应该为框架的使用者提供一个配置文件,要求对方将累的信息写入配置文件

然后框架自己去加载需要的模块。

配置文件代码:

'''提供配置文件,提供类的路径'''
CLASS_PATH = "libs.plugins.WinCmd"

最后的框架代码:

# 该功能为主程序框架
import importlib
import settings


def run(plugin):  # 接收插件作为参数,该参数实际指的是类WinCmd实例化的对象
    while True:
        cmd = input('请输入指令:').strip()  # cmd 是指你自己输入的指令,也就是我们在类中定义的方法。
        if cmd == 'exit':
            break
        # 判断对象中是否存在该方法
        if hasattr(plugin,cmd):
            # 取出该方法并执行
            func = getattr(plugin,cmd)
            func()

    print('see you !')

# 创建一个插件对象,调用主框架来使用
# linuxcmd  = plugins.LinuxCmd()  # 实例化该对象作为插件
# run(linuxcmd )  # 主函数调用该对象
'''
提出问题?虽然我们可以用框架调用对象,但是我们不知道实例化对象的类,也不知道包在哪里在哪里!
即linuxcmd  = plugins.LinuxCmd() 中类LinuxCmd和包plugins 不知道在哪
 解决方法:模块的动态导入。。。
'''
# 从配置文件中拿到模块和类名称
path = settings.CLASS_PATH
model_path, cls_name = path.rsplit(".",1)  # 字符串切割,解压赋值
# print(cls_name)
# print(model_path)
pl = importlib.import_module(model_path)  # 找到类所在的模块,用变量名pl接收
cls = getattr(pl,cls_name)  # 通过getattr方法中找到类,用cls接收
# print(cls)
obj = cls()
run(obj)

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

二、反射与动态导入模块

引用上述代码解答:  使用模块  importlib 导入模块

'''
当我们使用框架时,不能提前知道所提供的类的相关信息。所以我们使用动态导入模块来获取信息
'''
import importlib  # 导入  导入模块

# 以当前执行文件为准,导入插件所在的模块
pl = importlib.import_module("libs.plugins")
'''
pl = importlib.import_module("libs.plugins")
这段代码为什么要用pl来接收?
因为我们是导入libs包中plugins 模块,这样就在当前执行文件的名称空间中就产生了一个名字指向plugins模块,
这个名字就是一个变量,我们用pl来接收!

'''
print(pl)
# 结果:<module 'libs.plugins' from 'H:\\PycharmProjects\\Py_learn_2\\练习3\\day24\\libs\\plugins.py'>
# 我们找到了模块就可以根据模块中类的名字来找到类的值,

cls = getattr(pl,'WinCmd')  # 万物皆对象,我们可以将类看做是模块plugins中的对象,使用getattr方法
print(cls)  # <class 'libs.plugins.WinCmd'>

#实例化产生对象
obj = cls()
# 对象调用方法
obj.cd()

三、元类

什么是元类?

就是创建类的类!

什么是类对象?

在Python中万物皆对象,类当然也是对象,对象是通过类实例化产生的,如果类也是对象的话,必然类对象也是有另一个类实例化产生的默认情况下所有类的元类都是type

案例:

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

元类的作用?

可以高度的自定义一个类,

如何利用type类自定义一个类,即模仿class 创造类?

# 直接调用type类来产生类对象
# 一个类的三个基本组成部分
# 1.类的名字(字符类型)  2.类的父类们  (是一个元组或列表)  3.类的名称空间(字典类型)

代码:

cls = type('DOG',(),{})
print(cls)  # <class '__main__.DOG'>

class P:
    pass
print(P)  # <class '__main__.P'>

 

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

案例:规定创建类时类名首字母要大写

思路如下:

'''
类也是对象,也有自己的类,

我们的需求是创建类对象做一些限制 

想到了初始化方法  我们只要找到类对象的类(元类),覆盖其中 init方法就能实现需求 

当然我们不能修改源代码,所以应该继承type来编写自己的元类,同时覆盖init来完成需求
'''

代码:

# 因为type类我们不能修改,所以我我们要自定义元类
class MyType(type):
    def __init__(cls,cls_name,bases,dict):
        super().__init__(cls_name,bases,dict)
        print(cls_name,bases,dict)
        if not cls_name.istitle():

            raise Exception('类名称大写。。。给老子记住!')

#指定元类
class person(metaclass=MyType):
    pass
# 执行结果:Exception: 类名称大写。。。给老子记住!

元类中__call__方法

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

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

'''

案例:

class MyType(type):
    def __init__(cls,name,bases,dict):
        super().__init__(name,bases,dict)
        print('元类 初始化操作。。。')

    def __call__(cls,*args,**kwargs):
        print('元类  call 操作!')

class Person(metaclass=MyType):
    def __init__(self,name):
        self.name = name
# 当类对象调用时会自动触发f__call__方法
p = Person('jason')

 案例:

"""需求:规定对象创建的时候要以关键字的形式传参"""

class Mytype(type):
    def __call__(self,*args,**kwargs):
        if args:
            raise Exception('不允许使用位置参数。。。')
        return super().__call__(*args,**kwargs)

class Person(metaclass=Mytype):
    def __init__(self,name):
        self.name = name

# p = Person('jason')  # 报错!Exception: 不允许使用位置参数。。。
p = Person(name='tank')
print(p.name)

使用场景:

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

当你想要控制类的创建过程时,就覆盖init方法

案例:   

实现将对象的所有属性名称转为大写

"""将对象所有属性名全部转为大写"""
class Mytype(type):
    def __call__(cls,*args,**kwargs):

        new_args = []
        for i in args:
            i = i.upper()
            new_args.append(i)

        print(new_args)  # ['JASON']
        print(kwargs)  # {}
        return super().__call__(*new_args,**kwargs)


class Person(metaclass=Mytype):
    def __init__(self,name):
        self.name = name

p = Person('jason')
print(p.name)   # JASON

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

四、补充new方法

"""
当你要创建类对象时,会首先执行元类中的__new__方法,拿到一个空对象,然后会自动调用__init__来对这个类进行初始化操作  
注意:,如果你覆盖了该方法则必须保证,new方法必须有返回值且必须是,对应的类对象
"""

 测试:

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
    def __init__(self,a,b,c):
        super().__init__(a,b,c)
        print("init run")
class A(metaclass=Meta):
    pass
print(A)

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

 

 

五、单例设计模式

'''
设计模式?用于解决某种固定问题的套路
例如:MVCMTV等
单例:指的是一个类产生一个对象
为什么要使用单例:单例是为了节省 资源,当一个类的所有对象属性全部相同时,则没有必要创建多个对象
'''

元类实现:

# 单例n元类
class Single(type):
    def __call__(self, *args, **kwargs):
        if hasattr(self,"obj"): #判断是否存在已经有的对象
            return getattr(self,"obj") # 有就返回

        obj = super().__call__(*args,**kwargs) # 没有则创建
        print("new 了")
        self.obj = obj # 并存入类中
        return obj


class Student(metaclass=Single):
    def __init__(self,name):
        self.name = name


class Person(metaclass=Single):
    pass

# 只会创建一个对象
Person()
Person()

 

 六、项目的生命周期

1、项目的生命周期

 2、三层架构(以选课系统为例)

 

关于 __call__ 和 __new__ 两个方法的新的理解

__call__总的来说就是 调用对象的时候会执行

代码演示:

class MyMeta(type):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print('init run...')


    def __call__(self, *args, **kwargs):
        print(' call run...')
        # return super().__call__(*args, **kwargs)


class Func(metaclass=MyMeta):
    def __init__(self,name):
        self.name = name

f = Func('kkk')
print(f)
'''
init run...
 call run...
None
'''

当我们只创建 类 Func时,会自动执行 __init__ 方法;

当调用类 Func创建对象时,即执行 f = Func('kkk')时会自动执行 __call__方法;

如果 __call__ 方法下没有代码 return super().__call__(*args, **kwargs),

对象 f 不会创建  print(f) 结果 是:None

代码演示:

class MyMeta(type):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print('init run...')


    def __call__(self, *args, **kwargs):
        print(' call run...')
        return super().__call__(*args, **kwargs)


class Func(metaclass=MyMeta):
    def __init__(self,name):
        self.name = name

f = Func('kkk')
print(f)
'''
init run...
 call run...
<__main__.Func object at 0x02D763B0>
'''

总结:

当我们覆盖 __call__方法时,如果不添加调用父类的 __call__ 方法是不会创建出对象的!!!

__call__ 方法中加入 return super().__call__(*args, **kwargs) 时,才会产生对象 f  

 



 

__new__ 方法也可控制对象的创建(我们在举例中用创建类来演示,也就是创建类对象)

 

看代码演示:

# 关于 __new__ 方法
class MyMeta(type):
    def __new__(cls, *args, **kwargs):
        # print(cls)  # 创建的类对象
        # print(args)
        # print(kwargs)
        print(' new runing...')
        # super().__new__(cls,*args, **kwargs)

    def __init__(cls):
        super().__init__(cls)
        print(" init run...")

class M(metaclass=MyMeta):  # 指定元类
    pass
'''
执行结果:
  new runing...
只执行__new__ 方法的代码,不走__init__ 方法的代码
'''

 

此时以上代码,当__new__ 和__init__ 方法同时存在时,只会执行 __new__ 方法,

注意—— 以上代码没有  “   return super().__new__(cls, *args, **kwargs)”

修改后代码:

# 关于 __new__ 方法
class MyMeta(type):
    def __new__(cls, *args, **kwargs):
        # print(cls)  # 创建的类对象
        # print(args)
        # print(kwargs)
        print(' new runing...')
        return super().__new__(cls,*args, **kwargs)

    def __init__(cls, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print(" init run...")

class M(metaclass=MyMeta):  # 指定元类
    pass

"""
执行结果:
     new runing...
     init run...
__new__ 和 __init__ 方法代码成功运行,且先执行__new__方法然后在执行 __init__方法
"""

 由以上代码我们可以知道;

    __new__ 方法就是创建一个空对象,如果return 返回这个对象的时候会自动调用 __init__方法来初始化这个空对象!!!!!