import 和form import 区别

声明:本文章是根据网上资料,加上自己整理和理解而成,仅为记录自己学习的点点滴滴。可能有错误,欢迎大家指正。

一、包/模块的基础知识

关于包和模块的相关基础知识,详见Python的包和模块的基础知识-CSDN博客

二、路径索引的顺序

首先,在Python中有内建函数(built-in)、第三方库(site-packages)以及自义库三种可以 import的模块。然后,在 import模块时,Python解释器的搜索顺序是先搜索built-in模块,然后搜索 sys.path这个路径列表中的模块

(1)built-in模块

输入下列代码:

import sys
print(sys.builtin_module_names)

你会看到一长串built-in模块的名字,如:

('_abc', '_ast', '_bisect', '_blake2', '_codecs', '_codecs_cn', '_codecs_hk', '_codecs_iso2022', '_codecs_jp', '_codecs_kr', '_codecs_tw', '_collections', '_contextvars', '_csv', '_datetime', '_functools', '_heapq', '_imp', '_io', '_json', '_locale', '_lsprof', '_md5', '_multibytecodec', '_opcode', '_operator', '_pickle', '_random', '_sha1', '_sha256', '_sha3', '_sha512', '_signal', '_sre', '_stat', '_statistics', '_string', '_struct', '_symtable', '_thread', '_tokenize', '_tracemalloc', '_typing', '_warnings', '_weakref', '_winapi', '_xxsubinterpreters', 'array', 'atexit', 'audioop', 'binascii', 'builtins', 'cmath', 'errno', 'faulthandler', 'gc', 'itertools', 'marshal', 'math', 'mmap', 'msvcrt', 'nt', 'sys', 'time', 'winreg', 'xxsubtype', 'zlib')

这些模块名称是 import动作最先搜索到的。

(2)什么是sys.path?

sys.path是一个Python解释器搜索模块的路径列表,里面保存了解释器可以索引的所有路径,可分为如下部分:

  • 当前脚本路径
  • PYTHONPATH路径(系统环境变量)
  • 虚拟环境路径
  • 第三方库site-packages路径

一般来说,第三方库会安装在site-packages路径下,当前脚本路径则是一些自定义模块,而PYTHONPATH是一个关于python的系统环境变量,它存放的是一系列位置。作用是告诉你“去哪里搜你想要的python包,并且搜到后即可在你的python环境下使用。虚拟环境路径则是Python虚拟环境保存的路径。

所以当来了一个 import命令时,Python解释器的搜索顺序就是

当然,这个sys.path中的索引顺序只是一个默认顺序,你完全可以在代码中通过sys模块修改这个顺序,在后文中你会看到如何对这个索引顺序进行修改。

具体我们可以在终端输入:方法一: import sys 和 sys.path 语句查看:

在该列表中,有几个重要的路径值得我们关注:

  • 空字符串(''):表示当前目录(当前脚本所在的路径)。
  • D:\\Soft\\anaconda3\\envs\\mypytorch\\python311.zip:Python标准库所在的路径。
  • D:\\Soft\\anaconda3\\envs\\mypytorch:虚拟环境所在的路径
  • D:\\Soft\\anaconda3\\envs\\mypytorch\\Lib\\site-packages:第三方库所在的路径

方法二:也可采用:命令行  python -m site

搜索的结果因为安装,版本和操作系统的原因都不会相同,每个人的结果可能都不会相同。因此,当我们import一些模块出现问题的时候,我们可以查看这个模块文件是不是在搜索路径中。

当我们想要把自己写的文件作为一个模块的时候,但是又不在搜索路径中,我们可以自己添加进去,比如我们直接使用sys.path.append()方法把想要引用的模块路径添加进去即可。也可以使用“sys.path.insert(0, new_path)”,将new_path路径添加为第一个搜索路径

如下图,我新增桌面路径,再次查看,可以看的已新增。

若想删除,可以用remove,如: sys.path.remove(r'C:\Users\10499\Desktop') 

当完成 import动作后,Python会把这些模块的名字和所在路径保存在一个字典里,相当于一个缓存,在后面需要运行这个模块代码时可以迅速查找到该部分代码。你可以通过 print(sys.modules)来查看当前Python解释器缓存(导入)了哪些模块。

import sys
print(sys.modules)

二、包/模块的导入:import 语句导入

包的导入作用:便于使用导入模块中的相关内容。

