《零基础入门学习Python》第051讲:__name__属性、搜索路径和包

0. 请写下这一节课你学习到的内容:格式不限,回忆并复述是加强记忆的好方式!

上一节课我们已经基本介绍了模块的作用以及用法,我们现在来简单回顾一下(温故而知新,可以为师矣),模块的主要作用有哪些呢?

封装、组织Python的代码:当代码量非常大的时候,我们可以有组织、有纪律的根据不同的功能将代码分割为不同的模块,这样,每个模块之间互相就是分隔开的、独立开的,那大家想想,代码是分开了容易阅读和测试还是放在一起好?我们当然是更愿意去阅读和测试一小段代码,而不是一来就劈头盖脸把整个程序读起。

模块的第二个作用就是实现代码的重用,比如说你写了一段发送邮件的代码,多次调试、优化之后你觉得这段代码非常棒了,那你可以将这段代码封装成一个独立的模块,那以后在任何程序需要发送邮件的时候,只要导入这个模块,调用相关的函数就可以实现了,而不是说每一次都重复写相同的代码。

(一)我们这一讲接着谈谈模块,第一个内容是:

if __name__ == '__main__'

相信大部分人在读别人的Python代码的时候,都会看到这么一句代码,但是却不知道有什么用,那今天我就来给大家解惑啦,我们先来举个例子吧:

还是上节课温度转换的例子,一般我们在写了一个模块之后,我们会单独对这个模块进行测试,因为一个大程序会有许多的模块,我们应该每个模块独立写完之后,在后面写一个独立的 test(),对这个模块的功能进行测试,而不是组装起来才测试,那就很难调试了。对于这个温度转换的模块,我们这里的测试就是对前面两个函数进行调用,如果正确,那就OK了

我们在模块里调用test(),然后模块就可以直接运行了。

 
  
  1. def c2f(cel):

  2. fah = cel * 1.8 + 32

  3. return fah

  4. def f2c(fah):

  5. cel = (fah - 32) / 1.8

  6. return cel

  7. def test():

  8. print("测试,0摄氏度 = %.2f华氏度"%c2f(0))

  9. print("测试,0华氏度 = %.2f摄氏度"%f2c(0))

  10. test()

运行结果:

 
  
  1. >>>

  2. ====== RESTART: C:\Users\XiangyangDai\Desktop\TemperatureConversion.py ======

  3. 测试,0摄氏度 = 32.00华氏度

  4. 测试,0华氏度 = -17.78摄氏度

经过测试,模块的功能是没问题的,然后我们在 calc.py 这段源代码里面调用这个模块的话:

 
  
  1. import sys

  2. sys.path.append("C:\\Users\\XiangyangDai\\Desktop")

  3. import TemperatureConversion as tc

  4. print("32摄氏度 = %.2f华氏度"%tc.c2f(32))

  5. print("99华氏度 = %.2f摄氏度"%tc.f2c(99))

运行结果:

 
  
  1. >>>

  2. =============== RESTART: C:\Users\XiangyangDai\Desktop\calc.py ===============

  3. 测试,0摄氏度 = 32.00华氏度

  4. 测试,0华氏度 = -17.78摄氏度

  5. 32摄氏度 = 89.60华氏度

  6. 99华氏度 = 37.22摄氏度

Python就不自觉的把模块中的测试代码也给打印出来了,那这并不是我们想要的啊,那么这种情况该怎么去避免呢?

避免这种情况的关键在于让Python知道该模块是作为程序运行还是导入到其他程序中,为了实现这一点,你就需要使用 __name__变量,

如果是在主程序里面使用这个 __name__变量的话,那得到的是个"__main__"

如果是在模块中调用 这个 __name__变量的话,那得到的就是这个模块的名字。

 
  
  1. >>> __name__

  2. '__main__'

  3. >>>

  4. >>> tc.__name__

  5. 'TemperatureConversion'

这样的话,你就不难理解这个 if __name__ == '__main__' 这句代码的意思了。

所以在上面的模块的测试文件中,需要这样改写:

 
  
  1. def c2f(cel):

  2. fah = cel * 1.8 + 32

  3. return fah

  4. def f2c(fah):

  5. cel = (fah - 32) / 1.8

  6. return cel

  7. def test():

  8. print("测试,0摄氏度 = %.2f华氏度"%c2f(0))

  9. print("测试,0华氏度 = %.2f摄氏度"%f2c(0))

  10. if __name__ == "__main__":

  11. test()

也就是说,只有在直接运行该模块代码时(也就是测试模块的时候),__name__ 才等于"__main__",这时才运行test(),打印测试结果,当调用该模块时, __name__ 是等于"模块名"的,就不会打印测试结果了。

(二)我们接下来探索第二个问题:搜索路径

