Python项目打包与部署(一):模块与包的概念与关系

当前各类Python教程鲜有涉及Python打包与部署技术,或者讲述过于表面化、片面化。 本人尝试从原理开始,结合实例,并给出标准操作步骤建议,为python编程爱好者提供一份较为详实的Python项目打包与部署参考教程。

本教程其它章节

虽然 Python 是动态类型编程语言,不需要提前编译。但1个Python项目也是由一组.py文件、数据文件、资源文件等组成,大部分项目还会引用第3方库,也存在依赖管理。因此,python项目管理与其它语言如java,C++是类似的。而构建1个 Python 项目时,模块与包是我们面临的基础概念。

1、模块、包的概念

Python中的模块(Module), 就是一个单独的.py文件,其中包含变量定义,函数定义、类定义、以及其它可执行语句。模块是一个独立的代码单元,可以用解释器直接运行,可以导入到其他模块中。

另一方面,包(Package) 是一个目录中所包含的模块集合。包允许我们将多个相关模块组合在一个公共命名空间下,从而更容易组织和构建我们的代码库。

将代码分解为模块和包可以带来巨大的好处:

  • 可维护性。将代码分解为模块,有助于我们对整个应用程序的独立部分进行更改,而不会影响整个应用程序,因为模块的设计仅用于处理应用程序的一部分。
  • 可重复使用性。这是软件开发的关键部分,我们只需编写一次代码,就可以根据需要在应用程序的许多不同部分中多次使用它。这使我们能够编写干净、简洁的代码。
  • 方便分工合作。模块化代码,团队的不同开发者可以分别承担同一应用程序的不同部分(模块),而不会相互干扰。
  • 可读性。将代码分解为模块和包可以提高代码的可读性。可以很容易地分辨出文件中不同代码的功能。例如,我们可能有一个名为databaseConnection. py的文件:仅从名称上我们就可以知道这个文件处理数据库连接。

2、模块详解

模块包含可执行语句及函数定义。这些语句用于初始化模块,且仅在 import 语句 第一次 遇到模块名时执行。 (文件作为脚本运行时,也会执行这些语句。)

每个模块都有自己的私有命名空间,它会被用作模块中定义的所有函数的全局命名空间。 因此,模块作者可以在模块内使用全局变量而不必担心与用户的全局变量发生意外冲突。 另一方面,如果你知道要怎么做就可以通过与引用模块函数一样的标记法 modname.itemname 来访问一个模块的全局变量。

2.1 创建模块的示例

打开IDE或文本编辑器,创建一个文件,将其命名为sample. py并输入以下代码:

py 复制代码
# sample.py

# create a variable in the module
sample_variable  = "This is a string variable in the sample.py module"

# A function in the module
def say_hello(name):
  return f"Hello, {name}  welcome to this simple module."

# This is another function in the module
def add(a, b):
  return f"{a} + {b} is = {a+b}"

print(sample_variable)
print(say_hello("小明"))
print(add(2, 3))

上面的代码定义了一个名为sample. py的模块。它包含一个名为sample_variable的变量,其值是字符串"This is a string variable in the sample.py module"。此模块还包含两个函数定义。调用时,say_hello ()函数接收一个名称参数,如果我们将一个名称传递给它,它会返回一个欢迎消息。add ()函数返回传递给它的两个数字的和。

虽然模块是用于程序或应用程序的其他部分的,但我们可以单独运行它们。要运行此模块,我们需要在开发环境中安装Python。我们可以使用以下命令在终端上运行它:

复制代码
python sample.py 

或者

复制代码
python3 sameple.py

运行结果

复制代码
This is a string variable in the sample.py module
Hello, 小明  welcome to this simple module.
2 + 3 is = 5

我们可以将其作为独立模块运行,但大多数使用场景下,1个模块通常是导入其他模块或Python 主程序来使用的。因此,要在另一个模块中使用一个模块中的变量、函数和类,我们必须导入该模块。有不同的方法来导入模块,所以让我们来看看如何导入模块

2.2 import 语句

用import module_name 可将另1个模块内容导入到本模块中。如在 another_module.py中使用 import sample

py 复制代码
# another_module.py

import sample

