Например, если у меня следующая структура:
base.py
foo = 1
bar = 2
extended.py
from base import *
baz = 3
qux = 4
Я хочу получить только переменные, определенные в extended.py.
Я пробовал использовать dir
и "проверить",
import inspect
import extended
vars = dir(extended)
members = inspect.getmembers(extended)
но это дает vars = ['bar', 'baz', 'foo', 'qux', ...]
и members=[('bar', 2), ('baz', 3), ('foo', 1), ('qux', 4), ...]
Есть ли способ сделать это в python, учитывая эту структуру того, как определяется extended.py?
Я решил дать этому взору из личного интереса, он, вероятно, сработает для того, что вы хотите, я не смотрел, как работают более сложные задания, например. назначения срезов и т. д.:
import ast
with open("extended.py") as f:
code = f.read()
tree = ast.parse(code)
assignments = [
node
for node in ast.walk(tree)
if isinstance(node, ast.Assign)
]
target_variables = set()
for a in assignments:
print("{}: {}".format(a, ast.dump(a)))
for target in a.targets:
target_variables.add(target.id)
print("Found {} assignments to these variable names:".format(len(assignments)))
print(target_variables)
Это также содержит назначения в функции, что, вероятно, не то, что он хочет.
В приведенном конкретном примере упоминаются только модули, выполняющие from x import *
, которые (в отличие от других ответов здесь) этого не отображаются. В вопросе ничего не говорится об определениях функций, поэтому я не делаю никаких предположений о желаемом для них поведении.
Я нашел несколько простой способ сделать это, возможно, не идеально, но он будет работать:
def find_names_of_module(module):
code = compile(open(module.__file__).read(), module.__name__, "exec")
return code.co_names
К сожалению, это также возвращает модули, импортированные с помощью from module import *
или import module
, а также функции. Вы можете отфильтровать его, чтобы в нем не было модулей и функций, если они вам не нужны.
Если вам нужны только имена переменных верхнего уровня, вот настройка ответа Тома Далтона, которая включает только переменные верхнего уровня:
def find_names_of_module(module):
with open(module.__file__) as f:
code = f.read()
tree = ast.parse(code)
assignments = (node for node in tree.body if isinstance(node, ast.Assign))
return {target.id for a in assignments for target in a.targets}
Стоит отметить, что последняя часть не найдет переменные верхнего уровня, которые определены в условном выражении или цикле верхнего уровня, даже if True: foo = 'bar'
.
@TomDalton Так вы говорите, что более вероятно, что у него есть определения переменных внутри операторов if, чем у него есть объявления переменных внутри функций?
Я не говорю этого и не утверждаю, что знаю, какая из этих возможностей (если какая-либо из них) верна.
Если вы можете изменить структуру, я бы пошел с этим решением:
base.py
foo = 1
bar = 2
extension.py
baz = 3
qux = 4
extended.py
from base import *
from extension import *
В противном случае я не уверен, действительно ли этот код работает и должен быть лучший способ, но вы можете использовать разницу между членами модулей:
import inspect
import base
import extended
base_members = inspect.getmembers(base)
ext_members = inspect.getmembers(extended)
ext_only_members = [member for member in ext_members if member not in base_members]
Поскольку содержимое списка не хешируется, вы не можете использовать набор для получения diff.
Если бы вы чувствовали себя действительно смелыми, вы могли бы использовать пакет AST для анализа интересующего вас модуля, а затем вытащить из него назначения переменных ...