导入模块 循环导入问题 模块


 视频笔记

一 模块介绍

  1、什么是模块:

    模块就是一系列功能的集合体,分为三大类

      I:内置的模块
      II:第三方的模块
      III:自定义的模块

      ps:模块有四种形式
      1 使用python编写的.py文件
      2 已被编译为共享库或DLL的C或C++扩展
      3 把一系列模块组织到一起的文件夹(注:文件夹下有一个__init__.py文件,该文件夹称之为包)
      4 使用C编写并链接到python解释器的内置模块

  2、为何要有模块:

    I:内置与第三的模块拿来就用,无需定义,这种拿来主义,可以极大地提升自己的开发效率

    II:自定义的模块

      可以将程序的各部分功能提取出来放到一模块中为大家共享使用,好处是减少了代码冗余,程序组织结构更加清晰

  3、如何用模块

    3.1import 模块名  如:import foo

    首次导入模块会做三件事:

      1、执行源文件代码

       2、产生一个新的名称空间用于存放源文件执行过程中产生的名字

       3、在当前执行文件所在的名称空间中得到一个名字foo,该名字指向2中产生的名称空间

       之后的导入,都是直接引用首次导入产生的foo.py名称空间,不会重复执行代码

    1.引用

print(foo.x)
print(foo.get)
print(foo.change)

    强调1:模块名.名字,是指名道姓地问某一个模块要名字对应的值,不会与当前名称空间中的名字发生冲突

    强调2:无论是查看还是修改操作的都是模块本身,与调用位置无关

    2.可以以逗号为分隔符在一行导入多个模块
      建议如下所示导入多个模块
      import time
      import foo
      import m
      不建议在一行同时导入多个模块
      import time,foo,m

    3.导入模块的规范

      I. python内置模块

      II. 第三方模块

      III. 程序员自定义模块

    4.模块中的as

      import ...as ...

      import foo as f 

      f.get()

    5.自定义模块的命名应该采用纯小写+下划线的风格

    6.可以在函数内导入模块

      def func():

        import foo

    7.impot导入模块在使用时必须加前缀"模块."

    优点:肯定不会与当前名称空间中的名字冲突

    缺点:加前缀显得麻烦

    3.2 from-import 语句

    from...import...与import语句基本一致,唯一不同的是:使用from foo import x,get,change,Foo则可以在当前执行文件中直接引用模块foo中的名字,如下

from foo import x,get,change #将模块foo中的x和get导入到当前名称空间
a=x #直接使用模块foo中的x赋值给a
get() #直接执行foo中的get函数
change() #即便是当前有重名的x,修改的仍然是源文件中的x

    from ... import ...导入也发生了三件事

      1、产一个模块的名称空间

      2、运行foo.py将运行过程中产生的名字都丢到模块的名称空间去

      3、在当前名称空间拿到一个名字,该名字与模块名称空间中的某一个内存地址

    from...impot...导入模块在使用时不用加前缀

      优点:代码更精简

      缺点:容易与当前名称空间混淆

    另外from语句支持from foo import 语法代表将foo中所有的名字都导入到当前位置:不推荐使用

 
from foo import * #把foo中所有的名字都导入到当前执行文件的名称空间中,在当前位置直接可以使用这些名字

a=x
get()
change()
obj=Foo()
 

    模块的编写者可以在自己的文件中定义__all__变量用来控制*代表的意思

 
#foo.py
__all__=['x','get'] #该列表中所有的元素必须是字符串类型,每个元素对应foo.py中的一个名字
x=1
def get():
    print(x)
def change():
    global x
    x=0
class Foo:
    def func(self):
       print('from the func')
 

    这样我们在另外一个文件中使用*导入时,就只能导入__all__定义的名字了

 
from foo import * #此时的*只代表x和get

