关于 Python、Virtualenv、VSC 集成及其他的一些说明
这是一篇关于 Python、Virtualenv、VSC 集成及其他主题的说明。
背景
Python 被认为是一种易学易用的语言,但本文并非关于 Python 语言。它是关于创建一个隔离环境,以便我们能够确定地运行 Python 程序。
在本文中,我附带了几个简单的 Python 文件。我将使用它们向您展示拥有一个隔离环境来运行 Python 程序的好处。顺便说一句,如果您使用 Ubuntu 风味的 Linux,这个链接 https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa 对您来说是一个很棒的链接。
有多少个 Python?
要运行 Python 程序,我们通常需要在计算机上拥有三个组件。
- 一个 Python 解释器
- 一个 Python 包安装程序(pip)实例,用于安装依赖包
- 一个搜索路径,解释器会在其中查找运行我们程序所需的依赖包
多个解释器
Python 宣称的优势之一是它在所有计算机上都随时可用。这也意味着我们可能在不知情的情况下拥有一个 Python 解释器版本。在我的 Linux Mint 17.2 Cinnamon 64 位系统中,我至少有 3 个 Python 解释器。
当我们安装操作系统时,它会附带一些预安装的 Python 解释器。这些 Python 解释器对于操作系统的正常运行很重要。我们不应该在不完全了解自己在做什么的情况下将其删除。
多个包安装程序
为了让事情更复杂,我们的计算机上实际上有多个 pip 实例。
多个搜索路径
当我们运行 Python 程序时,我们需要确保使用了正确的解释器实例。让我们看看 python-search-path.py 文件。
# https://docs.pythonlang.cn/3/tutorial/modules.html#the-module-search-path
import sys
for item in sys.path:
print(item)
如果我们使用以下命令运行程序,我们可以找到 python
解释器使用的包搜索路径。
python python-search-path.py
如果我们使用 python3.4
解释器运行它,我们将看到一个完全不同的路径。
python3.4 python-search-path.py
现在我们知道计算机上的 Python 版本很复杂。更不稳定的是,我们可能正在使用与操作系统相同的 Python 版本。如果我们更新某个操作系统也使用的包,我们可能会给操作系统带来问题。在最坏的情况下,它甚至可能无法启动。我个人在玩某些 Python 版本后遇到过操作系统问题。
虚拟环境
我们可以努力确保我们使用所需的 Python 解释器。我们也可以努力将包安装到正确的搜索路径中,以使 Python 程序顺利运行。但我们也可以创建一个 虚拟环境,轻松而确定地运行 Python 程序。
- 虚拟环境确保我们使用所需的 Python 解释器。
- 虚拟环境确保我们使用所需的 Python 包安装程序 (pip)。
- 虚拟环境确保我们在环境中安装包,并在运行程序时在环境中查找它们。
物理上,虚拟环境是一个文件夹。这个文件夹包含了运行 Python 程序所需的一切。当我们安装一个包时,它也会被安装到这个文件夹中。
创建虚拟环境
要创建虚拟环境,我们需要安装 virtualenv 工具。在我的计算机上,我使用以下命令安装 virtualenv
。
sudo python3 -m pip install virtualenv
然后我们可以验证安装是否成功。
看起来 virtualenv
独立于任何特定版本的 Python。安装 virtualenv
后,我们可以使用以下命令创建虚拟环境
virtualenv -p /usr/bin/python3.4 environment-3.4
- 在创建虚拟环境时,我们指定了 Python 解释器的执行路径,它将成为我们使用虚拟环境时的 Python 解释器。
- 在创建虚拟环境时,我们指定了一个文件夹名称。它是用于保存虚拟环境的物理文件夹。
激活虚拟环境
要激活虚拟环境,我们可以发出以下命令
source environment-3.4/bin/activate
激活虚拟环境后,我们可以检查 Python 和 pip 的执行路径。
python python-search-path.py
激活虚拟环境后,我们可以看到所有的 python、pip 和搜索路径都位于虚拟环境文件夹中。我们可以使用以下命令停用虚拟环境
deactivate
如果我们要完全删除虚拟环境,只需删除 environment-3.4 文件夹即可。
在虚拟环境中安装包
出于实验目的,我在 python-flask-api.py 文件中创建了一个小的 flask 应用程序。
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/')
def message():
name = 'Song Li'
return jsonify(
username = name,
email = 'song.li@email.com',
)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000)
此应用程序需要 flask
包。激活虚拟环境后,我们可以使用以下命令安装包
python -m pip install flask
安装 flask
后,我们可以运行 python-flask-api.py 文件。
python python-flask-api.py
我们可以通过运行 python-inspect-path.py 文件进一步验证 flask
包是否安装在虚拟环境中。
import inspect
from flask import Flask
print(inspect.getfile(Flask))
创建 virtualenv 的另一种方法是使用 venv
。这是一篇关于 venv 的好文档。在 Ubuntu 中,我们需要手动安装 python3.9-venv
(或相应版本的 venv)。
apt-cache policy python3.9-venv
然后我们可以使用一个简单的命令创建 virtualenv。
python3.9 -m venv "virtualenv-directory-name"
我们可以参考 https://docs.pythonlang.cn/3/library/venv.html 以获取 venv 更高级的用法。
PIP Freeze
在虚拟环境中工作一段时间后,您可能已经安装了许多包。如果您想让另一个人运行您的程序,您需要告诉他们这些包和版本。激活虚拟环境后,您可以发出以下命令
python -m pip freeze > requirements.txt
它会创建一个名为 requirements.txt 的文件,其中包含虚拟环境中所有包的信息。
Click==7.0
Flask==1.0.4
itsdangerous==1.1.0
Jinja2==2.10.3
MarkupSafe==1.1.1
Werkzeug==0.16.1
我们可以使用以下命令在新虚拟环境中安装所有包
python -m pip install -r requirements.txt
我们也可以使用以下命令删除 requirements.txt 文件中的所有包
python -m pip uninstall -r requirements.txt -y
pip freeze
命令接受 --path
参数,它将 requirements.txt 文件限制为包含安装在特定路径中的包。
python3 -m pip freeze --path . > requirements.txt
pip install
命令接受 -t
或 --target
参数,将包安装到指定的路径。
python3 -m pip install -r requirements.txt -t .
Jupyter
使用 Python 非常方便。这是关于 Jupyter 的文档。我们可以在虚拟环境中安装 Jupyter。
python3.9 -m venv env-3.9
source env-3.9/bin/activate
pip install pip -U
上述命令创建一个虚拟环境并在其中升级 pip
。我们可以使用以下命令验证虚拟环境
which python
pip --version
我们可以使用以下命令安装 Jupyter
pip install jupyterlab
我们可以使用以下命令启动 Jupyter
jupyter-lab
如果我们要将 ipynb 文件转换为常规 python 文件,可以使用以下命令
python -m nbconvert --to script --no-prompt Untitled.ipynb
我们可以使用以下命令检查是否安装了 nbconvert
pip list | grep nbconvert
VSC 集成
Visual Studio Code 是一个不错的 Python 程序 IDE。如果您不熟悉 VSC,可以查看 我之前的说明。为了支持 Python,我只需要安装 Microsoft 的 Python 扩展。
为了能够使用虚拟环境的 Python 解释器运行和调试程序,我们只需创建如下所示的 launch.json 文件。它将 pythonPath
指向虚拟环境。
{
"version": "0.2.0",
"configurations": [
{
"name": "python-search-path.py",
"pythonPath": "${workspaceFolder}/environment-3.4/bin/python",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/python-search-path.py",
"console": "integratedTerminal"
},
{
"name": "python-inspect-path.py",
"pythonPath": "${workspaceFolder}/environment-3.4/bin/python",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/python-inspect-path.py",
"console": "integratedTerminal"
},
{
"name": "python-flask-api.py",
"pythonPath": "${workspaceFolder}/environment-3.4/bin/python",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/python-flask-api.py",
"console": "integratedTerminal"
}
]
}
如果您愿意,实际上可以将 python 可执行文件的路径添加到 settings.json 文件中。
{
"files.exclude": {
"**/.git": true,
"**/.gitignore": true,
"**/environment-3.4": true
},
"python.pythonPath": "${workspaceFolder}/environment-3.4/bin/python3.4"
}
在这种情况下,无论何时打开终端,虚拟环境都会自动激活。
Flask 和 Gunicorn
Flask 和 Django 是 Python 环境中的 Web 框架。python-flask-api.py 是一个简单的 Flask 应用程序。
import sys, os
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/')
def message():
return jsonify(pid = os.getpid())
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000)
我们可以使用以下命令启动应用程序
python python-flask-api.py
但当我们启动应用程序时,我们看到了以下警告。
我们需要找到一个好的服务器来托管应用程序。我们有很多选择,但 Gunicorn 是一个受欢迎的选择。我们实际上可以在虚拟环境中安装 Gunicorn。
python -m pip install gunicorn
安装 Gunicorn 后,我们可以在虚拟环境中运行它。
gunicorn --bind 0.0.0.0:8000 python-flask-api:app
如果一切顺利,我们现在可以通过 8000
端口号访问应用程序。我们还可以添加 -w
选项来指定工作进程的数量。通常,服务器中每个核心的工作进程数量为 2-4 个。
gunicorn --bind 0.0.0.0:8000 -w 4 python-flask-api:app
重要的是要知道 Flask 本身可以多线程运行(参考)。
if __name__ == '__main__':
app.run(threaded=True)
或
if __name__ == '__main__':
app.run(threaded=False, processes=3)
Nginx 负载均衡
Nginx 是一个流行的负载均衡器。通常使用 Nginx 来负载均衡多个 Flask 实例。要在我的 Linux Mint 计算机上安装 Nginx,我们可以使用以下命令。
apt-cache policy nginx
sudo apt-get install nginx
安装后,我们可以使用以下命令来操作 Nginx 服务器。
sudo service nginx start
sudo service nginx restart
sudo service nginx reload
sudo service nginx stop
service nginx status
因为我正在我的个人电脑上试验 Nginx,我不想让它在电脑启动时启动,所以我使用以下命令禁用该服务。
sudo update-rc.d nginx disable
为了设置负载均衡,我创建了一个名为 loadbalance.conf 的文件。
upstream pythonweb {
server localhost:4000;
server localhost:3000;
server localhost:2000;
}
# This balances all the requests
# It also disable caching
server {
listen 80;
location / {
proxy_pass "http://pythonweb/";
add_header Cache-Control
'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
}
}
# nginx.conf file
# Comment out this line - include /etc/nginx/sites-enabled/*;
#include /home/song/sandbox/p-virtualenv-excercise/loadbalance.conf;
在我的计算机上,我需要将此文件“包含”到 /etc/nginx/nginx.conf 文件中,并注释掉默认的 /etc/nginx/sites-enabled/*。
#include /etc/nginx/sites-enabled/*;
include /home/song/sandbox/p-virtualenv-excercise/loadbalance.conf;
配置准备就绪后,我们可以重启或重载 Nginx 服务器。我们还需要在不同的终端上启动 3 个 Flask 应用程序实例,端口号分别为 2000/3000/4000。以下是在端口号 2000 上启动实例的命令。
gunicorn --bind 0.0.0.0:2000 python-flask-api:app
如果一切顺利,我们就可以通过端口号 80 访问应用程序,请求会在 3 个 Flask 实例之间进行负载均衡。
Python 类与面向对象
Python 支持类和面向对象。在本节中,我们将使用一些示例来快速总结 Python 类和面向对象。
示例 1 - Python 空类和动态属性
# Python allows empty class
class EmptyClass:
pass
# Create an instance of the class
obj_1 = EmptyClass()
# Attrubutes can be added to an object dynamically
obj_1.attribute1 = "The attribute 1"
obj_1.attribute2 = "The attribute 2"
print(obj_1.attribute1 + ' - ' + obj_1.attribute2)
# The __dict__ has all the information
print('The __dict__:')
print(obj_1.__dict__)
# Attributes can be deleted from an object
del obj_1.attribute2
print(obj_1.__dict__)
print()
与其他语言中的类不同,Python 类不需要任何实现。
- 实例属性可以动态添加到 Python 对象中;
- 实例属性(包括
__dict__
属性)也可以动态地从对象中删除; __dict__
属性维护 Python 对象的所有信息。
运行以上程序,我们可以看到以下结果
The attribute 1 - The attribute 2
The __dict__:
{'attribute1': 'The attribute 1', 'attribute2': 'The attribute 2'}
{'attribute1': 'The attribute 1'}
示例 2 - Python 构造函数和实例方法
# A class with an instructor and an instance method
class AClass:
def __init__(self, attr_1, attr_2):
self.attr_1 = attr_1
self.attr_2 = attr_2
def print_attributes(self):
print("{} - {}".format(self.attr_1, self.attr_2))
# Create two instances
obj_1 = AClass('obj_1_a1', 'obj_1_a2')
obj_2 = AClass('obj_2_a1', 'obj_2_a2')
# Call the instance method
# Each object has its own instance attributes
obj_1.print_attributes()
obj_2.print_attributes()
print()
尽管我们可以向 Python 对象添加动态属性,但初始化属性最常见的方法是通过构造函数。
- Python 构造函数名为
__init__
,构造函数的第一个参数是对象的引用; - Python 实例方法也将其第一个参数作为对象的引用。按照惯例,此参数命名为
self
。 - 当调用构造函数和实例方法时,Python 会隐式传入
self
参数。
运行以上程序,我们可以看到以下结果
obj_1_a1 - obj_1_a2
obj_2_a1 - obj_2_a2
示例 3 - 类属性
# A class with both instance and class attributes
class AClass:
a_class_attr = 'The class attribute'
def print_class_attribute_by_self_reference(self):
print(self.a_class_attr)
def print_class_attribute_by_class_name(self):
print(AClass.a_class_attr)
# Create an instance of AClass
obj_1 = AClass()
# A class attribute can be accessed by
# both the instance reference and the class name
obj_1.print_class_attribute_by_self_reference()
obj_1.print_class_attribute_by_class_name()
print()
# Add a instance attribute with the same name
obj_1.a_class_attr = 'This can override the class attribute'
obj_1.print_class_attribute_by_self_reference()
obj_1.print_class_attribute_by_class_name()
print()
# Delete the instance attribute
del obj_1.a_class_attr
obj_1.print_class_attribute_by_self_reference()
obj_1.print_class_attribute_by_class_name()
print()
除了实例属性,Python 类还可以有类级别属性。
- 类属性可以通过对象引用和类名访问;
- 如果对象有一个与类属性同名的实例属性,并且该属性通过对象引用访问,则实例属性会覆盖类属性。
运行以上程序,我们可以看到以下结果。
The class attribute
The class attribute
This can override the class attribute
The class attribute
The class attribute
The class attribute
示例 4 - 类方法和静态方法
# A class with an instance method, a class method,
# and a static method
class AClass:
a_class_attr = 'The class attribute'
def __init__(self):
self.a_instance_attr = 'The instance attribute'
def an_instance_method(self):
print("From the instance method - {}".format(self.a_instance_attr))
@classmethod
def a_class_method(cls):
print("From the class method - {}".format(cls.a_class_attr))
@staticmethod
def a_static_mathod():
print('Print from a static method')
# Create an instance of AClass and call the methods
obj_1 = AClass()
obj_1.an_instance_method()
obj_1.a_class_method()
obj_1.a_static_mathod()
print()
# Class and static methods can be accessed by the class name
AClass.a_class_method()
AClass.a_static_mathod()
一个 Python 类可以有类方法和静态方法。
- 使用
@classmethod
注解一个方法使其成为类方法。当该方法被调用时,类的引用会作为第一个参数隐式传入。按照惯例,该参数命名为cls
; - 使用
@staticmethod
注解一个方法使其成为static
方法。当该方法被调用时,对象引用和类引用都不会隐式传入。
运行以上程序,我们可以看到以下结果。
From the instance method - The instance attribute
From the class method - The class attribute
Print from a static method
From the class method - The class attribute
Print from a static method
示例 5 - 继承
# The parent class
class ParentClass:
def __init__(self):
self.parent_attr = 'Parent Attribute'
def print(self):
print("Print from parent class - {}".format(self.parent_attr))
# The child class
class ChildClass(ParentClass):
def __init__(self):
super().__init__()
self.child_attr = 'Child Attribute'
def print(self):
print("Print from child class - {}".format(self.child_attr))
# Create instances for both classes
# and call the print method
parent_obj = ParentClass()
parent_obj.print()
child_obj = ChildClass()
child_obj.print()
print()
# The the polymorphism behavior, kind of ...
print('Test polymophism')
objects = [parent_obj, child_obj]
for obj in objects:
obj.print()
与其他面向对象语言一样,Python 支持继承。
super()
方法可用于访问父类中的方法;- 如果子类实现了相同的方法,则该方法被覆盖;
- Python 对象表现出与其他语言相似的多态行为。
运行以上程序,我们可以看到以下结果
Print from parent class - Parent Attribute
Print from child class - Child Attribute
Test polymophism
Print from parent class - Parent Attribute
Print from child class - Child Attribute
Spark 和 PySpark
关注点
- 这是一篇关于 Python、Virtualenv、VSC 和其他内容的说明。
- 希望您喜欢我的帖子,并希望这篇说明能对您有所帮助。
历史
- 2020年1月28日:初始版本