函数能够让代码复用,学习完第四章的函数内容,我迫不及待的写了一些简单的功能来练习函数,我创建了一个叫math_utils.py的文件,写了如下的代码:
# 加法运算def add(x, y): return x + y# 减法运算def sub(x, y): return x - y# 乘法运算def mul(x, y): return x * y# 除法运算def div(x, y): return x / y# 十进制转二进制def decimal_to_bin(num): if not isinstance(num, int) or num < 0: raise ValueError('仅支持非负十进制计算') return bin(num)# 求和def sum_num(nums): total = 0 for n in nums: total += n return totalif __name__ == "__main__": add_res = add(1, 2) print(add_res) sub_res = sub(2, 1) print(sub_res) mul_res = mul(2, 2) print(mul_res) div_res = div(5, 2) print(div_res) bin_res = bin(2) print(bin_res) sum_res = sum_num([1, 2, 3]) print(sum_res) print('-' * 20)写完这些代码,并一把运行正常,让我有了一些小小的成就感,但这时一个困惑闯进了我的脑海,这代码复用固然很好,但如果只能在这一个文件中用,那我的代码岂不是要无限扩张,不利于阅读和管理,让其他文件也复用就更有意义了。我很快想到了把代码加载到其他文件中,那不就可以让其他文件使用了,于是我又创建了一个math_test.py的文件,并在其中写下了这样的代码:
# 需要使用正确的相对路径或绝对路径with open("ch5/common_utils/math_utils.py", encoding='utf-8') as f: file_content = f.read()exec(file_content)res = add(1, 2)print(res)这么做是可以做到跨文件函数复用的,但这是个野路子,因为这么做会存在一些问题,比如多个文件存在相同的函数名时会存在冲突问题,后加载的文件会覆盖之前文件的函数名,这可能与我们的本意违背。再比如无法精确控制导入问题,一旦加载整个文件就意味着所有的函数都被导入了。还有就是路径依赖问题,读取文件和当前使用文件路径一旦有变就会影响函数加载。实际上使用这种方式加载文件复用函数的问题不止这些,我就不再一一举例了。
很显然,Python有更好的跨文件复用函数的方法,这就是本章我们要学习的模块和包。
在Python中,一个.py的文件就是一个模块。模块里面可以包含变量、函数、类等。
如果说函数帮我们解决的是微观的代码复用问题,那么模块和后面要说到的包是帮助我们解决更为宏观的问题,它能够更好的应对代码规模变大以后的组织、管理、命名冲突等问题。
使用模块导入,我们就能更优雅的使用上面提到的math_utils.py模块了,代码如下:
import math_utilsres = math_utils.add(1, 2)print(res)使用import导入模块,模块名就是文件名。它的基本语法格式是:
import 模块名是不是非常的简洁?而且你可能还会发现它避免了math_utils.py中if __name__ == "__main__":这部分代码的执行。
除了我们使用的import math_utils这种直接将这个模块导入的方式,还有其他3种导入模块的方式。
如果觉得上面的模块名.函数名的调用方式每次都要写完整的模块名太长了,那是不是可以简化一下?当然可以,我们可以给它起个别名,这么写:
import math_utils as mures = mu.add(1, 2)print(res)它的基本语法格式是:
import 模块名 as 别名如果想精确控制导入的函数,比如我现在只想使用add函数,不使用其他函数,那就可以这样写:
from math_utils import addres = add(1, 2)print(res)它的基本语法格式是:
# 导入模块中指定的内容(函数、变量、类),多个使用英文逗号分割from 模块名 import 函数名1,类名1还有一种方式是导入模块中的所有函数,如下:
from math_utils import *res = add(1, 2)print(res)它的基本语法格式是:
from 模块 import *使用*通配符来表示导入所有内容,但这种方式并不推荐使用,因为容易引发不同模块之间的命名冲突,也就是同名覆盖问题。
如果说我现在就是很执着,就想用这种方式导入模块,但又不想一股脑的把所有的内容导入进来,还想有一定的控制,这还有办法吗?
有,我们可以在math_utils.py这个模块的顶部定义__all__,比如我现在在math_utils中控制add和sub函数的导出,就可以这么写:
__all__ = ["add", "sub"]你可能已经不止一次注意到我们在示例中写过这样一行代码:if __name__ == "__main__":。这行代码的作用类似于其他编程语言中的main函数,也就是程序执行的入口,在Python中这行代码其实是具备双重作用的,除了被当做直接运行当前脚本的入口,当作为模块导入时,还可以避免这部分代码被执行。它比较常见的用途是写测试代码。
__name__是模块的属性,它的值是固定的__main__吗?
其实并不是。这要看情况,在当前文件被直接运行时,它的值是__main__,当一个模块被导入到其他文件时,它的值就是模块名,这一点我们可以做一个简单的验证,我们现在可以在math_utils.py中增加一个新的函数:
def print_name(): print(__name__)之后我们可以再创建一个新的文件来调用math_utils模块中的这段代码,如下:
import math_utilsmath_utils.print_name()执行上述代码会输出:math_utils,输出的结果就验证了__name__属性并非一个固定值。
除了__name__,Python中还有几个比较常见的模块属性,我列了一个表格,如下:
__name__ | ||
__file__ | ||
__doc__ | ||
__package__ | ||
__path__ | ||
__spec__ | ||
__dict__ | ||
__loader__ | ||
__cached__ | ||
__version__ | ||
__author__ | ||
__all__ | ||
__builtins__ |
我们在前面写到的math_utils.py属于是自定义模块,在Python中提供了很多内置模块,这些模块是Python解释器中自带的,覆盖了系统交互、数据处理、文件处理、网络通信等多个领域,能够帮助我们完成大部分的基础需求,我们来看一下这些较为常用的内置模块。
在查看这些内置库的时候,我发现了一个有意思的问题,比如在VSCode中追踪os模块的源代码的时候,可以看到它的路径是在Python安装目录的Lib文件夹下,有一个os.py文件存在,但在追踪sys模块时,发现VSCode并没有跳转到对应的sys.py,这是因为Python中的一些内置模块并非都是用Python代码编写的,有一些模块为了更好的性能和底层访问是用C语言编写的。
还有更多的内置模块我们就不再一一列出了,随着我们在开发过程中业务的需要就会逐渐接触到更多。
内置模块提供了比较基础和通用的功能,如果只使用内置模块,有些复杂的业务就需要从零开始编写代码,开发效率比较低,这时候就需要第三方模块登场了。所谓第三方模块,就是非官方开发的模块,可以是个人、组织或社区。使用第三方模块,我们主要需要掌握安装和管理的内容。
Python主流的第三方模块管理工具是pip,这是Python自带的命令。下面我们来看一下管理第三方模块的一些命令:
# 安装pip install 模块名 # 安装模块pip install 模块名==版本号 # 安装指定版本,常用解决兼容性问题pip install -U 模块名 # 升级模块到最新版本# 卸载pip uninstall 模块名 -y # -y表示跳过确认# 查看已安装的模块pip list # 列出所有已安装模块信息和版本pip show 模块名 # 列出指定模块的相关信息,内容会比较多 # 依赖环境管理pip freeze > requirements.txt # 将当前环境所有依赖模块导出到文件pip install -r requirements.txt # 从文件中批量安装依赖安装完第三方模块之后,使用我们上文提到的模块导入方式就可以正常使用了。那这些第三方模块被安装在了什么位置呢?
这就可以通过pip show 命令来查看指定的模块,执行命令后,在输出的内容的最后面的几行会包含有Location字段的内容,它就写明了安装位置,一般情况下,第三方模块安装在Python解释器安装目录的site-packages文件夹下,Windows系统是在Python解释器安装路径的Lib/site-packages文件夹下。
在安装第三方库的时候,有时候因为网络的问题,会出现安装失败或者安装速度极慢的问题,我们可以通过设置国内PyPI镜像源来提升安装速度。
常用的国内镜像源有清华大学、阿里云、豆瓣、中国科技大学、华为云、腾讯云等。镜像源的设置分为单次有效和永久有效两种方式。
我们以在Windows系统下使用清华大学镜像源安装pandas库为例。
单次有效在使用pip安装时可以这么写:
pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple也就是-i参数后加上镜像源地址,这种方式适用于临时安装,如果频繁使用,就会比较麻烦。
永久有效就需要我们写配置文件了,在Windows系统中打开文件资源管理器,在地址栏中直接输入%APPDATA%\pip\回车即可,这个路径实际上就是C:\Users\你的用户名\AppData\Roaming\pip文件夹下,如果pip文件夹不存在,自己手动创建一个,在pip文件夹下创建一个pip.ini,在文件中写如下内容:
[global]index-url = https://pypi.tuna.tsinghua.edu.cn/simple[install]trusted-host = pypi.tuna.tsinghua.edu.cntrusted-host是为了防止因HTTP源或网络问题导致的SSL警告。
配置完成后我们可以在命令行中输入:pip config list来验证是否配置成功,输入命令后会显示类似如下的内容,则表示配置成功了:
global.index-url='https://pypi.tuna.tsinghua.edu.cn/simple'install.trusted-host='pypi.tuna.tsinghua.edu.cn'我们还可以通过pip config list -v命令可以查看配置文件会从哪些位置加载。
MacOS或Linux系统可以在~/.config/pip/pip.conf 或 ~/.pip/pip.conf文件中配置。
pypi.org是官方的第三方模块的仓库,pip安装模块时默认就是从这个仓库下载的,上面我们也介绍了通过配置国内镜像源来加速下载。而且这个官方仓库上还可以手动下载.whl文件,这对于一些无网络环境安装模块会比较友好。
学习了如何管理第三方模块,下面我们来看一下常见的一些第三方模块:
在第三方模块使用这部分内容的最后,我还想聊一下模块卸载的问题,当你安装了一个第三方模块时你会发现它也会有一些模块依赖,当你卸载模块的时候,你会发现它只卸载了你指定的模块,这很合理,因为一个模块的依赖也可能是其他模块的依赖,但有时候你是知道你要删除的这个模块大概率是没有其他模块依赖的,不删除也只能残留在那里占用你的空间,虽然我们可以pip show的命令来查看依赖,但是这样一个一个去删除也很麻烦,甚至有时候误判了还可能删除了你以为没有其他模块依赖实际上还被其他模块依赖的模块,Python当然对这个问题也有解决方案,其中一个方法是使用pip3-autoremove来管理,是这么使用的:
# 安装工具pip install pip-autoremove# 卸载模块并自动删除其未被其他包使用的依赖pip-autoremove 模块名 -y另外一个方法是创建一个虚拟环境,项目结束不需要时可以直接删除整个虚拟环境相关的内容,关于这个内容我们会在文章后面的工程实践部分来具体聊聊。
每一个.py文件是一个模块,那么模块多了如何管理呢?
这就是要提到的包,包可以说是模块的集合,将多个相关的模块放在一个文件夹下管理就形成了包,包中除了包含模块还可以包含子包。
包中一般包含一个__init__.py的文件,在Python3.3以后可以省略,即便这样仍然建议在包中创建一个__init__.py,因为这既可以明确包的边界又可以兼容旧版本。
导入包和导入模块语法基本上是一致的,不太一致的地方是需要将包名也加在路径里。
下面我们写一个例子来更清晰的说明包的使用,在文章的前面我们已经创建了一个math_utils.py的模块,现在我们再创建一个string_utils.py的模块,在这个模块中我们写一个将驼峰命名法转换为下划线命名法的函数,如下:
import redef camel_to_snake(s: str) -> str: """ 将驼峰命名法字符串转换为下划线命名法 例如: "helloWorld" -> "hello_world", "HelloWorld" -> "hello_world" :param s: 驼峰命名的字符串 :return: 下划线命名的字符串 """ if not isinstance(s, str): raise TypeError("输入必须是字符串类型") # 匹配大写字母,在其前添加下划线,最后转小写 s = re.sub(r'(?<!^)(?=[A-Z])', '_', s).lower() return s然后我们以下面这种结构来组织这个包,在__init__.py中什么内容都不写,如下:
ch5 |——common_utils |——init__.py |——math_utils.py |——string_utils.py |——module_test.pystring_utils.py这个模块其实写不写都可以,我们这里是为了体现包中可以包含多个模块。
然后,我们在module_test.py中测试common_utils这个包,导入之后进行函数的简单测试,如下:
import common_utils.math_utils as mures = mu.add(1, 2)print(res)在导入时带上包名就可以正常使用了,但是如果我现在调用common_utils包的.py脚本和它不在同级目录还能行吗?比如现在的结构调整成这样:
ch5 |——common_utils |——init__.py |——math_utils.py |——string_utils.py |——test |——module_test.py如果你再次执行module_test.py会发现有错误提示:ModuleNotFoundError: No module named 'common_utils'。
哎?怎么提示没有这个模块了?
这就涉及到模块的导入机制和模块的搜索机制了,这里我们先提供几种解决方法。
一种是在项目的根目录下创建一个pyproject.toml,这个文件是现代Python项目的统一配置文件,有了它之后就可以构建系统、定义项目元数据、管理依赖等。
我们在ch5这个工程中创建一个pyproject.toml文件,目录结构如下:
ch5 |——common_utils |——init__.py |——math_utils.py |——string_utils.py |——test |——module_test.py |——pyproject.toml在pyproject.toml中配置如下内容:
[project]name = "common_utils"version = "0.0.1"[tool.setuptools.packages.find]where = ["."] # 当前目录找包写完配置文件,然后在终端中通过命令行切换到pyproject.toml的同级目录,也就是项目的根目录,执行pip install -e .,这就和安装第三方包(模块)一样,之后使用方式也和第三方包一样了,不会再出现ModuleNotFoundError的问题,其中-e是editable的缩写,代表可编辑,其中的.表示当前路径。
第二种方法是使用pytest测试框架,使用了这个测试框架之后也可以帮我们避免ModuleNotFoundError问题,如果你在使用Python脚本做模块测试,这种方式其实非常推荐,通过pip install pytest安装完之后,直接在项目的根目录直接运行pytest即可,它会自动执行项目中以test_开头或以_test结尾的.py文件,文件中的函数要以test_开头。
还有一种方式是修改sys.path,它能够解决问题,但通常不推荐这么做,这里就不再深入讨论了。
__init__.py文件的作用我们在前面已经提到过__init__.py可以起到标识包的作用,它还有一些其他的作用:
__all__来定义from package import *导出的内容,还可以简化导入路径。关于简化导入路径我觉得可以详细聊聊,这是一个非常实用的特性。为了便于测试,先将我们之前的代码组织方式调整一下,如下:
ch5 |——common_utils |——init__.py |——math_utils.py |——string_utils.py |——module_test.py在module_test.py中写一个简单的测试common_utils包的代码,如下:
from common_utils.math_utils import addif __name__ == "__main__": print(add(2, 1)) # 输出:3如果我们在__init__.py中写一些处理的代码,就可以简化使用时的导入,代码如下:
# __init__.py中的代码from .math_utils import add# module_test.py中的代码from common_utils import addif __name__ == "__main__": print(add(2, 1))你会发现我们只需要导入common_utils即可,这是将内部的模块或函数提升到了顶层,如果包中包含子包,在使用子包时简化效果会更明显,这对于使用者来说更加方便。
我们在使用import导入模块时,Python解释器是如何搜索模块的?
它是通过以下步骤进行搜索的:
步骤1:先检查缓存模块,这是最快的方式,缓存模块存储在sys.modules中,查找不到会进入步骤2。
步骤2:查找内置模块。sys,math等都属于Python的内置模块,内置模块列表可以通过sys.builtin_module_names来查看。
说到内置内置模块这里让我想起了另外一个问题,就是标准库和内置模块,这两个概念实际上是有一些区别,Python解释器出厂自带的都属于标准库,而内置模块可以说是标准库中更核心的那一部分,如果不好区分,我们可以通过是否在sys.builtin_module_names来验证或者可以通过模块的__file__属性来验证,打印内置模块的__file__属性一般会有异常提示。想到这个问题是因为开始我也误以为os是内置模块,实际上它不是,它只是标准库。
步骤3:如果前面两个步骤都没有搜索到,那就要进入构建模块搜索路径了,解释器会根据sys.path中的列表进行搜索。在这个过程中也有搜索的优先级。
首先,搜索当前执行脚本的目录,其次搜索配置的PYTHONPATH环境变量,再次搜索Python标准库的安装目录(Lib文件夹)。最后搜索site-packages目录,这是第三方目录存放位置。
步骤4:找到模块后加载模块,并将模块放入到sys.modules中缓存,以便后续导入使用。
步骤5:如果最终没找到就会抛出ModuleNotFoundError异常,提醒导入失败。
之所以这一小块内容也要单独写一下是因为我在测试PYTHONPATH环境变量的设置过程中遇到了一些问题,所以觉得有必要写一下。我这里只讲一下Windows系统下的环境变量设置,其他系统类似,只是设置的位置不太一样。
在Windows中通过搜索应用搜索“环境变量”,找到“编辑系统环境变量”,点击进入环境变量设置,我是在用户变量中设置的,我觉得这个已经足够了,环境变量名就叫PYTHONPATH,变量值就是我们需要设置的模块的绝对路径。测试代码的结构是这样的:
ch5 |——common_utils |——init__.py |——math_utils.py |——string_utils.py |——test |——init__.py |——module_test.py我刚开始设置了PYTHONPATH环境变量之后,还是会出现ModuleNotFoundError异常,出现错误因为变量的路径我设置到了common_utils这一级,实际上是需要设置到ch5这一级文件夹路径就可以了。我们可以通过打印sys.path或者os.getenv('PYTHONPATH')来验证环境变量是否设置成功。
我们也可以使用sys.path.append()函数添加模块路径来达到同样的效果,这个路径也只需要设置到ch5文件夹这一级就可以了。
虽然设置环境变量可以达到在同一个项目中从一个模块调用其他模块的效果,但还是比较建议使用pyproject.toml配置或pytest的方式,这样会更标准和规范。
__pycache__与.pyc文件在执行过模块中的代码之后,我们会发现产生了一些__pycache__的文件夹,在这个文件夹中包含一些.pyc文件。看到带有cache就大概知道这是一种缓存,.pyc是Python Compiled File的缩写,它是解释器将.py文件编译的字节码文件,字节码是介于源代码与机器码之间的一种中间代码,能被Python解释器识别和执行。__pycache__文件夹就是专门来存放.pyc文件的。这样做的核心目的是减少重复编译的时间,提升程序运行效率。这些文件是可以删除的,但在下次运行时仍会产生。
在实际的开发中,尤其是多人协作的项目,会通过一些源代码版本管理工具(如Git)来管理我们的代码,在提交代码时,这样的文件是没必要提交的。
在Python的模块化编程中,这是一个比较容易踩到的坑,所谓循环导入,举一个简单的例子,a.py模块中导入了b.py模块,而在b.py模块中又导入了a.py,形成了依赖的闭环,在执行脚本时会引发ImportError或AttributeError异常。
导致这种问题一个很可能的原因是模块之间的职责设计不合理,职责不够单一,依赖不清晰。解决的方法也不止一种,比较推荐的是重构代码,拆分公共逻辑,让模块做到职责单一,也就是说模块职责设计好了就能很大程度上避免这类问题。
关于模块导入的部分还有一个绝对导入与相对导入的概念,这里也简单说一下。
绝对导入指的是从项目的顶层目录开始,写出模块完整的路径,形式是这样的:
from <package>.<subpackage>.<module> import <name>相对导入是使用点(.)来表示当前路径或上级目录,比如一个点(.)表示当前路径,两个点(..)表示上一级目录,以此类推。形式是这样的:
from .<module> import <name>from ..<module> import <name>在项目开发中,一套设计良好的目录结构至关重要,这对项目的可维护性、协作效率和扩展性都有重要的作用。虽然Python项目并没有像一些编程语言有比较固定的要求,但社区中也有广泛认可的结构,推荐使用src布局,下面列一个比较典型的目录结构,实际的项目中根据实际要求可能略有差异,但一般遵循src布局,也就是src放源代码,tests放测试代码,系统配置,其余还有一些文档、数据之类的内容,如下:
my_project/ # 项目根目录├── src/ # 源代码目录(推荐 "src" 布局避免导入冲突)│ └── my_package/ # 主包目录(与项目同名)│ ├── __init__.py # 标识为 Python 包│ ├── core.py # 核心模块│ ├── utils.py # 工具模块│ └── submodule/ # 子模块│ ├── __init__.py│ └── helpers.py │├── tests/ # 单元测试目录(pytest 常用)│ ├── __init__.py│ ├── test_core.py│ └── test_utils.py│├── docs/ # 文档目录(Sphinx/MkDocs)│ └── index.md│├── scripts/ # 可执行脚本(非模块代码)│ └── deploy.sh│├── data/ # 静态数据文件(可选)│ └── sample.csv│├── .gitignore # Git 忽略规则├── pyproject.toml # 构建系统配置(PEP 518,现代项目首选)├── setup.cfg # 包元数据(替代 setup.py)├── requirements.txt # 依赖列表(或用 Pipenv/Poetry)├── README.md # 项目说明└── LICENSE # 开源协议对于Web项目,比如使用Django框架,它可以创建框架自带的目录结构,相对固定。这里不在继续深入讨论,等后面写到Web开发相关的文章时会有介绍。
做好依赖管理能够让项目从开发到部署更加可控和稳定,也能够降低协作开发的门槛,否则很容易出现“在我的电脑上运行正常啊”而在别人的电脑上运行起来总是各种问题。即便是一个小型项目也有必要做好依赖管理,除非说我们做这个项目就是自己玩,不考虑其他的。下面我们来介绍几种比较主流的依赖管理方案。
这是最基础的依赖管理方式,本质上是纯文本文件记录,每行记录一个依赖的包及版本。它的呈现方式是这样的:
colorama==0.4.6 iniconfig==2.3.0 packaging==25.0 pip3-autoremove==2.0.1使用方式也很简单,如下:
# 生成依赖文件 pip freeze > requirements.txt # 安装依赖 pip install -r requirements.txtrequirements.txt管理依赖库的方式固然简单,但现代Python项目中更推荐使用pyproject.toml作为统一的配置入口文件,它也能管理项目依赖。
Poetry是一个现代化的依赖管理和打包工具,它是一个集成了依赖管理、虚拟环境管理、项目打包、发布的一站式解决方案。它网站上的口号是:“Python packaging and dependency management made easy”。推荐的安装方式是:
# Linux, macOS, Windows (WSL) curl -sSL https://install.python-poetry.org | python3 - # Windows (Powershell) (Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -当然也有其他的安装方式,你可以到官方文档上去了解。安装完成之后可以用poetry --version来验证是否已经可以正常使用,正常会输出类似这样的内容Poetry (version 2.2.1)。如果不能输出可能要配置环境变量,在安装的过程中命令行中也有输出提示,按照提示来做即可。
如果想卸载Poetry,可以执行这样的命令:
# Linux, macOS, Windows (WSL) curl -sSL https://install.python-poetry.org | python3 - --uninstall # Windows (Powershell) (Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py - --uninstall如果怕删除不干净,可以通过命令来查看,在Windows环境下执行where poetry就可查看poetry的安装路径,还有一部分是虚拟环境,直接执行删除,虚拟环境的内容可能不会被删除,我们在后面虚拟环境的部分再继续来聊如何删除。
下面我们来看一下它的基本用法:
# 初始化项目(poetry-demo是自定义的项目名) poetry new poetry-demo # 添加依赖(requests,pytest都是依赖库的名字) poetry add requests poetry add pytest --group dev # 分组管理 # 移除依赖(requests是依赖库名) poetry remove requests # 安装所有依赖 poetry install # 运行脚本(script.py是自己想运行的脚本) poetry run python script.py如果你使用poetry进行了项目的创建测试,会发现它产生的项目目录是按照src布局的方式创建的,结构如下:
poetry-demo ├── pyproject.toml ├── README.md ├── src │ └── poetry_demo │ └── __init__.py └── tests └── __init__.pyuv是一个由Rust编写的Python包和项目管理工具。它号称依赖解析速度比pip快10-100倍,而且可以替代pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv等工具,它官网有一句介绍是这样的:“An extremely fast Python package and project manager, written in Rust.”。我们可以通过下面的一些命令进行安装:
# Windows(PowerShell命令窗口) powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" # macOS,Linux curl -LsSf https://astral.sh/uv/install.sh | sh # 除了curl,也可以使用wget命令 wget -qO- https://astral.sh/uv/install.sh | sh # 也可以使用pipx和pip(这两种方式和系统无关) pipx install uv pip install uv在Windows下使用PowerShell安装,如果你的系统中有杀毒软件,有可能会阻止安装,这个允许即可,还有就是它自动下载的脚本实际上是从GitHub上下载的,如果你的网络访问不了GitHub就会安装失败。
PowerShell命令行方式安装是官方推荐的方式,在官方文档中给出了一个理由是,如果使用pip方式安装,在某些平台上可能安装失败,然后就要从源码构建,但这个过程需要Rust工具链,这种情况下用户所使用的平台上可能不具备这种能力。还有一些解释说使用PowerShell属于独立安装,不依赖Python,能够完全发挥高性能的优势。总之根据自己的实际情况安装即可。
安装完之后按照提示将提示的路径手动或者运行它给的命令加入到PATH环境变量即可,这个是加入到了用户环境变量下。之后,我们可以通过uv -V来验证安装情况,如果输出了uv的版本号则表明安装成功,否则就是失败了,需要排查问题了。Windows下uv命令的默认路径是C:\Users\你的用户名\.local\bin。
如果想卸载uv,我们以Windows环境为例,可以在PowerShell命令窗口中执行以下一些命令:
# 清理UV缓存 uv cache clean # 删除UV管理的Python版本目录(若存在,不存在会有找不到路径之类的提示) rm -rf "$(uv python dir)" # 删除UV工具目录(若存在,不存在会有找不到路径之类的提示) rm -rf "$(uv tool dir)"之后可以到C:\Users\你的用户名\.local\bin这个路径下清空uv相关的exe文件,还有就是环境变量中的当前用户环境变量的PATH下清除环境变量。
接下来我们来看一下它的基本用法:
# 创建项目(uv-demo是自定义的项目名) uv init uv-demo # 添加依赖库(requests,pandas,pytest是依赖库名,可以一次安装多个) uv add requests pandas uv add pytest --dev # 卸载依赖库(requests是依赖库名) uv remove requests # 运行脚本(main.py是要运行的脚本) uv run python main.py如果你使用uv工具来创建项目得到项目目录结构是这样的:
uv-demo ├── pyproject.toml ├── README.md ├── main.py ├── .python-version └── .gitignore它不像Poetry直接生成的是src布局的目录,如果你希望这样,可以生成目录之后自己来创建即可。
除了poetry,还有PDM,Hatch,Conda等依赖管理工具,这里就不再一一详细介绍了,有兴趣可以自行研究。
不同的项目很可能会依赖不同的版本的库,而pip的安装默认会把同名的库覆盖掉,也就是只保留了一个版本的库,即便你通过各种手段实现了可以同时共存多个库,Python解释器也未必会按照你所想的版本去加载,它有可能只加载找到的第一个或者最新的一个,如果在系统中只有一个Python解释器环境,面对这样的问题操作起来就会变得复杂,难以维护,而Python给出了一个解决方案就是虚拟环境,我们可以为每个项目创建一个独立的Python运行环境,这既保护了系统的(也就是全局的)Python环境,又避免了版本冲突的问题。
虚拟环境的本质是为每个项目创建独立的site-packages目录和 Python 解释器环境。这在项目开发中非常有必要,我们也非常推荐为每个项目创建自己的虚拟环境。
下面我们就来看看如何创建虚拟环境,这里我们介绍三种方式,一种是Python自带的venv工具,一种是我们前面聊到的Poetry,还有一种是uv。
在VSCode中创建虚拟环境非常简单。我们在VSCode中创建了项目目录和Python文件之后,将鼠标点击到任意一个.py文件,你会发现在VSCode的右下角有Python版本的标签,比如我们现在使用的是3.14.4版本,则会在右下角看到一个3.14.4的标签,点击它,就会在上方有一个弹出框,弹出框中有一个选项是Create Virtual Envirenment...,点击下一步之后继续选择它弹出的Venv Creates a .venv virtual envirenment in the current workspace,之后会让你选择Python的全局环境,后面标记有Global,选择它即可。
如果你项目中已经存在了requirements.txt文件,它会推荐你选择这个文件安装依赖,这个看自己需求,如果不想选,直接点击OK即可。等它执行完就成功创建了一个虚拟环境,你会发现在项目的根目录中多了一个.venv文件夹。
接下来我们可以启用这个虚拟环境,在VSCode中我们切换到控制台的TERMINAL标签,然后在右侧的+符号中选择下拉,创建一个Command Prompt,默认会选择powershell,这个需要经过一些设置才能执行脚本,所以就直接选择这个Command Prompt,也就是cmd命令行。在此时的控制台中切换到项目目录的.venv\Scripts文件夹下,执行activate或activate.bat命令就激活了虚拟环境,进入虚拟环境后,后续我们的项目就可以在虚拟环境中安装依赖库或执行代码了。退出虚拟环境可以执行执行deactivate.bat脚本。
通过VSCode是用可视化的方式创建虚拟环境,venv是Python自带的工具,命令行的方式也非常方便,下面我们看一下虚拟环境相关的操作:
# 1. 创建虚拟环境(假设项目目录为venv_project)cd venv_projectpython -m venv .venv # 最后一个.venv是虚拟环境目录名,通常命名为.venv# 2. 切换到.venv\Scripts目录下,激活虚拟环境# Windows(cmd):activate# Mac/Linux:source activate# 激活后命令行开头会出现(venv),表示已进入虚拟环境# 此时安装的库仅属于该环境pip install 模块名# 3. 导出依赖列表pip freeze > requirements.txt# 4. 退出虚拟环境(Windows)deactivate.bat# 5. 其他人拿到项目后,创建并激活虚拟环境,一键安装依赖pip install -r requirements.txtPoetry会自动创建虚拟环境,使用poetry new 项目名创建一个项目后,切换到项目的根目录,执行poetry install会自动创建虚拟环境。Windows下的默认目录在C:\Users\你的用户名\AppData\Local\pypoetry\Cache\virtualenvs路径下。
我们可以在已经使用Poetry创建的项目的根目录下执行poetry env info命令来查看虚拟环境的信息。我们在删除Poetry时,虚拟环境不会被自动删除,可以通过查看虚拟环境位置来删除。
如果我们想让Poetry创建的虚拟环境在本项目中而不是在它默认指定的路径下,可以通过以下命令来设置:
# 设置使用项目内的虚拟环境,设置为false则恢复为默认路径 poetry config virtualenvs.in-project true # 输出应显示:virtualenvs.in-project = true # Windows下 poetry config --list | findstr virtualenvs.in-project # Linux下 poetry config --list | grep virtualenvs.in-project现在我将虚拟环境配置到了项目内,我想删除掉默认路径下的虚拟环境,毕竟C盘空间可是很宝贵的,那就可以用下面的命令来删除:
# 列出当前项目的虚拟环境(默认路径) poetry env list # 删除旧环境(替换为实际环境名称) poetry env remove <旧环境名称>使用uv创建虚拟环境也非常的简单,先切换到项目的根目录,也就是和pyproject.toml在同级目录,然后执行uv venv的命令或者通过uv run main.py运行其中的Python文件也会自动创建,它自动创建的虚拟环境目录就在当前项目下,不需要配置。
编写了一定功能的模块/包,往小了说是有利于自己项目代码维护和复用,如果功能比较通用,能够解决帮助公司其他同事或项目组解决问题,也可以考虑搭建私有的仓库。如果你还想让更多的人用到你的代码,那就可以上传到PyPI,让全世界的开发者都可以pip install你的库,这将是一件非常有成就感的事情。
如果你准备发布你的模块,那更应该组织好你的目录结构,发布的大致流程是这样的:写代码 -> 配pyproject.toml -> python -m build -> twine upload。这个过程中需要你有PyPI账号,没有可以注册。如果你使用了Poetry之类的工具,这个过程将会更加简单。
我在这里只起到抛砖引玉的作用,有兴趣可以自行研究。
至此,这篇文章的主要内容就写完了,或许你已经迫不及待的想创建一个工程来练练手,亦或者因为你从头到尾认真看到这,已经看累了,但,别放弃,练习还是不可少,只有实战才能让我们精进。
我现在想做一个工具包,这个工具包包含两个模块,一个是验证模块,比如可以验证手机号、邮箱是否合法;另一个模块是文件读取工具,比如可以读取txt文件,可以读取csv文件。利用本章学习的内容实现这个工具包吧。当然,如果你有自己想法那就更好了,关键在于将想法实现为代码,开始动手试试吧!