x #可用
get() #可用
change() #不可用
Foo() #不可用
 

    关于import和from...import...的区别

    3.3 模块的搜索路径优先级

    无论是import还是from...import在导入模块时都涉及到查找问题

    优先级:1、内存(内置模块)2、硬盘:按照sys.path中存放的文件的顺序依次查找要导入的模块

import sys
print(sys.path)
#值为一个列表,存放了一系列的对文件夹
#其中第一个文件夹是当前执行文件所在的文件夹

    了解:sys.modules查看已经加载到内存中的模块

 
import sys
import foo # foo=模块的内存地址
def func():
    import foo # foo=模块的内存地址
func()
print('foo' in sys.modules)
 

    找foo.py就把foo.py的文件夹添加到环境变量中

sys.path.append(r'路径')

     

 

    3.4循环导入问题

    循环导入问题指的是在一个模块加载/导入的过程中导入另外一个模块,而在另外一个模块中又返回来导入第一个模块中的名字,由于第一个模块尚未加载完毕,所以引用失败、抛出异常。循环导入问题指的是在一个模块加载/导入的过程中导入另外一个模块,而在另外一个模块中又返回来导入第一个模块中的名字,由于第一个模块尚未加载完毕,所以引用失败、抛出异常

 
#m1.py
print('正在导入m1')
from m2 import y
x='m1'

#m2.py
print('正在导入m2')
from m1 import x
y='m2'

#run.py
import m1
 

    测试一

 
#1、执行run.py会抛出异常
正在导入m1
正在导入m2
Traceback (most recent call last):
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/aa.py", line 1, in <module>
    import m1
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module>
    from m2 import y
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in <module>
    from m1 import x
ImportError: cannot import name 'x'

#2、分析
先执行run.py--->执行import m1,开始导入m1并运行其内部代码--->打印内容"正在导入m1"
--->执行from m2 import y 开始导入m2并运行其内部代码--->打印内容“正在导入m2”--->执行from m1 import x,由于m1已经被导入过了,所以不会重新导入,所以直接去m1中拿x,然而x此时并没有存在于m1中,所以报错
 

    测试二

 
#1、执行文件不等于导入文件,比如执行m1.py不等于导入了m1
直接执行m1.py抛出异常
正在导入m1
正在导入m2
正在导入m1
Traceback (most recent call last):
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module>
    from m2 import y
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in <module>
    from m1 import x
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module>
    from m2 import y
ImportError: cannot import name 'y'

#2、分析
执行m1.py,打印“正在导入m1”,执行from m2 import y ,导入m2进而执行m2.py内部代码--->打印"正在导入m2",执行from m1 import x,此时m1是第一次被导入,执行m1.py并不等于导入了m1,于是开始导入m1并执行

 

 理论笔记

2.2 from-import 语句

from...import...与import语句基本一致,唯一不同的是:使用import foo导入模块后,引用模块中的名字都需要加上foo.作为前缀,而使用from foo import x,get,change,Foo则可以在当前执行文件中直接引用模块foo中的名字,如下

from foo import x,get,change #将模块foo中的x和get导入到当前名称空间 a=x #直接使用模块foo中的x赋值给a get() #直接执行foo中的get函数 change() #即便是当前有重名的x,修改的仍然是源文件中的x

无需加前缀的好处是使得我们的代码更加简洁,坏处则是容易与当前名称空间中的名字冲突,如果当前名称空间存在相同的名字,则后定义的名字会覆盖之前定义的名字。

另外from语句支持from foo import 语法,代表将foo中所有的名字都导入到当前位置

from foo import * #把foo中所有的名字都导入到当前执行文件的名称空间中,在当前位置直接可以使用这些名字 a=x get() change() obj=Foo()

如果我们需要引用模块中的名字过多的话,可以采用上述的导入形式来达到节省代码量的效果,但是需要强调的一点是:只能在模块最顶层使用的方式导入,在函数内则非法,并且的方式会带来一种副作用,即我们无法搞清楚究竟从源文件中导入了哪些名字到当前位置,这极有可能与当前位置的名字产生冲突。模块的编写者可以在自己的文件中定义__all__变量用来控制*代表的意思

