gpt4 book ai didi

python - python-记录请求的旅程

转载 作者:太空宇宙 更新时间:2023-11-04 04:13:30 25 4
gpt4 key购买 nike

我想记录一个请求在请求末尾访问过一次的所有方法,以进行调试。

我可以先从一个课程开始:

这是我想要的输出示例:


logging full trace once
'__init__': ->
'init_method_1' ->
'init_method_1_1'
'init_method_2'
'main_function': ->
'first_main_function': ->
'condition_method_3'
'condition_method_5'



这是我的部分尝试:

import types

class DecoMeta(type):
def __new__(cls, name, bases, attrs):

for attr_name, attr_value in attrs.items():
if isinstance(attr_value, types.FunctionType):
attrs[attr_name] = cls.deco(attr_value)

return super(DecoMeta, cls).__new__(cls, name, bases, attrs)

@classmethod
def deco(cls, func):
def wrapper(*args, **kwargs):

name = func.__name__
stacktrace_full.setdefault(name, [])
sorted_functions = stacktrace_full[name]
if len(sorted_functions) > 0:
stacktrace_full[name].append(name)
result = func(*args, **kwargs)
print("after",func.__name__)
return result
return wrapper

class MyKlass(metaclass=DecoMeta):

最佳答案

方法

我认为有两种不同的方法值得考虑这个问题:


“简单”日志记录元类,或者
Beefier元类可存储调用堆栈


如果您只需要在进行方法调用时就将它们打印出来,并且不关心保存方法调用堆栈的实际记录,那么第一种方法应该可以解决问题。

我不确定您要寻找哪种方法(如果您有什么特定的想法),但是如果您知道需要存储方法调用堆栈,除了打印调用之外,您可能还想跳到第二种方法方法。

注意:此后所有代码均假定存在以下导入:

from types import FunctionType


1.简单的记录元类

这种方法要容易得多,而且在您初次尝试时不需要太多额外的工作(取决于我们要考虑的特殊情况)。但是,正如已经提到的,此元类仅与日志记录有关。如果您确实需要保存方法调用堆栈结构,请考虑跳到第二种方法。

DecoMeta.__new__的更改

使用这种方法,您的 DecoMeta.__new__方法基本上保持不变。以下代码中最显着的更改是在 namespace中添加了“ _in_progress_calls”列表。 DecoMeta.decowrapper函数将使用此属性来跟踪已调用但未结束的方法数量。利用该信息,它可以适当缩进打印的方法名称。

还要注意,我们要通过 staticmethod装饰的 namespace属性中包含 DecoMeta.deco。但是,您可能不需要此功能。另一方面,您可能还想考虑进一步考虑 classmethod和其他因素。

您会注意到的另一项更改是创建了 cls变量,该变量将在返回之前直接进行修改。但是,您现有的遍历命名空间的循环,以及随后创建和返回类对象的操作,仍然可以解决问题。

DecoMeta.deco的更改


我们将 in_progress_calls设置为当前实例的 _in_progress_calls,以便稍后在 wrapper中使用
接下来,我们对您第一次尝试处理 staticmethod的方法进行了一些小的修改-如前所述,您可能想要或不想要的东西
在“日志”部分,我们需要为以下行计算 pad,在此行中打印被调用方法的 name。打印后,我们将当前方法 name添加到 in_progress_calls,通知其他方法进行中的方法
在“调用方法”部分,我们(可选)再次处理 staticmethod

除了这一小的更改,我们还通过在 self调用中添加 func参数来进行了一个很小但重要的更改。没有这个,使用 DecoMeta的类的常规方法将开始抱怨没有给出位置 self参数,这很重要,因为 func.__call__method-wrapper且需要实例我们的方法是绑定的。
第一次尝试的最后更改是删除最后一个 in_progress_calls值,因为我们已经正式调用了该方法并返回了 result


闭嘴,告诉我代码

class DecoMeta(type):
def __new__(mcs, name, bases, namespace):
namespace["_in_progress_calls"] = []
cls = super().__new__(mcs, name, bases, namespace)

for attr_name, attr_value in namespace.items():
if isinstance(attr_value, (FunctionType, staticmethod)):
setattr(cls, attr_name, mcs.deco(attr_value))
return cls

@classmethod
def deco(mcs, func):
def wrapper(self, *args, **kwargs):
in_progress_calls = getattr(self, "_in_progress_calls")

try:
name = func.__name__
except AttributeError: # Resolve `staticmethod` names
name = func.__func__.__name__

#################### Log ####################
pad = " " * (len(in_progress_calls) * 3)
print(f"{pad}`{name}`")
in_progress_calls.append(name)

#################### Invoke Method ####################
try:
result = func(self, *args, **kwargs)
except TypeError: # Properly invoke `staticmethod`-typed `func`
result = func.__func__(*args, **kwargs)

