Python基础入门自学——6

模块
在程序的开发过程中,随着代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。如果你从Python解释器退出并再次进入,之前的定义(函数和变量)都会丢失。因此,如果你想编写一个稍长些的程序,最好使用文本编辑器为解释器准备输入并将该文件作为输入运行。这被称作编写脚本 。

为了编写可维护的代码,亦或想在不同的程序中使用一个便捷的函数, 而不必把这个函数复制到每一个程序中去,把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少。为支持这些,Python有一种方法可以把定义放在一个文件里,并在脚本或解释器的交互式实例中使用它们。这样的文件被称作 模块 ;模块中的定义可以导入到其它模块或者主模块(在Python中,一个.py文件就称之为一个模块(Module)。模块是一个包含Python定义和语句的文件。文件名就是模块名后跟文件后缀 .py 。在一个模块内部,模块名(作为一个字符串)可以通过全局变量 __name__ 的值获得。

进入Python解释器,并用以下命令导入该模块:

当前的符号表中,这并不会直接进入到定义在 fibo 函数内的名称;它只是进入到模块名 fibo 中。你可以用模块名访问这些函数:

如果你想经常使用某个函数,你可以把它赋值给一个局部变量:

使用模块有什么好处?最大的好处是大大提高了代码的可维护性。其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括Python内置的模块和来自第三方的模块。

使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。但是也要注意,尽量不要与内置函数名字冲突。

如果不同的人编写的模块名相同怎么办?为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。

举个例子,一个abc.py的文件就是一个名字叫abc的模块,一个xyz.py的文件就是一个名字叫xyz的模块。

现在,假设我们的abc和xyz这两个模块名字与其他模块冲突了,于是我们可以通过包来组织模块,避免冲突。方法是选择一个顶层包名,比如mycompany,按照如下目录存放:

mycompany
├─ __init__.py
├─ abc.py
└─ xyz.py
引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。现在,abc.py模块的名字就变成了mycompany.abc,类似的,xyz.py的模块名变成了mycompany.xyz。

请注意,每一个包目录下面都会有一个__init__.py的文件这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany。

类似的,可以有多级目录,组成多级层次的包结构。比如如下的目录结构:

mycompany
 ├─ web
 │  ├─ __init__.py
 │  ├─ utils.py
 │  └─ www.py
 ├─ __init__.py
 ├─ abc.py
 └─ utils.py
文件www.py的模块名就是mycompany.web.www,两个文件utils.py的模块名分别是mycompany.utils和mycompany.web.utils。

 自己创建模块时要注意命名,不能和Python自带的模块名称冲突。例如,系统自带了sys模块,自己的模块就不可命名为sys.py,否则将无法导入系统自带的sys模块。
mycompany.web也是一个模块,该模块对应的.py文件应该就是web包下的__init__.py。

所以,模块是一组Python代码的集合,可以使用其他模块,也可以被其他模块使用。

创建自己的模块时,要注意:

模块名要遵循Python变量命名规范,不要使用中文、特殊字符;
模块名不要和系统模块名冲突,最好先查看系统是否已存在该模块,检查方法是在Python交互环境执行import abc,若成功则说明系统存在此模块。

模块可以包含可执行的语句以及函数定义。语句用于初始化模块。它们仅在模块第一次在 import 语句中被导入时才执行。 (当文件被当作脚本运行时,它们也会执行。)

每个模块都有它自己的私有符号表,该表用作模块中定义的所有函数的全局符号表。因此,模块的作者可以在模块内使用全局变量,而不必担心与用户的全局变量发生意外冲突。另一方面,如果你知道自己在做什么,则可以用跟访问模块内的函数的同样标记方法,去访问一个模块的全局变量,如:modname.itemname。

模块可以导入其它模块。习惯上但不要求把所有 import 语句放在模块(或脚本)的开头。被导入的模块名存放在调入模块的全局符号表中

import 语句有一个变体,它可以把名字从一个被调模块内直接导入到现模块的符号表里。例如:

这并不会把被调模块名引入到局部变量表里(因此在这个例子里,fibo 是未被定义的)。

还有一个变体甚至可以导入模块内定义的所有名称:

这会调入所有以非下划线(_)开头的名称。 在多数情况下,Python程序员都不会使用这个功能,因为它在解释器中引入了一组未知的名称,而它们很可能会覆盖一些你已经定义过的东西。

注意通常情况下从一个模块或者包内调入 * 的做法是不太被接受的, 因为这通常会导致代码的可读性很差。不过,在交互式编译器中为了节省打字可以这么用。


如果模块名称之后带有 as,则跟在 as 之后的名称将直接绑定到所导入的模块。

这会和 import fibo 方式一样有效地调入模块, 唯一的区别是它以 fibalias 的名称存在的。

这种方式也可以在用到 from 的时候使用,并会有类似的效果:

注解:出于效率的考虑,每个模块在每个解释器会话中只被导入一次。因此,如果更改了模块,则必须重新启动解释器, 或者,如果它只是一个要交互式地测试的模块,请使用 importlib.reload(),例如 import importlib; importlib.reload(modulename)。

Python本身就内置了很多非常有用的模块,只要安装完毕,这些模块就可以立刻使用。以内建的sys模块为例,编写一个hello的模块:

第1行和第2行是标准注释,第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;
第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用__author__变量把作者写进去,标注本模块的编写者信息;

以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。

后面开始就是真正的代码部分。

你可能注意到了,使用sys模块的第一步,就是导入该模块:

import sys
导入sys模块后,就有了变量sys指向该模块,利用sys这个变量,就可以访问sys模块的所有功能。

sys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:

运行python3 hello.py获得的sys.argv就是['hello.py'];

运行python3 hello.py Michael获得的sys.argv就是['hello.py', 'Michael']。

最后,注意到这两行代码:

if __name__=='__main__':
    test()
当在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。修改一下:


以脚本的方式执行模块,模块里的代码会被执行,如上图的上半部分,就好像你导入了模块一样,但是 __name__ 被赋值为 "__main__"。这经常用于为模块提供一个方便的用户接口,或用于测试(以脚本的方式运行模块从而执行一些测试套件)。

作用域
在一个模块中,可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。

正常的函数和变量名是公开的(public)可以被直接引用,比如:abc,x123,PI等;

类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__,__name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名;这种以双下划线开头,双下划线结尾的变量名叫做: 内置属性名或者魔法方法名。是python自己实现的属性和方法,一般不允许自定义类似此种命名方式的属性或者方法

类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc等;

以单下划线开头的变量叫做: 半私有的变量名;以双下划线开头的变量叫做: 私有变量名。他们的使用要涉及到类的概念。

半私有变量名:以此类名称命名的对象,需要分为两种情况

1.类外:
类外的半私有对象、私有对象,功能一致,均是在本模块中可以正常使用,但是不能被直接导入并调用。如果一定要在模块外使用,那么需要导入本模块,然后使用(模块名.变量名)进行调用

2.类中:
类中的半私有对象,仅仅是概念上的私有,默认不要在类外进行调用。实际在类外,均可以使用(实例名.变量名/类名.变量名)进行调用

私有变量名:以此类名称命名的对象,也需要分为两种情况

1.类外:
此种情况下的对象,和半私有对象一样,参照上面

2.类中:
类中的私有对象,在类外均不能直接调用,可以理解为真私有。但是,python中没有完全私有的对象,此种对象也是可以在类外进行调用的,这里涉及到一个概念:矫直
class A:

    def get_1(self):
        return 1

    def _get_2(self):
        return 2

    def __get_3(self):
        return 3

print(dir(A))

结果为:
['_A__get_3', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', 
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', 
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', 
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 
'__weakref__', '_get_2', 'get_1']

从打印结果中我们看到,类A并不存在__get_3这么一个属性,但是存在一个_A__get_3的属性,这就是矫直

python对于出现在类中的私有属性或者私有方法,进行矫直,矫直方法就是在私有属性名、私有方法名前添加(_类名)

那么,我们想要调用类的私有属性和方法的时候,就可以直接用矫直后的属性名进行调用。

之所以说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。

private函数或变量不应该被别人引用,那它们有什么用呢?请看例子:

在模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了,这样,调用greeting()函数不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即:

外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public

但是,这只是一种约定,而不是一种强制,我们依然可以通过直接调用_开头的函数:

关于dir()函数

dir()函数是内置函数,用于查找模块定义的名称。 它返回一个排序过的字符串列表:


如果没有参数,dir() 会列出你当前定义的名称:

注意:它列出所有类型的名称:变量,模块,函数,等等。dir() 不会列出内置函数和变量的名称。如果你想要这些,它们的定义是在标准模块 builtins 中。

模块搜索路径
当一个名为 spam 的模块被导入的时候,解释器首先寻找具有该名称的内置模块。如果没有找到,然后解释器从 sys.path 变量给出的目录列表里寻找名为 spam.py 的文件。sys.path 初始有这些目录地址:
--包含输入脚本的目录(或者未指定文件时的当前目录)。
--PYTHONPATH (一个包含目录名称的列表,它和shell变量 PATH 有一样的语法)。
--取决于安装的默认设置

注解
在支持符号链接的文件系统上,包含输入脚本的目录是在追加符号链接后才计算出来的。换句话说,包含符号链接的目录并 没有 被添加到模块的搜索路径上。

在初始化后,Python程序可以更改 sys.path。包含正在运行脚本的文件目录被放在搜索路径的开头处, 在标准库路径之前。这意味着将加载此目录里的脚本,而不是标准库中的同名模块。 除非有意更换,否则这是错误。

如果我们要添加自己的搜索目录,有两种方法:

一是直接修改sys.path,添加要搜索的目录:

>>> import sys
>>> sys.path.append('/Users/michael/my_py_scripts')
这种方法是在运行时修改,运行结束后失效。

第二种方法是设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。注意只需要添加你自己的搜索路径,Python自己本身的搜索路径不受影响。

安装第三方模块

在Python中,安装第三方模块,是通过包管理工具pip完成的。一般来说,第三方库都会在Python官方的pypi.python.org网站注册,要安装一个第三方库,必须先知道该库的名称,可以在官网或者pypi上搜索,比如Pillow的名称叫Pillow,因此,安装Pillow的命令就是:

pip install Pillow
耐心等待下载并安装后,就可以使用Pillow了。

安装常用模块
在使用Python时,我们经常需要用到很多第三方库,例如,上面提到的Pillow,以及MySQL驱动程序,Web框架Flask,科学计算Numpy等。用pip一个一个安装费时费力,还需要考虑兼容性。推荐直接使用Anaconda,这是一个基于Python的数据处理和科学计算平台,它已经内置了许多非常有用的第三方库,装上Anaconda,就相当于把数十个第三方模块自动安装好了,非常简单易用。

可以从Anaconda官网下载GUI安装包,安装包有500~600M,所以需要耐心等待下载。下载后直接安装,Anaconda会把系统Path中的python指向自己自带的Python,并且,Anaconda安装的第三方模块会安装在Anaconda自己的路径下,不影响系统已安装的Python目录。

安装好Anaconda后,重新打开命令行窗口,输入python,可以看到Anaconda的信息:

┌──────────────────────────────────────────┐
│Command Prompt - python                                                        - □ x │
├──────────────────────────────────────────┤
│Microsoft Windows [Version 10.0.0]                                                    │
│(c) 2015 Microsoft Corporation. All rights reserved.                            │
│                                                                                                            │
│C:\> python                                                                                          │
│Python 3.6.3 |Anaconda, Inc.| ... on win32                                          │
│Type "help", ... for more information.                                                   │
│>>> import numpy                                                                               │
│>>> _                                                                                                   │
└──────────────────────────────────────────┘
可以尝试直接import numpy等已安装的第三方模块。

编译过的”Python文件
为了加速模块载入,Python在 __pycache__ 目录里缓存了每个模块的编译后版本,名称为 module.version.pyc ,其中名称中的版本字段对编译文件的格式进行编码; 它一般使用Python版本号。例如,在CPython版本3.3中,spam.py的编译版本将被缓存为 __pycache__/spam.cpython-33.pyc。此命名约定允许来自不同发行版和不同版本的Python的已编译模块共存。

Python根据编译版本检查源的修改日期,以查看它是否已过期并需要重新编译。这是一个完全自动化的过程。此外,编译的模块与平台无关,因此可以在具有不同体系结构的系统之间共享相同的库。

Python在两种情况下不会检查缓存。首先,对于从命令行直接载入的模块,它从来都是重新编译并且不存储编译结果;其次,如果没有源模块,它不会检查缓存。为了支持无源文件(仅编译)发行版本, 编译模块必须是在源目录下,并且绝对不能有源模块。

小建议:
-- 可以在Python命令中使用 -O 或者 -OO 开关, 以减小编译后模块的大小。 -O 开关去除断言语句,-OO 开关同时去除断言语句和 __doc__ 字符串。由于有些程序可能依赖于这些,你应当只在清楚自己在做什么时才使用这个选项。“优化过的”模块有一个 opt- 标签并且通常小些。将来的发行版本或许会更改优化的效果。
-- 一个从 .pyc 文件读出的程序并不会比它从 .py 读出时运行的更快,.pyc 文件唯一快的地方在于载入速度。
-- compileall 模块可以为一个目录下的所有模块创建.pyc文件。

标准模块
Python附带了一个标准模块库,在单独的文档Python库参考(以下称为“库参考”)中进行了描述。一些模块内置于解释器中;它们提供对不属于语言核心但仍然内置的操作的访问,以提高效率或提供对系统调用等操作系统原语的访问。这些模块的集合是一个配置选项,它也取决于底层平台。例如,winreg 模块只在Windows操作系统上提供。一个特别值得注意的模块 sys,它被内嵌到每一个Python解释器中。变量 sys.ps1 和 sys.ps2 定义用作主要和辅助提示的字符串:

这两个变量只有在编译器是交互模式下才被定义。

sys.path 变量是一个字符串列表,用于确定解释器的模块搜索路径。该变量被初始化为从环境变量 PYTHONPATH 获取的默认路径或者如果 PYTHONPATH 未设置,则从内置默认路径初始化。你可以使用标准列表操作对其进行修改:

>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')
sysconfig.py文件中也许能看到sys.path的值的来源。


包是一种通过用“带点号的模块名”来构造 Python 模块命名空间的方法。 例如,模块名 A.B 表示 A 包中名为 B 的子模块。正如模块的使用使得不同模块的作者不必担心彼此的全局变量名称一样,使用加点的模块名可以使得 NumPy 或 Pillow 等多模块软件包的作者不必担心彼此的模块名称一样。

假设你想为声音文件和声音数据的统一处理,设计一个模块集合(一个“包”)。由于存在很多不同的声音文件格式(通常由它们的扩展名来识别,例如:.wav, .aiff, .au),因此为了不同文件格式间的转换,你可能需要创建和维护一个不断增长的模块集合。 你可能还想对声音数据还做很多不同的处理(例如,混声,添加回声,使用均衡器功能,创造人工立体声效果), 因此为了实现这些处理,你将另外写一个无穷尽的模块流。这是你的包的可能结构(以分层文件系统的形式表示):

当导入这个包时,Python搜索 sys.path 里的目录,查找包的子目录

必须要有 __init__.py 文件才能让 Python 将包含该文件的目录当作包。 这样可以防止具有通常名称例如 string 的目录在无意中隐藏稍后在模块搜索路径上出现的有效模块(这一句没有理解)。 在最简单的情况下,__init__.py 可以只是一个空文件,但它也可以执行包的初始化代码或设置 __all__ 变量。

包的用户可以从包中导入单个模块,例如:
import sound.effects.echo

这会加载子模块 sound.effects.echo 。但引用它时必须使用它的全名:sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

导入子模块的另一种方法是
from sound.effects import echo
这也会加载子模块 echo ,并使其在没有包前缀的情况下可用,因此可以按如下方式使用:echo.echofilter(input, output, delay=0.7, atten=4)

另一种形式是直接导入所需的函数或变量:
from sound.effects.echo import echofilter
同样,这也会加载子模块 echo,但这会使其函数 echofilter() 直接可用:echofilter(input, output, delay=0.7, atten=4)
请注意,当使用 from package import item 时,item可以是包的子模块(或子包),也可以是包中定义的其他名称,如函数,类或变量。 import 语句首先测试是否在包中定义了item;如果没有,它假定它是一个模块并尝试加载它。如果找不到它,则引发 ImportError 异常。

相反,当使用 import item.subitem.subsubitem 这样的语法时,除了最后一项之外的每一项都必须是一个包最后一项可以是模块或包,但不能是前一项中定义的类或函数或变量

是不是可以这样认为:import导入的最小单位是模块,而from...import...导入的最小单位是变量或函数,即是模块内部的更细粒度单元

从包中导入 *
当用户写 from sound.effects import * 会发生什么?理想情况下,人们希望这会以某种方式传递给文件系统,找到包中存在哪些子模块,并将它们全部导入。这可能需要很长时间,导入子模块可能会产生不必要的副作用,这种副作用只有在显式导入子模块时才会发生。

唯一的解决方案是让包作者提供一个包的显式索引。import 语句使用下面的规范:如果一个包的 __init__.py 代码定义了一个名为 __all__ 的列表它会被视为在遇到 from package import * 时应该导入的模块名列表。在发布该包的新版本时,包作者可以决定是否让此列表保持更新。包作者如果认为从他们的包中导入 * 的操作没有必要被使用,也可以决定不支持此列表。例如,文件 sound/effects/__init__.py 可以包含以下代码:

__all__ = ["echo", "surround", "reverse"]
这意味着 from sound.effects import * 将导入 sound 包的三个命名子模块。

如果没有定义 __all__,from sound.effects import * 语句 不会 从包 sound.effects 中导入所有子模块到当前命名空间;它只确保导入了包 sound.effects (可能运行任何在 __init__.py 中的初始化代码),然后导入包中定义的任何名称。 这包括 __init__.py 定义的任何名称(以及显式加载的子模块)。它还包括由之前的 import 语句显式加载的包的任何子模块。思考下面的代码:

import sound.effects.echo
import sound.effects.surround
from sound.effects import *
在这个例子中, echo 和 surround 模块是在执行 from...import 语句时导入到当前命名空间中的,因为它们定义在 sound.effects 包中。(这在定义了 __all__ 时也有效。)

虽然某些模块被设计为在使用 import * 时只导出遵循某些模式的名称,但在生产代码中它仍然被认为是不好的做法。

请记住,使用 from package import specific_submodule 没有任何问题! 实际上,除非导入的模块需要使用来自不同包的同名子模块,否则这是推荐的表示法。

子包参考
当包被构造成子包时(与示例中的 sound 包一样),可以使用绝对导入来引用兄弟包的子模块。例如,如果模块 sound.filters.vocoder 需要使用 sound.effects 包中 echo 模块,它可以使用 from sound.effects import echo 。

还可以使用import语句的 from module import name 形式编写相对导入。这些导入使用前导点来指示相对导入中涉及的当前包和父包。例如,在 surround 模块,你可以使用:

from . import echo
from .. import formats
from ..filters import equalizer
请注意,相对导入是基于当前模块的名称进行导入的。由于主模块的名称总是 "__main__" ,因此用作Python应用程序主模块的模块必须始终使用绝对导入。

多个目录中的包
包支持另一个特殊属性, __path__ 。它被初始化为一个列表,其中包含在执行该文件中的代码之前保存包的文件 __init__.py 的目录的名称。这个变量可以修改;这样做会影响将来对包中包含的模块和子包的搜索。

虽然通常不需要此功能,但它可用于扩展程序包中的模块集。

实际上,函数定义也是“被执行”的“语句”;模块级函数定义的执行在模块的全局符号表中输入该函数名。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值