#foo.py __all__=['x','get'] #该列表中所有的元素必须是字符串类型,每个元素对应foo.py中的一个名字 x=1 def get(): print(x) def change(): global x x=0 class Foo: def func(self): print('from the func')

这样我们在另外一个文件中使用*导入时,就只能导入__all__定义的名字了

from foo import * #此时的*只代表x和get x #可用 get() #可用 change() #不可用 Foo() #不可用

2.3 其他导入语法(as)

我们还可以在当前位置为导入的模块起一个别名

import foo as f #为导入的模块foo在当前位置起别名f,以后再使用时就用这个别名f f.x f.get()

还可以为导入的一个名字起别名

from foo import get as get_x get_x()

通常在被导入的名字过长时采用起别名的方式来精简代码,另外为被导入的名字起别名可以很好地避免与当前名字发生冲突,还有很重要的一点就是:可以保持调用方式的一致性,例如我们有两个模块json和pickle同时实现了load方法,作用是从一个打开的文件中解析出结构化的数据,但解析的格式不同,可以用下述代码有选择性地加载不同的模块

if data_format == 'json': import json as serialize #如果数据格式是json,那么导入json模块并命名为serialize elif data_format == 'pickle': import pickle as serialize #如果数据格式是pickle,那么导入pickle模块并命名为serialize data=serialize.load(fn) #最终调用的方式是一致的

2.4 循环导入问题

 

循环导入问题指的是在一个模块加载/导入的过程中导入另外一个模块,而在另外一个模块中又返回来导入第一个模块中的名字,由于第一个模块尚未加载完毕,所以引用失败、抛出异常,究其根源就是在python中,同一个模块只会在第一次导入时执行其内部代码,再次导入该模块时,即便是该模块尚未完全加载完毕也不会去重复执行内部代码

我们以下述文件为例,来详细分析循环/嵌套导入出现异常的原因以及解决的方案

m1.py

print('正在导入m1') from m2 import y x='m1'

m2.py

print('正在导入m2') from m1 import x y='m2'

run.py

import m1

测试一

#1、执行run.py会抛出异常 正在导入m1 正在导入m2 Traceback (most recent call last): File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/aa.py", line 1, in <module> import m1 File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module> from m2 import y File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in <module> from m1 import x ImportError: cannot import name 'x' #2、分析 先执行run.py--->执行import m1,开始导入m1并运行其内部代码--->打印内容"正在导入m1" --->执行from m2 import y 开始导入m2并运行其内部代码--->打印内容“正在导入m2--->执行from m1 import x,由于m1已经被导入过了,所以不会重新导入,所以直接去m1中拿x,然而x此时并没有存在于m1中,所以报错

 

测试二

#1、执行文件不等于导入文件,比如执行m1.py不等于导入了m1 直接执行m1.py抛出异常 正在导入m1 正在导入m2 正在导入m1 Traceback (most recent call last): File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module> from m2 import y File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in <module> from m1 import x File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module> from m2 import y ImportError: cannot import name 'y' #2、分析 执行m1.py,打印“正在导入m1”,执行from m2 import y ,导入m进而执行m2.py内部代码--->打印"正在导入m2",执行from m1 import x,此时m1是第一次被导入,执行m1.py并不等于导入了m1,于是开始导入m1并执行其内部代码--->打印"正在导入m1",执行from m1 import y,由于m1已经被导入过了,所以无需继续导入而直接问m2要y,然而y此时并没有存在于m2中所以报错

解决方案

