Python 开发指南:工程化、模块 ( module )、引入依赖

2022-09-1811:37:25编程语言入门到精通Comments1,048 views字数 4795阅读模式

Python 中,一个单独的 *.py 文件被称之为一个模块 ( module );多个模块组织成了一个包 ( package );一个庞大的项目由各种层次的包组成。有一点值得注意的是,父包并不会自动地导入子包的模块文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

引入依赖

import 既可以导入包,也可以导入包内具体的 模块。比如,先通过 conda 在项目环境中安装 numpy,然后再通过 import 将它导入到当前的脚本。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

# 可以同时引入多个包,多个包使用逗号相隔,下同。
import numpy

# 也可以通过 . 运算符单独导入包下的某个具体的模块。
# 这里仅作演示,我们事实上只导入 numpy 就足够了。
import numpy.core.multiarray


# 通过固定数据类型的方式,numpy 可以申请一块整齐且紧凑的连续内存空间保存数据。 
arr = numpy.array([1, 2, 3], dtype=float)
print(*arr)

导入的包或模块可以通过 as 关键字起别名。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

import numpy as np

arr = np.array([1, 2, 3], dtype=float)
print(*arr)

Python 还提供了一种 from .. import 语句,它支持更细粒度的导入。有两种用法:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

一:如果 from 后面是一个包,则可以 import 该包下的模块。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

from pkg import module1 as m1

m1.var1 # ok
var1    # error

二:如果 from 后面是一个模块,则可以 import 该模块下定义的变量,函数,类定义。这些被导入的内容可以直接使用,不需要再指定模块名文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

from pkg.module1 import var1 as v1, var2 as v2, func1 as f1,
v1 # ok

Script or Module

任何一个 Python 模块都有两种用途:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

  1. 作为组件为其它模块提供功能,包括变量,函数,类定义。
  2. 作为脚本送入 Python 解释器执行。

当一个模块被引用时,解释器会从头到尾执行内部所有的代码行以获取变量,函数,类的定义。但是,该模块仅作为组件被使用时,其作为脚本部分的逻辑一般是不需要被执行的。为了将这两部分职责区分开,可以在模块文件的顶级声明中编写一个特殊的条件分支:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

# some definations
def func(): pass
var = 10

print("as module")

if __name__ == '__main__':
	print("as script")
    # ...

该分支内的代码块 只有在该模块作为程序入口时 才会执行,即同时打印 as moduleas script。当它仅作为组件被其它模块引用时,解释器只会打印 as module文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

目录结构

一个大型的 Python 工程通常由多个包构成。比如,一个简单的 Python 工程可以包含以下层次:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

Project/
|
|-- project/
|   |-- test/
|   |   |-- __init__.py
|   |   |-- test_main.py
|   |   
|   |-- __init__.py
|   |-- main.py
|
|-- setup.py
|-- requirements.txt
|-- README.md

作为项目程序入口的模块一般命名为 main.py,但这并不是必须的。比如有些数据处理项目用多个可执行模块来提供不同的功能。这里简单介绍 __init__.pysetup.py 这两个模块。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

_init_.py

每个工程的子目录下面都可以创建一个特殊的模块:__init__.py,Python 会将任何包含这个模块的目录识别为包 Package。同时,当其它模块引入这个包时,Python 会首先加载 __init__.py 声明的逻辑。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

所有在 __init__.py 声明的,或者是通过 from .. import 语句块引入的变量,函数,类定义,都将被纳入该包的定义域下。任何引用了此包的模块都将自动地获得它们。换句话说,如果项目开发者在声明包 __init__.py 文件时就声明了对子包模块的导入,那么对于项目用户而言,他只需要单独导入这一个包即可。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

除了提供数据定义以外,项目开发者通常还会在这里编写一些初始化,或者是功能验证等前置工作。如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

# Let users know if they're missing any of our hard dependencies
hard_dependencies = ("numpy", "pytz", "dateutil")
missing_dependencies = []

for dependency in hard_dependencies:
    try:
        __import__(dependency)
    except ImportError as e:
        missing_dependencies.append(f"{dependency}: {e}")
        
if missing_dependencies:
    raise ImportError(
        "Unable to import required dependencies:\n" + "\n".join(missing_dependencies)
    )
del hard_dependencies, dependency, missing_dependencies

这是来自 pandas/__init__.py 模块的一部分代码。在初始化过程中,pandas 会首先尝试从环境中引入 numpypytzdateutil 包,并在导入失败时抛出 ImportError 异常。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

setup.py*

如果你并不专门从事 Python 工具库的开发,而是聚焦于构建可运行的应用或数据处理任务,则此模块就不是必须的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

前文曾介绍过如何使用 pip 将项目的依赖导出到 requirements.txt 文件,这份文件相当于声明了能够保证项目正常运行的环境 env。假设我们编写的是可直接运行的 Python 项目,并将它开源到了远程的 Git 仓库上,那么这份文件将有助于其它 clone 项目的用户快速复制出项目的启动环境。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

