如何检测 class / 变量是否在 Python 3 中导入?

How do I detect if a class / variable was imported in Python 3?

这是script_one.py的内容:

x = "Hello World"

这是script_two.py的内容:

from script_one import x
print(x)

现在,如果我 运行 script_two.py 输出将是:

>>> Hello World

我需要的是检测 x 是否被导入的方法。
这就是我想象的 script_one.py 的源代码的样子:

x = "Hello World"
if x.has_been_imported:
  print("You've just imported \"x\"!")

然后如果我 运行 script_two.py 输出 "should" 是:

>>> Hello World
>>> You've just imported "x"!

这叫什么,Python3中有这个功能吗?如何使用?

你不能。恐怕花在检测上的努力是浪费时间。

Python 导入包括以下步骤:

  • 通过查看 sys.modules 检查模块是否已加载。
    • 如果模块还没有加载,加载它。这将创建一个新的模块对象,添加到 sys.modules,包含执行顶级代码产生的所有对象。
  • 在导入命名空间中绑定名称。名称的绑定方式取决于所选择的确切 import 变体。
    • import module 将名称 module 绑定到 sys.modules[module] 对象
    • import module as othername 将名称 othername 绑定到 sys.modules[module] 对象
    • from module import attribute 将名称 attribute 绑定到 sys.modules[module].attribute 对象
    • from module import attribute as othername 将名称 othername 绑定到 sys.modules[module].attribute 对象

在这种情况下,重要的是要认识到 Python 名称只是引用;所有 Python 对象(包括模块)都存在于堆中,并随着对它们的引用数量的增加而下降。如果您需要了解其工作原理,请参阅此 great article by Ned Batchelder on Python names

你的问题可以有两种解释:

  • 您想知道模块已经导入。执行模块中的代码时(如 x = "Hello World"),它已被导入。所有的。 Python这里不加载只是x,全有或全无。
  • 您想知道其他代码是否正在使用特定名称。您必须跟踪该对象存在哪些其他引用。这是一项艰巨的任务,涉及递归检查 gc.get_referrers() object chain 以查看其他 Python 对象现在可能引用 x.

在以下任何情况下,后一个目标变得更加困难:

  • import script_one,然后使用script_one.x;此类引用可能存在时间太短,您无法检测到。
  • from script_one import x,然后 del x。除非其他东西仍然在导入的命名空间中引用相同的字符串对象,否则该引用现在已经消失并且无法再被检测到。
  • import sys; sys.modules['script_one'].x 是引用相同字符串对象的合法方式,但这算作导入吗?
  • import script_one,然后 list(vars(script_one).values()) 将创建模块中定义的所有对象的列表,但这些引用是列表中的索引,未命名。这算进口吗?

以前好像是不可能的。但是自从 python 3.7+ 在模块级别引入 __getattr__ 以来,现在看起来是可能的。至少我们可以区分一个变量是from module import varable还是import module; module.variable导入的。

思路是检测上一帧的AST节点,是否是一个Attribute:

script_one.py


def _variables():
  # we have to define the variables 
  # so that it dosen't bypass __getattr__
  return {'x': 'Hello world!'}

def __getattr__(name):
  try:
    out = _variables()[name]
  except KeyError as kerr:
    raise ImportError(kerr)
  
  import ast, sys  
  from executing import Source
  
  frame = sys._getframe(1)
  node = Source.executing(frame).node
  if node is None:
    print('`x` is imported')
  else:
    print('`x` is accessed via `script_one.x`')

  return out

script_two.py

from script_one import x
print(x)
# `x` is imported
# 'Hello world!'

import script_one
print(script_one.x)
# `x` is accessed via `script_one.x`
# 'Hello world!'