如何检查文件是否从 Python 中的另一个文件导入

Yag*_*nci 8 python import

假设我有这样的项目结构

src
??? app
    ??? main.py
    ??? db
    ?   ??? database.py
    ??? models
    ?   ??? model_a.py
    ?   ??? model_b.py
    ??? tests
        ??? test_x.py
        ??? test_y.py
Run Code Online (Sandbox Code Playgroud)

我想检查哪个文件使用了另一个文件中的函数。我在 main.py 中有一个名为 Test 的类

class Test:
    pass
Run Code Online (Sandbox Code Playgroud)

我在model_a

from ..main import Test
Run Code Online (Sandbox Code Playgroud)

但在model_b我用

from ..main import Test
from ..db.database import Data
Run Code Online (Sandbox Code Playgroud)

我想检查哪个文件使用另一个文件,就像tree命令一样,只需要一个文件夹名称就足够了,所以我尝试了一种旧方法,但它效率低下,很脏,这不是我所期望的。方法是我在srcnamed 中创建了一个文件check.py,我导入了所有包

from app.db import database
from app.models import model_a, model_b
from app.tests import test_x, test_y
from app import main 

print('__file__={0:<35} | __name__={1:<20} | __package__={2:<20}'.format(__file__,__name__,str(__package__)))
Run Code Online (Sandbox Code Playgroud)

我在所有文件的底部添加了这一行

print('__file__={0:<35} | __name__={1:<20} | __package__={2:<20}'.format(__file__,__name__,str(__package__)))
Run Code Online (Sandbox Code Playgroud)

所以当我跑步时,check.py我得到了这个结果

__file__=/home/yagiz/Desktop/struct/src/app/main.py | __name__=app.main             | __package__=app                 
__file__=/home/yagiz/Desktop/struct/src/app/db/database.py | __name__=app.db.database      | __package__=app.db              
__file__=/home/yagiz/Desktop/struct/src/app/models/model_a.py | __name__=app.models.model_a   | __package__=app.models          
__file__=/home/yagiz/Desktop/struct/src/app/models/model_b.py | __name__=app.models.model_b   | __package__=app.models          
__file__=/home/yagiz/Desktop/struct/src/app/tests/test_x.py | __name__=app.tests.test_x     | __package__=app.tests           
__file__=/home/yagiz/Desktop/struct/src/app/tests/test_y.py | __name__=app.tests.test_y     | __package__=app.tests           
__file__=/home/yagiz/Desktop/struct/src/check.py | __name__=__main__             | __package__=None   
Run Code Online (Sandbox Code Playgroud)

结果很脏,不符合我的期望有没有办法获得这样的输出?

main.py = app/models/model_a, app/models/model_b   # These files imports something from main.py
models_b = None                                    # No file imports from models_b
Run Code Online (Sandbox Code Playgroud)

更新,我尝试了@Hessam Korki 的建议它不起作用

我抬起头的源代码modulefinder,我发现它增加了在每个import语句是不是对我有用的一个badmodule。

这是怎么回事,首先我创建了一个函数,我还创建了另一个项目结构。

src
??? empty.py
??? __init__.py
??? main.py
??? module_finder.py
??? other
?   ??? other.py
??? test
?   ??? some_app.py
??? this_imports.py
Run Code Online (Sandbox Code Playgroud)

这是module_finder.py包含我的功能的

from modulefinder import ModuleFinder
file_names = ["this_imports.py", "main.py", "test/some_app.py", "other/other.py", "empty.py"]

def check_imports(file_names):
    finder = ModuleFinder()
    for file in file_names:
        finder.run_script(file)
        print("\n", file)
        for name, mod in finder.modules.items():
            print('%s: ' % name, end='')
            print(','.join(list(mod.globalnames.keys())[:3]))

            print('\n'.join(finder.badmodules.keys()))
Run Code Online (Sandbox Code Playgroud)

空文件是空的(如预期的那样),在main.py我有

class Test:
    pass
Run Code Online (Sandbox Code Playgroud)

this_imports.py我只有

from src.main import Test
Run Code Online (Sandbox Code Playgroud)

other/other.py我有

from src.main import Test 
from src.test import DifferentTest
Run Code Online (Sandbox Code Playgroud)

而对于最后一个test/some_app.py我有

from src.main import Test

class DifferentTest:
    pass
Run Code Online (Sandbox Code Playgroud)

所以结果应该是:

empty.py = None
main.py = None
other/other.py = src.main , src.test
test/some_app.py = src.main
this_imports.py = src.main 
Run Code Online (Sandbox Code Playgroud)

但是该函数给出了错误的结果,这是输出:

 Filename:  this_imports.py
__main__: Test
src.main

 Filename:  main.py
__main__: Test,__module__,__qualname__
src.main

 Filename:  test/some_app.py
__main__: Test,__module__,__qualname__
src.main

 Filename:  other/other.py
__main__: Test,__module__,__qualname__
src.main
src.test

 Filename:  empty.py
__main__: Test,__module__,__qualname__
src.main
src.test
Run Code Online (Sandbox Code Playgroud)

Pro*_*osh 3

您正在寻找的是在包模块中找到导入依赖项。您可以对包目录运行静态分析并解析语法树 ( ) 中的导入节点ast,并构建依赖关系图。像下面这样:

import os
from ast import NodeVisitor, parse
import networkx as nx

class Dependency():
    def __init__(self, root):
        self.root = root
        self.base = os.path.basename(root)
        self.dependency = nx.DiGraph()
        self.visitor = NodeVisitor()
        self.visitor.visit_ImportFrom = self.visit_ImportFrom
        
        self.current_node = None
        self.dependency.add_node = self.base
        
    def visit_ImportFrom(self, node):
        self.dependency.add_edge(node.module, self.current_node)
        self.visitor.generic_visit(node)
        
    def run(self):
        for root, dirs, files in os.walk(self.root):
            for file in files:
                full_path = os.path.join(root+os.sep, file)
                loc = full_path.split(self.root+os.sep)[1].replace(os.sep,'.')
                self.current_node = self.base+'.'+loc
                with open(full_path) as fp:
                    src = fp.read()
                    tree = parse(src)
                    self.visitor.generic_visit(tree)
                    
        dependency = {}
        for src, target in nx.dfs_edges(self.dependency):
            if src in dependency:
                dependency[src].add(target)
            else:
                dependency[src] = set([target])
                
        return dependency
Run Code Online (Sandbox Code Playgroud)

对于要映射导入依赖项的任何包的根位置,您需要执行以下操作:

root = "path/to/your/src"
d = Dependency(root)
d.run()
Run Code Online (Sandbox Code Playgroud)

这将返回依赖树(作为字典)。注意,我们只解析了ImportFrom,您需要添加Import使其完整。另外,这里所有的导入都被假定为绝对的(即没有..等)。如果需要,您也可以添加它(检查节点level的字段ImportFrom来执行此操作)。