• Python进阶:反射


    反射简介

    反射就是通过字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,一种基于字符串的事件驱动。简单说,在Python中,能够通过一个对象,找出其typeclassattributemethod的能力,称为反射或自省。

    具有反射能力的函数有type(),isinstance()getattr()等。

    可使用反射的地方:

    • 反射类中的变量:静态属性,类方法,静态方法;
    • 反射对象中的变量、对象属性、普通方法;
    • 反射模块中的变量;
    • 反射本文件中的变量。

    为了方便阐述反射的用法,先定义一个类:

    class Point:
        def __init__(self, x, y):
            self.x = x
            self.y = y
            
        def __str__(self):
            return "{} and {}".format(self.x, self.y)
        
        def show(self):
            print(self.x, self.y)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    先对类实例化,然后看一下__dict__方法。

    >>> p = Point(4, 5)
    >>> print(p)
    4 and 5
    >>> print(p.__dict__)
    {'x': 4, 'y': 5}
    
    >>> p.__dict__['y'] = 16
    >>> print(p.__dict__)
    {'x': 4, 'y': 16}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上例通过属性字典__dict__来访问对象的属性,本质上就是利用反射的能力,但是上面的例子中,访问的方式不优雅,Python提供了内置的函数。

    反射用法

    getattr

    判断类、对象或者模块中是否有相应的属性或方法。
    用法:

    getattr(obj, str, default=None) 
    
    • 1

    判断obj中是否有str属性,有就返回,没有时如果有传入第三参数就返回第三参数,没有就报错。

    例子1:下面两个函数等价。

    >>> print(p.__dict__)
    {'x': 4, 'y': 16}
    >>> print(getattr(p, '__dict__'))
    {'x': 4, 'y': 16}
    
    • 1
    • 2
    • 3
    • 4

    这验证了上面说的__dict__本质上就是利用反射的能力。

    例子2:getattr()使用方法

    >>> p = Point(4, 5)
    >>> getattr(p, "x")
    4
    >>> getattr(p, "y")
    16
    >>> print(getattr(p, '__dict__'))
    {'x': 4, 'y': 5}
    
    >>> getattr(p, "z", "NotFound")
    'NotFound'
    >>> getattr(p, "z")
    Traceback (most recent call last):
      File "D:\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py", line 3331, in run_code
        exec(code_obj, self.user_global_ns, self.user_ns)
      File "", line 1, in <module>
        getattr(p, "z")
    AttributeError: 'Point' object has no attribute 'z'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    settattr

    设置属性object的属性,存在则覆盖,不存在则新增。第三参数为新的属性值。

    setattr(object, name, value)
    
    • 1

    例子:settattr()使用方法

    >>> p1 = Point(4, 5)
    >>> p2 = Point(10, 10)
    
    >>> print(p1.__dict__)
    {'x': 4, 'y': 5}
    >>> print(p2.__dict__)
    {'x': 10, 'y': 10}
    
    >>> setattr(p1, 'y', 16)
    >>> setattr(p1, 'z', 10)
    >>> print(p1.__dict__)
    {'x': 4, 'y': 16, 'z': 10}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    hasattr

    判断对象是否有这个名字的属性,有就返回True,没有就返回Falsename必须为字符串

    hasaattr(object,name)
    
    • 1

    例子:动态调用方法

    >>> if hasattr(p1, 'show'):
    >>>     print(getattr(p1, 'show'))
        
    <bound method Point.show of <__main__.Point object at 0x0000014572376488>>
    
    • 1
    • 2
    • 3
    • 4

    动态增加方法

    >>> if not hasattr(Point, 'add'):
    >>>     setattr(Point, 'add', lambda self, other: Point(self.x + other.x, self.y + other.y))
        
    >>> print(Point.add)
    <function <lambda> at 0x0000014572371558>
    >>> print(p1.add)
    <bound method <lambda> of <__main__.Point object at 0x0000014572376488>>
    >>> print(p1.add(p2))
    14 and 26
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    >>> if not hasattr(p1, 'sub'):
    >>>     setattr(p1, 'sub', lambda self, other: Point(self.x - other.x, self.y - other.y))
    
    >>> print(p1.sub(p1, p2))
    -6 and 6
    
    >>> print(p1.__dict__)
    {'x': 4, 'y': 16, 'z': 10, 'sub': <function <lambda> at 0x0000014572381DC8>}
    
    >>> print(Point.__dict__)
    {'__module__': '__main__', '__init__': <function Point.__init__ at 0x00000145722E04C8>, '__str__': <function Point.__str__ at 0x00000145722E0318>, 'show': <function Point.show at 0x00000145722E0288>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None, 'add': <function <lambda> at 0x0000014572371558>}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    反射相关的魔术方法

    getattr()

    class Base:
        n = 0
        
    class Point(Base):
        z = 6
        
        def __init__(self, x, y):
            self.x = x
            self.y = y
            
        def show(self):
            print(self.x, self.y)
            
        def __getattr__(self, item):
            return item
        
    >>> p1.x
    4
    >>> p1.z
    6
    >>> p1.n
    0
    >>> p1.t
    't'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    实例属性会按照继承关系寻找,如果找不到,就会执行__getattr__()方法,如果没有这个方法,就会抛出AttributeError异常标识找不到属性。

    setattr()

    class Base:
        n = 0
        
    class Point(Base):
        z = 6
        
        def __init__(self, x, y):
            self.x = x
            self.y = y
            
        def show(self):
            print(self.x, self.y)
            
        def __getattr__(self, item):
            return item
        
        def __setattr__(self, key, value):
            print(key, value)
            
    # --------------------------------------------------
    >>> p1 = Point(4, 5)
    x 4
    y 5
    >>> print(p1.x)
    x
    >>> print(p1.z)
    6
    >>> print(p1.n)
    0
    >>> print(p1.t)
    t
    # --------------------------------------------------
    >>> p1.x = 50
    >>> print(p1.x)
    x
    >>> print(p1.__dict__)
    {}
    >>> p1.__dict__['x'] = 60
    >>> print(p1.__dict__)
    {'x': 60}
    >>> p1.x
    60
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    实例通过.点号设置属性,例如self.x=x,就会调用__setattr__(),属性要加到实例的__dict__中,就需要自己完成。

    setattr()方法,可以拦截堆实例属性的增加,修改操作,如果要设置生效,需要自己操作实例的__dict__

    class Base:
        n = 200
        
    class A(Base):
        z = 100
        d = {}
        
        def __init__(self, x, y):
            self.x = x
            setattr(self, 'y', y)
            self.__dict__['a'] = 5
            
        def __getattr__(self, item):
            print(item)
            return self.d[item]
        
        def __setattr__(self, key, value):
            print(key, value)
            self.d[key] = value
            
    >>> a = A(4, 5)
    x 4
    y 5
    >>> print(a.__dict__)
    {'a': 5}
    >>> print(A.__dict__)
    A.__dict__
    mappingproxy({'__module__': '__main__',
                  'z': 100,
                  'd': {'x': 4, 'y': 5},
                  '__init__': <function __main__.A.__init__(self, x, y)>,
                  '__getattr__': <function __main__.A.__getattr__(self, item)>,
                  '__setattr__': <function __main__.A.__setattr__(self, key, value)>,
                  '__doc__': None})
    >>> print(a.x, a.y)
    x
    y
    4 5
    >>> print(a.a)
    5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    delattr()

    class Point:
        z = 5
        
        def __init__(self, x, y):
            self.x = x
            self.y = y
            
        def __delattr__(self, item):
            print(item)
            p = Point(14, 5)
    
            
    >>> p = Point(3, 4)
    >>> del p.x
    x
    >>> p.z=15
    >>> del p.z
    z
    >>> del p.Z        
    Z
    >>> print(Point.__dict__)
    {'__module__': '__main__', 'z': 5, '__init__': <function Point.__init__ at 0x0000019E93B01318>, '__delattr__': <function Point.__delattr__ at 0x0000019E93B013A8>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    getattribute()

    class Base:
        n = 0
        
    class Point(Base):
        z = 6
        
        def __init__(self, x, y):
            self.x = x
            self.y = y
            
        def __getattr__(self, item):
            return item
        
        def __getattribute__(self, item):
            return item
         
    >>> p1 = Point(4, 5)
    >>> print(p1.__dict__)
    __dict__
    >>> print(p1.x)
    x
    >>> print(p1.z)
    z
    >>> print(p1.n)
    n
    >>> print(p1.t)
    t
    >>> print(Point.__dict__)
    {'__module__': '__main__', 'z': 6, '__init__': <function Point.__init__ at 0x000001F5EB7063A8>, '__getattr__': <function Point.__getattr__ at 0x000001F5EB706558>, '__getattribute__': <function Point.__getattribute__ at 0x000001F5EB706168>, '__doc__': None}
    >>> print(Point.z)
    6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    实例的所有的属性访问,第一个都会调用__getattribute__方法,它阻止了属性的查找,该方法应该返回值或者抛出一个AttributeError异常。

    • 该方法的返回值将作为属性查找的结果。
    • 如果抛出AttributeError异常,则会直接调用__getattr__方法,因为属性没有找到,__getattribute__方法中为了避免在该方法中无限递归,它的实现应该永远调用基类的同名方法以访问需要的任何属性。

    需要注意的是,除非明确知道__getattrtbute__方法用来做什么,否则不要使用。

    使用场景

    • input
      用户输入的如果是a,那么就打印1,如果输入的是b就打印2
    • 文件
      从文件中读出的字符串,想转换成变量的名字
    • 网络
      将网络传输的字符串转换成变量的名字

    参考

  • 相关阅读:
    如何从iPhone恢复错误删除的照片
    springboot hibernate-validator 校验
    『亚马逊云科技产品测评』活动征文|阿里云服务器&亚马逊服务器综合评测
    数据挖掘 知识发现过程与应用结构
    Mac修改Mysql8.0密码
    Java实现截取视频第一帧
    某大学作业
    代码随想录算法训练营第三十四天| LeetCode1005. K 次取反后最大化的数组和、LeetCode134. 加油站、LeetCode135. 分发糖果
    【开源打印组件】vue-plugin-hiprint初体验
    混合使用设计模式:策略模式+工厂模式+模板方法模式
  • 原文地址:https://blog.csdn.net/qq_37085158/article/details/126401372