sys._getframe()是用来获取当前函数的调用句柄的方法,返回一个FrameType的对象
class FrameType:
f_back: Optional[FrameType]
f_builtins: Dict[str, Any]
f_code: CodeType
f_globals: Dict[str, Any]
f_lasti: int
f_lineno: int
f_locals: Dict[str, Any]
f_trace: Optional[Callable[[FrameType, str, Any], Any]]
if sys.version_info >= (3, 7):
f_trace_lines: bool
f_trace_opcodes: bool
def clear(self) -> None: ...
f_back:链路中的上一个函数对象
f_locals: 函数携带的请求参数
f_code: 当前code信息,包括函数名代码位置等信息
class CodeType:
"""Create a code object. Not for the faint of heart."""
co_argcount: int
if sys.version_info >= (3, 8):
co_posonlyargcount: int
co_kwonlyargcount: int
co_nlocals: int
co_stacksize: int
co_flags: int
co_code: bytes
co_consts: Tuple[Any, ...]
co_names: Tuple[str, ...]
co_varnames: Tuple[str, ...]
co_filename: str
co_name: str
co_firstlineno: int
co_lnotab: bytes
co_freevars: Tuple[str, ...]
co_cellvars: Tuple[str, ...]
利用以上方法可以获取到整个调用链路的函数,一个简单的例子:
def frame(a, b):
return sys._getframe()
if __name__ == '__main__':
f = frame(2,3)
print(f.f_locals)
print(f.f_code)
print(f.f_code.co_name)
output:
{'a': 2, 'b': 3}
<code object frame at 0x7f9865e0cea0, file "hooks.py", line 25>
frame
可以查到frame函数的名字和请求参数,进而扩展到类中
class Hook:
"""
hook 方法,实例化后,获取调用链路上一个函数名及参数信息,call后执行函数
"""
def __init__(self, obj: object()):
_ancestors = obj.__class__.mro()
_f_back = sys._getframe().f_back
self.cls_name = _f_back.f_code.co_name
self.kwargs = _f_back.f_locals
self.kwargs.pop('self')
if len(_ancestors) > 2: # 超过一层继承抛出异常
raise MethodUnimplemented(f'method: {self.cls_name} unimplemented')
def __call__(self, obj: object()):
return getattr(obj, self.cls_name)(**self.kwargs)
class Base:
def add(self, num):
return Hook(self)
class A(Base):
def __init__(self, result):
self.result = result
def add(self, num):
return self.result + num
if __name__ == '__main__':
b = Base()
h = b.add(23)
print(h.cls_name, h.kwargs)
a = A(10)
print(h(a))
output:
add {'num': 23}
33
Base类定义了一个add方法,在A类中对add方法进行了实现,正常情况下使用A类的add方法如a.add(23),add方法是立刻执行的,这里可以使用Base方法先虚构一个add事件,代码中return Hook(self)返回的Hook对象包含了方法名和参数信息,当在后面需要执行add事件的时候只需要h(a)使用Hook携带的call方法即可通过反射执行add方法。
通常的应用场景有:
1.不希望立即执行当前函数
2.批量操作
3.多个继承类对同一个方法参数进行调用invoke