【问题标题】:How can I find out which module a name is imported from?如何找出从哪个模块导入名称?
【发布时间】:2017-12-04 17:18:35
【问题描述】:

在Python程序中,如果名称存在于程序的命名空间中,是否有可能找出该名称是否是从某个模块导入的,如果是,它是从哪个模块导入的?

【问题讨论】:

    标签: python python-3.x debugging module namespaces


    【解决方案1】:

    您可以通过__module__ 属性查看函数在哪个模块中定义。 From the Python Data model documentation on __module__:

    定义函数的模块的名称,如果不可用,则为无。

    例子:

    >>> from re import compile
    >>> compile.__module__
    're'
    >>> def foo():
    ...     pass
    ... 
    >>> foo.__module__
    '__main__'
    >>>
    

    The Data model later mentions 类也具有相同的属性:

    __module__ 是定义类的模块名称。

    >>> from datetime import datetime
    >>> datetime.__module__
    'datetime'
    >>> class Foo:
    ...     pass
    ... 
    >>> Foo.__module__
    '__main__'
    >>> 
    

    您也可以使用诸如intlist 之类的内置名称来执行此操作。您可以从builtins 模块访问它们。

    >>> int.__module__
    'builtins'
    >>> list.__module__
    'builtins'
    >>> 
    

    我可以在没有from builtins import int, list 的情况下使用intlist。那么intlist 如何在我的程序中可用?

    这是因为 intlist 是内置名称。您不必为 Python 显式导入它们就可以在当前命名空间中找到它们。您可以在CPython virtual machine source code 中亲自查看。正如@user2357112 提到的,当全局查找失败时会访问内置名称。这是相关的sn-p:

    if (v == NULL) {
        v = PyDict_GetItem(f->f_globals, name);
        Py_XINCREF(v);
        if (v == NULL) {
            if (PyDict_CheckExact(f->f_builtins)) {
                v = PyDict_GetItem(f->f_builtins, name);
                if (v == NULL) {
                    format_exc_check_arg(
                                PyExc_NameError,
                                NAME_ERROR_MSG, name);
                    goto error;
                }
                Py_INCREF(v);
            }
            else {
                v = PyObject_GetItem(f->f_builtins, name);
                if (v == NULL) {
                    if (PyErr_ExceptionMatches(PyExc_KeyError))
                        format_exc_check_arg(
                                    PyExc_NameError,
                                    NAME_ERROR_MSG, name);
                    goto error;
                }
            }
        }
    }
    

    在上面的代码中,CPython 首先在全局范围内搜索名称。如果失败,那么它会回退并尝试从当前执行的框架对象中的内置名称映射中获取名称。 f->f_builtins 就是这样。

    您可以使用sys._getframe() 从 Python 级别观察此映射:

    >>> import sys
    >>> frame = sys._getframe()
    >>> 
    >>> frame.f_builtins['int']
    <class 'int'>
    >>> frame.f_builtins['list']
    <class 'list'>
    >>> 
    

    sys._getframe() 返回调用堆栈顶部的帧。在这种情况下,它是模块范围的框架。从上面可以看出,框架的f_builtins 映射包含intlist 类,因此Python 自动为您提供了这些名称。换句话说,它将它们“构建”到范围中;因此术语“内置”

    【讨论】:

    • 谢谢。是否可以查出intlist等内置类型是从哪个模块导入的?
    • @Tim 是的。在您的 REPL 中尝试 int.__module__list.__module__。在builtins 模块中有定义。
    • 我可以在没有from builtins import int,list 的情况下使用intlist。那么intlist 如何在我的程序中可用?
    • @Tim 因为intlist 是Python 中的内置名称。您不必显式导入它们。
    • @Tim:它们不是从任何地方导入的。虽然从技术上讲,有一个 __builtin__builtins (依赖于版本)模块使用内置命名空间作为其模块命名空间,但您可以使用内置名称的原因不是通过任何形式的导入,并且说 builtins 是定义这些类型的地方是有问题的。
    【解决方案2】:

    如果由于某种原因源不可用,您可以使用inspect 中的getmodule,它会尽力通过抓取__module__(如果存在)然后回退到其他替代方案来找到该模块。

    如果一切顺利,您将返回一个模块对象。从中,您可以获取 __name__ 以获取模块的实际名称:

    from inspect import getmodule
    from collections import OrderedDict
    from math import sin    
    
    getmodule(getmodule).__name__
    'inspect'
    getmodule(OrderedDict).__name__
    'collections'
    getmodule(sin).__name__
    'math'
    

    如果它没有找到任何东西,它会返回None,所以你必须对此进行特殊处理。一般来说,这会为您封装逻辑,因此您无需自己编写函数来实际获取__module__(如果存在)。

    这不适用于没有附加此信息的对象。作为后备,您可以尝试传入类型以规避它:

    o = OrderedDict()
    getmodule(o)                # None
    getmodule(type(0)).__name__ # 'collections'
    

    但这并不总是产生正确的结果:

    from math import pi
    getmodule(type(pi)).__name__ 
    'builtins'
    

    【讨论】:

      【解决方案3】:

      您也可以查看globals(),它会输出一个字典,其中包含所有 python 使用的名称,但还有您在命名空间中声明的变量、模块和函数。

      >>> x = 10
      >>> import os
      >>> globals() # to big to display here but finish with 
      # {... 'x': 10, 'os': <module 'os' from '/usr/local/lib/python2.7/os.py'>}
      

      因此,您可以测试变量是否是这样声明的:

      if globals()['x']:
        print('x exist')
      
      try:
        print(globals()['y'])
      except KeyError:
        print('but y does not')
      
      # x exist
      # but y does not
      

      也适用于模块:

      print(globals()['os']) # <module 'os' from '/usr/local/lib/python2.7/os.py'>
      
      try:
        print(globals()['math'])
      except KeyError:
        print('but math is not imported')
      
      # <module 'os' from '/usr/local/lib/python2.7/os.py'>
      # but math is not imported
      

      【讨论】:

      • 谢谢。你为什么用print(globals())而不是globals()
      • 我在进行测试时没有使用 python 控制台,这就是我需要print 的原因,但如果它便于阅读,我可以删除它。
      【解决方案4】:

      除非代码做一些不寻常的事情,比如直接更新 globals,否则源代码应该指出每个变量的来源:

      x = 10                         # Assigned in the current module
      from random import randrange   # Loaded from random
      from functools import *        # Loads everything listed in functools.__all__
      

      【讨论】:

      • 谢谢。如果你的源代码有几个 import 语句from ... import *,,你怎么知道一个变量来自哪个模块?
      • “from somemod import ** 语句失去了对变量来自哪个模块的所有跟踪。但是,除了 from-imports 之外,您还可以运行自己的“import somemod”并按照上面列出的方法进行操作.
      【解决方案5】:

      一些对象(但远非全部)具有属性__module__

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-08-27
        • 1970-01-01
        • 1970-01-01
        • 2012-05-20
        • 1970-01-01
        • 2022-06-25
        • 1970-01-01
        • 2012-12-18
        相关资源
        最近更新 更多