嵌入式开发:从电源管理到Python应用打包
1. 电源管理
Linux具备复杂的电源管理功能,主要有以下四个组件:
-
CPUFreq
:改变每个处理器核心的操作性能点(OPP),在核心忙碌但有带宽空闲时降低功耗,从而有机会降低频率。在ACPI规范中,OPP被称为P状态。
-
CPUIdle
:当预计CPU一段时间内不会被唤醒时,选择更深层次的空闲状态。在ACPI规范中,空闲状态被称为C状态。
-
Runtime pm
:关闭不需要的外设。
-
系统睡眠模式
:将整个系统置于低功耗状态,通常由最终用户控制,例如按下待机按钮。在ACPI规范中,系统睡眠状态被称为S状态。
大部分电源管理工作由板级支持包(BSP)完成,我们的主要任务是确保其针对预期用例进行正确配置。只有选择系统睡眠状态这一组件,需要编写代码让最终用户能够进入和退出该状态。
电源管理组件流程
graph LR
A[CPUFreq] --> B[改变OPP降低功耗]
C[CPUIdle] --> D[选择更深空闲状态]
E[Runtime pm] --> F[关闭不需要的外设]
G[系统睡眠模式] --> H[用户控制进入低功耗状态]
2. Python应用打包概述
Python是机器学习领域最流行的编程语言,随着机器学习在日常生活中的普及,在边缘设备上运行Python的需求日益增长。然而,打包Python应用进行部署仍然是一个尚未完全解决的问题。我们将探讨多种打包Python模块的方法以及何时选择使用它们。
技术要求
为了跟随示例操作,需要在基于Linux的主机系统上安装以下软件包:
- Python:Python 3解释器和标准库
- pip:Python 3的包安装器
- venv:用于创建和管理轻量级虚拟环境的Python模块
- Miniconda:conda包和虚拟环境管理器的最小安装程序
- Docker:用于在容器内构建、部署和运行软件的工具
建议使用Ubuntu 20.04 LTS或更高版本。若要在Ubuntu上安装venv,可执行以下命令:
sudo apt install python3-venv
重要提示:在学习conda部分之前,不要安装Miniconda,因为它会干扰依赖系统Python安装的早期pip练习。
安装Docker
在Ubuntu 20.04 LTS上安装Docker的步骤如下:
1. 更新软件包仓库:
sudo apt update
- 安装Docker:
sudo apt install docker.io
- 启动Docker守护进程并设置开机自启:
sudo systemctl enable --now docker
- 将自己添加到docker组:
sudo usermod -aG docker <username>
请将
<username>
替换为你的用户名。建议创建自己的Ubuntu用户账户,而不是使用默认的ubuntu用户账户,该账户通常用于管理任务。
3. 追溯Python打包的起源
Python打包领域充满了失败的尝试和被遗弃的工具,依赖管理的最佳实践在Python社区中经常变化,今年推荐的解决方案明年可能就无法使用。在研究这个主题时,要注意信息的发布时间,不要相信过时的建议。
大多数Python库使用distutils或setuptools进行分发,包括Python包索引(PyPI)上的所有包。这两种分发方法都依赖于一个setup.py项目规范文件,Python包安装器(pip)使用该文件来安装包。pip还可以在项目安装后生成或冻结精确的依赖列表,这个可选的requirements.txt文件与setup.py一起使用,以确保项目安装的可重复性。
3.1 distutils
distutils是Python最初的打包系统,自Python 2.0起就包含在Python标准库中。它提供了一个同名的Python包,可以在setup.py脚本中导入。尽管distutils仍然随Python一起发布,但它缺少一些重要功能,因此现在不建议直接使用,setuptools已成为其首选替代品。
distutils可能仍然适用于简单项目,但社区已经转向其他解决方案。如今,distutils主要出于遗留原因而存在,许多Python库最初发布时,distutils是唯一的选择,现在将它们迁移到setuptools需要大量工作,并且可能会影响现有用户。
3.2 setuptools
setuptools扩展了distutils,增加了对复杂结构的支持,使大型应用程序更容易分发。它已成为Python社区中事实上的打包系统。与distutils一样,setuptools提供了一个同名的Python包,可以在setup.py脚本中导入。
setuptools引入了一个命令行实用工具easy_install(现已弃用)和一个名为pkg_resources的Python包,用于运行时包发现和访问资源文件。setuptools还可以生成作为其他可扩展包(如框架和应用程序)插件的包,通过在setup.py脚本中注册入口点,供其他包导入。
在Python中,“distribution”有不同的含义,它是用于分发版本的包、模块和其他资源文件的版本化存档。“release”是Python项目在特定时间点的版本化快照。“package”和“distribution”这两个术语经常被Python开发者互换使用,简单来说,distribution是你下载的内容,而package是安装和导入的模块。
发布版本可能会产生多个分发形式,如源代码分发和一个或多个预构建分发。不同平台可能有不同的预构建分发,例如包含Windows安装程序的分发。预构建分发意味着在安装前不需要构建步骤,但不一定是预编译的,例如Wheel(.whl)格式的预构建分发可能不包含编译后的Python文件。包含编译扩展的预构建分发称为二进制分发。
3.3 setup.py示例
假设你正在开发一个Python小程序,例如查询远程REST API并将响应数据保存到本地SQL数据库的程序。要将程序及其依赖项打包部署,需要定义一个setup.py脚本,供setuptools使用。
以下是一个简单的示例,假设程序最初只有一个名为follower.py的文件:
$ tree follower
follower
└── follower.py
可以将这个模块拆分为三个独立的模块,并将它们放在一个名为follower的嵌套目录中:
$ tree follower/
follower/
└── follower
├── fetch.py
├── __main__.py
└── store.py
__main__.py
模块是程序的入口点,包含大部分顶层、面向用户的功能。
fetch.py
模块包含发送HTTP请求到远程REST API的函数,
store.py
模块包含将响应数据保存到本地SQL数据库的函数。要将这个包作为脚本运行,需要向Python解释器传递 -m 选项:
$ PYTHONPATH=follower python -m follower
PYTHONPATH
环境变量指向目标项目包目录所在的位置,
follower
参数告诉Python运行follower包的
__main__.py
模块。将包目录嵌套在项目目录中,为程序发展成由多个具有各自命名空间的包组成的大型应用程序奠定了基础。
现在可以创建一个最小的setup.py脚本,供setuptools打包和部署项目:
from setuptools import setup
setup(
name='follower',
version='0.1',
packages=['follower'],
include_package_data=True,
install_requires=['requests', 'sqlalchemy']
)
install_requires
参数是一个外部依赖列表,项目运行时需要自动安装这些依赖。在示例中,没有指定这些依赖的版本或获取来源,只是请求类似
requests
和
sqlalchemy
的库。这样分离策略和实现可以方便在需要修复bug或添加功能时,轻松替换官方PyPI版本的依赖。在依赖声明中添加可选的版本说明符是可以的,但在setup.py中硬编码分发URL作为
dependency_links
原则上是错误的。
packages
参数告诉setuptools在项目发布时要分发哪些包。由于每个包都定义在父项目目录的子目录中,在这个例子中,唯一要分发的包是
follower
。要在分发中包含数据文件,需要将
include_package_data
参数设置为
True
,这样setuptools会查找
MANIFEST.in
文件并安装其中列出的所有文件。以下是
MANIFEST.in
文件的内容:
include data/events.db
如果数据目录包含需要包含的嵌套数据目录,可以使用
recursive-include
来包含所有内容:
recursive-include data *
最终的目录结构如下:
$ tree follower
follower
├── data
│ └── events.db
├── follower
│ ├── fetch.py
│ ├── __init__.py
│ └── store.py
├── MANIFEST.in
└── setup.py
setuptools在构建和分发依赖其他包的Python包方面表现出色,这得益于它的入口点和依赖声明等功能,而这些功能是distutils所没有的。setuptools与pip配合良好,并且会定期发布新版本。Wheel构建分发格式的创建是为了取代setuptools最初的Egg格式,随着流行的setuptools扩展用于构建Wheel以及pip对安装Wheel的良好支持,这一努力基本取得了成功。
4. 使用pip安装Python包
现在我们知道如何在setup.py脚本中定义项目的依赖项,但如何安装这些依赖项呢?如何升级或替换依赖项?何时可以安全地删除不再需要的依赖项?管理项目依赖是一项棘手的任务,幸运的是,Python自带了一个名为pip的工具,可以在项目早期提供帮助。
4.1 pip简介
pip的1.0版本于2011年4月4日发布,大约与Node.js和npm兴起的时间相同。在成为pip之前,这个工具名为pyinstall,它于2008年创建,作为当时随setuptools捆绑的easy_install的替代方案。现在easy_install已被弃用,setuptools建议使用pip代替。
由于pip包含在Python安装程序中,并且系统上可以安装多个版本的Python(例如2.7和3.8),了解正在运行的pip版本很有帮助:
$ pip --version
如果系统上未找到pip可执行文件,可能是使用的是Ubuntu 20.04 LTS或更高版本,并且没有安装Python 2.7。这种情况下,在后续操作中用pip3代替pip,用python3代替python:
$ pip3 --version
如果系统有python3但没有pip3可执行文件,可以在基于Debian的发行版(如Ubuntu)上安装:
$ sudo apt install python3-pip
pip将包安装到一个名为site-packages的目录中,要查找site-packages目录的位置,可以运行以下命令:
$ python3 -m site | grep ^USER_SITE
需要注意的是,从这里开始显示的pip3和python3命令仅适用于Ubuntu 20.04 LTS或更高版本,这些版本不再安装Python 2.7。大多数Linux发行版仍然带有pip和python可执行文件,如果系统已经提供了这些命令,请使用它们。
4.2 使用pip管理包
查看已安装的包
要获取系统上已安装的包列表,可以使用以下命令:
$ pip3 list
这个列表显示pip本身也是一个Python包,虽然可以使用pip来升级自己,但建议至少从长期来看不要这样做,后续介绍虚拟环境时会解释原因。
要获取site-packages目录中安装的包列表,可以使用以下命令:
$ pip3 list --user
这个列表应该为空或比系统包列表短很多。
安装项目依赖
回到之前的follower项目示例,进入包含setup.py的父follower目录,然后运行以下命令:
$ pip3 install --ignore-installed --user .
pip将使用setup.py来获取并安装
install_requires
中声明的包到site-packages目录。
--user
选项指示pip将包安装到你的site-packages目录而不是全局安装。
--ignore-installed
选项强制pip将系统上已存在的任何必需包重新安装到site-packages目录,以确保没有依赖项缺失。现在再次列出site-packages目录中的所有包:
$ pip3 list --user
输出结果可能如下:
| Package | Version |
| ---------- | -------- |
| certifi | 2020.6.20 |
| chardet | 3.0.4 |
| follower | 0.1 |
| idna | 2.10 |
| requests | 2.24.0 |
| SQLAlchemy | 1.3.18 |
| urllib3 | 1.25.10 |
这次应该可以看到
requests
和
SQLAlchemy
都在包列表中。
查看包详细信息
要查看刚刚安装的
SQLAlchemy
包的详细信息,可以执行以下命令:
$ pip3 show sqlalchemy
显示的详细信息包含
Requires
和
Required-by
字段,这两个字段都是相关包的列表。可以使用这些字段的值和连续的
pip show
调用来跟踪项目的依赖树,但安装一个名为
pipdeptree
的命令行工具可能更方便。
卸载包
当
Required-by
字段为空时,表明可以安全地从系统中卸载该包。如果没有其他包依赖于要删除包的
Requires
字段中的包,那么也可以安全地卸载这些包。以下是使用pip卸载
sqlalchemy
的方法:
$ pip3 uninstall sqlalchemy -y
末尾的
-y
选项用于抑制确认提示。要一次卸载多个包,只需在
-y
之前添加更多包名。这里省略了
--user
选项,因为pip足够智能,当包也全局安装时,会先从site-packages目录卸载。
搜索包
有时需要一个具有特定用途或使用特定技术的包,但不知道其名称。可以使用pip从命令行对PyPI进行关键字搜索,但这种方法通常会产生太多结果。在PyPI网站(https://pypi.org/search/)上搜索包更容易,该网站允许按各种分类器过滤结果。
4.3 requirements.txt文件
pip install
会安装包的最新发布版本,但通常我们希望安装已知与项目代码兼容的特定版本的包。最终,我们会想要升级项目的依赖项,但在介绍如何升级之前,先了解如何使用
pip freeze
来固定依赖项。
需求文件允许精确指定pip为项目安装哪些包和版本。按照惯例,项目需求文件通常命名为
requirements.txt
。需求文件的内容只是一个
pip install
参数列表,列举了项目的依赖项。这些依赖项都有精确的版本,这样在有人尝试重新构建和部署项目时就不会有意外情况发生。在项目仓库中添加
requirements.txt
文件是确保可重复构建的良好实践。
回到follower项目,在安装所有依赖项并验证代码按预期工作后,现在可以冻结pip为我们安装的包的最新版本。pip有一个
freeze
命令,用于输出已安装的包及其版本。将该命令的输出重定向到
requirements.txt
文件:
$ pip3 freeze --user > requirements.txt
现在有了
requirements.txt
文件,克隆项目的人可以使用
-r
选项和需求文件名来安装所有依赖项:
$ pip3 install --user -r requirements.txt
自动生成的需求文件格式默认使用精确版本匹配(
==
)。例如,
requests==2.22.0
这一行告诉pip要安装的
requests
版本必须恰好是2.22.0。在需求文件中还可以使用其他版本说明符,如最小版本(
>=
)、版本排除(
!=
)和最大版本(
<=
)。最小版本(
>=
)匹配大于或等于右侧的任何版本,版本排除(
!=
)匹配除右侧版本外的任何版本,最大版本匹配小于或等于右侧的任何版本。
可以在一行中使用逗号分隔多个版本说明符:
requests >=2.22.0,<3.0
pip安装需求文件中指定的包时,默认会从PyPI获取所有包。可以通过在
requirements.txt
文件顶部添加以下行来覆盖PyPI的URL(https://pypi.org/simple/):
--index-url http://pypi.mydomain.com/mirror
搭建和维护自己的私有PyPI镜像需要付出不少努力。当只需要修复项目依赖的bug或添加功能时,覆盖包源而不是整个包索引更有意义。
4.4 升级项目依赖
使用pip将项目依赖项升级到PyPI上发布的最新版本相当简单:
$ pip3 install --upgrade –user -r requirements.txt
在验证使用
requirements.txt
安装的最新版本依赖项不会破坏项目后,可以将它们重新写入需求文件:
$ pip3 freeze --user > requirements.txt
确保冻结操作没有覆盖需求文件中的任何覆盖项或特殊版本处理。撤销任何错误并将更新后的
requirements.txt
文件提交到版本控制。
4.5 处理版本兼容性问题
在某些时候,升级项目依赖项可能会导致代码出错。新的包版本可能会引入回归问题或与项目不兼容。需求文件格式提供了处理这些情况的语法。假设项目中一直使用
requests
的2.22.0版本,现在发布了3.0版本。根据语义版本控制的惯例,主版本号的递增表示
requests
3.0版本包含对该库API的重大更改。可以这样表达新的版本需求:
requests ~= 2.22.0
兼容版本说明符(
~=
)依赖于语义版本控制,兼容意味着大于或等于右侧版本且小于下一个主版本号(例如,
>= 1.1
且
== 1.*
)。之前也可以更明确地表达相同的
requests
版本需求:
requests >=2.22.0,<3.0
如果一次只开发一个Python项目,这些pip依赖管理技术可以很好地工作。但很可能会在同一台机器上同时处理多个Python项目,每个项目可能需要不同版本的Python解释器。仅使用pip处理多个项目的最大问题是,它会将所有包安装到特定Python版本的同一用户site-packages目录中,这使得很难在不同项目之间隔离依赖项。
pip与Docker结合用于部署Python应用程序效果很好。可以将pip添加到基于Buildroot或Yocto的Linux镜像中,但这仅能实现快速的板载实验。像pip这样的Python运行时包安装器不适合Buildroot和Yocto环境,在这些环境中,希望在构建时定义嵌入式Linux镜像的全部内容。pip在Docker等容器化环境中工作得很好,在这些环境中,构建时和运行时的界限通常很模糊。可以使用
pip freeze
生成的
requirements.txt
文件来指导从
meta-python
层为自己的层配方选择依赖项。Buildroot和Yocto都以系统范围的方式安装Python包,因此接下来要讨论的虚拟环境技术不适用于嵌入式Linux构建,但它们确实可以更轻松地生成准确的
requirements.txt
文件。
5. 管理Python虚拟环境
仅使用pip管理多个Python项目的依赖时,会将所有包安装到同一用户site-packages目录,难以隔离不同项目的依赖。而Python虚拟环境可以解决这个问题,下面介绍使用
venv
管理Python虚拟环境。
5.1
venv
简介
venv
是Python标准库中的一个模块,用于创建和管理轻量级的虚拟环境。它可以为每个项目创建独立的Python环境,包含独立的Python解释器和包安装目录,避免不同项目之间的依赖冲突。
5.2 创建和激活虚拟环境
在Ubuntu系统上,若要使用
venv
,需先确保已安装:
sudo apt install python3-venv
创建一个名为
myenv
的虚拟环境:
python3 -m venv myenv
激活虚拟环境:
- 在Linux或macOS上:
source myenv/bin/activate
- 在Windows上:
myenv\Scripts\activate
激活虚拟环境后,命令行提示符前会显示虚拟环境的名称,此时安装的包会被安装到虚拟环境的site-packages目录中,而不是系统的site-packages目录。
5.3 在虚拟环境中安装和管理包
在激活的虚拟环境中,可以使用
pip
正常安装、升级和卸载包,例如安装
requests
包:
pip install requests
查看虚拟环境中已安装的包:
pip list
当不再需要某个包时,可进行卸载:
pip uninstall requests
5.4 退出虚拟环境
当完成项目工作,需要退出虚拟环境时,执行以下命令:
deactivate
退出虚拟环境后,命令行提示符会恢复正常,此时安装的包会回到系统的site-packages目录。
6. 使用conda安装预编译二进制文件
6.1 conda简介
conda是一个通用的跨平台包和虚拟环境管理器,它不仅可以管理Python包,还可以管理其他语言的包和依赖项。conda提供了丰富的预编译二进制包,安装速度快,并且可以方便地创建和管理虚拟环境。
6.2 安装Miniconda
Miniconda是conda的最小化安装程序,只包含conda及其依赖项。在安装Miniconda之前,不要安装它,因为它会干扰早期依赖系统Python安装的pip练习。
在Linux系统上安装Miniconda的步骤如下:
1. 下载Miniconda安装脚本:
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
- 运行安装脚本:
bash Miniconda3-latest-Linux-x86_64.sh
-
按照安装向导的提示完成安装,安装过程中会提示是否将conda添加到系统环境变量中,选择
yes。 - 安装完成后,关闭并重新打开终端,使环境变量生效。
6.3 使用conda创建和管理虚拟环境
创建一个名为
mycondaenv
的Python 3.8虚拟环境:
conda create -n mycondaenv python=3.8
激活虚拟环境:
conda activate mycondaenv
在虚拟环境中安装包,例如安装
numpy
:
conda install numpy
查看虚拟环境中已安装的包:
conda list
当不再需要某个包时,可进行卸载:
conda uninstall numpy
退出虚拟环境:
conda deactivate
6.4 conda与pip的比较
| 比较项 | conda | pip |
|---|---|---|
| 包管理范围 | 支持多种语言的包和依赖项 | 主要用于Python包管理 |
| 包来源 | 有自己的包仓库,包含大量预编译二进制包 | 主要从PyPI获取包 |
| 虚拟环境管理 | 方便创建和管理虚拟环境 |
本身不具备强大的虚拟环境管理功能,需借助
venv
|
| 依赖解决 | 能更好地解决复杂的依赖问题 | 依赖解决能力相对较弱 |
7. 使用Docker部署Python应用程序
7.1 Docker简介
Docker是一个用于构建、部署和运行软件的容器化工具。它可以将应用程序及其依赖项打包成一个独立的容器,从而实现应用程序在不同环境中的快速部署和运行。
7.2 使用Docker部署Python应用的步骤
7.2.1 创建Dockerfile
在项目根目录下创建一个名为
Dockerfile
的文件,内容如下:
# 使用Python官方镜像作为基础镜像
FROM python:3.8-slim
# 设置工作目录
WORKDIR /app
# 复制项目文件到工作目录
COPY . /app
# 安装项目依赖
RUN pip install --no-cache-dir -r requirements.txt
# 暴露应用程序的端口
EXPOSE 8000
# 定义启动命令
CMD ["python", "app.py"]
7.2.2 构建Docker镜像
在包含
Dockerfile
的目录下,执行以下命令构建Docker镜像:
docker build -t mypythonapp .
其中,
-t
选项用于指定镜像的名称和标签,
.
表示使用当前目录作为构建上下文。
7.2.3 运行Docker容器
构建完成后,运行Docker容器:
docker run -p 8000:8000 mypythonapp
-p
选项用于将容器内部的端口映射到主机的端口,这里将容器的8000端口映射到主机的8000端口。
7.3 Docker与其他部署方式的比较
| 部署方式 | 优点 | 缺点 |
|---|---|---|
| 直接部署 | 简单直接,无需额外工具 | 依赖管理困难,环境配置复杂 |
| 使用pip和虚拟环境 | 可以隔离项目依赖 | 不同环境配置可能不一致,部署过程繁琐 |
| 使用Docker | 环境隔离性好,部署快速,可移植性强 | 占用一定系统资源,学习成本较高 |
7.4 部署流程mermaid图
graph LR
A[创建Dockerfile] --> B[构建Docker镜像]
B --> C[运行Docker容器]
总结
本文围绕嵌入式开发中的电源管理和Python应用打包展开。在电源管理方面,介绍了Linux的四个主要电源管理组件,包括CPUFreq、CPUIdle、Runtime pm和系统睡眠模式,了解了如何确保电源管理配置正确。在Python应用打包方面,详细探讨了多种打包和部署方法:
- 追溯了Python打包的起源,了解了distutils和setuptools的特点和使用方法,以及如何编写setup.py脚本。
- 学习了使用pip安装、管理和升级Python包,以及如何使用requirements.txt文件固定和管理项目依赖。
- 掌握了使用
venv
和conda创建和管理Python虚拟环境,解决了多项目依赖隔离的问题。
- 学会了使用Docker将Python应用程序及其依赖项打包成容器,实现快速部署。
通过这些技术和方法,可以更高效地进行嵌入式开发,确保项目的可重复性和稳定性,同时提高开发和部署的效率。在实际应用中,可根据项目的具体需求和场景,选择合适的电源管理策略和Python应用打包部署方式。
超级会员免费看
63

被折叠的 条评论
为什么被折叠?



