本文转自: https://wiki.woodpecker.org.cn/moin/MiscItems/2008-11-25

问题

cleven <shenglipang@gmail.com>

回覆至     python-cn@googlegroups.com
收件人 python-cn@googlegroups.com
日期 2008年11月25日 下午 12:01
主旨 [CPyUG:72341] import嵌套的问题

看了《Python源码剖析》,里面提到的嵌套import的问题还是没有弄明白,各位给看一下吧。

[A.py]
from B import D
class C:pass [B.py]
from A import C
class D:pass

为什么执行A的时候不能加载D呢?

如果将A.py改为:import B就可以了。

这是怎么回事呢?

Robert Chen:详解

Robert Chen <search.pythoner@gmail.com>
回覆至 python-cn@googlegroups.com
收件人 python-cn@googlegroups.com
日期 2008年11月25日 下午 1:41
主旨 [CPyUG:72362] Re: import嵌套的问题

恩,这跟Python内部import的机制是有关的,具体到from B import D,Python内部会分成几个步骤:

  1. 在sys.modules中查找符号"B"
  2. 如果符号B存在,则获得符号B对应的module对象<module B>

    • 从<module B>的__dict__中获得符号"D"对应的对象,如果"D"不存在,则抛出异常

  3. 如果符号B不存在,则创建一个新的module对象<module B>,注意,这时,module对象的__dict__为空

    • 执行B.py中的表达式,填充<module B>的__dict__

    • 从<module B>的__dict__中获得"D"对应的对象,如果"D"不存在,则抛出异常

所以,这个例子的执行顺序如下:

1、执行A.py中的from B import D
由于是执行的python A.py,所以在sys.modules中并没有<module B>存在,
首先为B.py创建一个module对象(<module B>),
注意,这时创建的这个module对象是空的,里边啥也没有,
在Python内部创建了这个module对象之后,就会解析执行B.py,其目的是填充<module B>这个dict。 2、执行B.py中的from A import C
在执行B.py的过程中,会碰到这一句,
首先检查sys.modules这个module缓存中是否已经存在<module A>了,
由于这时缓存还没有缓存<module A>,
所以类似的,Python内部会为A.py创建一个module对象(<module A>),
然后,同样地,执行A.py中的语句 3、再次执行A.py中的from B import D
这时,由于在第1步时,创建的<module B>对象已经缓存在了sys.modules中,
所以直接就得到了<module B>,
但是,注意,从整个过程来看,我们知道,这时<module B>还是一个空的对象,里面啥也没有,
所以从这个module中获得符号"D"的操作就会抛出异常。
如果这里只是import B,由于"B"这个符号在sys.modules中已经存在,所以是不会抛出异常的。

ZQ:图解

编译追踪

hiter的日记:

问题代码如下:

A.py
from A import B
class B(object):pass
>>> import A
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/john/pythonstudy/mypython/bin/A.py", line 9, in <module>
from A import B
ImportError: cannot import name B
>>>

阅读代码后发现: 字节码大概

9           0 LOAD_CONST               0 (-1)
3 LOAD_CONST 1 (('B',))
6 IMPORT_NAME 0 (A)
9 IMPORT_FROM 1 (B)
12 STORE_NAME 1 (B)
15 POP_TOP 10 16 LOAD_CONST 2 ('B')
19 LOAD_NAME 2 (object)
22 BUILD_TUPLE 1
25 LOAD_CONST 3 (<code object B at 0xb7a1fa88, file "A.py", line 10>)
28 MAKE_FUNCTION 0
31 CALL_FUNCTION 0
34 BUILD_CLASS
35 STORE_NAME 1 (B) 12 38 LOAD_CONST 4 ('hi')
41 PRINT_ITEM
42 PRINT_NEWLINE
43 LOAD_CONST 5 (None)
46 RETURN_VALUE
  • 可以看出整个import的过程是:先import A,然后再import A然后报错。
  • 经过分析发现原因是:在import A时,虚拟机发现sys.modules(在import_submodule中会做检查)中没有加载过A,然后新建了一个A的module,新建的module是空的,需要向里面加入__builtin__,__file__等属性(在执行下一个import的时候,新module的dict将作为globals(locals)传给执行(A)字节码时使用),然后虚拟机会将这个新的module加入sys.modules中,至此虚拟机的调用堆栈如下:(代码行号可能不对,因为源码中加入了很多调试输出代码)

