关于Python引入外部模块的import语句的讨论

2023-12-14 00:01:12


在前面文章代码中,常常出现了from xx import *,有读者指出这种导入模块的方式存在一定的风险,因此我针对这个问题进行了一定的扩展研究。

引入模块的常用语句

在写代码过程中,我们常常需要用到一些外部库(模块)的方法,而在使用这些外部库之前,需要先把外部库引入当前的项目空间中,常用的引入外部模块的方法有以下3种,这里以引入 pandas 库为例:

(1)使用 import 语句导入模块

import pandas
import pandas as pd

(2)使用 from … import … 导入模块方法

from pandas import DataFrame
from pandas import DataFrame as df_fun
from pandas import *	# 暴露模块的所有变量及方法

(3)使用 import 导入模块

这个方法不常用,如下代码等价于 import pandas as pd

pd = __import__("pandas")

引入模块的实际过程

先来看当我们用 import 引入模块时,Python实际发生了什么过程。

(1)Python 预加载模块

在Python初始化运行环境的时候,会预先加载一批内建模块到内存中,这些预加载的模块的信息通过字典的形式存储在 sys.modules,可以通过以下代码查看(仅展示部分):

import sys
print("预加载模块:", )
for modules_name, info_ in sys.modules.items():
    print(modules_name, info_)
加载模块:
sys <module 'sys' (built-in)>
builtins <module 'builtins' (built-in)>
_frozen_importlib <module '_frozen_importlib' (frozen)>
_imp <module '_imp' (built-in)>
_thread <module '_thread' (built-in)>
_warnings <module '_warnings' (built-in)>
encodings <module 'encodings' from 'F:\\Anaconda\\Lib\\encodings\\__init__.py'>
......

(2)引入外部库的信息

当我们要 import 一个模块时,会先在 sys.modules 当中搜索该模块名,若搜索得到,则将该模块名添加到当前代码的局部命名空间。例如,我们引入加载模块中的 encodings 模块:

print(dir())
import encodings
print(dir())

可以看到,在局部命名空间中,引入了 encodings 模块,此时可以在调用该模块的属性及方法。

['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'encodings']

反之,如果我们引入的模块不在 sys.modules 当中,则会去指定的存放外部模块信息的文件夹下搜索是否存在该模块,若存在,则引入该模块的相关信息。例如引入 pandas 模块,并打印模块信息:

print(sys.modules['pandas'] if 'pandas' in sys.modules.keys() else "No Exist")
import pandas 
print(sys.modules['pandas'] if 'pandas' in sys.modules.keys() else "No Exist")
No Exist
<module 'pandas' from 'F:\\Anaconda\\Lib\\site-packages\\pandas\\__init__.py'>

值得注意的是,这里pandas模块信息显示该模块的文件夹地址,也就是说引入模块,引入的是模块的地址,调用的时候还是原来文件夹下的模块

综上可知,当引入一个模块时,该模块对应的字符串,会被引入局部命名空间,以及 sys.modules 当中。例如,import pandas,则字符串 'pandas'会被引入局部命名空间和sys.modules中;如果是 import pandas as pd,则字符串'pd'会被引入局部命名空间和sys.modules中。

不同引入方式的区别

基于上部分对 import 的介绍,我们来看下 from xx import xxx 的引入方式有什么不一样。

print(sys.modules['pandas'] if 'pandas' in sys.modules.keys() else "No Exist")
print(dir())

from pandas import DataFrame

print(sys.modules['pandas'] if 'pandas' in sys.modules.keys() else "No Exist")
print(dir())
No Exist
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys']

<module 'pandas' from 'F:\\Anaconda\\Lib\\site-packages\\pandas\\__init__.py'>
['DataFrame', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys']

发现,在 sys.modules 中创建的模块名还是 pandas,但在命名空间中新增的字符串是 DataFrame,此时 pandas 库下的 DataFrame 方法直接暴露在命名空间中,可以直接进行调用。

df = DataFrame()
print(type(df))
<class 'pandas.core.frame.DataFrame'>

进一步地,就会知道,当我们用 from pandas import *,就会将该模块的全部变量及方法暴露到当前的命名空间当中。

由此可见:不同的引入模块方式存在一定的区别,而这些区别,往往会在一些场景下引发问题,例如,使用 from xx import xxx 的方法,可能在引入具有相同方法名的不同模块时,造成命名空间的冲突。.

建议性结论

综上所述,得到几点关于在使用 import 引入外部模块时要注意的建议性结论:

  1. 为了防止命名空间的冲突,尽量使用 import xx 的形式,或者用 import xx as x 的形式。需要调用模块方法时用 模块名.方法名();除非有些 模块名.方法名()过于冗长繁琐,可以少量地使用该语句;
  2. 尽量避免使用 from a import *,该语句将模块的全部属性和方法导入到命名空间,极易造成命名冲突,也就是部分引入的信息被后来的引入信息所覆盖。
  3. 多尝试重命名 as 引入的模块(子模块、方法),不仅可以使引用更加方便,且可以人为地避免命名冲突。

文章来源:https://blog.csdn.net/Linshaodan520/article/details/134883308
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。