像我们遇到一个问题:写好的模块应该摆在哪里?有人说,“上节课你不是说应该放在和导入模块的文件的源文件放在同一个文件夹吗”,这是一个方案,你要调用哪个模块,导入哪个模块,把他们全部放在Python模块的文件夹下,但是有的人就说了,“我并不喜欢把所有的程序放在同一个文件夹下,我想通过文件夹的形式更好的组织我的代码”,这是可以实现的,但是在此之前,你必须先理解搜索路径这个概念。

Python模块的导入需要一个路径搜索的过程,也就是说,你导入一个叫做 hello 的模块,Python会在预定义好的搜索路径里面找一个叫做 hello.py 的模块文件,如果有,则导入模块,如果没有,则导入失败,而这个搜索路径呢,就是一个列表,一组目录,我们可以 sys 模块中的 path 变量把它显示出来:

 
  
  1. >>> import sys

  2. >>> sys.path

  3. ['D:\\ProgramFiles\\Anaconda3\\Lib\\idlelib', 'D:\\ProgramFiles\\Anaconda3\\python35.zip', 'D:\\ProgramFiles\\Anaconda3\\DLLs', 'D:\\ProgramFiles\\Anaconda3\\lib', 'D:\\ProgramFiles\\Anaconda3', 'D:\\ProgramFiles\\Anaconda3\\lib\\site-packages', 'D:\\ProgramFiles\\Anaconda3\\lib\\site-packages\\Sphinx-1.4.6-py3.5.egg', 'D:\\ProgramFiles\\Anaconda3\\lib\\site-packages\\win32', 'D:\\ProgramFiles\\Anaconda3\\lib\\site-packages\\win32\\lib', 'D:\\ProgramFiles\\Anaconda3\\lib\\site-packages\\Pythonwin', 'D:\\ProgramFiles\\Anaconda3\\lib\\site-packages\\setuptools-27.2.0-py3.5.egg']

sys 是与系统相关的模块,Python 会从上面的路径中一个一个的去搜索有没有相关的模块。这里需要解释的是,不同的系统的 sys.path 显示的路径是不一样的,这里最佳的存放模块的位置是 '...\\lib\\site-packages',该文件夹本来就是设置类存放模块的。

另外,按照这里逻辑来说,你只需要告诉 Python 你的模块文件在哪里找,Python在导入模块的时候就可以正确找到,例如:我们想把桌面上的一个模块导入,我们直接导入的话,是找不到的,因为桌面的路径是不属于 sys.path 的搜索路径的,我们就可以把 桌面路径加到搜索路径中去。

 
  
  1. import sys

  2. sys.path.append("C:\\Users\\XiangyangDai\\Desktop")

这时候,再导入就不会出错了,对于其他任意的文件夹,都可以这样操作。

(三)接下来谈最后一个概念:包(package)

在实际开发中,一个大型的系统通常有成千上万个Python模块,那么使用模块定义Python的功能还是远远不够的,因为如果你把这些模块全部放在一起,那你想想看,成千上万个文件放在一个文件夹是什么概念,非常乱,非常糟糕,而且还会有命名冲突的可能,那么因此呢,Python中也出现了包的概念,什么是包呢,其实就像我们刚才做的,把模块分门别类的放到不同的文件夹,然后把各个文件夹的位置告诉Python。但是包的做法更加简洁,创建一个包也非常简单,步骤如下:

1、创建一个文件夹,用于存放相关的模块,文件夹的名字就是包的名字;

例如,我们这里创建一个文件夹M1,然后把模块扔进去:

2、在文件夹中创建一个 __init__.py 的模块文件,内容可以为空:

3、将相关的模块放入文件夹中(在第一步中我们就已经放进去了)。

在第2步中,必须在包目录下创建一个__init__.py的模块,你可以是一个空文件,但是必须要有这个文件,因为这是Python的规定,用来告诉Python,把这个文件夹(这个目录)当做一个包来管理。

接下来,要导入包的模块,怎么导?其实也很简单,就是使用 包名.模块名 来导入就可以了

 
  
  1. import sys

  2. sys.path.append("C:\\Users\\XiangyangDai\\Desktop")

  3. import M1.TemperatureConversion as tc

  4. print("32摄氏度 = %.2f华氏度"%tc.c2f(32))

  5. print("99华氏度 = %.2f摄氏度"%tc.f2c(99))

  6. >>>

  7. =============== RESTART: C:\Users\XiangyangDai\Desktop\calc.py ===============

  8. 32摄氏度 = 89.60华氏度

  9. 99华氏度 = 37.22摄氏度


测试题(笔试,不能上机哦~)

0. __name__ 属性的含义是什么?

答:所有模块都有一个 __name__ 属性,__name__ 的值取决于如何应用模块,在作为独立程序运行的时候,__name__ 属性的值是 '__main__',而作为模块导入的时候,这个值就是该模块的名字了。

