稀有猿诉

十年磨一剑,历炼出锋芒,说话千百句,不如码二行。

Python Runtime Environment

Python是非常流行的通用编程语言,因其简洁和易读而广受喜爱,近年来由于深度学习的崛起更是让Python登顶为最受欢迎的编程语言。虽然已使用Python写了一些程序,但总是缺少深度的理解,加之前段时间因为升级pip导致一些依赖的module无法正常工作,于是要停下来把Python到底是如何工作的了解清楚。

注意:这不是一个基础的教程,也不是一个系统的教程,假定读者有一定的编程开发经验,并且已有Python基础。

Python解释器

这是Python语言中最重要的东西,它负责运行你写的代码,把我们的代码作为输入塞给Python解释器,就能得到我们期望的输出。

注意:本文中不纠结编译与解释的区别,以及说Python到底会不会把源代码进行编译。

现代比较流行的操作系统如Ubuntu LTS或者Mac OX都会预装Python,原因在于一方面Python太过流行,另外就是操作系统本身也会用到Python。

但需要特别注意的是,大Python有两个版本Python2和Python3,且并不兼容,稍学习过Python的都应该知道,现在Python2已经停止维护了,Python社区现只管Python3了,并且以后再说Python其实都是指Python3。本文当中除非特别说明,否则也都是Python3。

虽然说Python2已经停止维护了,但是由于历史的原因很多三方的库未能及时升级到Python3,所以现在的操作系统中仍会把两个版本的Python都集成,Python2的版本一般会是2.7.x,而Python3的版本一般会是3.7.x或者3.8.x。反正不会是最新的3.11或者3.14。

操作系统默认的命令python一般都仍指向Python2,而命令python3才是指向Python3。所以在使用解释器或者指定解释器的时候要注意。

注意:操作系统一般把程序安装某个位置,然后在系统路径中只添加指向其安装位置的二进制文件的链接,对于有兼容性不同版本的命令更是如此,比如/usr/bin/python,这是默认的命令,它是一个指向Python 2.7.x安装位置的一个链接。同理/usr/bin/python3也是一个链接。

可以用which命令来查看这些命令的最终指向:

1
2
➜  ~ which python3
/Library/Frameworks/Python.framework/Versions/3.7/bin/python3

需要特别注意的是,操作系统自带的Python一般不是最新的,所以有时候可能有会升级的需要,一般用软件包管理工具如apt-get 或者brew可以安装新版本的Python3,如3.11,但一般建议把它安装在一个不同的位置,也就是说不要直接覆盖操作系统预装的版本。并且,不要把默认链接python3指向新版本。这是因为,操作系统之所以选择特定的版本,是为了兼容性和稳定性,这个版本能保证操作系统的Python代码和库都能正常运行。如果我们用新版本覆盖了原装的,或者把默认的链接替换掉,有可能会对操作系统造成伤害,轻则发生奇怪的事情,重则可能要重装系统。

交互式环境

直接启动解释器会进入一个叫做REPL(Read Evaluate Print Loop)的环境,这是脚本语言都会有的一个环境,它的是意思读取指令(也即我们输入的Python语句),执行,打印输出,然后循环上述步骤。直到给一个退出命令(exit())才会终止。

另外一个更为高级一些的交互式Python运行环境就是非常著名的Jupyter

命令行

在Python世界里模块Modules是一个代码管理的基本单元,内置的库和三方的库都包含一个或者多个Module。Python有一个特殊功能,就是可以用命令行直接执行一个Module:

1
python3 -m http.server 8000

这就启动了一个非常简单的HTTP server。它的作用就是把一个叫做http.server的Module直接运行起来。

源代码式

更多的时候我们会把Python代码写入一个文件,然后再塞给解释器去执行。写一个简单的hello.py:

1
2
#!/usr/bin/env python3
print('Hello, world')

然后,运行它:

1
2
python3 hello.py
Hello, world

也可以直接执行hello.py

1
2
./hello.py
Hello, world

这里的区别就在于,前面加了解释器的,就会用命令行中的解释器去运行。而直接执行脚本文件,则会用第一行的叫做shebang的东西来找解释器,这里就是/usr/bin/env python3,这个意思是说用操作系统中的路径中的python3来作为解释器(作为执行这个脚本的命令)。

那么,利用shebang就可以指定特定的Python版本来运行脚本了。比如:

1
#!/usr/local/opt/python@3.11

环境变量

解释器在运行代码的时候用到一些库,核心module以及三方module,那么解释器是如何找到这些东西的呢?

  • PYTHONHOME 这是解释器的安装目录,通常用作module搜索的前缀来使用
  • PYTHONPATH 这是模块module的搜索目录,默认的是sys.path,这个是由Python预定义好了的,通过PYTHONPATH指定的搜索目录会加在sys.path的前面。所以如果想指定额外的Module目录或者想替换掉默认的module时就可以使用此变量。

除了一些解释器需要的底层的动态库之外,最重要的就是模块module目录了,默认的module搜索目录由sys.path定义,可以通过两种方式查看它有哪些路径:

