python中如何动态获得调用栈中类的类名?

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

回到顶部