1. 什么时候 __name__ 属性的值是 "__main__"?

答:模块在作为独立程序运行的时候,__name__ 属性的值是 '__main__'

2. 如果获得当前 Python 的搜索路径?

 
  
  1. >>> import sys

  2. >>> sys.path

  3. ['', 'C:\\Python34\\Lib\\idlelib', 'C:\\WINDOWS\\SYSTEM32\\python34.zip', 'C:\\Python34\\DLLs', 'C:\\Python34\\lib', 'C:\\Python34', 'C:\\Python34\\lib\\site-packages']

3. 如果你不想将相关的模块文件放在当前文件夹内,那最好的选择是?

答:放在 site-packages 文件夹,因为它就是用来存放你的模块文件的。

4. 如果你见到 import urllib.request 语句,那么这个 urllib 是什么?

答:是一个包,Python 把同类的模块放在一个文件夹中统一管理,这个文件夹称之为一个包。

urllib 是 Python 负责管理 URL 的包,用于访问网址(后边我们会讲到)。

5. Python 如何区分一个文件夹是普通文件夹还是包?

答:看文件夹中是否有 __init__.py 文件。

必须在包文件夹中创建一个 __init__.py 的模块文件,内容可以为空。可以是一个空文件,也可以写一些初始化代码。这个是 Python 的规定,用来告诉 Python 将该目录当成一个包来处理。


动动手(一定要自己动手试试哦~)

0. 执行下边 a.py 或 b.py 任何一个文件,都会报错,请改正程序。

注:这道题原理跟上一节课的课后作业(测试题 4、5)类似,如果上节课你搞懂了,这道题应该可以想出解决方案,不要轻易看答案,除非你已经抓破头皮……

 
  
  1. # a.py

  2. import b

  3. def x():

  4. print('x')

  5. b.y()

  6. # b.py

  7. import a

  8. def y():

  9. print('y')

  10. a.x()

  11. 执行 b.py 引发下边异常:

  12. >>>

  13. Traceback (most recent call last):

  14. File "/Users/FishC/Desktop/b.py", line 1, in

  15. import a

  16. File "/Users/FishC/Desktop/a.py", line 1, in

  17. import b

  18. File "/Users/FishC/Desktop/b.py", line 6, in

  19. a.x()

  20. AttributeError: 'module' object has no attribute 'x'

答:因为在执行 b.py 的加载过程中,需要创建新的模块对象 b,然后执行 b.py 对应的字节码。当遇到第一条语句(import a)的时候,Python 会转而去导入 a.py 并生成模块对象 a。同样遇到第一条语句(import b)的时候,Python 就跑去导入模块 b,此时发现 b 模块已经导入(在 sys.modules 中存在),继而执行 b 模块的字节码,当执行到 a.x() 的时候,由于模块 a 此时并未完全导入,所以抛出 AttributeError 异常。

怕有些鱼油可能看不懂,小甲鱼给大家整理下,看 Python 是如何被当成猴子耍的:

执行 b.py -> import a -> 查找 a 模块 -> 未发现 a 模块对象 -> 导入 a.py -> import b -> 查找 b 模块 -> 发现 b 模块对象 -> 接着往下执行字节码(import a 已执行过,Python 有机制确保不重复导入,因而不会再执行) -> a.x() -> 在 a 模块中找不到 x(),因为 a 还没有被完全导入嘛……

好了,解决的方案也很简单,用这节课的知识,就是使用 if __name__ == "__main__" 来确保 Python 不要在导入的过程中调用不该调用的函数。

所以应该这么写:

 
  
  1. # a.py

  2. import b

  3. def x():

  4. print('x')

  5. if __name__ == "__main__":

  6. b.y()

  7. # b.py

  8. import a

  9. def y():

  10. print('y')

  11. if __name__ == "__main__":

  12. a.x()

1. 下边是一个 Python 项目的基本结构,请你合理组织它们,便于维护和使用。

项目下载地址:https://download.csdn.net/download/qq_41556318/10837965

《零基础入门学习Python》第051讲:__name__属性、搜索路径和包_第1张图片

答:通过将相关的模块组织成包,使项目结构更为完善和合理。从而增强代码的可维护性和实用性。

以下提供一个可供参考的Python项目结构(仅供参考,没有硬性规定):

|----README/
|    |----readme.txt
|    |----LICENSE.txt
|    |----requirents.txt
|    |----setup.py
|----docs/
|    |----help.html
|    |----quickstart.html
|----test/
|    |----__init__.py
|    |----test_basic.py
|    |----test_advanced.py
|----package/
|    |----__init__.py
|    |----moduleA.py
|    |----moduleB.py
|    |----moduleC.py
|    |----static/
|    |    |----images/
|    |    |----sounds/
|----setup.py

你可能感兴趣的:(python零基础,python)