我正在从事一个 Python 库项目,该项目从未在公共 API 和私有 API 之间进行过清晰的划分,现在随着我们的重构,这变得越来越成为一个问题。我可以访问我们所有内部客户的代码,现在我正试图弄清楚他们实际上是如何使用我们的哪些对象的。我想知道我们的用户在使用它们时认为哪些对象、函数、方法和属性是公共 API,哪些位永远不会被访问,因此可以被视为私有实现细节。
我们的库有很好的类型注释,所以我可以使用关于我们项目类型的知识。我想使用类型检查器的原因是类型检查器会知道在
a = C(d=D()); b=a.d.method(a)
中我们有对 C.d
的读取权限和对 D.method()
的调用(以 C
作为参数),那就是我需要知道什么。
因此,我考虑滥用 mypy 并编写一个插件来观察和报告我们的哪些东西是如何调用的。在这个问题中,我只说属性访问,解决我的概念问题。
我可以编写一个插件框架来检查属性钩子中的名称是否来自我们的包,在这种情况下我返回一个回调钩子打印它是什么类型的访问,并且只返回 default_attr_type。到目前为止,很容易。但是我怎样才能跟踪我所在的文件,这样我就不会在我们的包中返回内部属性访问,只返回与我们的公共 API 规范相关的外部访问位置?
from mypy.plugin import AttributeContext, Plugin
from mypy.types import Type
from mypy.nodes import MemberExpr, SuperExpr, node_kinds
class ObserverPlugin(Plugin):
basename = "simu"
module = ""
def get_attribute_hook(self, value):
# HOW do I get the current module name??
if value.startswith(self.basename) and not self.module.startswith(self.basename):
def log_and_do_nothing(ctx: AttributeContext) -> Type:
# Get the type of attribute access – still to be improved, but this is where from
new = ""
if isinstance(ctx.context, SuperExpr):
kind = "via super()"
elif isinstance(ctx.context, MemberExpr):
kind = node_kinds.get(ctx.context.kind) or "read access"
if ctx.context.is_new_def:
new = "new "
else:
kind = type(ctx.context)
# TODO: Better way to aggregate these than just printing, but that's for later
print(f"{value}: {ctx.default_attr_type} ({new}{kind})")
return ctx.default_attr_type
return log_and_do_nothing
def plugin(version: str):
return ObserverPlugin
啊,终于找到了。在回调中,我可以获得 AttributeContext 参数,它带有 api 属性,它是一个 TypeChecker 实例,并且具有一个路径属性,您可以将其与您设置的路径属性相匹配。
def log_and_do_nothing(ctx: AttributeContext) -> Type:
is_internal = ctx.api.path.startswith(self.path)
if is_internal:
return ctx.default_attr_type
# Get the type of attribute access – still to be improved, but this is where from
new = ""
if isinstance(ctx.context, SuperExpr):
kind = "via super()"
elif isinstance(ctx.context, MemberExpr):
kind = node_kinds.get(cast(int, ctx.context.kind)) or "read access"
if ctx.context.is_new_def:
new = "new "
else:
kind = type(ctx.context)