in_progress_calls.pop(-1)
return result
return wrapper


它有什么作用?

这是一些虚拟类的代码,我在您期望的示例输出后尝试进行建模:

设定

不要过多地关注此块。这只是一个愚蠢的类,其方法调用其他方法

class MyKlass(metaclass=DecoMeta):
def __init__(self):
self.i_1()
self.i_2()

#################### Init Methods ####################
def i_1(self):
self.i_1_1()

def i_1_1(self): ...
def i_2(self): ...

#################### Main Methods ####################
def main(self, x):
self.m_1(x)

def m_1(self, x):
if x == 0:
self.c_1()
self.c_2()
self.c_4()
elif x == 1:
self.c_3()
self.c_5()

#################### Condition Methods ####################
def c_1(self): ...
def c_2(self): ...
def c_3(self): ...
def c_4(self): ...
def c_5(self): ...




my_k = MyKlass()
my_k.main(1)
my_k.main(0)


控制台输出

`__init__`
`i_1`
`i_1_1`
`i_2`
`main`
`m_1`
`c_3`
`c_5`
`main`
`m_1`
`c_1`
`c_2`
`c_4`


2. Beefy元类存储调用堆栈

因为我不确定您是否真正想要这个,而且您的问题似乎更着重于问题的元类部分,而不是调用堆栈存储结构,所以我将集中于如何增强上述元类以处理所需的操作。然后,我将简单介绍一下存储调用堆栈并使用简单的占位符结构“存根”代码的那些部分的多种方式。

我们需要的显而易见的事情是一个持久的调用堆栈结构,以扩展临时 _in_progress_calls属性的范围。因此,我们可以在 DecoMeta.__new__的顶部添加以下未注释的行:

namespace["full_stack"] = dict()
# namespace["_in_progress_calls"] = []
# cls = super().__new__(mcs, name, bases, namespace)
# ...


不幸的是,显而易见性到此为止,如果您想跟踪非常简单的方法调用堆栈之外的任何内容,事情会很快变得棘手。

关于我们如何保存调用堆栈,有一些事情可能会限制我们的选择:


我们不能使用以方法名称作为键的简单dict,因为在生成的任意复杂的调用堆栈中,方法X很有可能多次调用方法Y。
我们不能假设对方法X的每次调用都会调用相同的方法,正如您的“有条件”方法示例所表明的那样。这意味着我们不能说对X的任何调用都会产生调用堆栈Y,并将该信息整齐地保存在某个地方
我们需要限制新的 full_stack属性的持久性,因为我们在 DecoMeta.__new__中基于类进行了声明。如果我们不这样做,那么 MyKlass的所有实例将共享同一个 full_stack,从而迅速破坏了其用途。


因为前两个高度依赖于您的偏好/要求,并且因为我认为您的问题更关注问题的元类方面,而不是调用堆栈的结构,所以我将从解决第三点开始。

为了确保每个实例都有自己的 full_stack,我们可以添加一个新的 DecoMeta.__call__方法,只要我们创建 MyKlass的实例(或使用 DecoMeta作为元类的任何实例),就会调用该方法。只需将以下内容放入 DecoMeta

def __call__(cls, *args, **kwargs):
setattr(cls, "full_stack", dict())
return super().__call__(*args, **kwargs)


最后一步是弄清楚如何构造 full_stack并添加代码以将其更新为 DecoMeta.deco.wrapper函数。

深入嵌套的字符串列表,命名顺序调用的方法以及这些方法调用的方法,依此类推...应该可以完成工作并回避上面提到的前两个问题,但这听起来很杂乱,所以我会让您决定是否实际需要它。

例如,我们可以使用键 full_stack和值 Tuple[str]来使 List[str]为字典。请注意,在上述两个问题条件下,此操作都会自动失败;但是,它的确说明了如果您决定更进一步的话, DecoMeta.deco.wrapper必需进行的更新。

仅需要添加两行:

首先,在 DecoMeta.deco.wrapper签名的正下方,添加以下未注释的行:

full_stack = getattr(self, "full_stack")
# in_progress_calls = getattr(self, "_in_progress_calls")
# ...


其次,在 print调用之后的“日志”部分中,添加以下未注释的行:

# print(f"{pad}`{name}`")
full_stack.setdefault(tuple(in_progress_calls), []).append(name)
# in_progress_calls.append(name)
# ...


TL; DR

如果我将您的问题解释为要求确实只记录日志方法调用的元类,那么第一种方法(上面在“简单日志记录元类”标题下概述)应该很好用。但是,如果您还需要保存所有方法调用的完整记录,则可以按照“存储调用堆栈的Beefy元类”标题下的建议开始。

如果您还有其他问题或疑问,请告诉我。我希望这是有用的!

关于python - python-记录请求的旅程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55902280/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com