• 小白学python系列————【Day45】面向对象魔法方法及元类详细


    今日内容概要

    • 反射实战案例
    • 面向对象的魔法方法(双下方法)
    • 魔法方法实战演练
    • 元类简介
    • 创建类的两种方式
    • 元类的实际应用
    • 元类之双下new方法

    反射实战案例

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    面向对象魔法方法

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

    2.常见的魔法方法及触发条件
    (1)__init__

    class MyClass(object):
       def __init__(self,name):
           """实例化对象的时候自动触发"""
           print('__init__方法')
           
    obj = MyClass
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (2)__str__

    class MyClass(object):
    	    def __str__(self):
           """
           对象被执行打印操作的时候会自动触发
               该方法必须返回一个字符串
               返回什么字符串打印对象之后就展示什么字符串
           """
           # print('__str__方法')
           print('这是类:%s 产生的一个对象')
           # return '对象:%s'%self
           # return '对象:%s'%self
           return ''
           
    obj = MyClass()
    print(obj)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (3)__call__

    class MyClass(object):
       def __call__(self, *args, **kwargs):
           """对象加括号调用 自动触发该方法"""
           print('__call__方法')
           print(args)
           print(kwargs)
           
    obj = MyClass()
    obj(1,2,name='jaosn',age = 18)
    
    '''
    __call__方法
    (1, 2)
    {'name': 'jaosn', 'age': 18}
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (4)__getattr__

    class MyClass(object):
       def __getattr__(self, item):
           """当对象获取一个不存在的属性名 自动触发
               该方法返回什么 对象获取不存在的属性名就会得到什么
               形参item就是对象想要获取的不存在的属性名
           """
           print('__getattr__', item)
           return '您想要获取的属性名:%s不存在' % item
    
    obj = MyClass()
    print(obj.age)
    
    '''
    __getattr__ age
    您想要获取的属性名:age不存在
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (5)__setattr__

    class MyClass(object):
       def __init__(self,name):
           """实例化对象的时候自动触发"""
           print('__init__方法')
           self.name = name
       def __setattr__(self, key, value):
           """对象操作属性值的时候自动触发>>>:  对象.属性名=属性值"""
           print("__setattr__")
           print(key)
           print(value)
           # super().__setattr__(key, value)  __str__重写之后才需要写这个
    
    obj = MyClass('jason')
    print(obj.__dict__)   # {'name': 'jason'}
    obj.name = 'kevin'
    print(obj.__dict__)   # {'name': 'kevin'}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (6)__del__

    class MyClass(object):
       def __del__(self):
           """对象在被删除(主动 被动)的时候自动触发"""
           print('__del__')
    
    obj = MyClass()
    del obj      # __del__
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (7)__gatattribute__

    class MyClass(object):
       def __getattribute__(self, item):
           print('__getattribute__')
           """对象获取属性的时候自动触发 无论这个属性存不存在
               当类中既有__getattr__又有__getattribute__的时候 只会走后者
           """
           # 补充之回到__getattr__方法:
           return super(MyClass, self).__getattribute__(item)  复杂写法
           return super().__getattribute__(item)  # 简便写法
    
    obj = MyClass()
    obj.name        # __getattribute__
    print(obj.name)     # None
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (8)__enter__与__exit__

    class MyClass(object):
       def __enter__(self):
           """对象被with语法执行的时候自动触发 该方法返回什么 as关键字后面的变量名就能得到什么"""
           print('__enter__')
           return 123
       def __exit__(self, exc_type, exc_val, exc_tb):
           """对象被with语法执行并运行完with子代码之后 自动触发"""
           print('__exit__')
    
    obj = MyClass()
    with obj as f:
       pass
    print(f)     # 123
    '''
    __enter__
    __exit__
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.表格说明

    项目Value
    __ init__对象实例化的时候自动触发
    __ str__对象被执行打印(print、前端展示)操作的时候自动触发,该方法必须返回字符串类型的数据,很多时候用来更加精准的描述对象
    __ del__对象被执行(被动、主动)删除操作之后自动执行
    __ getattr__对象查找不存在名字的时候自动触发
    __ setattr__对象在执行添加属性操作的时候自动触发 >>> obj.变量名=变量值
    __ call__对象被加括号调用的时候自动触发
    __ enter__对象被执行with上下文管理语法开始自动触发 ,该方法返回什么as后面的变量名就会得到什么
    __ exit__对象被执行with上下文管理语法结束之后自动触发
    __ getattribute__只要对象查找名字无论名字是否存在都会执行该方法,如果类中有__getattribute__方法 那么就不会去执行__getattr__方法
    __ new__在类被实例化前,最先被执行的方法,主要用来去创建一个完全空白的对象

    魔法方法笔试题

    需求:补全下面代码,执行之后不报错

    class Context:
    	pass
    
    with Context() as f:
    	f.do_something()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    答题方法如下:

    class Context:
       def __enter__(self):
           return self
       def __exit__(self, exc_type, exc_val, exc_tb):
           pass
       def do_something(self):
           pass
    with Context() as f:
       f.do_something()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    元类简介

    1.元类的引出
    (1)基础阶段我们使用type来查找数据的数据类型;
    (2)但是学了面向对象之后,发现查看的不是数据类型,而是数据所属的类;

    s1 = '哈哈哈 今天下午终于可以敲代码了!!!'
    l2 = [60, 80, 100, 120, 150, 200]
    d = {'name': '死给我看', 'age': 18}
    print(type(s1))  # 
    print(type(l2))  # 
    print(type(d))  # 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (3)而是数据所属的类型,其实本质还是通过各个类产生了对象。

    class str:
    	pass
    h = 'hello'    str('hello')
    
    • 1
    • 2
    • 3

    (4)我们可以理解为type用于查看产生当前对象的类是谁

    class MyClass:
       pass
    obj = MyClass()
    print(type(obj))  # 查看产生对象obj的类:
    print(type(MyClass))  # 查看产生对象MyClass的类:
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (5)通过上述推导,得出最后结论,自定义的类都是由type类产生的,我们将产生类的类称之为“元类!!!”

    产生类的两种方式

    1.方式一:class关键字

    class MyClass:
    	pass
    
    • 1
    • 2

    2. 利用元类type
    type(类名,类的父类,类的名称空间)

    class MyClass1:
       pass
    
    print(MyClass1.__dict__,MyClass1)
    # {'__module__': '__main__', '__dict__': , '__weakref__': , '__doc__': None} 
    res = type('MyClass2',(),{})
    print(res.__dict__,res)
    # {'__module__': '__main__', '__dict__': , '__weakref__': , '__doc__': None} 
    obj = res()
    print(obj)  # <__main__.MyClass2 object at 0x00000209EA66D390>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.类的补充
    学习元类其实就是掌握了类的产生过程,我们就可以在类的产生过程中高度定制化类的行为。
    eg:
    类名必须首字母大写
    上述需求就需要使用元类来控制类的产生过程 在过程中校验

    元类的基本使用

    1.自定义指定元类

    class MyMetaClass(type):
       pass
    """只有继承了type的类才可以称之为是元类"""
    class MyClass(metaclass=MyMetaClass):
       pass
    """如果想要切换产生类的元类不能使用继承 必须使用关键字metaclass声明"""
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.元类中双下init用于实例化类
    类中的__init__用于实例化对象;元类中__init__用于实例化类.

    class MyMetaClass(type):
       def __init__(self, what, bases=None, dict=None):
           # print('别晕')
           # print('what', what)  类名
           # print('bases', bases) 类的父类
           # print('dict', dict) 类的名称空间
           if not what.istitle():
               # print('首字母必须大写 你会不会写python 面向对象学过吗 lowB')
               raise Exception('首字母必须大写 你会不会写python 面向对象学过吗 lowB')
           super().__init__(what, bases, dict)
    
    class Aaa(metaclass=MyMetaClass):
       pass   # 没有报错,说明实例化类成功
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    元类进阶

    元类不单单可以控制类的产生过程,其实也可以控制对象的!!!
    (1)对象加括号执行产生该对象类里面的双下call
    (2)类加括号执行产生该类的元类里面的双下call
    回想__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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    魔法方法之双下new方法

    1.类产生对象的步骤
    (1)产生一个空对象
    (2)自动触发__init__方法实例化对象
    (3)返回实例化好的对象

    2. __new__和__init__
    __new__方法专门用于产生空对象 骨架
    __init__方法专门用于给对象添加属性 血肉

    作业展示

    1.自定义字典并且让字典具备
    d.key = value 修改键值对
    d.key = value 添加键值对

    class MyClass(dict):
        def __setattr__(self, key, value):
            self[key] = value
            super().__setattr__(key, value)
    
    
    obj = MyClass()
    obj.name = 'k'
    obj.age = 18
    obj.gender = 'male'
    print(obj)
    '''
    {'name': 'k', 'age': 18, 'gender': 'male'}
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    CentOS-7-x86_64 iso镜像的安装(Linux操作系统)
    大数据之 Hadoop 教程
    mybatis源码-注解sql
    如何创建自定义前端组件?
    【已解决】Git下载私有仓库时报错Authentication failed
    手撕Spring总结,Bean生命周期从始至终
    springboot(13):spring 过滤器和拦截器的区别
    微信小程序登录
    数据库内核面试中我不会的问题(4)
    P02项目诊断报警组件(学习操作日志记录、单元测试开发)
  • 原文地址:https://blog.csdn.net/DiligentGG/article/details/126083946