print(sample.sample_variable)
print(sample.say_hello("John"))
print(sample.add(2, 3))

上面的代码展示了如何从sample.py模块导入函数,使它们可以在another_module.py中使用。注意,当导入模块时,不需要包括.py扩展名

2.3 使用 from ... import ... 语句

还可以使用from关键字来导入特定的函数或变量。假设一个模块中定义了大量的函数和变量,我们不想全部使用它们。可以使用from关键字指定要使用的函数或变量:

python 复制代码
# another_module.py

from sample import add

print(add(10, 4))

上面的代码表明,我们已经从示例模块中专门导入了add()函数。

使用from关键字的另一个好处是,使用导入的函数时,无需对其进行命名或在其前面加上其父模块的名称。这将导致代码更加简洁易读。

2.4 使用 as 关键字

我们可以使用"as"为模块提供别名或备用名称。

有时,我们可能会定义相当长或不可读的模块名称。Python提供了一种为模块导入提供别名的方法,为此,我们将使用as关键字:

py 复制代码
# another_module.py

import sample as sp

result = sp.add(5, 5)
print(result)
print(sp.say_hello("Jason"))

这段代码显示了样本模块的导入,其中模块被赋予别名sp。使用sp与调用sample 完全相同。同样可以访问变量和函数。

使用上述三种方法,我们可以在另一个模块中使用一个模块的变量或函数,从而增强了应用程序的可读性,因为我们不需要将代码放在一个文件中。

在命名模块名称时,最好使用小写字母,两个词之间使用_下列线分隔。例如,有一个用于处理数据库连接的模块,可以将其命名为database_connection.py。此外,请记住Python中的名称区分大小写,因此在导入时请确保使用正确的模块名称。

总的来说,使用模块,可以让我们以可读和可维护的方式创建和组织代码。

2.5 在代码中获取模块名

模块内部,通过全局变量 name 可以获取模块名(即字符串), 对前面的例子稍加修改,在sample内部, 以及another_module.py中获取模块名

py 复制代码
# another_module.py

import sample

print(sample.sample_variable)
print(sample.say_hello("John"))
print(sample.add(2, 3))
print(__name__)

在another_module.py中,获取sample.py模块名

py 复制代码
# another_module.py

import sample as sp

result = sp.add(5, 5)
print(result)
print(sp.say_hello("Jason"))
print(sp.__name__) 

3. 关于包的详解

Python中的包是将相关模块组织到目录中的一种方式。它提供了一种更好的代码组织方式,通常对于完成同1功能,或属于同一组件的模块使用包来进行分组。

包是通过使用"."点号来表示同1个包、模块、函数(全局变量)的层级关系。例如,模块名称A.B指定了名为A的包中名为B的子模块。

1) Package包, Module模块与文件的关系

Package(包), Module(模块), file(文件) 三者关系,

  • Package由模块组成,相当于Modules 的命名空间
  • Module, 包含功能代码的.py 代码文件也称为 module , 但不能把 config.py , __init__.py称为模块。

2) 包与目录的关系

虽然在形式上,可以把1个包理解为1个目录,但并非是包含代码的目录都是包,

二者区分的关键是, 包目录包含 __init__.py 文件,不包含此文件的不能称之为包目录

Package可以包含 sub package, 但其目录下也必须有__init__.py

使用时,用点号来表示层次关系

mypkg.mymodule.func_1()

3) 导入包,模块, 函数

i假设当前项目,是统一处理声音文件与声音数据的项目。声音文件的格式很多(通常以扩展名来识别,例如:.wav,.aiff,.au),因此,为了不同文件格式之间的转换,需要创建和维护一个不断增长的模块集合。为了实现对声音数据的不同处理(例如,混声、添加回声、均衡器功能、创造人工立体声效果),还要编写无穷无尽的模块流。下面这个分级文件树展示了这个包的架构:

复制代码
sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

如上一节中提到,包含__init__.py 的目录就是1个包。 因此上述项目中,sound是项目主目录,同时也是1个包,其下还有3个子包, filters, effects, formats 。每个子包下有若干模块。

如何导入子包。
++绝对引用:++

模块 sound.filters.vocoder需要使用 sound.effects 子包下的echo module, 这样引用:

from sound.effects import echo