当然,我们开发的也可以是更加基础的 Python 底层库,而其它开发者可以通过 pip install pkg 命令将我们的工具包作为依赖项引入并安装。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

这时候就需要一个名为 setuptools 的工具将项目进行打包,它也被 conda 预装好了。打包时需携带版本号,开源协议,项目主页,开发者邮箱,第三方依赖 ( 重要 ) 等元数据信息,而我们只需要在 setup.py ( 另一个途径是 setup.cfg ) 中按照格式进行设置即可。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

下面的 setup.py 是一份简单的演示。工具包依赖的其他库可以通过 install_requires 参数进行配置,pip 在安装此工具包时会通过读取该项配置解决依赖问题。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

from setuptools import setup

setup(
    name='pythonProject3',
    version='1.0.0',
    packages=['project', 'project.core'],
    author='lijunhu',
    author_email='junhuwiki@163.com',
    description='a sample',
    # 如果你确信依赖是向后兼容的,也可以设置成 'numpy>=1.22.0'
    install_requires=[
        'numpy==1.22.0'
    ]
)

在 PyCharm UI 上方的 Tools > Create setup.py / Run setup task 可以快速生成一份 setup 模板,并选择打包方式,比如 .egg.whl ( 可被 pip 直接安装 ),或者是 Windows 平台可运行的 .rmi,乃至 Linux 平台的 *.rpm文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

打包好的文件会生成在项目根路径下的 /dist 目录。取它用户可以通过 pip install your_project.whi 将此工具包安装到环境下,同时 pip 会自行下载 1.22.0 版本的 numpy 并安装到环境中。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

可以参考以下连接:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

setup.py 与 requirements.txt 区别 - SegmentFault 思否文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

Python 中的 requirements.txt 与 setup.py_deephub的博客-CSDN博客_requirements.txt文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

花了两天,终于把 Python 的 setup.py 给整明白了 - 知乎 (zhihu.com)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

Python的打包工具(setup.py)实战篇 - 尹正杰 - 博客园 (cnblogs.com)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

命令行参数解析

工程项目的入口程序通常会附带配置项,用户可以在程序启动之前传入必要的参数,或者是做额外的配置。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

python main.py --address "hadoop1" "hadoop2" "hadoop3" --num 3

Python 在标准库中内置了 argparse 库,它可以帮助开发者进行命令行参数解析。在脚本的内部,我们仅仅需要实例化一个 ArgumentParser 解析器对象,然后将设置的命令行参数添加到该对象内部即可。约定上,以 - 为前缀的标识符被识别为配置项,而后面的参数为该配置项的值,见下方的实现:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

import argparse

parser = argparse.ArgumentParser(description="config your cluster")
parser.add_argument("--address", "-a", help="your server ip", nargs="+", type=str,required=True)
parser.add_argument("--num", "-n", help="number of the slaves", type=int, default="3")

这里做一些简要说明:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

  1. 通过 help 参数给出该配置项相关的提示信息。
  2. 通过 type 指定参数值的类型。
  3. 通过 default 指定用户未指定配置项时的默认值。
  4. 通过 nargs="+" 表示该配置项至少接收一个或更多的值。该配置项的值会被收集到一个列表 list 内部。
  5. 通过 required=True 表示该项配置是 必须的

这里仅罗列出常用的命令行参数配置。完整内容可以参考官网:argparse — Parser for command-line options, arguments and sub-commands — Python 3.10.5 documentation 以及:argparse模块用法实例详解 - 知乎 (zhihu.com)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

通过调用解析器的 parse_args() 方法来提取用户从外部传入的参数。出于测试的目的,这里手动以数组形式传入命令行参数:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

# conf = parser.parse_args()
conf = parser.parse_args(["-a", "hadoop1", "hadoop2", "hadoop3", "--num", "3"])

解析成功的 conf 为 Namespace 类型的命名空间。直接通过 conf.{arg} 即可从中提取出解析出的配置项值。如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

print(conf.num)  # 3
print(conf.address)  # ['hadoop1', 'hadoop2', 'hadoop3']

这一手段是基于重写 __getattr__() 魔法函数的属性动态注入来实现的,见后文的元编程技术。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

argparse 预留了 -h--help 配置项,它可以根据开发者的设置打印出帮助信息。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

"""
    usage: main.py [-h] --address ADDRESS [ADDRESS ...] [--num NUM]

    config your cluster

    optional arguments:
      -h, --help            show this help message and exit
      --address ADDRESS [ADDRESS ...], -a ADDRESS [ADDRESS ...]
                            your server ip
      --num NUM, -n NUM     number of the slaves
"""
parser.parse_args(["-h"])

作者:花花子
来源:稀土掘金文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27839.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/ymba/27839.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定