gpt4 book ai didi

python-3.x - 在python包中查找某些方法和函数的所有用法

转载 作者:行者123 更新时间:2023-12-05 02:12:30 25 4
gpt4 key购买 nike

给定一个包含某些模块的 python 包,我想找到包中定义的方法和函数的所有用法。我在想类似 pycharms 的东西 find usages ,其中给定一个函数或方法,它会向您显示调用此方法/函数的所有行。

假设我的包有很多模块,我想查找 module_x 中定义的函数和方法的用法。使用 inspectdir 我可以找到 module_x

中定义的所有可调用对象
import inspect

callables = [method_name for method_name in dir(module)
if callable(getattr(module, method_name))]

module_inspected = inspect.getmodule(module)
module_file = module_inspected.__file__

module_x_callables = []

for name, member in inspect.getmembers(module):
# to see if the definitions are defined/imported in the member_file that we are looking
if name in callables:
module_x_callables.append(member)
member_file = inspect.getmodule(member).__file__
# print('{}: {},{}'.format(name, member, callable(member)))
print('{}'.format(name))
print('{}'.format(member))
# print('parent: {}'.format(inspect.getmodule(member)))
print('member_file: {}'.format(member_file))
if member_file == module_file:
source, line_no = inspect.findsource(member)
print(line_no)
print('\n')

注意:这种方法不会捕获类内部的方法,但没关系。假设我想找到 module_x 中定义的函数的所有用法。

我的问题是:如何扫描包中的其他模块并查看它们是否正在使用 module_x 中的任何 def,如果是,请返回行号。

我尝试使用 ast,遍历树并尝试找到所有 ast.Call。这实际上返回了所有调用,但我不知道如何检查这是否返回了 module_x 中定义的所有结果。更重要的是,我正在考虑使用正则表达式,但例如在两个不同的模块中可能有两个名为 test_func 的函数。使用这种方法,我如何知道我调用的是哪一个?

string_code = open(file,'r').read()
tree = ast.parse(string_code)
for node in ast.walk(tree):
#print(node)
if isinstance(node, ast.Call):
print('call')
print(ast.dump(node))
print(inspect.getmodule(node))
print(func.value)
print(func.attr)
print('\n')

因此,总而言之,我的问题是:如何探索文件或模块并找到 module_x 中定义的函数和方法的所有用法和行号。谢谢;)

最佳答案

您只需要关心实际导入到您当前正在检查的模块中的名称。请注意,这里很少有并发症:

  • 导入的名称可以从其他模块获得,以便从当前模块导入;模块 bar 中的 import foo 使 bar.foo 可从外部使用。所以 from bar import foo 实际上与 import foo 是一回事。
  • 任何对象都可以存储在列表、元组中,成为另一个对象的属性,存储在字典中,分配给替代名称,并且可以动态引用。例如。存储在列表中的导入属性,由索引引用:

    import foo
    spam = [foo.bar]
    spam[0]()

    调用 foo.bar 对象。可以通过 AST 分析跟踪其中一些用途,但 Python 是一种高度动态的语言,您很快就会遇到限制。例如,您无法确定 spam[0] = random.choice([foo.bar, foo.baz]) 会产生什么。

  • 通过使用 globalnonlocal 语句,嵌套函数作用域可以改变父作用域中的名称。所以一个人为的功能,如:

    def bar():
    global foo
    import foo

    将导入模块 foo 并将其添加到全局命名空间,但仅在调用 bar() 时。跟踪这一点很困难,因为您需要跟踪 bar() 实际被调用的时间。这甚至可能发生在当前模块之外 (import weirdmodule; weirdmodule.bar())。

如果您忽略这些复杂性,只关注 import 语句中使用的名称,那么您需要跟踪 ImportImportFrom 节点和跟踪范围(因此您知道本地名称是否掩盖了全局名称,或者是否将导入的名称导入了本地范围)。然后查找引用导入名称的 Name(..., Load) 节点。

我之前介绍过跟踪范围,请参阅 Getting all the nodes from Python AST that correspond to a particular variable with a given name .对于此操作,我们可以将其简化为一堆字典(封装在 collections.ChainMap() instance 中),并添加导入:

import ast
from collections import ChainMap
from types import MappingProxyType as readonlydict


class ModuleUseCollector(ast.NodeVisitor):
def __init__(self, modulename, package=''):
self.modulename = modulename
# used to resolve from ... import ... references
self.package = package
self.modulepackage, _, self.modulestem = modulename.rpartition('.')
# track scope namespaces, with a mapping of imported names (bound name to original)
# If a name references None it is used for a different purpose in that scope
# and so masks a name in the global namespace.
self.scopes = ChainMap()
self.used_at = [] # list of (name, alias, line) entries

def visit_FunctionDef(self, node):
self.scopes = self.scopes.new_child()
self.generic_visit(node)
self.scopes = self.scopes.parents

def visit_Lambda(self, node):
# lambdas are just functions, albeit with no statements
self.visit_Function(node)

def visit_ClassDef(self, node):
# class scope is a special local scope that is re-purposed to form
# the class attributes. By using a read-only dict proxy here this code
# we can expect an exception when a class body contains an import
# statement or uses names that'd mask an imported name.
self.scopes = self.scopes.new_child(readonlydict({}))
self.generic_visit(node)
self.scopes = self.scopes.parents

def visit_Import(self, node):
self.scopes.update({
a.asname or a.name: a.name
for a in node.names
if a.name == self.modulename
})

def visit_ImportFrom(self, node):
# resolve relative imports; from . import <name>, from ..<name> import <name>
source = node.module # can be None
if node.level:
package = self.package
if node.level > 1:
# go up levels as needed
package = '.'.join(self.package.split('.')[:-(node.level - 1)])
source = f'{package}.{source}' if source else package
if self.modulename == source:
# names imported from our target module
self.scopes.update({
a.asname or a.name: f'{self.modulename}.{a.name}'
for a in node.names
})
elif self.modulepackage and self.modulepackage == source:
# from package import module import, where package.module is what we want
self.scopes.update({
a.asname or a.name: self.modulename
for a in node.names
if a.name == self.modulestem
})

def visit_Name(self, node):
if not isinstance(node.ctx, ast.Load):
# store or del operation, means the name is masked in the current scope
try:
self.scopes[node.id] = None
except TypeError:
# class scope, which we made read-only. These names can't mask
# anything so just ignore these.
pass
return
# find scope this name was defined in, starting at the current scope
imported_name = self.scopes.get(node.id)
if imported_name is None:
return
self.used_at.append((imported_name, node.id, node.lineno))

现在,给定一个模块名称 foo.bar 和来自 foo 包中模块的以下源代码文件:

from .bar import name1 as namealias1
from foo import bar as modalias1

def loremipsum(dolor):
return namealias1(dolor)

def sitamet():
from foo.bar import consectetur

modalias1 = 'something else'
consectetur(modalias1)

class Adipiscing:
def elit_nam(self):
return modalias1.name2(self)

您可以解析上述内容并提取所有 foo.bar 引用:

>>> collector = ModuleUseCollector('foo.bar', 'foo')
>>> collector.visit(ast.parse(source))
>>> for name, alias, line in collector.used_at:
... print(f'{name} ({alias}) used on line {line}')
...
foo.bar.name1 (namealias1) used on line 5
foo.bar.consectetur (consectetur) used on line 11
foo.bar (modalias1) used on line 15

请注意,sitamet 范围内的 modalias1 名称不被视为对导入模块的实际引用,因为它被用作本地名称。

关于python-3.x - 在python包中查找某些方法和函数的所有用法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55831844/

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