++相对引用++

from . import echo 表示导入当前包下面的echo模块

from ... import formats 表示从父目录下导入 formats 子包。

4) 导入第3方包

第3方包,对于经过python社区验证的包,通常也称为库library, 导入方式,根据来源不同分为:

(1) 导入 pip安装的包

按PyPi 方式,即pip安装的第3方包,当导入时, Python解释器会到python安装目录下 Lib/site-packages/ 下查找,如果没找到,会报错。

py 复制代码
import numpy
import pandas 

(2) 手工导入第3方包

需要将第3方包绝对路径加入sys.path 变量中。如 第3方包pkg_a路径为/usr/local/pkg_a, 导入及使用示例:

python 复制代码
import sys
 
# appending a path
sys.path.append('/usr/local/pkg_a')
 
# importing required module
import pkg_a
from pkg_a import module_a
 
# accessing its content
module_a.foo()

4. 模块搜索路径

常见问题: 在test/ 测试文件导入包与模块时,常常遇到 import error 导入错误问题,其原因是test目录不是项目子包,按默认导入方式,无法找到模块路径。

在.py模块文件中,导入其它模块,如 spam时,解释器首先搜索python内置模块。这些模块名称列在sys.builtin_module_names中。如果找不到,它将在变量sys.path给定的目录列表中搜索名为spam.py的文件。

sys.path从以下位置初始化:

  • 命令行直接运行的.py脚本所在的目录(或未指定文件时的当前目录)。

  • PYTHONPATH (内容是目录列表)。

  • python安装目录下的 Lib/site-packages 子目录。

    import sys
    sys.path
    ['',
    'C:\opt\Python36\python36.zip',
    'C:\opt\Python36\DLLs',
    'C:\opt\Python36\lib',
    'C:\opt\Python36',
    'C:\Users\NanoDano\AppData\Roaming\Python\Python36\site-packages',
    'C:\opt\Python36\lib\site-packages',
    'C:\opt\Python36\lib\site-packages\win32',
    'C:\opt\Python36\lib\site-packages\win32\lib',
    'C:\opt\Python36\lib\site-packages\Pythonwin']

更改sys.path的3种方式:

(1) appending a path

复制代码
sys.path.append(``'C:/Users/Vanshi/Desktop'``)

(2) sys.path 初始化时会读python的系统变量 PYTHONPATH 。可以在启动脚本中修改PYTHONPATH, 语法与 环境变量path相同

复制代码
set PYTHONPATH=C:\pypath1\;C:\pypath2\
python -c "import sys; print(sys.path)"
# Example output
['', 'C:\\pypath1', 'C:\\pypath2', 'C:\\opt\\Python36\\python36.zip', 'C:\\opt\\Python36\\DLL

set 变量名=值 是临时添加,关闭terminal 后则失效。

永久性添加可修改环境变量, 应小心使用

setx ENV_NAME env_value,

如:

setx -m PATH "%PATH%;C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin"

Linux中添加环境变量命令

export PYTHONPATH='/some/extra/path'

(3) 使用site模块来修改sys.path

在程序代码中修改sys.path 变量, 这样系统启动时加载它,就可以找到相应模块了。

复制代码
import site
import sys

site.addsitedir('/the/path')  # Always appends to end
print(sys.path)
相关推荐
愚润求学几秒前
【Linux】网络基础
linux·运维·网络
不想写bug呀25 分钟前
多线程案例——单例模式
java·开发语言·单例模式
bantinghy31 分钟前
Linux进程单例模式运行
linux·服务器·单例模式
我不会写代码njdjnssj1 小时前
网络编程 TCP UDP
java·开发语言·jvm
小和尚同志1 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维
费弗里1 小时前
Python全栈应用开发利器Dash 3.x新版本介绍(1)
python·dash
帽儿山的枪手1 小时前
为什么Linux需要3种NAT地址转换?一探究竟
linux·网络协议·安全
李少兄9 天前
解决OSS存储桶未创建导致的XML错误
xml·开发语言·python
阿蒙Amon9 天前
《C#图解教程 第5版》深度推荐
开发语言·c#
就叫飞六吧9 天前
基于keepalived、vip实现高可用nginx (centos)
python·nginx·centos