1.import导入机制

第一步 - 查找 sys.modules 缓存 (保存了之前import的类库的缓存)

 模块的导入一般是在文件头使用import关键字。import一个模块相当于先逐条的运行被导入模块中的语句,并构建一个模块对象,这个模块对象是一个命名空间,可以通过点号访问该对象的属性;然后在本命名空间建立一个与被导入模块命名空间的联系相当于在本命名空间新建了一个变量,这个变量名称是被导入模块的名称,指向被导入模块的命名空间

将该模块对象会被保存在sys.modules中,sys.modules是一个全局字典,键是在该解释器中被导入的所有模块的名称,值是该模块的描述,包括模块在硬盘中地址的记录。 sys.modules从 Python 程序启动就加载到了内存,用于保存当前已导入(加载)的所有模块名和模块对象。

要注意的是,在 Python 的模块查找中,sys.modules 起到缓存作用,避免了模块的重复加载。对于被导入模块的运行,只会在其在该解释器中第一次被导入的时候才会被运行,之后如果其再次被导入,便不会再被运行;更具体的讲,一个模块被import,python会先检查sys.modules这个字典中是否有这个模块名称,如果有则会直接调用,不再重新导入运行;因此,一个模块往往只在第一次被导入时才会被运行,之后的导入都会直接调用该对象,不会再运行;当然,如果我们想要重新导入在运行,比如有时候我们更新了某个模块,这时可以用reload内置函数(2.x)或者标准模块imp中的reload函数(3.x)进行重新导入。

第二步 - 查找 sys.path 与当前脚本运行目录

若需要导入的模块在 sys.modules 缓存中没有找到,解释器主要是在如下几个路径中搜索test.py文件:当前脚本路径→PYTHONPATH路径→虚拟环境路径→site-packages路径,具体见路径索引顺序的介绍。

这些模块文件后缀可以是 py、pyc、pyd,找到后将模块加载到内存,并加入到 sys.modules 字典缓存,最后将模块的名字加入当前模块的 Local 名字空间中。

2. import导入方法

(1)import   模块名 语句:导入整个模块

现:如下建立包package1(绿框),里面包含子包package2(红框)和__init__.py和test1模块

在test1.py中写代码:

s = "Hello 包1"
a = [100, 200, 300]
print("This is a 模块1")

def fun1(par):
    print('包1中的test1:', par)
    return

可以看到,在这个python文件里面创建了一下几个对象:

  • s,一个字符串
  • a,一个列表
  • fun1,一个函数

假定这个文件路径正确,我们就可以把test1.py作为一个模块,在别的文件中引用:如在new1.py文件里面写入代码:

运行结果为:

我们可以看的只打印了一次:This is a 模块1。

具体的运行过程为:

执行第一句时,会先查找sys.modules是否有缓存,因为test.py是第一次被导入,没查找;然后查找 sys.path 并按路径顺序搜索,最终在当前脚本运行目录查到,并运行一遍test.py里面的内容,所以就先打印了“This is a 模块1”。最后将 该模块内容保存在sys.modules 缓存中。

执行第二句,调用test1模块的字符串s,并打印“Hello 包1”;

执行第四句时,会先查找sys.modules是否有缓存,因为是第二次导入模块test1.py,判断到这个模块变量已存在,所以不执行。所以“This is a 模块1”只被打印了一次。

上下级模块的导入方法

假如test.py内容为:

导入下一级模块:

那么如何在new1.py中导入test2.py的内容了?可以采用:import  下一级文件夹名.下一级文件名。即在new1.py 输入以下代码:

import Package2.test2
print(Package2.test2.s) #调用时,要带上Package2

导入上一级模块:

思考一下,用new2.py导如test1.py内容?可以采用:import  上一级文件夹名.上一级文件名。即即在new2.py 输入以下代码::

import Package1.test1
print(Package1.test1.s) #调用时,要带上Package1

(2)import  模块名1,模块名2导入整个模块1和模块2模块

当需要导入多个模块时,可用import  模块名1,模块名2。如:

import test1,Package2.test2
print(test1.s)
print(Package2.test2.s)

运行结果为:

This is a 模块1
Hello 包1
Hello 包2

(3)import   模块名 as 别名:导入整个模块,并起别名

当导入模块过长时,可用import   模块名 as 别名。如:

import test1 as t1,Package2.test2  as t2
print(t1.s)
print(t2.s)

运行结果为:

This is a 模块1
Hello 包1
Hello 包2

三、from 和 import区别

(1)from 模块1  import   模块1的函数或变量

 python中,import和from都可以导入模块,import是将整个模块导入并构建模块对象,模块对象就是一个命名空间,其有自己的独立的作用域而from看似是导入模块中某些属性,但其实依然是导入整个模块,然后把部分属性对象的引用值复制到主模块的作用域中

如:多数情况下,以下两种情况在效果上是等价的。

import test1
test1.fun1('ss1')
from  test1 import fun1
fun1('ss1')

运行结果均是:

从运行结果可以看出(即出现了This is a 模块1),import form 仍然是先执行了test.py 的所有内容。区别是import方式引用了整个test1.pyfrom  import方式只引用了 test1.py中的 fun1函数。而且 from相比于import只是额外的多了对指定变量进行引用值的复制这一步,并且还删除了module对象,因此我们无法在主模块中获取module对象,也就无法获取module对象的其他属性。

只引用 fun1函数,可能造成代码中的变量名混乱,譬如你的代码中本来就有一个名为 fun1的函数,这时候用第二种方式导入,会悄无声息地替换掉代码中原本的 fun1函数,从而引起命名空间混乱。而引用整个 module时,解释器会运行 module中的所有代码,如果 module中有很耗时而我们又不需要的运算,第一种方式会存在冗余资源消耗。

如:

 这里需要特别注意的是,对于from,尽管其和import有上述的等价关系,但是del module语句并不是说将module这个对象完全删除了,其只是减少了其在模块中的一次引用,但是该模块对象在内存中依然是存在的,只有当一个对象的引用次数为0时才会被解释器回收,但是该对象依然在sys.modules中还有引用,因此该模块对象依然在内存中。对于对于from的导入,实际上并不仅仅导入了模块中特定的属性,而是整个模块依然是被完整导入了的,虽然我们无法直接在主模块中访问该对象,但是可以通过sys.modules['modulename']的方式获取该模块对象,然后再通过点号获取其所有的属性。如:

import sys
from test1 import a
#虽我们无法直接在主模块中访问s对象,
#但是可以通过sys.modules['modulename']的方式获取test1模块对象,
#然后再通过点号获取其所有的属性
print(sys.modules['test1'].s) 

运行结果为:(没报错)

This is a 模块1
Hello 包1

所以,我们尽管首先进行了from式的导入,看似是导入部分属性,但是后面再通过import导入同一模块的时候,由于sys.modules中有该模块对象,因此不会再次导入再次运行。

此外,更隐晦的一点是,由于from语句复制的是引用,两个变量的引用指向的对象值是一样的,因此如果被from导入的是一个可变对象,且对其进行了修改,那么后续import的同一模块的相应属性也会被修改,看如下例子:


#先from导入a
from test1 import a
a = 400 #修改对象a的值,原a= [100, 200, 300]
 
#再import 整个模块
import test1
print(test1.a) #可以发现再次被导入的module中的a属性也被修改了,原因是实际上没有重新导入,而是直接调用sys.module中的该模块对象,而该对象的a属性在上述语句中被修改了,因为from只是将test1.a的引用复制到主模块的变量s中,其是共享一个对象值的
 

运行结果:


 

(2) from 模块1  import  *(不提倡)

这种导入模块的方式是官方不提倡的,因为刚才们提到用 from import的方法会产生变量名混乱,但是 from test1 import fun1毕竟还是指定了导入的函数名,开发者还是可以很容易地察觉到问题。而 from test1 import *这种方式会让开发者导入test1中的所有公有类,函数,变量,从而使当前脚本中被导入了很多未知的变量名,让代码的管理变得更加复杂和不可控。

不过,我们还是有办法控制  from test1 import *的行为的——用 __all__属性。

__all__ 是针对模块公开接口的一种约定,以提供了”白名单“的形式暴露接口。如果定义了__all__,其他文件中使用from xxx import *导入该文件时,只会导入 __all__ 列出的成员,可以其他成员都被排除在外。

如:先修改test1中代码:新添加__all__ = ["fun1"] 

__all__ = ["fun1"] # from module import * 只会导入fun1
s = "Hello 包1"
a = [100, 200, 300]
print("This is a 模块1")

再在new1中导入:

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值