#0  PyImport_AddModule (name=0xbfd91673 "A") at Python/import.c:617                                                            <-------PyImport_AddModule 在这里
#1 0x08106271 in PyImport_ExecCodeModuleEx (name=0xbfd91673 "A", co=0xb7da6748, pathname=0xbfd8f533 "A.pyc") at Python/import.c:653
#2 0x08106c67 in load_source_module (name=0xbfd91673 "A", pathname=0xbfd8f533 "A.pyc", fp=0x821bd60) at Python/import.c:963
#3 0x081085cf in load_module (name=0xbfd91673 "A", fp=0x821bd60, buf=0xbfd905d3 "A.py", type=1, loader=0x0) at Python/import.c:1753
#4 0x0810a39b in import_submodule (mod=0x818c888, subname=0xbfd91673 "A", fullname=0xbfd91673 "A") at Python/import.c:2433 <--------import_submodule 在这里
#5 0x081098bb in load_next (mod=0x818c888, altmod=0x818c888, p_name=0xbfd91654, buf=0xbfd91673 "A", p_buflen=0xbfd9166c)
at Python/import.c:2234
#6 0x08108e1c in import_module_level (name=0x0, globals=0xb7de82b4, locals=0xb7de82b4, fromlist=0x818c888, level=-1) at Python/import.c:2005
#7 0x081093a1 in PyImport_ImportModuleLevel (name=0xb7de115c "A", globals=0xb7de82b4, locals=0xb7de82b4, fromlist=0x818c888, level=-1)
at Python/import.c:2076
#8 0x080d8809 in builtin___import__ (self=0x0, args=0xb7d9de34, kwds=0x0) at Python/bltinmodule.c:47
#9 0x0814d04b in PyCFunction_Call (func=0xb7dcf5ac, arg=0xb7d9de34, kw=0x0) at Objects/methodobject.c:77
#10 0x08062974 in PyObject_Call (func=0xb7dcf5ac, arg=0xb7d9de34, kw=0x0) at Objects/abstract.c:1861
#11 0x080ecad2 in PyEval_CallObjectWithKeywords (func=0xb7dcf5ac, arg=0xb7d9de34, kw=0x0) at Python/ceval.c:3446
#12 0x080e7b33 in PyEval_EvalFrameEx (f=0x821bc04, throwflag=0) at Python/ceval.c:2068
#13 0x080eaf9e in PyEval_EvalCodeEx (co=0xb7d9ab08, globals=0xb7de82b4, locals=0xb7de82b4, args=0x0, argcount=0, kws=0x0, kwcount=0,
defs=0x0, defcount=0, closure=0x0) at Python/ceval.c:2840
#14 0x080e013e in PyEval_EvalCode (co=0xb7d9ab08, globals=0xb7de82b4, locals=0xb7de82b4) at Python/ceval.c:494
#15 0x08116ab0 in run_mod (mod=0x8220378, filename=0x81653bb "<stdin>", globals=0xb7de82b4, locals=0xb7de82b4, flags=0xbfd92f70,
arena=0x81c5cd8) at Python/pythonrun.c:1273
#16 0x081151e1 in PyRun_InteractiveOneFlags (fp=0xb7f4d440, filename=0x81653bb "<stdin>", flags=0xbfd92f70) at Python/pythonrun.c:792
#17 0x08114e54 in PyRun_InteractiveLoopFlags (fp=0xb7f4d440, filename=0x81653bb "<stdin>", flags=0xbfd92f70) at Python/pythonrun.c:723
#18 0x08114cac in PyRun_AnyFileExFlags (fp=0xb7f4d440, filename=0x81653bb "<stdin>", closeit=0, flags=0xbfd92f70) at Python/pythonrun.c:692
#19 0x08059d60 in Py_Main (argc=1, argv=0xbfd93074) at Modules/main.c:523
#20 0x08058e26 in main (argc=136033156, argv=0xb7dc37b4) at ./Modules/python.c:23
  • 可以新建module并将其加入sys.modules是在函数PyImport_ExecCodeModuleEx中完成的,此后,就会将新module的dict作为locals(globals)传给执行A字节码的函数,在执行A字节码时,发现需要IMPORT_NAME A,这时虚拟机会发现在sys.modules中已经存在A,所以会直接返回这个A的module,而在接下来的IMPORT_FROM时,会从这个module中试图找到B,而此时这个module里虚拟机只加载了__builtin__,__file__等属性,加载B的字节码还没执行到(也不可能执行到),所以虚拟机就会抛出无法加载B的异常。

  • 在<python源码剖析>前言中提到这样一个问题:

 [A.py]
from B import D
class C:
pass
[B.py]
from A import C
class D:
pass

这里无法加载D,这个问题是和本文一开始提出的问题相似的。

总结:

IMPORT_NAME字节码命令的执行流程如
  1. 假设需要import A,那么虚拟机首先在sys.modules中查找是否已经load过A,
  2. 找到则返回该对象,命令结束;
  3. 如果没有找到,那么虚拟机会新建一个module对象,
  4. 然后向module对象中添加必要的属性(builtin等),

  5. 然后用这个module中的dict作为globals(locals)执行A,
  6. 然后返回

