反射概述
反射相关方法:getattr、setattr、hasattr
反射相关魔术方法:__getattr__()__setattr__()__delattr__()

反射

反射(reflection)是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。(维基百科-反射

反射相关方法

  • getattr(object,name[, default]):通过name返回object的属性值
    • 当属性不存在,将使用default返回
    • 如果没有default,则抛出AttributeError
    • name必须为字符串
  • setattr(object,name, value):object的属性存在,则覆盖,不存在,新增
  • hasattr(object,name):判断对象是否有这个名字的属性,name必须为字符串
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Point<{self.x},{self.y}>"

    def show(self):
        print(self)

p = Point(1, 2)

print(getattr(p, 'x'), getattr(p, 'y'))
print(getattr(p, 'x', 1000))  # 1
# print(getattr(p, 'i'))  # AttributeError: 'Point' object has no attribute 'i'
print(getattr(p, 'i', 100))

print(p.__dict__)
print(getattr(p, '__dict__'))

# 动态调用方法
if hasattr(p, 'show'):
    getattr(p, 'show')()

# 动态增加属性
if not hasattr(p, 'z'):
    setattr(p, 'z', 10)

print(p.__dict__)

# 为类动态增加方法
if not hasattr(Point, 'add'):
    setattr(Point, 'add', lambda self, other: Point(self.x + other.x, self.y + other.y))

print(Point.add)
print(p.add)  # <bound method <lambda> of Point<1,2>>,有绑定效果

p1 = Point(2, 3)
print(p.add(p1))

# 为实例动态增加方法
if not hasattr(p, 'sub'):
    setattr(p, 'sub', lambda self, other: Point(self.x - other.x, self.y - other.y))
print(p.sub)  # <function <lambda> at 0x103833400>,无绑定效果

# print(p.sub(p1))  # missing 1 required positional argument: 'other'
print(p.sub(p, p1))
----------输出结果-------------------
1 2
1
100
{'x': 1, 'y': 2}
{'x': 1, 'y': 2}
Point<1,2>
{'x': 1, 'y': 2, 'z': 10}
<function <lambda> at 0x100cfdea0>
<bound method <lambda> of Point<1,2>>
Point<3,5>
<function <lambda> at 0x100e8e400>
Point<-1,-1>

不建议为实例动态增加方法

反射相关魔术方法

__getattr__()__setattr__()__delattr__() 这三个魔术方法,这里主要介绍前两个

__getattr__

class Base:
    n = 200

class Point(Base):
    z = 100

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __getattr__(self, item):
        print("~~~~~~~in getattr~~~~~~")
        return item, '~~~~~~~~~'


p = Point(1, 2)
print(p.x)  # 1
print(p.z)  # 100
print(p.n)  # 200
print(p.m)  # 找不到调用__getattr__

print(p.__dict__)

结论:

  1. 实例属性会按照继承关系找,如果找不到,就会执行 __getattr__() 方法,如果无该魔术方法,就会抛出AttributeError异常表示找不到属性
  2. 实例属性查找顺序为:
    instance.__dict__(实例的字典) --> instance.__class__.__dict__(实例的类字典) --> 继承的父类(直到object)的__dict__ --> 找不到则调用__getattr__()

__setattr__

class Base:
    n = 200


class Point(Base):
    z = 100

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __getattr__(self, item):
        print("~~~~~~~in getattr~~~~~~")
        return item, '~~~~~~~~~'

    def __setattr__(self, key, value):
        print(key, value, '++++++++++++')

p = Point(1, 2)
print(p.z)
print(p.n)
print(p.x)
p.x = 100
print(p.x)  # 1 ? 100 ?

print(1, p.__dict__)  # {}

p.m = 200
print(2, p.__dict__)  # {}

p.__dict__['y'] = 600
print(3, p.__dict__)  # {'y': 600}
print(p.y)

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

  • 修改如上例子,添加属性到实例字典中
class Base:
    n = 200

class Point(Base):
    z = 100

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __getattr__(self, item):
        print("~~~~~~~in getattr~~~~~~")
        return item, '~~~~~~~~~'

    def __setattr__(self, key, value):
        print(key, value, '++++++++++++')
        # self.key = value   # RecursionError: maximum recursion depth exceeded
        self.__dict__[key] = value


p = Point(1, 2)
print(Point.__dict__)
print(dir(Point))
print(p.z)  # 100
print(p.n)  # 200
print(p.x)  # 1
p.x = 100  # __setattr__
print(p.x)  # 100
print(1, p.__dict__)
print(p.m)  # __getattr__
p.m = 200  # __setattr__
print(2, p.__dict__)

结论:

  1. __setattr__ 方法,可以拦截对实例属性的增加、修改操作
  2. 如果要设置生效,需要自己操作实例的__dict__

__delattr__

class Point:
    Z = 5
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __delattr__(self, item):
        print('Can not del {}'.format(item))

p = Point(14, 5)
del p.x
p.z = 15
del p.z
del p.Z
print(Point.__dict__)
print(p.__dict__)
del Point.Z
print(Point.__dict__)

结论:

  1. 利用__delattr__可以阻止通过实例来删除属性的操作
  2. 但是通过类依然可以删除属性

__getattribute__

class Base:
    n = 100


class Point:
    z = 5

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __getattr__(self, item):
        print('~~~~ in getattr ~~~~~')
        return item, '++++++'

    def __getattribute__(self, item):
        print('~~~ in getattribute ~~~~')
        # raise AttributeError(f"{__class__} object has no attribute {item}")  # AttributeError: <class '__main__.Point'> object has no attribute m
        # return self.__dict__[item]  # RecursionError: maximum recursion depth exceeded while calling a Python object
        # return object.__getattribute__(self, item)  # 避免无限递归
        return item, '------'


p = Point(1, 2)
print(p.x)
print(p.m)
print(p.n)
print(p.__dict__)
print(Point.__dict__)
print(p.z)
print(Point.z)

结论:

  1. 实例的所有的属性访问,第一个都会调用 __getattribute__ 方法,它阻止了属性的查找,该方法应该返回(计算后的)值或者抛出一个AttributeError异常
  2. 它的return值将作为属性查找的结果。
  3. 如果抛出AttributeError异常(即属性没有找到),则会直接调用 __getattr__ 方法
  4. 对类的属性访问不受影响
  5. 除非明确知道 __getattribute__方法用来做什么,否则不建议使用它
  6. 实例最终查找顺序:__getattribute__() --> instance.__dict__(实例的字典) --> instance.__class__.__dict__(实例的类字典) --> 继承的祖先类(直到
    object)的__dict__ -->找不到则调用__getattr__()

参考

  • magedu