[英]find all usages of certain methods and functions in a python package
Given a package of python that have certain modules, I want to find all the usages of the methods and functions defined in the package, I am thinking in something like pycharms find usages in which given a function or method it shows you all the lines in which this method/function was called. 给定一个具有某些模块的python软件包,我想找到该软件包中定义的方法和函数的所有用法,我在考虑pycharms之类的用法,在给定的函数或方法中它显示了所有内容。调用此方法/函数的方式。
Let stay my package have a lot of modules and I want to look for the the usages of the functions and methods defined in module_x
. 让我的包中有很多模块,我想看看
module_x
定义的函数和方法的用法。 Using inspect
and dir
I can find all the callables defined in module_x
使用
inspect
和dir
我可以找到在module_x
定义的所有可调用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')
Note: I this that methods inside classes will not be captured by this approach, but never mind. 注意:我这样说,类内的方法不会被这种方法捕获,但是没关系。 Lets say that I want to find all the usages of functions defined in
module_x
. 可以说我想查找
module_x
定义的函数的所有用法。
My question is: how can I scan the other modules in the package and see if they are using any of the defs in module_x
, and if they are, return me the line numbers. 我的问题是:如何扫描软件包中的其他模块,查看它们是否正在使用
module_x
任何module_x
,如果是,请向我返回行号。
I tried to use ast
, walking the tree and trying to find all the ast.Call
. 我试图使用
ast
,走到树上,并试图找到所有的ast.Call
。 This actually retruns me all the calls, but I don't know how to check if this returns are defined in module_x
. 实际上,这会重新运行所有调用,但是我不知道如何检查是否在
module_x
中定义了此返回。 Even more, I was thinking in something using regex but for example there could be to functions called test_func
in two different modules. 甚至,我还在考虑使用正则表达式,但是例如可以在两个不同的模块中使用名为
test_func
函数。 Using this approach, how do I know which one I am calling? 使用这种方法,我怎么知道我要呼叫哪一个?
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')
So, to end, my question is: how can I explore a file or a module and find all the usages and the line number of functions and methods defined in module_x
. 因此,最后,我的问题是:如何浏览文件或模块并找到
module_x
定义的所有用法以及函数和方法的行数。 Thank you ;) 谢谢 ;)
You only need to care about names that were actually imported into the module you are currently inspecting. 您只需要关心实际导入到当前正在检查的模块中的名称。 Note that there are few complications here:
请注意,这里的并发症很少:
import foo
in the module bar
makes bar.foo
available from the outside. bar
import foo
使bar.foo
可以从外部使用。 So from bar import foo
is really the same thing as import foo
. from bar import foo
实际上与import foo
相同。 Any object can be stored in a list, a tuple, become an attribute on another object, be stored in a dictionary, assigned to an alternative name, and can be referenced dynamically. 任何对象都可以存储在列表,元组中,成为另一个对象的属性,存储在字典中,分配给备用名称,并且可以动态引用。 Eg an imported attribute stored in a list, referenced by index:
例如,存储在列表中的导入属性,由索引引用:
import foo spam = [foo.bar] spam[0]()
calls the foo.bar
object. 调用
foo.bar
对象。 Tracking some of these uses through AST analysis can be done, but Python is a highly dynamic language and you'll soon run into limitations. 可以通过AST分析来跟踪其中的某些用途,但是Python是一种高度动态的语言,您很快就会遇到限制。 You can't know what
spam[0] = random.choice([foo.bar, foo.baz])
will produce with any certainty, for example. 例如,您不知道
spam[0] = random.choice([foo.bar, foo.baz])
将产生什么。
With the use of the global
and nonlocal
statements, nested function scopes can alter the names in parent scopes. 通过使用
global
和nonlocal
语句,嵌套函数作用域可以更改父作用域中的名称。 So a contrived function like: 因此,人为的功能如下:
def bar(): global foo import foo
would import the module foo
and add it to the global namespace, but only when bar()
is called. 会导入模块
foo
并将其添加到全局名称空间,但仅在调用bar()
时才如此。 Tracking this is difficult, as you need to track when bar()
is actually called. 跟踪此操作很困难,因为您需要跟踪实际调用
bar()
时间。 This could even happen outside of the current module ( import weirdmodule; weirdmodule.bar()
). 这甚至可能发生在当前模块之外(
import weirdmodule; weirdmodule.bar()
)。
If you ignore those complications, and focus only on the use of the names used in import
statements, then you need to track Import
and ImportFrom
nodes, and track scopes (so you know if a local name masks a global, or if an imported name was imported into a local scope). 如果您忽略了这些复杂性,仅关注于
import
语句中使用的名称,则需要跟踪Import
和ImportFrom
节点,并跟踪作用域(这样您就可以知道本地名称是否掩盖了全局名称,或者是否知道了导入名称已导入本地范围)。 You then look for Name(..., Load)
nodes that reference the imported names. 然后,您查找引用导入名称的
Name(..., Load)
节点。
I've covered tracking scopes before, see Getting all the nodes from Python AST that correspond to a particular variable with a given name . 我之前已经介绍了跟踪范围,请参阅从Python AST获取所有与给定名称的特定变量对应的节点 。 For this operation we can simplify this to a stack of dictionaries (encapsulated in a
collections.ChainMap()
instance ), and add imports: 对于此操作,我们可以将其简化为一堆字典(封装在
collections.ChainMap()
实例中 ),并添加导入:
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))
Now, given a module name foo.bar
and the following source code file from a module in the foo
package: 现在,给定模块名称
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)
you can parse the above and extract all foo.bar
references with: 您可以解析以上内容,并使用以下命令提取所有
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
Note that the modalias1
name in the sitamet
scope is not seen as an actual reference to the imported module, as it is being used as a local name instead. 请注意,
sitamet
范围中的modalias1
名称不会被视为对导入模块的实际引用,因为它已被用作本地名称。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.