概述
运行时,区别于编译时,指的是程序被加载到内存中执行的时候
反射,reflection,指的是运行时获取类型定义信息
一个对象能够在运行时,像照镜子一样,反射处其类型信息
简单说,在python中,能够通过一个对象,找出其type、class、attribute或method的能力,称为反射或者自省
具有反射能力的函数有type()、isinstance()、callable()、dir()、getattr()等
反射相关的函数和方法
需求
有一个Point类,查看它实例的属性,并修改它。动态为实例增加属性
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return "Point({}, {})".format(self.x, self.y)
def show(self):
print(self.x, self.y)
p = Point(4, 5)
print(p) # Point(4, 5)
print(p.__dict__) # {'x': 4, 'y': 5}
p.__dict__['y'] = 16 # {'x': 4, 'y': 16}
print(p.__dict__) # {'x': 4, 'y': 16, 'z': 10}
p.z = 10
print(p.__dict__)
print(dir(p))
print(p.__dir__())
上例通过属性字典__dict__
来访问对象的属性,本质上也是利用的反射的能力。
Python提供了内置的函数
内建函数 | 意义 |
---|---|
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 __str__(self):
return "Point({}, {})".format(self.x, self.y)
def show(self):
print(self)
p1 = Point(4, 5)
p2 = Point(10, 10)
print(repr(p1), repr(p2))
print(p1.__dict__) # {'x': 4, 'y': 5}
setattr(p1, 'y', 16)
setattr(p1, 'z', 16)
print(getattr(p1, '__dict__')) # {'x': 4, 'y': 16, 'z': 16}
# 动态调用方法
if hasattr(p1, 'show'):
getattr(p1, 'show')() # Point(4, 16)
# 动态增加方法
# 为类增加方法
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 0x000002018E6E51E0>
print(p1.add) # <bound method <lambda> of <__main__.Point object at 0x000001EC6C2A86A0>>
print(p1.add(p2)) # 绑定 # Point(14, 26)
# 为实例增加方法,未绑定
if not hasattr(p1, 'sub'):
setattr(p1, 'sub', lambda self, other: Point(self.x - other.x, self.y - other.y))
print(p1.sub(p1, p1)) # Point(0, 0)
print(p1.sub) # <function <lambda> at 0x0000018C324E5620>
# add在谁里面, sub在谁里面
print(p1.__dict__) # {'x': 4, 'y': 16, 'z': 16, 'sub': <function <lambda> at 0x00000180472D5620>}
print(Point.__dict__)
# {'__module__': '__main__', '__init__': <function Point.__init__ at 0x00000180472D5158>,\
# '__str__': <function Point.__str__ at 0x00000180472D5268>, 'show': <function Point.show at 0x00000180472D5598>, \
# '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>,\
# '__doc__': None, 'add': <function <lambda> at 0x00000180472D51E0>}
思考
- 这种动态增加属性的方式和装饰器修饰一个类、Mixin方式的差异在哪里
这种动态增删属性的方式是运行时改变类或者实例的方式,但是装饰器或Mixin都是定义时就决定了,因此反射能力具有更大的灵活性
练习
- 命令分发器,通过名称找到对应的函数执行
思路:名称找对象的方法
class Dispatcher:
def __init__(self):
pass
def reg(self, name, fn):
setattr(self, name, fn)
def run(self):
while True:
cmd = input('>>>').strip()
if cmd == 'quit':
break
getattr(self, cmd, lambda :print('Unknowm Cmd {}'.format(cmd)))()
dis = Dispatcher()
dis.reg('ls', lambda :print('ls'))
dis.run()
上例中使用getattr方法找到对象的属性的方式,比自己维护一个字典来建立名词和函数之间的关系的方式好多了
反射相关的魔术方法
__getattr__()
、__setattrr__()
、__delattr__()
__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 "missing {}".format(item)
p1 = Point(4,5)
print(p1.x) # 4
print(p1.z) # 6
print(p1.n) # 0
print(p1.y) # 5
实例属性会按照继承关系找,如果找不到,就会执行__getattr__()
方法,如果没有这个方法,就会抛出AttrbuteError异常表示找不到属性
- 查找属性的顺序:
instance.__dict__
-->instance.__class__.__dict__
-->继承的祖先类(直到object)的__dict
--> 找不到调用__getattr__()
__setattrr__()
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 "missing {}".format(item)
def __setattr__(self, key, value):
print("setattr {}={}".format(key,value))
p1 = Point(4,5)
print(1, p1.x)
print(2, p1.z)
print(3, p1.n)
print(4, p1.t)
p1.x = 50
print(5, p1.x)
print(6, p1.__dict__)
p1.__dict__['x'] = 60
print(7, p1.__dict__)
print(8, p1.x)
执行结果
setattr x=4
setattr y=5
1 missing x
2 6
3 0
4 missing t
setattr x=50
5 missing x
6 {}
7 {'x': 60}
8 60
实例通过.点号设置属性,例如self.x = x
属性赋值,就会调用__setattr_-()
,属性要加到实例的__dict__
中,就需要自己完成
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 "missing {}".format(item)
def __setattr__(self, key, value):
print("setattr {}={}".format(key,value))
self.__dict__[key] = value # 操作字典
__setattr__()
方法,可以拦截对实例属性的增加、修改操作,如果要设置生效,需要自己操作实例的__dict__
例子
__getattr__
和__setattr__
综合使用
class B:
b = 200
class A(B):
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('setattr key ', key)
print('setattr key ', value)
self.d[key] = value
def __delattr__(self, item):
print('can not del {}'.format(item))
a = A(4, 5)
print(a.__dict__)
print(A.__dict__)
print(a.x , a.y)
print(a.a)
# 执行结果
setattr key x
setattr key 4
setattr key y
setattr key 5
{'a': 5}
{'__module__': '__main__', 'z': 100, 'd': {'x': 4, 'y': 5},\
'__init__': <function A.__init__ at 0x000001062E6C5158>,\
'__getattr__': <function A.__getattr__ at 0x000001062E6C5268>, \
'__setattr__': <function A.__setattr__ at 0x000001062E6C5598>, \
'__delattr__': <function A.__delattr__ at 0x000001062E6C5620>, \
'__doc__': None}
------ x
------ y
4 5
5
__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(4, 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__)
执行结果
Can not del x
Can not del z
Can not del Z
{'__module__': '__main__', 'Z': 5, '__init__': <function Point.__init__ at 0x0000029FBF8B5158>, \
'__delattr__': <function Point.__delattr__ at 0x0000029FBF8B5268>, '__dict__': <attribute '__dict__' of 'Point' objects>, \
'__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
{'x': 4, 'y': 5, 'z': 15}
{'__module__': '__main__', '__init__': <function Point.__init__ at 0x0000029FBF8B5158>, \
'__delattr__': <function Point.__delattr__ at 0x0000029FBF8B5268>, '__dict__': <attribute '__dict__' of 'Point' objects>,\
'__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
可以阻止通过实例来删除属性的操作。但是通过类依然可以删除属性
__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 "missing {}".format(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 0x0000016930285158>,\
'__getattr__': <function Point.__getattr__ at 0x0000016930285268>, \
'__getattribute__': <function Point.__getattribute__ at 0x0000016930285598>, '__doc__': None}
print(Point.z) # 6
实例的所有的属性访问,第一个都会调用__getattribute__
方法,它阻止了属性的查找,该方法应该返回(计算后的)值或者抛出一个AttributeError异常
- 它的return值将作为属性查找的结果
- 如果抛出AttributeError异常,则会直接调用
__getattr__
方法,表示属性没有找到
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 "missing {}".format(item)
def __getattribute__(self, item):
# raise AttributeError("Not Found")
# pass
# return self.__dict__[item] # RecursionError
return object.__getattribute__(self, item)
p1 = Point(4, 5)
print(p1.__dict__) # {'x': 4, 'y': 5}
print(p1.x) # 4
print(p1.z) # 6
print(p1.n) # 0
print(p1.t) # missing t
print(Point.__dict__)
# {'__module__': '__main__', 'z': 6, '__init__': <function Point.__init__ at 0x0000029AF7765158>, \
'__getattr__': <function Point.__getattr__ at 0x0000029AF7765268>, \
'__getattribute__': <function Point.__getattribute__ at 0x0000029AF7765598>, '__doc__': None}
print(Point.z) # 6
__getattribute__
方法中为了避免在该方法中无限的递归,它的实现应该永远调用基类的同名方法打以访问需要的任何属性,例如object.__getattribute__(self, item)
- 除非明确地知道
__getattribute__
方法用来做什么,否则不要使用它
总结
魔术方法 | 意义 |
---|---|
__getattr__() | 当通过搜索实例、实例的类及祖先类查不到属性,就会调用此方法 |
__setattr__() | 通过. 点访问实例属性,进行增加、修改都要调用它 |
__delattr__() | 当通过实例来删除属性时调用此方法 |
__getattribute__() | 实例所有的属性调用都从这个方法开始 |
- 属性查找顺序:
实例调用__getattribute__()
-->instance.__dict__
-->instance.__class__.__dict__
-->继承的祖先类(直到object)的__dict__
–>调用__getattr__()