(转)Python中的模块循环导入问题的更多相关文章

  1. python中的模块和包

    模块 一 什么是模块 模块就是一组功能的集合体,可以通过导入模块来复用模块的功能. 比如我在同一个文件夹定义两个.py文件,分别命名为A.py和B.py,那么可以通过在A文件里通过import B来使 ...

  2. Python中的模块介绍和使用

    在Python中有一个概念叫做模块(module),这个和C语言中的头文件以及Java中的包很类似,比如在Python中要调用sqrt函数,必须用import关键字引入math这个模块,下面就来了解一 ...

  3. Python 中包/模块的 `import` 操作

    版权声明:博客为作者原创,允许转载,但必须注明原文地址: https://www.cnblogs.com/byronxie/p/10745292.html 用实例来说明 import 的作用吧. 创建 ...

  4. python 历险记(五)— python 中的模块

    目录 前言 基础 模块化程序设计 模块化有哪些好处? 什么是 python 中的模块? 引入模块有几种方式? 模块的查找顺序 模块中包含执行语句的情况 用 dir() 函数来窥探模块 python 的 ...

  5. python中os模块中文帮助

    python中os模块中文帮助   python中os模块中文帮助文档文章分类:Python编程 python中os模块中文帮助文档 翻译者:butalnd 翻译于2010.1.7——2010.1.8 ...

  6. python中argparse模块简单使用

    python中argparse模块简单使用 简介 argparse是python用于解析命令行参数和选项的标准模块.argparse模块的作用是用于解析命令行参数. 使用步骤 1.首先导入该模块 2. ...

  7. Python中使用模块和库编程

    """ python中使用模块和库编程 导入模块 import modulename [as alias] from modulename import fun1,fun ...

  8. Python中optionParser模块的使用方法[转]

    本文以实例形式较为详尽的讲述了Python中optionParser模块的使用方法,对于深入学习Python有很好的借鉴价值.分享给大家供大家参考之用.具体分析如下: 一般来说,Python中有两个内 ...

  9. python中threading模块详解(一)

    python中threading模块详解(一) 来源 http://blog.chinaunix.net/uid-27571599-id-3484048.html threading提供了一个比thr ...

随机推荐

  1. 为你的机器学习模型创建API服务

    1. 什么是API 当调包侠们训练好一个模型后,下一步要做的就是与业务开发组同学们进行代码对接,以便这些‘AI大脑’们可以顺利的被使用.然而往往要面临不同编程语言的挑战,例如很常见的是调包侠们用Pyt ...

  2. [CF981F]Round Marriage[二分+霍尔定理]

    题意 洛谷 分析 参考了Icefox 首先二分,然后考虑霍尔定理判断是否有完美匹配.如果是序列的话,因为这里不会出现 \(j<i,L(i)<L(j)\) 或者 \(j<i,R(i)& ...

  3. Java 多线程(三)之线程状态及其验证

    目录 线程状态 Thread.State 状态类型 定义 说明 状态转换 状态验证 「NEW」-> 「RUNNABLE」 -> 「TERMINATED」 「RUNNABLE」 -> ...

  4. Synchronous/Asynchronous:任务的同步异步,以及asynchronous callback异步回调

    两个线程执行任务有同步和异步之分,看了Quora上的一些问答有了更深的认识. When you execute something synchronously, you wait for it to ...

  5. css小技巧::not()选择器的妙用

    比如,要实现下面的效果(例如:一个列表的最后一项没有边框): See the Pen gmrGOV by 杨友存 (@Gavin-YYC) on CodePen. 一般的文档结构如下: <!-- ...

  6. 读取配置文件的URL,使用httpClient发送Post和Get请求,实现查询快递物流和智能机器人对话

    1.主要jar包: httpclient-4.3.5.jar   httpcore-4.3.2.jar 2.目录结构如图所示: 3.url.properties文件如下: geturl=http:// ...

  7. 小刘的机器学习---SVM

    前言: 这是一篇记录小刘学习机器学习过程的随笔. 正文: 支持向量机(SVM)是一组用于分类, 回归和异常值检测的监督学习方法. 在分类问题中,SVM就是要找到一个同时离各个类别尽可能远的决策边界即最 ...

  8. Bitcoin区块验证

    目录 区块的生成 区块的验证链接 验证过程 Merkle Tree结构 区块的生成 矿工在挖矿前要组建区块 将coinbase交易打包进区块 将交易池中高优先级的交易打包进区块 优先级 = 交易的额度 ...

  9. 右键添加使用Sublime打开

    网上教程大多是教你怎么改注册表,有点麻烦. 我根据教程改完之后导出来供大家使用,更方便快捷. Windows Registry Editor Version 5.00 [HKEY_CLASSES_RO ...

  10. 《Linux内核分析与实现》 第四周 读书笔记

    第五章 系统调用 20135307 张嘉琪 5.1 与内核通信 系统调用在用户空间进程和硬件设备之间添加了一个中间层,该层主要作用有三个: 它为用户空间提供了一种硬件的抽象接口 系统调用保证了系统的稳 ...