反射简介

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

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

可使用反射的地方:

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

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

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

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

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

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

反射用法

getattr

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

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

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

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

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

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

例子2:getattr()使用方法

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

settattr

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

  1. setattr(object, name, value)

例子:settattr()使用方法

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

hasattr

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

  1. hasaattr(object,name)

例子:动态调用方法

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

动态增加方法

  1. >>> if not hasattr(Point, 'add'):
  2. >>> setattr(Point, 'add', lambda self, other: Point(self.x + other.x, self.y + other.y))
  3. >>> print(Point.add)
  4. <function <lambda> at 0x0000014572371558>
  5. >>> print(p1.add)
  6. <bound method <lambda> of <__main__.Point object at 0x0000014572376488>>
  7. >>> print(p1.add(p2))
  8. 14 and 26
  1. >>> if not hasattr(p1, 'sub'):
  2. >>> setattr(p1, 'sub', lambda self, other: Point(self.x - other.x, self.y - other.y))
  3. >>> print(p1.sub(p1, p2))
  4. -6 and 6
  5. >>> print(p1.__dict__)
  6. {'x': 4, 'y': 16, 'z': 10, 'sub': <function <lambda> at 0x0000014572381DC8>}
  7. >>> print(Point.__dict__)
  8. {'__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>}

反射相关的魔术方法

getattr()

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

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

setattr()

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

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

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

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

delattr()

  1. class Point:
  2. z = 5
  3. def __init__(self, x, y):
  4. self.x = x
  5. self.y = y
  6. def __delattr__(self, item):
  7. print(item)
  8. p = Point(14, 5)
  9. >>> p = Point(3, 4)
  10. >>> del p.x
  11. x
  12. >>> p.z=15
  13. >>> del p.z
  14. z
  15. >>> del p.Z
  16. Z
  17. >>> print(Point.__dict__)
  18. {'__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}

getattribute

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

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

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

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

使用场景

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

参考