面向对象魔法方法,元类

87 阅读5分钟

反射实战案例

1.加载配置文件纯大写的配置
# 配置文件加载:获取配置文件中所有大写的配置 小写的直接忽略  组织成字典
    import settings
    new_dict = {}
    # print(dir(settings))  # dir获取括号中对象可以调用的名字
    # ['AGE', 'INFO', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'desc', 'name']
    for i in dir(settings):
        if i.isupper():  # 如果名字是纯大写 那么获取该大写名字对应的值   'AGE'   'INFO'
            v = getattr(settings, i)
            new_dict[i] = v
    print(new_dict)
2.模拟操作系统cmd终端执行用户命令
    class WinCmd(object):
    def dir(self):
        print('dir获取当前目录下所有的文件名称')

    def ls(self):
        print('ls获取当前路径下所有的文件名称')

    def ipconfig(self):
        print('ipconfig获取当前计算机的网卡信息')
        
    obj = WinCmd()
    while True:
        cmd = input('请输入您的命令>>>:')
        if hasattr(obj, cmd):
            cmd_name = getattr(obj, cmd)
            cmd_name()
        else:
            print('%s 不是内部或外部命令,也不是可运行的程序或批处理文件' % cmd)

面向对象双下方法

魔法方法其实就是类中定义的双下方法,之所以会叫魔法方法原因是这些方法都是到达某个条件自动触发 无需调用。
	eg: __init__方法在给对象设置独有数据的时候自动触发(实例化)

class MyClass(object):
    def __init__(self, name):
        # 实例化对象的时候触发
        print('__init__方法')
        self.name = name

    def __str__(self):
        # 对象被执行打印操作的时候触发,该方法必须返回一个字符串数据,
        # 返回什么就会展示什么字符串
        print('__str__方法')
        return 'aaa'

    def __call__(self, *args, **kwargs):
        # 对象被加括号调用的时候触发
        # 对象本不能加括号调用,定义该方法后就可以加括号调用了
        # 且括号内传的参数都会被args 和 kwargs接收
        print('__call__方法')

    def __del__(self):
        # 对象被主动删除(del 关键字)或被动删除(垃圾回收机制)的时候触发
        print('__del__方法')

    def __getattr__(self, item):
        # 对象查找不存在的属性名的时候触发(避免报错)
        # item就是查找的变量名,类型是字符串(将变量名自动转成字符串,方便操作)
        print('__getattr__', item)

    def __setattr__(self, key, value):
        # 对象在执行添加属性的操作时自动触发(obj.变量名 = 变量值)
        # 注意:__init__初始化的时候也会执行,有几个属性就执行几次
        print('__setattr__')
        print(key, value)
        # 可以用来筛选赋值操作,例如:
        if key == 'gender':
            raise Exception('你不能拥有gender属性')
        # 注意,我们进行以上操作的时候相当于重写了父类object里的__setattr__方法,需要super()一下
        super().__setattr__(key, value)

    def __getattribute__(self, item):
        # 只要对象查找名字,无论名字是否存在,都会执行该方法
        # 如果类中有该方法,那么久不会执行__getattr__方法
        print('__getattribute__ 方法')

    def __enter__(self):
        # 对象开始被执行with上下文管理语法时自动触发
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        # 对象被执行完with语法(子代码全部执行完毕)之后触发
        pass

元类

"""
基础阶段我们使用type来查找数据的数据类型
但是学了面向对象之后 发现查看的不是数据类型 而是数据所属的类

我们定义的数据类型 其实本质还是通过各个类产生了对象
    class str:
        pass
    h = 'hello'  str('hello')

我们也可以理解为type用于查看产生当前对象的类是谁
"""
class MyClass:
    pass
obj = MyClass()
print(type(obj))  # 查看产生对象obj的类:<class '__main__.MyClass'>
print(type(MyClass))  # 查看产生对象MyClass的类:<class 'type'>
"""
通过上述推导 得出结论 自定义的类都是由type类产生的
我们将产生类的类称之为 '元类'
"""

产生类的两种方式

方式1class 类名:
class C1():
    pass
print(C1)  # <class '__main__.C1'>
方式2type(类名, 父类, 类的名称空间)
C1 = type('C1', (), {})
print(C1)  # <class '__main__.C1'>

元类的基本使用

学习元类的目的:'元类能够控制类的创建,学习元类我们就可以高度定制类的行为'
需求:要求类的首字母必须大写
思考:在哪里编写定制化代码  由已知推未知
	对象的产生过程>>>   类里边的__init__方法
    类的产生过程??? >>> 元类里的__init__方法>>>修改元类

注意:修改元类的时候不能直接继承我们自己构造的‘元类’,必须指定关键字参数'metaclass=类名'   

class MyTypeClass(type):
    def __init__(cls, cls_name, cls_bases, cls_dict):
        print(cls, cls_name, cls_bases, cls_dict)
        # <class '__main__.C1'> C1 () {'__module__': '__main__', '__qualname__': 'C1'}
        # 这三个参数分别是:类名,类的父类,类的名称空间
        if not cls_name.istitle():  # 判断首字母是否大写
            raise Exception('类名首字母必须大写')
        super().__init__(cls_name, cls_bases, cls_dict)  # 重新调用父类的__init__方法


class C1(metaclass=MyTypeClass):
    pass    

元类进阶操作

1.回想__call__方法
对象加括号会自动执行产生对象的类里边的__call__方法,并且该方法返回什么,对象加括号就得到什么
推导:类也可以看做是一个对象,那么类加括号会自动执行元类(产生类的类)里边的__call__方法,同样,该方法返回什么,类加括号就得到什么

class MyMetaClass(type):
    def __call__(self, *args, **kwargs):
        print('__call__')
        if args:
            raise Exception('必须用关键字参数传参')
        super().__call__(*args, **kwargs)

class MyClass(metaclass=MyMetaClass):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print('__init__')

# 需求:实例化对象 所有的参数都必须采用关键字参数的形式
obj = MyClass('jason', 18)
# obj = MyClass(name='jason', age=18)

总结
"""
如果我们想高度定制对象的产生过程,可以操作元类里面的__call__
如果我们想高度定制类的产生过程,可以操作元类里面的__init__
"""

双下new方法

"""
类产生对象的步骤
	1.产生一个空对象
	2.自动触发__init__方法实例化对象
	3.返回实例化好的对象
"""
__new__方法专门用于产生空对象		      骨架
__init__方法专门用于给对象添加属性	      血肉