Как получить список всех не импортированных имен в модуле Python?

данный модуль, содержащий :

import stuff
from foo import Foo
from bar import *

CST = True

def func(): pass

как я могу определить функцию get_defined_objects, чтобы я мог сделать:

print(get_defined_objects('path.to.module'))
{'CST': True, 'func', <function path.to.module.func>}

сейчас единственное решение, которое я могу себе представить, чтобы прочитать исходный файл модуля, извлечь определенные имена с re.search(r'^(?:def|class )?(w+)(?:s*=)?' затем импортируйте модуль и найдите пересечение с __dict__.

есть что-то чище ?

5 ответов


здесь что-то для вас, чтобы начать с помощью ast. Обратите внимание, что этот код не охватывает все возможные случаи, хотя он должен обрабатывать, например, множественное назначение должным образом. Рассмотрим расследование astструктуры данных и API более тесно, если вы хотите получить доступ к скомпилированному коду, например.

import ast

with open('module.py') as f:
    data = f.read()
    tree = ast.parse(data)
    elements = [el for el in tree.body if type(el) in (ast.Assign, ast.FunctionDef, ast.ClassDef)]

result = {}

for el in elements:
    if type(el) == ast.Assign:
        for t in el.targets:
            if type(el.value) == ast.Call:
                result[t.id] = el.value.func.id + '()'
            else:
                for attr in ['id', 'i', 's']:
                    try:
                        result[t.id] = getattr(el.value, attr)
                        break
                    except Exception as e:
                        pass
    elif type(el) == ast.FunctionDef:
        result[el.name] = '<function %s>' % el.name
    else:
        result[el.name] = '<class %s>' % el.name

print result
#

mod = "foo"
import ast, inspect
import importlib

mod = importlib.import_module(mod)
p = ast.parse(inspect.getsource(mod))

from collections import defaultdict

data = defaultdict(defaultdict)

for node in p.body:
    if isinstance(node, (ast.ImportFrom, ast.Import)):
        continue
    if isinstance(node, (ast.ClassDef, ast.FunctionDef)):
        data["classes"][node.name] =  mod.__dict__[node.name]
    elif isinstance(node, ast.Assign):
        for trg in node.targets:
            if isinstance(node.value, ast.Num):
                data["assignments"][trg.id] = node.value.n
            elif isinstance(node.value, ast.Str):
                data["assignments"][trg.id] = node.value.s
            else:
                data["assignments"][trg.id] = mod.__dict__[trg.id]

выход:

есть хорошее объяснение здесь это список того, что делают разные типы и их атрибуты, на которых это основано:

class Nodes(ast.NodeVisitor):
    def __init__(self):
        self.data = defaultdict()
        super(Nodes, self).__init__()

    def visit_FunctionDef(self, node):
        self.data[node.name] = mod.__dict__[node.name]
        print("In FunctionDef  with funcion {}".format(node.name))

    def visit_ClassDef(self, node):
        self.data[node.name] = mod.__dict__[node.name]

    def visit_Assign(self, node):
        for trg in node.targets:
            if isinstance(node.value, (ast.Str, ast.Num, ast.Dict, ast.List, ast.ListComp, ast.NameConstant)):
                self.data[trg.id] = mod.__dict__[trg.id]
        self.generic_visit(node)

    def visit_Name(self, node):
        """
        class Name(idctx)
        A variable name. id holds the name as a string
        and ctx is either class Load class Store class Del.
        """
        print("In Name with {}\n".format(node.id))
    #
    def visit_Dict(self, node):
        """
        class Dict(keys, values)
        A dictionary. keys and values
        hold lists of nodes with matching order
        """
        print("In Dict  keys = {}, values = {}\n".format(node.keys,node.values))


    def visit_Set(self,node):
        """
        class Set(elts)
        A set. elts holds a list of
        nodes representing the elements.
        """
        print("In Set  elts = {}\n".format(node.elts))

    def visit_List(self, node):
        """
        class List(eltsctx)
        lts holds a list of nodes representing the elements.
        ctx is Store if the container
        is an assignment target
        (i.e. (x,y)=pt), and Load otherwise.
        """
        print("In List  elts = {}\nctx = {}\n".format(node.elts,node.ctx))

    def visit_Tuple(self, node):
        """
        class Tuple(eltsctx)
        lts holds a list of nodes representing the elements.
        ctx is Store if the container
        is an assignment target
        (i.e. (x,y)=pt), and Load otherwise.
        """
        print("In Tuple  elts = {}\nctx = {}\n".format(node.elts,node.ctx))

    def visit_NameConstant(self, node):
        """
        class NameConstant(value)
        True, False or None. "value" holds one of those constants.
        """
        print("In NameConstant getting value {}\n".format(node.value))


    def visit_Load(self, node):
        print("In Load with node {}\n".format(node.func))


    def visit_Call(self, node):
        """
        class Call(func, args, keywords, starargs, kwargs)
        A function call. func is the function,
        which will often be a Name or Attribute object. Of the arguments:
        args holds a list of the arguments passed by position.
        keywords holds a list of keyword objects representing arguments
        passed by keyword.starargs and kwargs each hold a single node,
        for arguments passed as *args and **kwargs.
        """
        print("In Call with node {}\n".format(node.func))


    def visit_Num(self, node):
        print("In Num getting value {}\n".format(node.n))

    def visit_Str(self, node):
        print("In Str getting value {}\n".format(node.s))
f = Nodes()
f.visit(p)
print(f.data)

байт-код hack для Python 3.4+. Возможно из-за dis.get_instructions.

import dis
import importlib
from itertools import islice
import marshal
import os


def consume_iterator(it, n=1):
    next(islice(it, n, n), None)


def get_defined_names(module_path):
    path, module_name = os.path.split(module_path)
    module_name = module_name[:-3]
    module_object = importlib.import_module(module_name)
    pyc_name = '{}.cpython-34.pyc'.format(module_name)
    pyc_path = os.path.join(path, '__pycache__/', pyc_name)

    with open(pyc_path, 'rb') as f:
        f.read(12)  # drop the first 12 bytes
        code = marshal.load(f)
        # dis.disassemble(code)  # see the byte code
        instructions = dis.get_instructions(code)
        objects = {}

        for instruction in instructions:
            if instruction.opname == 'STORE_NAME':
                objects[instruction.argval] = getattr(module_object,
                                                      instruction.argval)
            elif instruction.opname == 'IMPORT_NAME':
                consume_iterator(instructions, 2)
            elif instruction.opname == 'IMPORT_FROM':
                consume_iterator(instructions, 1)
        return objects


print(get_defined_names('/Users/ashwini/py/so.py'))

для файл типа:

#/Users/ashwini/py/so.py
import os
from sys import argv, modules
from math import *
from itertools import product


CST = True

from itertools import permutations, combinations
from itertools import chain
E = 100
from itertools import starmap

def func(): pass

for x in range(10):
    pass

class C:
    a = 100

d = 1

выход будет:

{'d': 1, 'E': 100, 'CST': True, 'x': 9, 'func': <function func at 0x10efd0510>, 'C': <class 'so.C'>}

гораздо лучше, как кто-то уже упоминал в комментариях, будет анализировать исходный код с помощью АСТ module и узнать имена переменных оттуда.


хотя я принял ответ,не помешает опубликовать решение, которое я использовал. Это смесь между другими предложениями :

import ast
import inspect
import importlib

from types import ModuleType

def extract_definitions(module):
    """ Returns the name and value of objects defined at the top level of the given module.

        :param module: A module object or the name of the module to import.
        :return: A dict {'classes': {}, 'functions': {}, 'assignments': {}} containing defined objects in the module.
    """

    if not isinstance(module, ModuleType):
        module = importlib.import_module(module)

    tree = ast.parse(inspect.getsource(module))

    definitions = {'classes': {}, 'functions': {}, 'assignments': {}}

    for node in tree.body:

        if isinstance(node, ast.ClassDef):
            definitions["classes"][node.name] = getattr(module, node.name)
        elif isinstance(node, ast.FunctionDef):
            definitions["functions"][node.name] = getattr(module, node.name)
        elif isinstance(node, ast.Assign):
            # for unpacking, you need to loop on all names
            for target in node.targets:
                definitions["assignments"][target.id] = getattr(module, target.id)

    return definitions

Я добавил возможность импорта из строки или объекта модуля, затем удалил синтаксический анализ значений и заменил его простым getattr из исходного модуля.


непроверенные

def unexported_names (module):
    try:
        return [name for name in module.__dict__ if name not in module.__all__]
    except AttributeError:
        return [name for name in module.__dict__ if name.startswith('_')]