1
2
3
4
5
6
7
8
9
10
11
➜  ~ python3 -m site
sys.path = [
    '/Users/alexhilton',
    '/Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip',
    '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7',
    '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload',
    '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages',
]
USER_BASE: '/Users/alexhilton/Library/Python/3.7' (doesn't exist)
USER_SITE: '/Users/alexhilton/Library/Python/3.7/lib/python/site-packages' (doesn't exist)
ENABLE_USER_SITE: True

或者通过代码:

1
2
3
4
5
6
7
8
  ~ python3
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 16:52:21)
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print(sys.path)
['', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages']
>>> exit()

可以看到,它会包含当前路径,以及预定义的一些目录。重点看site-packages这个目录,这是所有第三方module安装的目录,Python的包管理工具pip也都是安装在些位置的。所以说,用pip安装完成后,不用特殊指定目录就能找到module。

解决多版本的site-pakcages冲突

每一个解释器都能找到它的site-packages,换句话说如果装了不同版本的Python,就会有多个site-packages路径。就比如,在我的电脑上面有三个版本的Python:

  • Python2 系统中的默认python指向Python2 /usr/bin/python 2.7.16 site packages: ‘/Library/Python/2.7/site-packages’,

  • Python3.7 系统带有Python3,由命令/usr/local/bin/python3指向 /Library/Frameworks/Python.framework/Versions/3.7/bin/python3 site packages ‘/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages’,

  • Python3.11 由brew升级安装得到的。被安装在 二进制是在:/usr/local/opt/python@3.11 site-packages /usr/local/lib/python3.11/site-packages

那么,假如想要把三个site-packages里面的module都得到使用的话,就可以用PYTHONPATH来把三个路径都加进来。

IDE(PyCharm)

对于集成开发环境(IDE)来说,也是可以配置的,几乎都可以配置从解释器,到环境变量都是可以配置的

pip

pip是Python的包管理工具,可以用它来安装Python生态中的第三方module,可以理解为apt-get之于Ubutu,brew之于Mac,只不过pip能安装都是Python的module。基础的使用教程可以看这里

注意:pip是与Python版本绑定的,比如pip对应于Python2,pip3对应Python3,因为它是包管理工具,它下载的module都会安装在对应的site-packages里面,而从前面的讨论知道每个解释器是有自己的site-packages,因此pip要对其解释器对应,这样才能安装到正确的位置上。

这样就会有问题,比如安装了多个版本,那么最后一个安装的Python版本的pip会把之前的都覆盖掉。比如我的电脑上最后用brew安装了Python 3.11,然后pip就被替换成了python3.11的,且pip与pip3是一样的了,它安装的module都是在Python 3.11的那个site-packages里面。

1
2
3
4
➜  ~ pip3 --version
pip 23.1.2 from /usr/local/lib/python3.11/site-packages/pip (python 3.11)
➜  ~ pip --version
pip 23.1.2 from /usr/local/lib/python3.11/site-packages/pip (python 3.11)

注意,因为/usr/local/bin/python3 仍链向了系统预装的python3.7,所以3.11并未有真正的安装成功,最后的系统链接并未有完全修改掉,比如python3仍指向预装的3.7。但比较神奇的是pip被替换为了3.11的。而且,它的site位置也与预装 的不一致了。因为我之前已经用pip3安装了很多三方Module,安装了Python3.11后突然报错找不到module了,原因就在于,现在pip认识的site 位置(3.11的),只有比较少的库,而原来的,仍在原来的位置。

Virtual Environment

由于可能有多个Python版本,会有不同的site-packages,可能会比较混乱,因此就有了虚拟运行环境这一个的技术,它可以为某一个项目,或者某一类项目设定一个虚拟运行环境,在这个环境里面可以安装任何module,它与宿主操作系统,和其他虚拟环境都是隔离的,互不影响。

虚拟环境还能解决依赖地狱问题,比如A项目需要依赖一个1.0版本的module C,但B项目却需要依赖1.4版本的module C,这时如果都在操作系统本机环境去折腾就相当麻烦,也极容易打破项目的稳定性,甚至会影响操作系统的稳定性。这时虚拟环境就能很好的解决这个问题。

venv

这是官方推荐的虚拟环境管理器,从Python 3.3版本就变成官方的标准module了,从Python3.6以后就是官方推荐的虚拟环境管理器。它的优点很明显,就是不需要额外安装了,且是官方支持的。缺点也很明显,就是不够强大,在软件国度里一般官方的东西都是方便易用,但不够强大。

virtualenv

这个是最为流行的虚拟环境管理器,它出现的非常早,功能也非常的强大,官方的venv其实相当于virutalenv的一个简化版本。甚至这个不单单能隔离Python,也能当成一个系统级别的虚拟环境来使用。

如果项目不是特别多特别复杂的话,其实用官方的venv就够了,毕竟不用折腾,直接拿过来就用了,并且大多数情况下也够用了。但如果项目很多,依赖特别复杂,那还是用virtualenv,毕竟它足够强大。

How it works

需要注意,所创建的虚拟环境里面用的Python版本,就是你运行venv时的Python版本,同时从上面的讨论来看,pip的版本也是与Python相关的,简单来理解,你命令行中默认的python3是3.7的,那么你创建的虚拟环境里面的Python就是3.7的,pip也是3.7的。venv的缺点是不够灵活,它只能使用操作系统中已有的Python版本,并且是在创建环境时就指定好了。

相比之下virtualenv就强大很多了,它能自由指定虚拟环境中的Python版本,甚至是操作系统中还未安装的版本,它的指定方式是在创建环境通过参数-p来指定,当然,如果你不指定,它也是用运行virtualenv所使用的那个Python版本。

此外,virtualenv还能继承操作系统中的site-packages,这就更为强大了,可以减少一些非常基础的module的重复安装。总之virtualenv相当强大,建议还是直接上virtualenv。

实在不行就上Docker

其实最为彻底,最为专业的应用级别虚拟化环境就是docker了,venv或者virtualenv仅是隔离Python的依赖环境,一般来说一个应用也会用到Python以外的东西或者影响到运行环境的,即使它是用Python编写的,所以如果 想彻底一些隔离,那就直接上Docker

参考资料

Comments