有时候我们会碰到这样的需求,需要执行对象的某个方法,或是需要对对象的某个字段赋值,而方法名或是字段名在编码代码时并不能确定,需要通过参数传递字符串的形式输入。
这个机制被称为反射(反过来让对象告诉我们他是什么),或是自省,用于实现在运行时获取未知对象的信息。
反射是个很吓唬人的名词,听起来高深莫测,在一般的编程语言里反射相对其他概念来说稍显复杂,一般来说都是作为高级主题来讲;但在Python中反射非常简单,用起来几乎感觉不到与其他的代码有区别,使用反射获取到的函数和方法可以像平常一样加上括号直接调用,获取到类后可以直接构造实例;不过获取到的字段不能直接赋值,因为拿到的其实是另一个指向同一个地方的引用,赋值只能改变当前的这个引用而已。
In [1]:
#coding: UTF-8
import sys # 模块,sys指向这个模块对象
def foo(): pass # 函数,foo指向这个函数对象
class Cat(object): # 类,Cat指向这个类对象
def __init__(self, name='kitty'):
self.name = name
def sayHi(self): # 实例方法,sayHi指向这个方法对象,使用类或实例.sayHi访问
print self.name, 'says Hi!' # 访问名为name的字段,使用实例.name访问
cat = Cat() # cat是Cat类的实例对象
print Cat.sayHi # 使用类名访问实例方法时,方法是未绑定的(unbound)
print cat.sayHi # 使用实例访问实例方法时,方法是绑定的(bound)
In [2]:
cat = Cat('kitty')
print cat.name # 访问实例属性
cat.sayHi() # 调用实例方法
print dir(cat) # 获取实例的属性名,以列表形式返回
if hasattr(cat, 'name'): # 检查实例是否有这个属性
setattr(cat, 'name', 'tiger') # same as: a.name = 'tiger'
print getattr(cat, 'name') # same as: print a.name
getattr(cat, 'sayHi')() # same as: cat.sayHi()
代码块可以由类源代码、函数源代码或是一个简单的语句代码编译得到。 co_argcount: 普通参数的总数,不包括*参数和**参数。 co_names: 所有的参数名(包括*参数和**参数)和局部变量名的元组。 co_varnames: 所有的局部变量名的元组。 co_filename: 源代码所在的文件名。 co_flags: 这是一个数值,每一个二进制位都包含了特定信息。较关注的是0b100(0x4)和0b1000(0x8),如果co_flags & 0b100 != 0,说明使用了*args参数;如果co_flags & 0b1000 != 0,说明使用了**kwargs参数。另外,如果co_flags & 0b100000(0x20) != 0,则说明这是一个生成器函数(generator function)。
In [3]:
co = cat.sayHi.func_code
print co
print co.co_argcount # 1
print co.co_names # ('name',)
print co.co_varnames # ('self',)
print co.co_flags & 0b100 # 0
栈帧表示程序运行时函数调用栈中的某一帧。函数没有属性可以获取它,因为它在函数调用时才会产生,而生成器则是由函数调用返回的,所以有属性指向栈帧。想要获得某个函数相关的栈帧,则必须在调用这个函数且这个函数尚未返回时获取。你可以使用sys模块的_getframe()函数、或inspect模块的currentframe()函数获取当前栈帧。这里列出来的属性全部是只读的。
In [4]:
def add(x, y=1):
f = sys._getframe() # same as inspect.currentframe()
print locals()
print f.f_locals # same as locals()
print f.f_back # <frame object at 0x...>
return x+y
add(2)
Out[4]:
In [5]:
def div(x, y):
try:
return x/y
except:
print sys.exc_info()
tb = sys.exc_info()[2] # return (exc_type, exc_value, traceback)
print tb
print tb.tb_lineno # "return x/y" 的行号
div(1, 0)
currentframe()
会返回位于栈顶的帧,对应当前函数。 getargvalues()
返回一个 tuple
, 包含:
dict
, 名为locals
, 每对 key:value
为 变量名:变量值
。结合它们,可以显示调用栈中不同的函数参数和局部变量。
In [6]:
import inspect
def add(x, y=1, *z):
print inspect.getargvalues(inspect.currentframe())
return x + y + sum(z)
add(2)
Out[6]:
In [7]:
def recurse(limit):
local_variable = '.' * limit
# locals 包含了并非 recurse() 参数的 local_variable: '.'
print(limit, inspect.getargvalues(inspect.currentframe()))
if limit <= 0:
return
recurse(limit - 1)
return local_variable
if __name__ == '__main__':
recurse(2)
In [ ]: