Python语法速查: 11. 模块与打包

返回目录

 

本篇索引

(1)导入模块的两种基本方法

(2)模块对象

(3)自定义模块

(4)模块的加载和编译

(5)

(6)分发和distutils

 

 (1)导入模块的两种基本方法

● 方法一(推荐)

import math      # 导入math模块中的所用函数
math.sqrt(2)     # 使用模块中的函数

为模块提供别名:

import math as mymath      # 指定模块为别名
mymath.sqrt(2)

用逗号分隔导入多个模块:

import os, sys, re      # 多个模块在一句中导入

 

● 方法二(谨慎使用)

from math import sqrt      # 仅导入math模块中的sqrt函数
sqrt(2)                    # 使用模块中的函数

为不同模块中的重名函数指定别名:

from math import sqrt as sqrt1       # 导入math模块中的sqrt函数,并重命名为sqrt1
from cmath import sqrt as sqrt2      # 导入cmath模块中的sqrt函数,并重命名为sqrt2

sqrt1(5)    # 运行math模块中的sqrt函数
sqrt2(5)    # 运行cmath模块中的sqrt函数

从一个模块中导入多个符号(函数、变量):

from math import sqrt, pi      # 导入math模块中的sqrt函数、pi变量
sqrt(2)                    # 使用模块中的函数
print(pi)                  # 使用模块中的变量

从一个模块中导入所有符号:

from math import *      # 导入math模块中的所有函数、变量
sqrt(2)                    # 使用模块中的函数
print(pi)                  # 使用模块中的变量

注意:from module import * 语句只能在程序最顶层使用,不能在函数体中使用这种导入语句。

 

一般import语句可以出现在程序中的任何位置(除了上面的 from module import *),但是每个模块中的代码仅加载和执行一次(无论import语句被使用了多少次), 后续的import语句仅将模块名称绑定到前一次导入所创建的模块对象。这样可以有效地防止两个模块互相导入而导致的无限循环。

可以在sys模块的modules变量中(即:sys.modules)查看所有当前加载的模块。

 

 

 

 (2)模块对象

模块也是Python中的第一类对象,既然是对象,就可以将它分配给变量、放置在列表等容器中、或作为数据传递。 当使用语句 import foo 时,就会将模块对象赋值给名称 foo,即产生一个名为 foo 的模块对象,它也有自己的属性、方法。 下表列出了模块对象的一些通用属性。

属性说明
m.__dict__ 模块内命名空间字典,用法同普通对象的__dict__
m.__doc__ 模块的文档字符串
m.__name__ 模块名称
m.__file__ 用于加载模块的文件
m.__path__ 在模块对象引用其他包时定义

 

可以用内置函数dir()来查看模块中的所有符号(函数、变量、类),甚至以下划线开头的符号(为模块内部使用符号)也会列出。 可以使用以下语句仅查看普通符号

[n for n in dir(copy) if not n.startswith('_')]

可以使用模块的__file__属性,来查看模块的源代码文件(但不是所有模块都有效)。

 

 

 

 (3)自定义模块

 

● 基本用法

自定义模块 cal

# 模块文件:cal.py
def add(x, y):
    return a + b

在其他文件中使用:

import cal
a = cal.add(1,2)

可以通过在模块内部定义__all__变量来控制哪些符号可以在被使用 from module import * 语句时导出。

# 模块文件:cal.py

__all__  = ['add',]   # 仅可以导出 add()函数

def add(x, y):
    return x + y
    
def sub(x, y):
    return x - y

 

● 模块内变量的作用域

一般对于模块中定义的全局变量,被外部主程序使用 from ... import ... 导入后,不会影响模块内部函数的绑定规则。 因此,该模块中函数的命名空间始终是定义该函数的模块,而不会是主程序的全局变量命名空间。 下例中,主程序 main.py 中的全局变量a不影响模块中的a。

# cal.py 文件
a = 10
def print_a():
    print(a)

    
# main.py 文件
from cal import a, print_a

a = 20          
print_a()    # 打印结果为 10

在主程序中的 a = 20 语句,本质上只是将全局变量名称 a 指向新的数值20,而不是去改变模块中的 a 变量的值。

 

● 模块以主程序的形式执行

一般模块文件都是被其他文件载入后执行,如果要单独测试模块文件,可在模块文件中添加测试代码, 并且以主程序的方式运行这个模块。这个主要通过在模块中通过判断__name__变量来实现。如下例所示:

# cal.py
......

if __name__ == '__main__'
    # 模块以主程序运行时执行的内容
    test_something()

当直接运行cal.py时:

>>> python cal.py

Python解释器会为这个模块中自动添加一个名为__name__的变量,并赋值其为 '__main__' 字符串, 模块文件中可依靠这个变量来判断自己现在是不是作为主程序在运行。若是,则可在if语句中添加若干测试代码。

若模块被作为 import 导入,则模块中的__name__变量即自动为这个模块的模块名,而不是 '__main__'。

 

● 模块搜索路径

使用import语句加载模块时,Python解释器会搜索 sys.path 中的目录列表,其中第一个条目通常是空字符串 '', 表示当前目录。各个条目在sys.path中排列的顺序决定了加载模块时的搜索顺序,可为此列表添加新条目以增加搜索目录。

也可以将多个模块文件打包成一个zip压缩文件添加到搜索路径中。例如,你创建了2个模块文件mod_a.py, mod_b.py, 并将它们打包成了一个mymods.zip压缩文件,可以如下方式导入:

import sys
sys.path.apppend('mymods.zip')
import mod_a, mod_b

如果你的压缩包内文件比较多,并且有目录层次结构,可在append语句中直接指明模块文件在zip文件中内部的目录位置:

import sys
sys.path.apppend('/tmp/mymods.zip/cal/sub')
import mod_a, b

除了zip文件,还可以使用由setuptools库创建的.egg文件包,.egg文件实际上只是添加了额外元数据 (如版本号、依赖关系等)的zip文件。另外,只能从压缩文件中导入 .py,.pyw,.pyc,.pyo 类型的文件。

 

 

 

 (4)模块的加载和编译

当使用 import foo 语句加载一个模块时,Python解释器会在sys.path中的每个目录下依次搜索以下文件:

(1)目录foo,它定义了一个包(包的说明详见下文)

(2)foo.pyd, foo.so, foomodule.so, foomodule.dll(已编译的扩展)

(3)foo.pyo(只适用于使用了-O或-OO选项时)

(4)foo.pyc

(5)foo.py(若在Windows系统上,还会查找.pyw文件)

若找不到,则Python解释器将检查该名称(foo)是否为内置的模块名称,如果不存在匹配的名称,将引发importError异常。

对于.py文件,首次导入模块时,它会被编译为字节码并作为.pyc文件另存。在后续的导入操作中,Python解释器将只加载这个编译好的.pyc文件, 除非.py文件有新的修改。

.pyo文件与Python解释器的-O选项结合使用,其删除了行号、断言和其他调试信息,因此文件更小,运行速度也稍快。 而若使用-OO选项,则还删除文档字符串。

如果包含模块.py文件的目录不允许写入(如权限不够或者被压缩在.zip文件中),将不会创建编译文件。 Python解释器的-B选项也可以禁止生成这些文件。

 

● 重新加载模块

Python一般不太支持重新加载模块,前面已说过,不管你使用多少次import语句, 同样的模块仅加载一次。不过,如果你仅在交互命令行中简单用一下还是可以的。

Python2中可使用reload()内置函数。Python3.4以后,可使用importlib模块。

 

>>> import importlib
>>> importlib.reload(testmodule)

 

 

 

 (5)包

包(package)可以将一组模块放到一个包名称下,并且可以形成目录层次结构。通过创建一个与包同名的目录, 并在该目录中创建__init__.py文件,就可以定义一个包。然后可以向该目录中放入其他源文件、编译后的文件、子包等等。

下例中定义了一个名为graphics的包,其目录结构如下:

graphics/
    __init__.py
    shape/
        __init__.py
        square.py
        circle.py
        ...
    color/
        __init__.py
        red.py
        green.py
        ...
    text.py
    pic.py

 

● __init__.py文件

只要第一次导入包中的任何部分,就会执行文件__init__.py中的代代码。这个文件可以为空,也可以包含特定的初始化代码。 在 import 语句执行期间,遇到的所有__init__.py文件都会执行。例如,执行 import graphics.shape.square时, 会先执行graphics目录中的__init__.py,再执行shape目录中的__init__.py。

如果执行以下语句:

from graphics.shape import *

由于Python无法确定shape目录下哪些模块文件需要导入,结果,该语句只会导入__init__.py文件中指定的名称。 可以通过在__init__.py中定义__all__变量来指定这个要导入的名称列表:

# graphics/shape/__init__.py文件
__all__ = ['square', 'circle']

单独导入包名称不会导入包中包含的所有子模块,例如:

import graphics
graphis.shape.square.draw()   # 失败!

由于在import graphics语句执行时,会执行其__init__.py文件,所以可以在这个文件中定义加载所有子模块的语句:

# graphics/__init__.py
from . import shape, color
# graphics/shape/__init__.py
from . import square, circle

 

● 子模块导入其他子模块

假设 shape 模块想要导入同目录下的 circle模块,可使用相对导入:

# shape.py
from . import circle

假设 shape 模块想要导入其他目录下的 red 模块,也可以使用相对导入:

# shape.py
from ..color import red

相对导入只能在同一个包中使用,若要导入其他不同包内的模块,需使用其他包的完整路径名称。 导入同一个包的其他子模块,也可以使用完整路径名称。

 

● __path__变量

Python导入一个包时,它将定义特殊变量__path__,该变量包含一个目录列表,查找包的子模块时将搜索这个列表。 如果有必要,包可以在__init__.py文件中向__path__列表提供更多可搜索的目录。

 

 

 

 (6)分发和distutils

使用python的distutils工具包可以很方便的分发自己的程序和包。要使用distutils模块,你先要组织好你的源代码目录结构, 然后写一个配置文件 setup.py 来告诉distutils如何打包你写的程序,最后使用相关命令操作一下就可以发布了,以下是具体步骤:

 

a. 组织源代码架构

假设你写了一个包,既可以自己单独运行,又可以作为库被别的Python程序调用,这个包的目录结构如下:

mycal/
    README.txt
    Documentation.txt
    libmycal.py       # 一个库模块
    mycalpkg/         # 一个支持模块包
        __init__.py 
        add.py
        sub.py
    runmycal.py       # 将作为 python runmycal.py 的脚本
    setup.py          # 给distutils用的配置文件

 

b. 编写setup.py配置文件

# setup.py
from distutils.core import setup

setup(name = "mycal",
      version = "1.0",
      py_modules = ['libmycal'],
      packages = ['mycalpkg'],
      scripts = ['runmycal.py'])

上面的参数并不是全都都必须,可以根据需要进行取舍。比如,如果你的程序里没有mycal包,则可以省略packages这一项。

setup常见参数表:

参数说明
name 包的名称(必须)
version 版本号(必须)
author 作者名称
author_email 作者电子邮件地址
maintainer 维护者名称
maintainer_email 维护者电子邮件地址
url 包的主页
description 包的简短描述
long_description 包的详细描述
download_url 包的下载地址

 

c. 创建分发版本

① 创建源代码发布版本

python setup.py sdist

运行完以上命令后,你的mycal目录下,会多出一个MANIFEST文件(其中包括所有文件的列表)和一个dist目录,在dist目录下即为你这个库的源代码压缩安装包: mycal-1.0.tar.gz

别人要安装你这个包,只需解压这个压缩文件,并在解压后的mycal目录下,输入以下命令,即可安装这个包:

# python setup.py install

在Linux上,模块和包通常安装在Python库中名为site-packages的目录下(可检查sys.path的值)。在Windows上, 通常安装在Scripts目录下。如果用户没有相关目录的写入权限,也可以使用 --prefix 选项来指定要安装的位置:

$ python setup.py install --prefix=/home/tom/pypackages

 

② 创建二进制发布版本(rpm或windows)

可以通过--help-formats参数来查看当前distutils支持的发布格式,以下是命令行及运行结果:

# python setup.py bdist --help-formats
List of available distribution formats:
  --formats=rpm      RPM distribution
  --formats=gztar    gzip'ed tar file
  --formats=bztar    bzip2'ed tar file
  --formats=xztar    xz'ed tar file
  --formats=ztar     compressed tar file
  --formats=tar      tar file
  --formats=wininst  Windows executable installer
  --formats=zip      ZIP file
  --formats=msi      Microsoft Installer

然后可以通过 --formats参数指定要发布的二进制格式,比如:

# python setup.pu bdist --formats=rpm      (发布rpm版本)
  或者
# python setup.pu bdist --formats=wininst      (发布windows版本)

 

● 使用py2exe和py2app

上面用wininst建立的安装程序非常简陋,也没有卸载的功能。更专业一点的话,可以使用py2exe来创建Windows下的安装程序, 它建立的安装包可以在没有安装Python解释器的计算机上使用。如果要创建在Mac OS上的安装文件的话,可使用py2app。 详细的使用方法可详见其官网文档。

 

 

 

返回目录

 

posted @ 2020-01-21 18:47  初级电路与软件研究  阅读(324)  评论(0编辑  收藏  举报