Как проверить, импортируется ли файл из другого файла в Python - PullRequest
9 голосов
/ 04 августа 2020

Предположим, у меня есть такая структура проекта

src
└── app
    ├── main.py
    ├── db
    │   └── database.py
    ├── models
    │   ├── model_a.py
    │   └── model_b.py
    └── tests
        ├── test_x.py
        └── test_y.py

Я хочу проверить, какой файл использует класс или функцию из другого файла. У меня есть класс под названием Test в main.py

class Test:
    pass

Я использовал этот класс в model_a,

from ..main import Test

Но в model_b я использовал

from ..main import Test
from ..db.database import Data

Я хочу проверить, какой файл использует другой файл, как и команда tree, достаточно просто имени папки, поэтому я попробовал старый метод, но он был неэффективным, грязным, и я не ожидал этого. Метод заключался в том, что я создал файл в src с именем 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__)))

И я добавил эту строку в конец всех файлов

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

Итак когда я запускаю 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   

Результат грязный и не соответствует моим ожиданиям. Есть ли способ получить такой результат?

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

Обновление, я попробовал предложение @Hessam Korki это не работает .

Я просмотрел исходный код modulefinder и нашел его добавляет badmodule в каждый оператор импорта, который мне не нужен.

Вот как это произошло go, сначала я создал функцию, а также создал другую структуру проекта.

src
├── empty.py
├── __init__.py
├── main.py
├── module_finder.py
├── other
│   └── other.py
├── test
│   └── some_app.py
└── this_imports.py

Вот 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()))

Пустой файл пустой (как и ожидалось), в main.py у меня есть

class Test:
    pass

В this_imports.py у меня только

from src.main import Test

В other/other.py у меня

from src.main import Test 
from src.test import DifferentTest

И для последнего в test/some_app.py у меня

from src.main import Test

class DifferentTest:
    pass

Так что ж Это должно быть:

empty.py = None
main.py = None
other/other.py = src.main , src.test
test/some_app.py = src.main
this_imports.py = src.main 

Но функция дает неправильный результат, вот результат:

 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

Ответы [ 2 ]

4 голосов
/ 08 августа 2020

Я считаю, что python Modulefinder эффективно решит вашу проблему. В Modulefinder (). Items () есть ключ с именем '__main__', который содержит модули, которые были импортированы в файл python. После запуска сценария в вашем проекте и сохранения данных в соответствии с вашими целями, вы должны быть готовы к go

2 голосов
/ 14 августа 2020

То, что вы ищете, - это найти зависимости импорта в модулях вашего пакета. Вы можете запустить статический c анализ в каталоге вашего пакета и проанализировать узлы импорта в синтаксических деревьях (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

Для root местоположения любого пакета, который вы хотите сопоставить зависимости импорта, вам необходимо сделать следующее:

root = "path/to/your/src"
d = Dependency(root)
d.run()

Это вернет дерево зависимостей (как диктант). Обратите внимание, мы проанализировали только ImportFrom, вам нужно добавить Import, чтобы завершить его. Кроме того, здесь предполагается, что весь импорт является абсолютным (т.е. нет .. и c). При необходимости вы также можете добавить это (проверьте поле level узла ImportFrom, чтобы сделать это).

...