python中如何动态获得调用栈中类的类名?
我们都知道,python有一定的反射能力,比如动态获得函数名:
def my_caller(): my_callee()
def my_callee():
import inspect
frame = inspect.stack()[1].frame
print('the caller is \'{}\'.'.format(frame.f_code.co_name))
my_caller()
以上代码可以打印出:
the caller is 'my_caller'.
但是当caller是一个类时:
class Caller(object): def run1(self):
my_callee()
@classmethod
def run2(clazz):
my_callee()
@staticmethod
def run3():
my_callee()
caller = Caller()
caller.run1()
caller.run2()
Caller.run3()
以上代码可以打印出方法名,却打印不出类名:
the caller is 'run1'.the caller is 'run2'.
the caller is 'run3'.
那么,有没有办法,动态地获得调用栈中类的类名呢?
回答:
我自己倒是找到了一个不很干净的方法,而且对于staticmethod就无能为力了:
def my_callee(): import inspect
frame = inspect.stack()[1].frame
method_name = frame.f_code.co_name
c = frame.f_code
clazz_name = None
if c.co_argcount > 0:
first_arg = frame.f_locals[c.co_varnames[0]]
if hasattr(first_arg, method_name) and getattr(first_arg, method_name).__code__ is c:
if inspect.isclass(first_arg):
clazz_name = first_arg.__qualname__
else:
clazz_name = first_arg.__class__.__qualname__
if clazz_name is None:
print('the caller is \'{}\'.'.format(method_name))
else:
print('the caller is \'{}.{}\'.'.format(clazz_name, method_name))
del frame
class Caller(object):
def run1(self):
print('\nCaller.run1()')
my_callee()
@classmethod
def run2(clazz):
print('\nCaller.run2()')
my_callee()
@staticmethod
def run3():
print('\nCaller.run3()')
my_callee()
def run4():
print('\nrun4()')
my_callee()
def run():
c = Caller()
c.run1()
c.run2()
c.run3()
run4()
run()
以上代码将输出:
Caller.run1()
the caller is 'Caller.run1'.
Caller.run2()
the caller is 'Caller.run2'.
Caller.run3()
the caller is 'run3'.
run4()
the caller is 'run4'.
可以看到,这种方法是无法区分普通函数与staticmethod的。
自己事后又分析了一下,感觉python其实并不是纯粹的面向对象语言,到了执行层面,都转化成函数调用了,也就是code对象,而解释器有没有把这个code与其对应的函数对象关联起来(其实是有关联的,只不过是单向的,每个function和method都有一个__code__属性,就是code对象),所以导致无法从code反向逆推出是来自哪个function或method。
所以如果要想反向定位,也就只能自己来维护 code -> function/method 的关系了。其实实现起来也简单,只要把所有的类都扫描一遍就可以了(性能会有点低,真有需要可以把映射关系缓存下来,不用每次都全类扫描),例如:
def my_callee(): import inspect
# 取得frame和code
frame = inspect.stack()[1].frame
code = frame.f_code
# 取得缓存
if not hasattr(my_callee, 'code_cache')
cache = {}
setattr(my_callee, 'code_cache', cache)
else:
cache = getattr(my_callee, 'code_cache')
# 检查缓存是否命中
code_id = id(code)
if code_id in cache:
print('the caller is \'{}\'.'.format(cache[code_id]))
# 通过第一个参数self(实例方法)或cls(类方法)进行判定,速度最快
method_name = frame.f_code.co_name
clazz_name = None
if code.co_argcount > 0:
first_arg = frame.f_locals[code.co_varnames[0]]
if hasattr(first_arg, method_name) and getattr(first_arg, method_name).__code__ is code:
if inspect.isclass(first_arg):
clazz_name = first_arg.__qualname__
else:
clazz_name = first_arg.__class__.__qualname__
qualname = method_name if clazz_name is None else '{}.{}'.format(clazz_name, method_name)
print('the caller is \'{}\'.'.format(qualname))
cache[code_id] = qualname
return
# 扫描f_globals中的所有类的方法和所有的函数,并在缓存中记录下来它们的id和名称
for k,v in frame.f_globals.items():
if inspect.isclass(v):
for kk in v.__dict__:
vv = getattr(v, kk)
if inspect.isfunction(vv):
cache[id(vv.__code__)] = vv.__qualname__
elif inspect.isfunction(v):
cache[id(v.__code__)] = v.__qualname__
# 把所有的类和函数都扫描过一遍后,再回过头来定位code
if code_id in cache:
print('the caller is \'{}\'.'.format(cache[code_id]))
else:
print('impossible: {}'.format(code_id))
# 释放
del frame
回答:
self.__class__
回答:
def my_callee(): import inspect
frame = inspect.stack()
print(frame)
print(frame[1].code_context)
print(frame[2].code_context)
以上是 python中如何动态获得调用栈中类的类名? 的全部内容, 来源链接: utcz.com/p/937660.html