可以通过inspect.currentframe()获取调用frame对象,通过f_locals属性获取self绑定的对象:
import inspect
def get_some_info():
# get the call frame of the calling method
frame = inspect.currentframe().f_back
try:
# try to access the caller's "self"
try:
self_obj = frame.f_locals['self']
except KeyError:
return None
# get the class of the "self" and return its name
return type(self_obj).__name__
finally:
# make sure to clean up the frame at the end to avoid ref cycles
del frame
这样做的缺点是它依赖于第一个参数被命名为“self”。在某些情况下,我们会使用不同的名称,例如在编写元类时:
class MyMeta(type):
def __call__(cls, *args, **kwargs):
get_some_info() # won't work!
如果你有一个带有self 变量的函数,它可能会产生意想不到的结果:
def not_a_method():
self = 3
print(get_some_info()) # output: int
我们可以解决这两个问题,但需要做很多工作。我们可以通过调用代码对象的co_varnames 属性来检查“self”参数的名称。而为了检查调用函数是否真的是类中定义的方法,我们可以遍历self的MRO,尝试找到调用我们的方法。最终的结果就是这个怪物:
def get_some_info():
# get the call frame of the calling method
frame = inspect.currentframe().f_back
try:
# find the name of the first variable in the calling
# function - which is hopefully the "self"
codeobj = frame.f_code
try:
self_name = codeobj.co_varnames[0]
except IndexError:
return None
# try to access the caller's "self"
try:
self_obj = frame.f_locals[self_name]
except KeyError:
return None
# check if the calling function is really a method
self_type = type(self_obj)
func_name = codeobj.co_name
# iterate through all classes in the MRO
for cls in self_type.__mro__:
# see if this class has a method with the name
# we're looking for
try:
method = vars(cls)[func_name]
except KeyError:
continue
# unwrap the method just in case there are any decorators
try:
method = inspect.unwrap(method)
except ValueError:
pass
# see if this is the method that called us
if getattr(method, '__code__', None) is codeobj:
return self_type.__name__
# if we didn't find a matching method, return None
return None
finally:
# make sure to clean up the frame at the end to avoid ref cycles
del frame
这应该可以正确处理几乎所有你扔给它的东西:
class Base:
def my_method(whatever):
print(get_some_info())
@functools.lru_cache() # could be any properly implemented decorator
def my_decorated_method(foo):
print(get_some_info())
@classmethod
def my_class_method(cls):
print(get_some_info())
class A(Base):
pass
def not_a_method(self=3):
print(get_some_info())
A().my_method() # prints "A"
A().my_decorated_method() # prints "A"
A.my_class_method() # prints "None"
not_a_method() # prints "None"
print(get_some_info()) # prints "None"