# 方案一:导入语句放到最后,保证在导入时,所有名字都已经加载过 # 文件:m1.py print('正在导入m1') x='m1' from m2 import y # 文件:m2.py print('正在导入m2') y='m2' from m1 import x # 文件:run.py内容如下,执行该文件,可以正常使用 import m1 print(m1.x) print(m1.y) # 方案二:导入语句放到函数中,只有在调用函数时才会执行其内部代码 # 文件:m1.py print('正在导入m1') def f1(): from m2 import y print(x,y) x = 'm1' # 文件:m2.py print('正在导入m2') def f2(): from m1 import x print(x,y) y = 'm2' # 文件:run.py内容如下,执行该文件,可以正常使用 import m1 m1.f1()

注意:循环导入问题大多数情况是因为程序设计失误导致,上述解决方案也只是在烂设计之上的无奈之举,在我们的程序中应该尽量避免出现循环/嵌套导入,如果多个模块确实都需要共享某些数据,可以将共享的数据集中存放到某一个地方,然后进行导入

 

2.5 搜索模块的路径与优先级

模块其实分为四个通用类别,分别是:

1、使用纯Python代码编写的py文件

2、包含一系列模块的包

3、使用C编写并链接到Python解释器中的内置模块

4、使用C或C++编译的扩展模块

在导入一个模块时,如果该模块已加载到内存中,则直接引用,否则会优先查找内置模块,然后按照从左到右的顺序依次检索sys.path中定义的路径,直到找模块对应的文件为止,否则抛出异常。sys.path也被称为模块的搜索路径,它是一个列表类型

>>> sys.path ['', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python35.zip', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5', ..., '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages'

列表中的每个元素其实都可以当作一个目录来看:在列表中会发现有.zip或.egg结尾的文件,二者是不同形式的压缩文件,事实上Python确实支持从一个压缩文件中导入模块,我们也只需要把它们都当成目录去看即可。

 

sys.path中的第一个路径通常为空,代表执行文件所在的路径,所以在被导入模块与执行文件在同一目录下时肯定是可以正常导入的,而针对被导入的模块与执行文件在不同路径下的情况,为了确保模块对应的源文件仍可以被找到,需要将源文件foo.py所在的路径添加到sys.path中,假设foo.py所在的路径为/pythoner/projects/

import sys sys.path.append(r'/pythoner/projects/') #也可以使用sys.path.insert(……) import foo #无论foo.py在何处,我们都可以导入它了

2.6 区分py文件的两种用途

一个Python文件有两种用途,一种被当主程序/脚本执行,另一种被当模块导入,为了区别同一个文件的不同用途,每个py文件都内置了__name__变量,该变量在py文件被当做脚本执行时赋值为“__main__”,在py文件被当做模块导入时赋值为模块名

 

作为模块foo.py的开发者,可以在文件末尾基于__name__在不同应用场景下值的不同来控制文件执行不同的逻辑

#foo.py ... if __name__ == '__main__': foo.py被当做脚本执行时运行的代码 else: foo.py被当做模块导入时运行的代码

通常我们会在if的子代码块中编写针对模块功能的测试代码,这样foo.py在被当做脚本运行时,就会执行测试代码,而被当做模块导入时则不用执行测试代码。

2.7 编写一个规范的模块

 

我们在编写py文件时,需要时刻提醒自己,该文件既是给自己用的,也有可能会被其他人使用,因而代码的可读性与易维护性显得十分重要,为此我们在编写一个模块时最好按照统一的规范去编写,如下

#!/usr/bin/env python #通常只在类unix环境有效,作用是可以使用脚本名来执行,而无需直接调用解释器。 "The module is used to..." #模块的文档描述 import sys #导入模块 x=1 #定义全局变量,如果非必须,则最好使用局部变量,这样可以提高代码的易维护性,并且可以节省内存提高性能 class Foo: #定义类,并写好类的注释 'Class Foo is used to...' pass def test(): #定义函数,并写好函数的注释 'Function test is used to…' pass if __name__ == '__main__': #主程序 test() #在被当做脚本执行时,执行此处的代码


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM