鱼C论坛

 找回密码
 立即注册
查看: 1157|回复: 4

[已解决]为什么把 from a import x 改为 import a 就不会报错了

[复制链接]
发表于 2022-3-27 09:48:49 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
# a.py
from b import y
def x():
    print('x')

# b.py
from a import x
def y():
    print('y')

>>> 
Traceback (most recent call last):
  File "/Users/FishC/Desktop/a.py", line 1, in <module>
    from b import x
  File "/Users/FishC/Desktop/b.py", line 1, in <module>
    import a
  File "/Users/FishC/Desktop/a.py", line 1, in <module>
    from b import x
ImportError: cannot import name 'x'
# a.py
import b

def x():
print('x')

# b.py
import a

def y():
    print('y')

a.x()
最佳答案
2022-3-28 10:58:20
本帖最后由 isdkz 于 2022-3-28 11:04 编辑

关于导入你得知道个点。

第一:不管是 from .. import ... 还是 import ... 的方式导入,都会初始化整个包或模块(包会执行整个 __init__.py文件,模块会执行整个模块源文件

理由就是 被导入的对象 可能会使用到 模块或包 中的其它对象

eg:
# d.py
def test():
    global a              # 用到了后面的 a
    print(a)

a = 5
>>> from d import test   
>>> a              # a 并不存在
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> test()         # 可以打印出 a 对象的值
5
>>>


第二:被初始化的模块或包会添加进 sys.modules,即使没有初始化完成,(为了防止多次初始化,也就是说同一个包或模块被多次导入只会初始化一次),

直到整个初始化过程结束,初始化失败则从 sys.modules 中移除

第三:模块在初始化过程中遇到需要被导入的对象会将它引入当前命名空间,

可以通过 dir() 或 globals() 或 locals() 来查看命名空间,dir() 返回的信息比较简洁,这里使用 dir()
eg:
#c.py
print('before the define test')
import sys
print('modules[-1]:', tuple(sys.modules.keys())[-1])     # modules是一个字典,包含模块名和模块对象,这里只取键(模块名),倒数第一个是最后一个被初始化的对象
print('first:\n', dir())
print('define the test')
def test():
    pass
print('second:\n', dir())
print('after the define test')

>>> from c import test
before the define test
modules[-1]: c   # 在整个模块初始化完成之前,modules 已经存在 c
first:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys']          # 在定义 test 之前,还无法将 test 导入当前命名空间,所以 dir() 的结果没有 test
define the test
second:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys', 'test']      # 在定义 test 之后,可以 test 导入当前命名空间,所以 dir() 的结果有 test
after the define test
>>> import c                # 多次导入不会再初始化,不管以何种方式导入
>>> from c import test            
>>>


验证初始化失败则从 sys.modules 中移除
在 c.py 后面加上引发错误的语句:
print('before the define test')
import sys
print('modules[-1]:', tuple(sys.modules.keys())[-1]) 
print('first:\n', dir())
print('define the test')
def test():
    pass
print('second:\n', dir())
print('after the define test')
l             # 引发错误的语句

>>> from c import test
before the define test
modules[-1]: c                  # 有 c
first:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys']
define the test
second:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys', 'test']                    # 有 test
after the define test
Traceback (most recent call last):                 # 初始化失败
  File "<stdin>", line 1, in <module>
  File "D:\secureworks\important\study\Program\python\test\fishc\b\c.py", line 10, in <module>
    l
NameError: name 'l' is not defined
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']      # 没有test
>>> import sys
>>> tuple(sys.modules.keys())
('sys', 'builtins', '_frozen_importlib', '_imp', '_thread', '_warnings', '_weakref', '_io', 'marshal', 'nt', 'winreg', '_frozen_importlib_external', 'time', 'zipimport', '_codecs', 'codecs', 'encodings.aliases', 'encodings', 'encodings.utf_8', '_signal', '_abc', 'abc', 'io', '__main__', '_stat', 'stat', '_collections_abc', 'genericpath', 'ntpath', 'os.path', 'os', '_sitebuiltins', 'pywin32_system32', 'pywin32_bootstrap', 'site', 'atexit')          # 没有 c
>>>

可以看出,初始化失败后会将 被导入的对象 从当前命名空间移除,且将模块或类从 modules 中移除

先来看一下相对导入的报错:
ImportError: cannot import name 'x' from partially initialized module 'a' (most likely due to a circular import)


翻译过来就是:无法从 部分初始化 的模块 a 中导入 x (很可能是循环导入)

注意一个关键词,部分初始化,也就是说模块在初始化的过程中没有初始化(执行)完成

还记得模块不会多次初始化吗,即使它没有初始化完成。

而循环导入就是遇到了这样的问题,比如 执行 a.py,

a.py 中有 from b import y,它会先去初始化 b(从头执行到尾执行 b.py),找到 y 的话会把它引入到 当前命名空间。

b.py 开始执行,有一句 from a import x,开始初始化 a (从头执行到尾执行 a.py),又执行到了 a.py 中的 from b import y,

它发现 y 还没有存在于当前命名空间,而且还没找到 y,理应再次去初始化 b,

但是 b 已经被初始化过(存在于 sys.modules),因为同一个模块不会初始化两次,它没法了,只能将错误抛出。


这类似于 多线程编程 当中的死锁,两个人都需要 钳子和螺丝刀 才能完成工作,我有钳子,你有螺丝刀,

不过两个人都很自私,都想把自己手头上的工作完成才把自己手头上的工具给交出去,但是没有另一件工具又无法把手头上的工作完成,

这就是一个无解的问题,被称为死锁


直接 import 没有这种问题就是因为它可以直接找得到并被引入命名空间,也就是说它不要工具

如果非要 from ... import... 的话,解决这个问题的方法就是其中一个人做一下让步,或者都做一下让步。
eg1: a让步
# a.py
# a 让步,先把工具交出去
def x(): 
    print('x')
from b import y
# b.py
from a import x
def y():
    print('y')
可以执行 b.py

eg2: b让步
# a.py
from b import y
def x():
    print('x')
# b.py
# b 让步,先把工具交出去
def y():
    print('y')
from a import x
可以执行 a.py

eg3:都让步
# a.py
def x():
    print('x')
from b import y
# b.py
def y():
    print('y')
from a import x
a.py 和 b.py 都可以执行
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-3-27 09:54:14 From FishC Mobile | 显示全部楼层
你这不是循环调用么
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-3-27 09:56:47 | 显示全部楼层
qq1151985918 发表于 2022-3-27 09:54
你这不是循环调用么

确实是,但是为什么直接用 import 就不会出现问题呢
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-3-27 12:31:20 | 显示全部楼层
小丑9 发表于 2022-3-27 09:56
确实是,但是为什么直接用 import 就不会出现问题呢
https://blog.csdn.net/qq_41375609/article/details/106981053
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-3-28 10:58:20 | 显示全部楼层    本楼为最佳答案   
本帖最后由 isdkz 于 2022-3-28 11:04 编辑

关于导入你得知道个点。

第一:不管是 from .. import ... 还是 import ... 的方式导入,都会初始化整个包或模块(包会执行整个 __init__.py文件,模块会执行整个模块源文件

理由就是 被导入的对象 可能会使用到 模块或包 中的其它对象

eg:
# d.py
def test():
    global a              # 用到了后面的 a
    print(a)

a = 5
>>> from d import test   
>>> a              # a 并不存在
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> test()         # 可以打印出 a 对象的值
5
>>>


第二:被初始化的模块或包会添加进 sys.modules,即使没有初始化完成,(为了防止多次初始化,也就是说同一个包或模块被多次导入只会初始化一次),

直到整个初始化过程结束,初始化失败则从 sys.modules 中移除

第三:模块在初始化过程中遇到需要被导入的对象会将它引入当前命名空间,

可以通过 dir() 或 globals() 或 locals() 来查看命名空间,dir() 返回的信息比较简洁,这里使用 dir()
eg:
#c.py
print('before the define test')
import sys
print('modules[-1]:', tuple(sys.modules.keys())[-1])     # modules是一个字典,包含模块名和模块对象,这里只取键(模块名),倒数第一个是最后一个被初始化的对象
print('first:\n', dir())
print('define the test')
def test():
    pass
print('second:\n', dir())
print('after the define test')

>>> from c import test
before the define test
modules[-1]: c   # 在整个模块初始化完成之前,modules 已经存在 c
first:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys']          # 在定义 test 之前,还无法将 test 导入当前命名空间,所以 dir() 的结果没有 test
define the test
second:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys', 'test']      # 在定义 test 之后,可以 test 导入当前命名空间,所以 dir() 的结果有 test
after the define test
>>> import c                # 多次导入不会再初始化,不管以何种方式导入
>>> from c import test            
>>>


验证初始化失败则从 sys.modules 中移除
在 c.py 后面加上引发错误的语句:
print('before the define test')
import sys
print('modules[-1]:', tuple(sys.modules.keys())[-1]) 
print('first:\n', dir())
print('define the test')
def test():
    pass
print('second:\n', dir())
print('after the define test')
l             # 引发错误的语句

>>> from c import test
before the define test
modules[-1]: c                  # 有 c
first:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys']
define the test
second:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys', 'test']                    # 有 test
after the define test
Traceback (most recent call last):                 # 初始化失败
  File "<stdin>", line 1, in <module>
  File "D:\secureworks\important\study\Program\python\test\fishc\b\c.py", line 10, in <module>
    l
NameError: name 'l' is not defined
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']      # 没有test
>>> import sys
>>> tuple(sys.modules.keys())
('sys', 'builtins', '_frozen_importlib', '_imp', '_thread', '_warnings', '_weakref', '_io', 'marshal', 'nt', 'winreg', '_frozen_importlib_external', 'time', 'zipimport', '_codecs', 'codecs', 'encodings.aliases', 'encodings', 'encodings.utf_8', '_signal', '_abc', 'abc', 'io', '__main__', '_stat', 'stat', '_collections_abc', 'genericpath', 'ntpath', 'os.path', 'os', '_sitebuiltins', 'pywin32_system32', 'pywin32_bootstrap', 'site', 'atexit')          # 没有 c
>>>

可以看出,初始化失败后会将 被导入的对象 从当前命名空间移除,且将模块或类从 modules 中移除

先来看一下相对导入的报错:
ImportError: cannot import name 'x' from partially initialized module 'a' (most likely due to a circular import)


翻译过来就是:无法从 部分初始化 的模块 a 中导入 x (很可能是循环导入)

注意一个关键词,部分初始化,也就是说模块在初始化的过程中没有初始化(执行)完成

还记得模块不会多次初始化吗,即使它没有初始化完成。

而循环导入就是遇到了这样的问题,比如 执行 a.py,

a.py 中有 from b import y,它会先去初始化 b(从头执行到尾执行 b.py),找到 y 的话会把它引入到 当前命名空间。

b.py 开始执行,有一句 from a import x,开始初始化 a (从头执行到尾执行 a.py),又执行到了 a.py 中的 from b import y,

它发现 y 还没有存在于当前命名空间,而且还没找到 y,理应再次去初始化 b,

但是 b 已经被初始化过(存在于 sys.modules),因为同一个模块不会初始化两次,它没法了,只能将错误抛出。


这类似于 多线程编程 当中的死锁,两个人都需要 钳子和螺丝刀 才能完成工作,我有钳子,你有螺丝刀,

不过两个人都很自私,都想把自己手头上的工作完成才把自己手头上的工具给交出去,但是没有另一件工具又无法把手头上的工作完成,

这就是一个无解的问题,被称为死锁


直接 import 没有这种问题就是因为它可以直接找得到并被引入命名空间,也就是说它不要工具

如果非要 from ... import... 的话,解决这个问题的方法就是其中一个人做一下让步,或者都做一下让步。
eg1: a让步
# a.py
# a 让步,先把工具交出去
def x(): 
    print('x')
from b import y
# b.py
from a import x
def y():
    print('y')
可以执行 b.py

eg2: b让步
# a.py
from b import y
def x():
    print('x')
# b.py
# b 让步,先把工具交出去
def y():
    print('y')
from a import x
可以执行 a.py

eg3:都让步
# a.py
def x():
    print('x')
from b import y
# b.py
def y():
    print('y')
from a import x
a.py 和 b.py 都可以执行
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-1-11 22:50

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表