diff --git a/datadevelop/quick_start/etl_develop/index.html b/datadevelop/quick_start/etl_develop/index.html index 14a42b4..e1a1e7f 100644 --- a/datadevelop/quick_start/etl_develop/index.html +++ b/datadevelop/quick_start/etl_develop/index.html @@ -1401,9 +1401,9 @@
将如下配置更新到配置文件中,因为项目默认使用dev环境配置,则需要在configs/dev.toml
中增加如下内容:
# spark configs
-spark_master = 'local[*]'
-spark_config.spark.driver.memory = '3G'
-spark_config.spark.executor.memory = '16G'
+spark_master = 'local[*]'
+spark_config.spark.driver.memory = '3G'
+spark_config.spark.executor.memory = '16G'
spark_config.spark.sql.debug.maxToStringFields = 100
ETL任务完成后需要注册插件:
因为项目默认使用poetry
管理虚拟环境,则需要在pyproject.toml
文件增加中增加如下内容:
[tool.poetry.plugins."etl_tasks"]
-task_name = "{task_class_path}:TaskExample"
+
使用如下命令将项目插件更新到环境中:
poetry install
diff --git a/datadevelop/quick_start/release/index.html b/datadevelop/quick_start/release/index.html
index d40669b..6ba31d3 100644
--- a/datadevelop/quick_start/release/index.html
+++ b/datadevelop/quick_start/release/index.html
@@ -1251,10 +1251,10 @@ 注册Task
将main
命令行入口和上述实现的Task
类注册到命名空间中。
编辑pyproject.toml
文件,增加poetry插件如下内容:
[tool.poetry.plugins.console_scripts]
-automotive_data_etl = "automotive_data_etl.cmdline:main"
+automotive_data_etl = "automotive_data_etl.cmdline:main"
-[tool.poetry.plugins."etl_tasks"]
-automotive_task = "automotive_data_etl.tasks.automotive_task.task:AutomotiveDataTask"
+[tool.poetry.plugins."etl_tasks"]
+automotive_task = "automotive_data_etl.tasks.automotive_task.task:AutomotiveDataTask"
这么做的目的是将AutomotiveDataTask
注册到entry_points
中, 然后在程序中使用importlib.metadata
根据名称空间查找。而 stevedore
则是封装了查找的复杂逻辑,让使用插件更简单。
diff --git a/guidelines/advanced/configuration/index.html b/guidelines/advanced/configuration/index.html
index 5beac1d..6396354 100644
--- a/guidelines/advanced/configuration/index.html
+++ b/guidelines/advanced/configuration/index.html
@@ -1465,7 +1465,7 @@ 使用配置文件
使用环境变量
Dynaconf 支持加载环境变量,也可以使用 .env 文件。
在使用环境变量时,同样和配置文件一样,支持完全覆盖,和自动合并。
-需要额外强调一点的是, Dynaconf 初始化的时,使用了 envvar_prefix=BLOG
。 Dynaconf 会自动加载以 BLOG_
开头的
+
需要额外强调一点的是, Dynaconf 初始化的时候,使用了 envvar_prefix=BLOG
。 Dynaconf 会自动加载以 BLOG_
开头的
环境变量。包括 ENVVAR_FOR_DYNACONF='BLOG_SETTINGS'
配置的 Dynaconf 加载配置文件的环境变量 BLOG_SETTINGS
。
所以在使用环境变量的时候,不要错误的将 BLOG_SETTINGS
环境变量指定其他内容,而造成不必要的错误。
# 使用环境变量配置单值
diff --git a/guidelines/advanced/exception/index.html b/guidelines/advanced/exception/index.html
index 16c27f9..95984ca 100644
--- a/guidelines/advanced/exception/index.html
+++ b/guidelines/advanced/exception/index.html
@@ -1347,9 +1347,9 @@ 异常管理
几乎所有编程语言中都有异常。异常可以快速指出程序出现的问题,便于排查。开发人员也可以根据情况抛出自定义异常,
以指示期望的内容和实际不相符。良好的异常设计和使用习惯,可以提高程序的质量。
介绍
-Python 中的异常分为两类,一是句法错误,一类是异常。
+Python 中的异常分为两类,一是语法错误,一类是异常。
句法错误
-句法错误是用来指示 Python 编码不符合句法规范的:
+语法错误是用来指示 Python 编码不符合语法规范的:
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
@@ -1563,7 +1563,7 @@ 实践
开发实践中,异常信息对诊断程序非常重要。所以在使用和处理异常时,请遵循如下几点:
- 需要处理异常时使用
try...except...finally
捕获
-- 处理异常时,如果没有继续抛出异常,需要输入日志信息。除非你知道不输出任何信息不会造成拍错困难。
+- 处理异常时,如果没有继续抛出异常,需要输入日志信息。除非你知道不输出任何信息不会造成排错困难。
- 项目级别,一定要定义一个项目的基类异常。项目中其他自定义异常必须继承该基类异常。这么做的目的是可以在外层逻辑通过捕获基类
异常来只捕获抛出的自定义异常。
- 项目异常要以
ERROR
结尾。和标准异常命名类似。
diff --git a/guidelines/advanced/logging/index.html b/guidelines/advanced/logging/index.html
index 683a59f..0455631 100644
--- a/guidelines/advanced/logging/index.html
+++ b/guidelines/advanced/logging/index.html
@@ -1347,20 +1347,20 @@ Logging
日志是对软件执行时所发生事件的一种追踪方式。软件开发人员对他们的代码添加日志调用,借此来指示某事件的发生。
一个事件通过一些包含变量数据的描述信息来描述(比如:每个事件发生时的数据都是不同的)。开发者还会区分事件的重要性,
重要性也被称为 等级 或 严重性。有一个好的日志实践,能让开发调试流程更顺畅,出现问题能更快速精准定位。
-本文不会以最基础的方式讲述 Python logging 的使用,而是以当前总结的实践方式结合实际操作案例展示 Logging 的使用,所以在阅读文章钱,
+
本文不会以最基础的方式讲述 Python logging 的使用,而是以当前总结的实践方式结合实际操作案例展示 Logging 的使用,所以在阅读文章前,
你应该提前了解 日志 HOWTO 和
Python 的日志记录工具 两篇文档。
1. 简单使用
在一般开发中,对于临时开发的项目,可能为了快速完成任务,项目中大量使用了 print
将调试信息输出到控制台。
-项目后期就会出现调试困难等问题。本节会提供在简单环境下快速使用日志方式。
+项目后期就会出现调试困难等问题。本节会提供在简单环境下快速使用日志的方式。
1.1 单文件使用
-对于单文件的使用,直接使用根日志对象即可。由于默认的日志级别为 WARNING
,所以需要使用更低级别的日志是无法显示的。
+对于单文件的使用,直接使用根日志对象即可。由于默认的日志级别为 WARNING
,所以使用更低级别的日志是无法显示的。
-如果后续开发有要控制日志级别的需求,直接在开始初始化日志配置就可以了;
+如果后续开发有要控制日志级别的需求,直接在开始的时候初始化日志配置就可以了。
"""Simple logging"""
import logging
@@ -1370,7 +1370,7 @@ 1.1 单文件使用
logging.warning('I love you ~')
logging.debug('I love you too ~')
-当需要输出更详细的日志信息,如执行时间 、 日志级别 、 线程或进程信息,都可以很方便的控制。
+当需要输出更详细的日志信息,如执行时间、日志级别、线程或进程信息等,都可以很方便的控制。
"""Simple logging"""
import logging
@@ -1385,7 +1385,7 @@ 1.1 单文件使用
logging.warning('I love you ~')
logging.debug('I love you too ~')
-虽然使用 print
能更快速的在控制太输出想要看到的内容,但从上面的示例来看,直接使用默认的日志输出也是很方便的,唯一的区别可能
+
虽然使用 print
能更快速的在控制台输出想要看到的内容,但从上面的示例来看,直接使用默认的日志输出也是很方便的,唯一的区别可能
就是需要导入了。而使用日志的话,想要在后续增加输出更精确的信息就显得比较灵活。
日志格式所支持的字段请参考 LogRecord 属性 。
这也是为什么建议优先使用日志的原因。
diff --git a/guidelines/project_management/distribution/index.html b/guidelines/project_management/distribution/index.html
index 19c9ad5..2641dbf 100644
--- a/guidelines/project_management/distribution/index.html
+++ b/guidelines/project_management/distribution/index.html
@@ -1621,8 +1621,8 @@ 1. 项目准备
项目采用 src
目录结构,项目描述信息都在 pyproject.toml
中定义。
2. 项目打包
2.1 打包工具
-由于历史原因, Python 的打包走了很长一段路了,但和其他语言的打包工具相比,为了还是有很长一段路要走。
-在 PEP-517 中,提到了当前 Python 构建系统的不足和响应的解决方案。
+
由于历史原因, Python 的打包走了很长一段路了,但和其他语言的打包工具相比,未来还是有很长一段路要走。
+在 PEP-517 中,提到了当前 Python 构建系统的不足和相应的解决方案。
其主要就是解决让 Python 支持更加灵活的构建系统。
PEP-518 则提出为项目指定一个最小的构建系统。
2.1.1 setuptools
@@ -1650,8 +1650,8 @@ 2.1.1 setuptools
2.1.1.1 示例配置
pyproject.toml :
[build-system]
-requires = ["setuptools", "wheel"]
-build-backend = "setuptools.build_meta"
+requires = ["setuptools", "wheel"]
+build-backend = "setuptools.build_meta"
增加 setup.py
或者 setup.cfg
两种有其一即可。然后在文件中定义配置。
推荐使用 setup.cfg
。
@@ -1737,14 +1737,14 @@ 2.1.2 flit
2.1.2.1 示例配置
pyproject.toml :
[build-system]
-requires = ["flit_core >=2,<4"]
-build-backend = "flit_core.buildapi"
+requires = ["flit_core >=2,<4"]
+build-backend = "flit_core.buildapi"
[tool.flit.metadata]
-module = "foobar"
-author = "Sir Robin"
-author-email = "robin@camelot.uk"
-home-page = "https://github.com/sirrobin/foobar"
+module = "foobar"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "https://github.com/sirrobin/foobar"
2.1.2.2 通用方式构建
安装 PEP-517 规范的包生成器 build 和 flit ,pip install build flit
。
@@ -1779,55 +1779,55 @@ 2.1.3 poetry
2.1.3.1 示例配置
[build-system]
-requires = ["poetry_core>=1.0.0"]
-build-backend = "poetry.core.masonry.api"
+requires = ["poetry_core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
[tool.poetry]
-name = "poetry-demo"
-version = "0.1.0"
-description = ""
-authors = ["Sébastien Eustace <sebastien@eustace.io>"]
+name = "poetry-demo"
+version = "0.1.0"
+description = ""
+authors = ["Sébastien Eustace <sebastien@eustace.io>"]
[tool.poetry.dependencies]
-python = "^3.10"
+python = "^3.10"
[tool.poetry.dev-dependencies]
-pytest = "^3.4"
+pytest = "^3.4"
2.2 打包构建(poetry)
现阶段选用 poetry
作为构建工具。
为项目指定所需要使用的构建工具。创建 pyproject.toml
文件,增加如下内容:
[tool.poetry]
-name = "file2mongo"
-version = "0.1.0"
-description = "File data to MongoDB"
-readme = "README.md"
-authors = ["demo <demo@example.com>"]
-license = "MIT"
+name = "file2mongo"
+version = "0.1.0"
+description = "File data to MongoDB"
+readme = "README.md"
+authors = ["demo <demo@example.com>"]
+license = "MIT"
classifiers = [
- "Operating System :: OS Independent",
- "Programming Language :: Python :: 3.10",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 3.10",
]
[tool.poetry.dependencies]
-python = "^3.10"
-dynaconf = "^3.1.9"
-click = "^8.1.3"
-pymongo = "^4.3.3"
+python = "^3.10"
+dynaconf = "^3.1.9"
+click = "^8.1.3"
+pymongo = "^4.3.3"
[tool.poetry.dev-dependencies]
-pylint = "^2.14.5"
-isort = "^5.10.1"
-pytest = "^7.1.2"
-mkdocs = "^1.3.1"
-mkdocs-material = "^8.4.1"
+pylint = "^2.14.5"
+isort = "^5.10.1"
+pytest = "^7.1.2"
+mkdocs = "^1.3.1"
+mkdocs-material = "^8.4.1"
-[tool.poetry.plugins."scripts"]
-file2mongo = "file2mongo.cmdline:main"
+[tool.poetry.plugins."scripts"]
+file2mongo = "file2mongo.cmdline:main"
[build-system]
-requires = ["poetry-core>=1.0.0"]
-build-backend = "poetry.core.masonry.api"
+requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
2.2.1 项目基本信息
tool.poetry 是项目基本描述信息,有项目名称,版本号,作者相关信息等。
@@ -1850,7 +1850,7 @@ 2.2.2 options
tool.poetry.scripts
: 安装包时将安装的脚本或可执行文件
tool.poetry.extras
:可选依赖项,增强包,但不是必需的,可选依赖项的集群。
tool.poetry.plugins
: 插件,可通过 importlib.metadata
导入
-tool.poetry.urls
: 自定义 url,发布pypi后展示
+tool.poetry.urls
: 自定义 url,发布 pypi 后展示
build-system
: 构建系统引用部分
2.2.2.1 入口点 EntryPoints
@@ -1858,7 +1858,7 @@ 2.2.2.1 入口点 EntryPoints
注册命令行 :
例如,要创建名为 foo
的控制台脚本,pyproject.toml
文件添加如下示例:
[tool.poetry.scripts]
-foo = "my_package.some_module:main_func"
+foo = "my_package.some_module:main_func"
2.2.3 构建
当配置完成后,就可以开始构建了。
diff --git a/guidelines/project_management/project_structure/index.html b/guidelines/project_management/project_structure/index.html
index 324a262..9342578 100644
--- a/guidelines/project_management/project_structure/index.html
+++ b/guidelines/project_management/project_structure/index.html
@@ -1367,7 +1367,7 @@ 2 src 结构
├── pyproject.toml
└── tests
-六年前的这篇文章 Packaging a python library 就详细阐述了使用 src
结构比简单结构的诸多有点。而现在也逐渐被社区作为一个标准遵循。虽然社区中有大量老的项目依然采用简单布局,但新项目推荐使用 src
结构。
+六年前的这篇文章 Packaging a python library 就详细阐述了使用 src
结构比简单结构的诸多优点。而现在也逐渐被社区作为一个标准遵循。虽然社区中有大量老的项目依然采用简单布局,但新项目推荐使用 src
结构。
如下面这个示例项目结构:
[tool.poetry]
-name = "example_etl"
-version = "0.1.0"
-description = "This is my first etl project."
-readme = "README.md"
-authors = ["test <test@example.com>"]
-license = "MIT"
+name = "example_etl"
+version = "0.1.0"
+description = "This is my first etl project."
+readme = "README.md"
+authors = ["test <test@example.com>"]
+license = "MIT"
classifiers = [
- "Operating System :: OS Independent",
- "Programming Language :: Python :: 3.10",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 3.10",
]
[tool.poetry.dependencies]
-python = "^3.10"
-dynaconf = "^3.1.12"
-click = "^8.1.3"
+python = "^3.10"
+dynaconf = "^3.1.12"
+click = "^8.1.3"
[tool.poetry.group.dev.dependencies]
-pylint = "^2.17.4"
-isort = "^5.12.0"
-pytest = "^7.3.1"
-tox = "^4.5.2"
-mkdocs = "^1.4.3"
-mkdocs-material = "^8.5.11"
-pytest-pylint = "^0.19.0"
-pre-commit = "^3.3.2"
+pylint = "^2.17.4"
+isort = "^5.12.0"
+pytest = "^7.3.1"
+tox = "^4.5.2"
+mkdocs = "^1.4.3"
+mkdocs-material = "^8.5.11"
+pytest-pylint = "^0.19.0"
+pre-commit = "^3.3.2"
-[tool.poetry.plugins."example_etl.extractor"]
-file = "example_etl.extractor.file:FileExtractor"
+[tool.poetry.plugins."example_etl.extractor"]
+file = "example_etl.extractor.file:FileExtractor"
-[tool.poetry.plugins."example_etl.loader"]
-file = "example_etl.loader.file:FileLoader"
+[tool.poetry.plugins."example_etl.loader"]
+file = "example_etl.loader.file:FileLoader"
-[tool.poetry.plugins."example_etl.transformer"]
-strip = "example_etl.transformer.strip:StripTransformer"
+[tool.poetry.plugins."example_etl.transformer"]
+strip = "example_etl.transformer.strip:StripTransformer"
[tool.poetry.scripts]
-example_etl = "example_etl.cmdline:main"
+example_etl = "example_etl.cmdline:main"
[build-system]
-requires = ["poetry-core"]
-build-backend = "poetry.core.masonry.api"
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
[tool.pytest.ini_options]
-testpaths = "tests"
-python_files = "tests.py test_*.py *_tests.py"
+testpaths = "tests"
+python_files = "tests.py test_*.py *_tests.py"
[tool.pylint.design]
max-line-length = 120
diff --git a/guidelines/tutorial/init_project/index.html b/guidelines/tutorial/init_project/index.html
index a7dd2b8..3edc01c 100644
--- a/guidelines/tutorial/init_project/index.html
+++ b/guidelines/tutorial/init_project/index.html
@@ -1450,42 +1450,42 @@ pyproject.toml
pyproject.toml
是项目的打包配置文件。文件前面 tool.poetry
中设置了项目的基本信息。 tool.poetry.dependencies
中配置了此项目的依赖库。
文件内容如下:
[tool.poetry]
-name = "example_etl"
-version = "0.1.0"
-description = "This is my first etl project."
-readme = "README.md"
-authors = ["test <test@example.com>"]
-license = "MIT"
+name = "example_etl"
+version = "0.1.0"
+description = "This is my first etl project."
+readme = "README.md"
+authors = ["test <test@example.com>"]
+license = "MIT"
classifiers = [
- "Operating System :: OS Independent",
- "Programming Language :: Python :: 3.10",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 3.10",
]
[tool.poetry.dependencies]
-python = "^3.10"
-dynaconf = "^3.1.12"
-click = "^8.1.3"
+python = "^3.10"
+dynaconf = "^3.1.12"
+click = "^8.1.3"
[tool.poetry.group.dev.dependencies]
-pylint = "^2.17.4"
-isort = "^5.12.0"
-pytest = "^7.3.1"
-tox = "^4.5.2"
-mkdocs = "^1.4.3"
-mkdocs-material = "^8.5.11"
-pytest-pylint = "^0.19.0"
-pre-commit = "^3.3.2"
+pylint = "^2.17.4"
+isort = "^5.12.0"
+pytest = "^7.3.1"
+tox = "^4.5.2"
+mkdocs = "^1.4.3"
+mkdocs-material = "^8.5.11"
+pytest-pylint = "^0.19.0"
+pre-commit = "^3.3.2"
[tool.poetry.scripts]
-example_etl = "example_etl.cmdline:main"
+example_etl = "example_etl.cmdline:main"
[build-system]
-requires = ["poetry-core"]
-build-backend = "poetry.core.masonry.api"
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
[tool.pytest.ini_options]
-testpaths = "tests"
-python_files = "tests.py test_*.py *_tests.py"
+testpaths = "tests"
+python_files = "tests.py test_*.py *_tests.py"
[tool.pylint.design]
max-line-length = 120
@@ -1611,7 +1611,7 @@ src/example_etl/config/__init__.py
<sys.prefix>/etc/example_etl/settings.yml
:操作系统外部配置文件。默认这个配置文件和项目默认配置文件的内容一致。
使用 EXAMPLE_ETL_<name>=<value>
环境变量传递
-优先级从从上倒下依次增大,优先级高的会覆盖优先级低的配置。
+优先级从从上到下依次增大,优先级高的会覆盖优先级低的配置。
"""
Configuration center.
Use https://www.dynaconf.com/
diff --git a/guidelines/tutorial/publish/index.html b/guidelines/tutorial/publish/index.html
index 196467b..2192cba 100644
--- a/guidelines/tutorial/publish/index.html
+++ b/guidelines/tutorial/publish/index.html
@@ -1264,7 +1264,7 @@ 发布
-注意:不建议将测试项目发布都 https://pypi.org/ 上。在 https://pypi.org/ 上的项目名称是全局唯一的,
+
注意:不建议将测试项目发布到 https://pypi.org/ 上。在 https://pypi.org/ 上的项目名称是全局唯一的,
所以如果你考虑到将项目发布到 https://pypi.org/ 上前,应定一个不存在名称。
安装测试
diff --git a/guidelines/tutorial/test/index.html b/guidelines/tutorial/test/index.html
index a126f4b..d8f999d 100644
--- a/guidelines/tutorial/test/index.html
+++ b/guidelines/tutorial/test/index.html
@@ -1328,7 +1328,7 @@ 测试
测试是保障安全上线一个重要的步骤,编写良好的测试,可以在发布之前尽可能避免 BUG 出现。
在修改功能后,也可以通过回归测试,检查现有功能的稳定性。
-编写单元测试过程,和开发顺序一直,现测试三个模块,再测试 manage
模块,最后测试调用逻辑。
+编写单元测试过程,和开发顺序一样,先测试三个模块,再测试 manage
模块,最后测试调用逻辑。
测试时,使用的是 pytest
工具,而不是使用 unittest
。
调整测试代码
1
diff --git a/index.html b/index.html
index 0dc3897..8b10eb9 100644
--- a/index.html
+++ b/index.html
@@ -1411,7 +1411,7 @@ Python 项目工程化开发指南
数据库
-- SQLALchemy
+- SQLAlchemy
diff --git a/introduction/install/index.html b/introduction/install/index.html
index 96e0d39..056cc53 100644
--- a/introduction/install/index.html
+++ b/introduction/install/index.html
@@ -1539,7 +1539,7 @@ 1.1.1 安装 Python 环境
如果想要将 Python 安装到默认目录,直接点击 Install Now
即可。
点击 Customize Installation
:
-此时可以选择可选的特性,不过还不是你不知道它们是做什么的,或者不清楚你是否需要它们,那么保持默认即可。
+
此时可以选择可选的特性,不过如果你不知道它们是做什么的,或者不清楚你是否需要它们,那么保持默认即可。
然后点击 Next
:
然后进行如下操作:
diff --git a/practices/web/index.html b/practices/web/index.html
index d8ef820..a652e61 100644
--- a/practices/web/index.html
+++ b/practices/web/index.html
@@ -1777,18 +1777,18 @@ 2.1 初始化项目结构
2.2 初始化项目基本信息
编辑 pyproject.toml
文件, 配置项目描述信息:
[tool.poetry]
-name = "example_blog"
-version = "0.1.0"
-description = "This is example blog system."
-authors = ["huagang517 <huagang517@126.com>"]
-readme = "README.md"
+name = "example_blog"
+version = "0.1.0"
+description = "This is example blog system."
+authors = ["huagang517 <huagang517@126.com>"]
+readme = "README.md"
[tool.poetry.dependencies]
-python = "^3.10"
+python = "^3.10"
[build-system]
-requires = ["poetry-core"]
-build-backend = "poetry.core.masonry.api"
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
2.3 增加项目自述文件
编写 README.md
文件
@@ -2038,7 +2038,7 @@ 3.1 创建命令行入口
查看 pyproject.toml
,将增加安装依赖:
创建 src/example_blog/cmdline.py
文件:
编辑 pyproject.toml
,将命令行入口注册到项目描述文件中:
[tool.poetry.scripts]
-example_blog = "example_blog.cmdline:main"
+example_blog = "example_blog.cmdline:main"
提交代码:
查看 pyproject.toml
,将增加安装依赖:
[tool.poetry.dependencies]
-click = "^8.1.3"
-dynaconf = "^3.1.11"
+click = "^8.1.3"
+dynaconf = "^3.1.11"
建立配置包,和配置文件:
查看 pyproject.toml
,将增加安装依赖:
[tool.poetry.dependencies]
-click = "^8.1.3"
-dynaconf = "^3.1.11"
-sqlalchemy = "^1.4.44"
-mysqlclient = "^2.1.1"
+click = "^8.1.3"
+dynaconf = "^3.1.11"
+sqlalchemy = "^1.4.44"
+mysqlclient = "^2.1.1"
编写 src/example_blog/config/settings.yml
,增加数据库配置信息:
# ######################################################################################################
@@ -2244,11 +2244,11 @@ 3.4 数据访问
查看 pyproject.toml
,将增加安装依赖:
[tool.poetry.dependencies]
-click = "^8.1.3"
-dynaconf = "^3.1.11"
-sqlalchemy = "^1.4.44"
-mysqlclient = "^2.1.1"
-pydantic = "^1.10.2"
+click = "^8.1.3"
+dynaconf = "^3.1.11"
+sqlalchemy = "^1.4.44"
+mysqlclient = "^2.1.1"
+pydantic = "^1.10.2"
创建 src/example_blog/schemas.py
,创建对象模型:
查看 pyproject.toml
,增加安装依赖:
[tool.poetry.dependencies]
-click = "^8.1.3"
-dynaconf = "^3.1.11"
-sqlalchemy = "^1.4.44"
-mysqlclient = "^2.1.1"
-pydantic = "^1.10.2"
-fastapi = "^0.88.0"
-uvicorn = "^0.20.0"
+click = "^8.1.3"
+dynaconf = "^3.1.11"
+sqlalchemy = "^1.4.44"
+mysqlclient = "^2.1.1"
+pydantic = "^1.10.2"
+fastapi = "^0.88.0"
+uvicorn = "^0.20.0"
创建 src/examp.e_blog/views.py
,创建视图:
查看 pyproject.toml
,将增加安装依赖:
[tool.poetry.dependencies]
-click = "^8.1.3"
-dynaconf = "^3.1.11"
-sqlalchemy = "^1.4.44"
-mysqlclient = "^2.1.1"
-pydantic = "^1.10.2"
-fastapi = "^0.88.0"
-uvicorn = "^0.20.0"
-alembic = "^1.8.1"
+click = "^8.1.3"
+dynaconf = "^3.1.11"
+sqlalchemy = "^1.4.44"
+mysqlclient = "^2.1.1"
+pydantic = "^1.10.2"
+fastapi = "^0.88.0"
+uvicorn = "^0.20.0"
+alembic = "^1.8.1"
初始化 alembic :
alembic init migration
@@ -3275,36 +3275,36 @@ 4.6.2 优化代码风格
5. 打包发布
到这一步, pyproject.toml
文件应该是这样的:
[tool.poetry]
-name = "example_blog"
-version = "0.1.0"
-description = "This is example blog system."
-authors = ["huagang <huagang517@126.com>"]
-readme = "README.md"
+name = "example_blog"
+version = "0.1.0"
+description = "This is example blog system."
+authors = ["huagang <huagang517@126.com>"]
+readme = "README.md"
[tool.poetry.dependencies]
-python = "^3.10"
-fastapi-sa = "^0.0.1.dev0"
-sqlalchemy = "^1.4.44"
-mysqlclient = "^2.1.1"
-pydantic = "^1.10.2"
-dynaconf = "^3.1.11"
-fastapi = "^0.88.0"
-uvicorn = "^0.20.0"
-alembic = "^1.8.1"
+python = "^3.10"
+fastapi-sa = "^0.0.1.dev0"
+sqlalchemy = "^1.4.44"
+mysqlclient = "^2.1.1"
+pydantic = "^1.10.2"
+dynaconf = "^3.1.11"
+fastapi = "^0.88.0"
+uvicorn = "^0.20.0"
+alembic = "^1.8.1"
[tool.poetry.group.dev.dependencies]
-pytest = "^7.2.0"
-isort = "^5.10.1"
-requests = "^2.28.1"
-pytest-mock = "^3.10.0"
-flake8 = "^6.0.0"
+pytest = "^7.2.0"
+isort = "^5.10.1"
+requests = "^2.28.1"
+pytest-mock = "^3.10.0"
+flake8 = "^6.0.0"
[tool.poetry.scripts]
-example_blog = "example_blog.cmdline:main"
+example_blog = "example_blog.cmdline:main"
[build-system]
-requires = ["poetry-core"]
-build-backend = "poetry.core.masonry.api"
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
在整个开发过程中,是逐步丰富此文件的。这是项目的描述文件,描述了打包的配置信息。
5.1 打包
diff --git a/quick_start/index.html b/quick_start/index.html
index ce474f6..da10fa9 100644
--- a/quick_start/index.html
+++ b/quick_start/index.html
@@ -1908,7 +1908,7 @@ 2.5 打包发布
但是安装到环境后去运行 cmdline.py
会比较麻烦,所以需要将 cmdline.py
注册成可执行命令。
修改 pyproject.toml
,增加如下内容:
[tool.poetry.plugins.console_scripts]
-word_count = "word_count.cmdline:main"
+word_count = "word_count.cmdline:main"
当使用 pip
命令将项目包安装到环境后,会自动注册一个 word_count
的可执行命令。
再次将本地项目以可编辑方式安装到当前 Python 环境:
diff --git a/search/search_index.json b/search/search_index.json
index 6ee4ed5..565b4ad 100644
--- a/search/search_index.json
+++ b/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Python \u9879\u76ee\u5de5\u7a0b\u5316\u5f00\u53d1\u6307\u5357","text":"\u6587\u6863\u76ee\u6807\uff1a
\u4ee5\u901a\u4fd7\u6613\u61c2\u7ed3\u6784\u6e05\u6670\u7684\u6587\u6863\u5411\u8bfb\u8005\u5c55\u793a\u5982\u4f55\u505a Python \u5de5\u7a0b\u5316
\u53d7\u4f17\u76ee\u6807\uff1a
- Python \u521d\u5b66\u8005
- Python \u521d\u7ea7\u5f00\u53d1
- Python \u4e2d\u7ea7\u5f00\u53d1
\u6307\u5357\u4e3b\u8981\u5305\u542b\u4ee5\u4e0b\u4e3b\u9898\uff1a
- \u5feb\u901f\u4e0a\u624b\uff08\u4e00\u4e2a\u6700\u901a\u7528\uff0c\u6700\u521d\u7ea7\u7684\u793a\u4f8b\u9879\u76ee\uff09
- \u5f00\u53d1\u524d\u51c6\u5907
- Python \u73af\u5883\u7684\u5b89\u88c5
- \u865a\u62df\u73af\u5883\u7ba1\u7406
- IDE \u7684\u9009\u62e9
- Python \u89c4\u8303
- \u98ce\u683c\u89c4\u8303
- \u8bed\u8a00\u89c4\u8303
- \u5e94\u7528\u5f00\u53d1\u5b9e\u8df5
- \u521d\u7ea7\u6559\u7a0b(\u4e00\u4e2a\u5305\u542b\u5b8c\u6574\u5f00\u53d1\u6d41\u7a0b\u7684\u793a\u4f8b\u9879\u76ee)
- \u521d\u59cb\u5316\u9879\u76ee
- \u529f\u80fd\u5f00\u53d1
- \u6d4b\u8bd5
- \u6253\u5305\u53d1\u5e03
- \u8fdb\u9636\u6559\u7a0b
- \u7c7b\u578b\u6807\u6ce8
- \u4f7f\u7528\u914d\u7f6e\u7cfb\u7edf
- \u5982\u4f55\u7528\u597d\u65e5\u5fd7
- \u5f02\u5e38\u7ba1\u7406
- \u5982\u4f55\u66f4\u597d\u5f97\u6d4b\u8bd5
- \u7528\u4fe1\u53f7\u89e3\u8026\u903b\u8f91
- \u652f\u6301\u63d2\u4ef6\u5316
- \u9879\u76ee\u7ba1\u7406
- \u4ee3\u7801\u68c0\u6d4b
- \u9879\u76ee\u7ed3\u6784
- \u6587\u6863\u7ba1\u7406
- \u6253\u5305\u53d1\u5e03
- \u5f00\u53d1\u5b9e\u8df5
- Web
- Fastapi
- Django
- Flask
- \u722c\u866b
- Scrapy
- aiohttp
- \u6570\u636e\u5e93
- SQLALchemy
- \u6570\u636e\u5f00\u53d1\u5b9e\u8df5
- \u521d\u7ea7\u6559\u7a0b
\u5982\u679c\u60a8\u5bf9\u6587\u6863\u6709\u4efb\u4f55\u5efa\u8bae\u6216\u610f\u89c1\uff0c\u6b22\u8fce\u63d0\u4ea4 issues \u8fdb\u884c\u8ba8\u8bba\u3002\u5f53\u7136\u6211\u4eec\u66f4\u671f\u5f85\u4e0e\u60a8\u5171\u540c\u534f\u4f5c\u5f00\u53d1\uff0c\u8ba9\u6587\u6863\u53d8\u5f97\u66f4\u52a0\u5b8c\u5584\u3002
"},{"location":"#_1","title":"\u4f7f\u7528\u65b9\u5f0f","text":""},{"location":"#1","title":"1. \u514b\u9686\u9879\u76ee","text":"git clone https://github.com/pyloong/pythonic-project-guidelines\n
"},{"location":"#2","title":"2. \u521d\u59cb\u5316\u73af\u5883","text":"\u9879\u76ee\u9884\u89c8\u9700\u8981\u5b89\u88c5 Python \u73af\u5883\u6765\u542f\u52a8 server\uff0c\u5f3a\u70c8\u5efa\u8bae\u4f7f\u7528 Python 3.10+ \u7684\u7248\u672c\u3002\u5982\u679c\u672c\u5730\u6ca1\u6709 Python \u73af\u5883\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528 Docker\u9884\u89c8\u670d\u52a1\u5668 \u6765\u542f\u52a8\u3002
"},{"location":"#21","title":"2.1 \u672c\u5730\u521d\u59cb\u5316","text":"\u521b\u5efa\u865a\u62df\u73af\u5883\uff1a
python3 -m venv .venv\nsource .venv/bin/activate\n
\u5b89\u88c5\u4f9d\u8d56\uff1a
pip install -r requirements.txt\n
"},{"location":"#22-docker","title":"2.2 \u4f7f\u7528 Docker \u521d\u59cb\u5316","text":"docker pull squidfunk/mkdocs-material:9.1.11\n
"},{"location":"#3","title":"3. \u9884\u89c8","text":""},{"location":"#31","title":"3.1 \u672c\u5730\u9884\u89c8","text":"mkdocs serve\n
"},{"location":"#32-docker","title":"3.2 \u4f7f\u7528 Docker \u9884\u89c8","text":"unix:
docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material:9.1.11\n
Windows:
docker run --rm -it -p 8000:8000 -v \"%cd%\":/docs squidfunk/mkdocs-material:9.1.11\n
"},{"location":"#_2","title":"\u534f\u4f5c\u89c4\u8303","text":"\u6587\u6863\u4f7f\u7528 Markdown \u7f16\u5199\uff0c\u4f7f\u7528 mkdocs \u914d\u5408 mkdocs-material \u4e3b\u9898\u6784\u5efa\u3002
- fork
- code
- pr
"},{"location":"quick_start/","title":"\u5feb\u901f\u4e0a\u624b","text":"\u8fd9\u662f\u4e00\u4e2a\u5feb\u901f\u4e0a\u624b\u7684\u5f00\u53d1\u6307\u5357\uff0c\u672c\u6587\u901a\u8fc7\u4e00\u4e2a\u5305\u542b\u4e3b\u8981\u77e5\u8bc6\u70b9\u7684\u7b80\u5355\u9879\u76ee\uff0c\u5411\u5f00\u53d1\u8005\u5c55\u793a\u4e00\u4e2a\u66f4\u7b26\u5408 Python \u89c4\u8303\u548c\u98ce\u683c\uff08Pythonic\uff09\u7684\u9879\u76ee\u5f00\u53d1\u6d41\u7a0b\u3002
\u793a\u4f8b\u9879\u76ee\u662f\u4e00\u4e2a\u5355\u8bcd\u7edf\u8ba1\u7684\u6f14\u793a\u7a0b\u5e8f\uff0c\u5982\u679c\u4f60\u60f3\u67e5\u770b\u5b8c\u6574\u793a\u4f8b\uff0c\u53ef\u4ee5\u6d4f\u89c8 Word Count \u9879\u76ee\u6e90\u7801\u3002
"},{"location":"quick_start/#1","title":"1. \u5f00\u53d1\u73af\u5883\u642d\u5efa","text":""},{"location":"quick_start/#11-python","title":"1.1 Python \u5f00\u53d1\u73af\u5883","text":"\u672c\u9879\u76ee\u4f7f\u7528 Python 3.10 \u3002\u5177\u4f53\u7248\u672c\u7684 Python \u73af\u5883\u53ef\u4ee5\u5728\u5b98\u7f51\u4e0b\u8f7d\u3002
"},{"location":"quick_start/#12","title":"1.2 \u5f00\u53d1\u5de5\u5177","text":"\u63a8\u8350\u4f7f\u7528 Pycharm \u5f00\u53d1\u5de5\u5177\uff0c\u53ef\u4ee5\u9009\u62e9\u514d\u8d39\u7684\u793e\u533a\u7248\u672c\u3002
Visual Studio Code \u662f\u5fae\u8f6f\u5f00\u53d1\u7684\u4e00\u6b3e\u514d\u8d39\u8f7b\u91cf\u7ea7\u6587\u672c\u7f16\u8f91\u5668\uff0c\u901a\u8fc7\u5b89\u88c5\u63d2\u4ef6\u53ef\u4ee5\u81ea\u5b9a\u4e49\u6210\u4e00\u6b3e\u529f\u80fd\u5f3a\u5927\u7684 IDE \u5f00\u53d1\u5de5\u5177\u3002\u76ee\u524d\u652f\u6301 Python \u7684\u63d2\u4ef6\u4f53\u7cfb\u5df2\u7ecf\u8f83\u4e3a\u5b8c\u5584\uff0c\u6b64\u65b9\u6848\u4e5f\u53ef\u4ee5\u4f5c\u4e3a\u5907\u7528\u3002
"},{"location":"quick_start/#13","title":"1.3 \u865a\u62df\u73af\u5883\u5de5\u5177","text":"\u63a8\u8350\u4f7f\u7528 Poetry \uff0c\u65e2\u5305\u542b\u4e86\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\u4e5f\u652f\u6301\u6253\u5305\u53d1\u5e03\u7b49\u529f\u80fd\u3002
\u5728\u5b89\u88c5\u597d Python \u73af\u5883\u540e\uff0c\u5e94\u8be5\u5728\u5168\u5c40\u73af\u5883\u4e2d\u5b89\u88c5 Poetry \u3002
sudo python -m pip install -U pip\nsudo pip install -U poetry\n
"},{"location":"quick_start/#14","title":"1.4 \u521d\u59cb\u5316\u9879\u76ee","text":"cookiecutter \u662f\u4e00\u4e2a\u901a\u8fc7\u9879\u76ee\u6a21\u677f\u521b\u5efa\u9879\u76ee\u7684\u547d\u4ee4\u884c\u5de5\u5177\u3002
\u5b89\u88c5 cookiecutter
sudo pip3 install -U cookiecutter\n
\u521d\u59cb\u5316\u9879\u76ee
cd workspace\ncookiecutter https://github.com/pyloong/cookiecutter-pythonic-project\n
\u8fd0\u884c\u547d\u4ee4\u540e\u4f1a\u51fa\u73b0\u4e0b\u9762\u7684\u914d\u7f6e\u8fc7\u7a0b\uff0c\u5982\u679c\u4f60\u4e0d\u6e05\u695a\u914d\u7f6e\u7684\u5177\u4f53\u7528\u9014\uff0c\u53ef\u4ee5\u76f4\u63a5\u6309\u56de\u8f66\u4f7f\u7528\u9ed8\u8ba4\u914d\u7f6e\uff0c\u9ed8\u8ba4\u914d\u7f6e\u4f7f\u7528\u9879\u76ee\u6a21\u677f\u521d\u59cb\u503c\u3002
\u276f cookiecutter https://github.com/pyloong/cookiecutter-pythonic-project\nproject_name [My Project]: Word Count\nproject_slug [word_count]: project_description [My Awesome Project!]: Word Count Project.\nauthor_name [Author]: test\nauthor_email [author@example.com]: test@example.com\nversion [0.1.0]: Select python_version:\n1 - 3.10\n2 - 3.11\nChoose from 1, 2 [1]: use_src_layout [y]: use_poetry [y]: use_docker [n]: Select ci_tools:\n1 - none\n2 - Gitlab\n3 - Github\nChoose from 1, 2, 3 [1]: init_skeleton [n]:\n
\u5982\u679c\u4f60\u5728\u4f7f\u7528\u9879\u76ee\u6a21\u677f\u8fc7\u7a0b\u4e2d\u6709\u4efb\u4f55\u95ee\u9898\u6216\u7591\u95ee\uff0c\u53ef\u4ee5\u901a\u8fc7\u53d1\u8d77 issues \u8fdb\u884c\u53cd\u9988\u3002
\u751f\u6210\u540e\u7684\u9879\u76ee\u7ed3\u6784\u5982\u4e0b\uff1a
word_count\n\u251c\u2500\u2500 .editorconfig\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 .pre-commit-config.yaml\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 docs\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 development.md\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 src\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 word_count\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 tests\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 conftest.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 settings.yml\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 test_version.py\n\u2514\u2500\u2500 tox.ini\n\n5 directories, 13 files\n
\u751f\u6210\u9879\u76ee\u7684 src
\u76ee\u5f55\u4e0b\u6709\u4e00\u4e2a\u9879\u76ee\u6a21\u5757\uff0c\u7528\u6765\u5b58\u653e\u9879\u76ee\u6e90\u4ee3\u7801\uff0c tests
\u76ee\u5f55\u7528\u6765\u7f16\u5199\u6a21\u5757\u7684\u76f8\u5173\u6d4b\u8bd5\u4ee3\u7801\u3002
pyproject.toml
\u5305\u542b\u9879\u76ee\u521d\u59cb\u4f9d\u8d56\uff0c\u548c\u9879\u76ee\u7684\u63cf\u8ff0\u4fe1\u606f\uff0ctox.ini
\u5b9a\u4e49\u4e86\u4efb\u52a1\u81ea\u52a8\u5316\u6267\u884c\u903b\u8f91\u3002
"},{"location":"quick_start/#15","title":"1.5 \u521d\u59cb\u5316\u9879\u76ee\u73af\u5883","text":"\u4f7f\u7528 poetry \u521d\u59cb\u5316\u4e00\u4e2a\u865a\u62df\u73af\u5883\u3002
cd word_count\npoetry install -v\n
\u521d\u59cb\u5316\u5b8c\u6210\u540e\uff0c\u4f1a\u751f\u6210\u4e00\u4e2a poetry.lock
\uff0c\u53ef\u4ee5\u7528\u6765\u9501\u5b9a\u751f\u4ea7\u73af\u5883\u5b89\u88c5\u5305\u7684\u7248\u672c\u548c\u4f9d\u8d56\u4fe1\u606f\u3002
"},{"location":"quick_start/#16-git","title":"1.6 \u521d\u59cb\u5316 Git","text":"\u63a8\u8350\u4f7f\u7528 Git \u5bf9\u9879\u76ee\u8fdb\u884c\u7248\u672c\u7ba1\u7406\u3002\u6240\u4ee5\u9700\u8981\u63d0\u524d\u5b89\u88c5 Git \uff0c\u5e76\u719f\u6089\u5e38\u7528\u7684 Git \u6982\u5ff5\u548c Git \u547d\u4ee4\u3002
git init\ngit config user.name test\ngit config user.email test@example.com\n\n# \u521d\u59cb\u5316\u9879\u76ee\u63d0\u4ea4\ngit add .\ngit commit -m \"feat: \u521d\u59cb\u5316\u9879\u76ee\u63d0\u4ea4\"\n
"},{"location":"quick_start/#17","title":"1.7 \u4f1a\u7528\u5230\u7684\u5176\u4ed6\u5de5\u5177","text":"\u5728\u751f\u6210\u7684 pyproject.toml
\u6587\u4ef6\u4e2d\uff0c\u9ed8\u8ba4\u6dfb\u52a0\u4e86\u4e00\u4e9b\u5f00\u53d1\u73af\u5883\u4e2d\u5e38\u7528\u7684\u5de5\u5177\u3002
isort
: isort \u662f\u4e00\u4e2a\u81ea\u52a8\u683c\u5f0f\u5316\u5bfc\u5165\u5de5\u5177 pylint
: pylint \u662f\u4e00\u4e2a\u68c0\u6d4b\u4ee3\u7801\u98ce\u683c\u5de5\u5177 pytest
: pytest \u662f\u4e00\u4e2a\u66f4\u52a0\u6613\u7528\u7684\u6d4b\u8bd5\u6846\u67b6\uff0c\u517c\u5bb9 unittest
\u6d4b\u8bd5\u6846\u67b6 pytest-cov
: pytest-cov \u662f pytest
\u7684 Coverage \u63d2\u4ef6\uff0c\u7528\u6765\u7edf\u8ba1\u6d4b\u8bd5\u8986\u76d6\u7387 mkdocs
: mkdocs \u662f\u4e00\u4e2a\u9879\u76ee\u6587\u6863\u6784\u5efa\u5de5\u5177\uff0c\u4f7f\u7528 markdown \u7f16\u5199\u5185\u5bb9\uff0c\u6784\u5efa\u751f\u6210\u6587\u6863\u9875\u9762\u3002 mkdocs-material
: mkdocs-material \u662f\u57fa\u4e8e mkdocs \u6784\u5efa\u6587\u6863\uff0c\u5e76\u63d0\u4f9b\u73b0\u4ee3\u5316\u4e3b\u9898\u7684\u5e93\u3002 tox
: tox \u662f\u4e00\u4e2a\u4efb\u52a1\u81ea\u52a8\u5316\u5de5\u5177
\u5982\u679c\u60f3\u8981\u4e86\u89e3\u76f8\u5173\u7684\u529f\u80fd\uff0c\u53ef\u4ee5\u9605\u8bfb\u5bf9\u5e94\u7684\u6280\u672f\u8bf4\u660e\u6587\u6863\u3002
"},{"location":"quick_start/#2","title":"2. \u529f\u80fd\u5f00\u53d1","text":"\u9996\u5148\u5c06\u9879\u76ee\u4ee5\u53ef\u7f16\u8f91\u65b9\u5f0f\u5b89\u88c5\u5230\u73af\u5883\u4e2d\uff1a
poetry install\n
\u8fd9\u6837\u505a\u7684\u76ee\u7684\u662f\u5c06 src
\u4e0b\u7684\u5305\u5b89\u88c5\u5230 Python \u73af\u5883\u4e2d\uff0c\u5426\u5219\u65e0\u6cd5\u6b63\u5e38\u5bfc\u5165\u5305\u4e2d\u7684\u6a21\u5757\u3002
"},{"location":"quick_start/#21","title":"2.1 \u529f\u80fd\u9700\u6c42","text":"\u63d0\u4f9b\u4e00\u4e2a\u4ece\u6587\u672c\u6587\u4ef6\u8bfb\u53d6\u6570\u636e\uff0c\u6570\u636e\u4ee5\u7a7a\u683c\u5206\u5272\u5355\u8bcd\uff0c\u7136\u540e\u7edf\u8ba1\u6587\u4ef6\u4e2d\u7684\u5355\u8bcd\u6570\u91cf\uff0c\u5e76\u5c06\u7ed3\u679c\u5199\u5165\u5230\u76ee\u6807\u6587\u4ef6\u4e2d\u3002
"},{"location":"quick_start/#22","title":"2.2 \u7f16\u5199\u8ba1\u6570\u5668","text":"\u5728 src/word_count/
\u4e0b\u521b\u5efa counter.py
\u6587\u4ef6\uff0c\u540c\u65f6\u52a0\u5165\u5982\u4e0b\u5185\u5bb9\uff1a
\"\"\"Count a file \"\"\"\nimport logging\nfrom collections.abc import Generator\nfrom pathlib import Path\n# Config root logger\nlogging.basicConfig(\nlevel=logging.DEBUG,\nformat='%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n)\ndef count(source_file: str, dest_file: str):\n\"\"\"\n Count source\n :param source_file:\n :param dest_file:\n :return:\n \"\"\"\nwords = read_from_file(Path(source_file))\ntotal = 0\nfor _ in words:\ntotal += 1\nwrite_to_file(Path(dest_file), total)\ndef read_from_file(source_file: Path) -> Generator[str, None, None]:\n\"\"\"\n :param source_file:\n :return:\n \"\"\"\n# Read source_file\nlogging.debug('Read file: %s', source_file)\nwith open(source_file, 'r', encoding='utf-8') as source_obj:\nfor line in source_obj:\nfor word in line.split(' '):\nyield word\ndef write_to_file(dest_file: Path, total_words: int) -> None:\n\"\"\"\n Write result to file\n :param dest_file:\n :param total_words:\n :return:\n \"\"\"\nlogging.debug('Count %s words, write to %d', dest_file, total_words)\nwith open(dest_file, 'w', encoding='utf-8') as dest_obj:\ndest_obj.write(f'Total count: {total_words}')\n
"},{"location":"quick_start/#221","title":"2.2.1 \u5bfc\u5165\u683c\u5f0f\u5316","text":"\u5728\u9879\u76ee\u6839\u76ee\u5f55\u8fd0\u884c isort \u5bf9\u5bfc\u5165\u8fdb\u884c\u683c\u5f0f\u5316\u3002
isort .\n
\u6b64\u64cd\u4f5c\u4f1a\u81ea\u52a8\u4fee\u6539\u4ee3\u7801\uff0c\u5c06\u5bfc\u5165\u7684\u5305\u683c\u5f0f\u5316\u3002\u5982\u679c\u60f3\u67e5\u770b\u533a\u522b\uff0c\u53ef\u4ee5\u8fd0\u884c\u5982\u4e0b\u547d\u4ee4\uff1a
isort . --check-only --diff\n
"},{"location":"quick_start/#222","title":"2.2.2 \u4ee3\u7801\u98ce\u683c\u68c0\u67e5","text":"\u5728\u9879\u76ee\u6839\u76ee\u5f55\u8fd0\u884c pylint \u68c0\u67e5\u4ee3\u7801\u662f\u5426\u89c4\u8303\uff0c\u662f\u5426\u7b26\u5408 PEP8 \u6807\u51c6\u3002
pylint tests src\n
\u6b64\u64cd\u4f5c\u4f1a\u5217\u51fa\u4ee3\u7801\u4e2d\u4e0d\u7b26\u5408\u89c4\u8303\u7684\u90e8\u5206\uff0c\u5e76\u663e\u793a\u5bf9\u5e94\u7684\u89c4\u8303\u540d\u79f0\u3002\u53ef\u4ee5\u5728\u8fd9\u91cc\u627e\u5230\u6240\u6709\u89c4\u5219\u3002
\u5728\u5b8c\u6210\u4fee\u6539\u540e\u518d\u6b21\u8fd0\u884c\u4e24\u4e2a\u547d\u4ee4\uff0c\u76f4\u5230\u90fd\u6ca1\u6709\u5f02\u5e38\u8f93\u51fa\u4e3a\u6b62\u3002
"},{"location":"quick_start/#223","title":"2.2.3 \u6d4b\u8bd5","text":"\u5982\u679c\u4f60\u4f7f\u7528\u7684\u662f Pycharm \u5f00\u53d1\uff0c\u53ef\u4ee5\u901a\u8fc7\u70b9\u51fb File
--> Settings
--> Tools
--> Python Integrated Tools
--> Testing
--> Default runner
\u9009\u62e9\u6d4b\u8bd5\u6846\u67b6\uff0c\u63a8\u8350\u4f7f\u7528 pytest
\u3002
\u4e3a\u4e86\u65b9\u4fbf\u4f7f\u7528 mock
\u9700\u8981\u5b89\u88c5 pytest-mock
\u6a21\u5757\uff0c\u53ef\u4ee5\u5728 pytest
\u7684 fixture
\u7279\u6027\u4e0a\u4f7f\u7528 mock
\u3002
\u5b89\u88c5\u5f00\u53d1\u73af\u5883\u4f9d\u8d56\uff1a
poetry add --group dev pytest-mock\n
\u6dfb\u52a0\u6d4b\u8bd5\u914d\u7f6e\uff0c\u5728 tests/conftest.py
\u4e2d\u52a0\u5165\uff1a
\"\"\"Test config\"\"\"\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\nimport pytest\n@pytest.fixture\ndef mock_path() -> Path:\n\"\"\"Mock a path, and clean when unit test done.\"\"\"\nwith TemporaryDirectory() as temp_path:\nyield Path(temp_path)\n
\u5728 tests/
\u4e0b\u6dfb\u52a0\u4e0e src/word_count
\u76ee\u5f55\u4e2d\u6587\u4ef6\u540d\u76f8\u540c\u7684\u6587\u4ef6\uff0c\u5e76\u5728\u6587\u4ef6\u540d\u524d\u6dfb\u52a0 test_
\u524d\u7f00\u3002
\u6dfb\u52a0\u6587\u4ef6 tests/test_counter.py
\uff1a
\"\"\"Test counter\"\"\"\nfrom pathlib import Path\nimport pytest\nfrom word_count.counter import count, read_from_file, write_to_file\n@pytest.fixture(name='mock_source_file')\ndef fixture_mock_source_file(mock_path) -> Path:\n\"\"\"mock source_file, this file has two words.\"\"\"\nwords = ['hello', ' ', 'words']\nsource_file = mock_path / 'source.txt'\nwith open(source_file, 'w', encoding='utf-8') as obj:\nobj.write(''.join(words))\nyield source_file\ndef test_read_from_file(mock_source_file):\n\"\"\"Test read_from_file\"\"\"\nresult = read_from_file(mock_source_file)\nassert sum(1 for _ in result) == 2\ndef test_write_to_file(mock_path):\n\"\"\"Test write_to_file\"\"\"\ndest_file = mock_path / 'dest.txt'\nwrite_to_file(dest_file, 100)\nwith open(dest_file, 'r', encoding='utf-8') as obj:\ntxt = obj.read()\nassert 'Total count: 100' in txt\ndef test_count(mocker, mock_path, mock_source_file):\n\"\"\"Test count\"\"\"\nmock_read_from_file = mocker.patch(\n'word_count.counter.read_from_file',\nreturn_value=list(range(10))\n)\nmock_write_to_file = mocker.patch(\n'word_count.counter.write_to_file'\n)\ndest_file = mock_path / 'dest.txt'\ncount(str(mock_source_file), str(dest_file))\nmock_read_from_file.assert_called_once_with(mock_source_file)\nmock_write_to_file.assert_called_once_with(dest_file, 10)\n
\u8fd0\u884c pytest
\uff0c\u8ba9\u6d4b\u8bd5\u6b63\u786e\u8fd0\u884c\u3002\u5982\u679c\u6d4b\u8bd5\u7528\u4f8b\u5931\u8d25\uff0c\u9700\u8981\u6839\u636e\u51fa\u9519\u5806\u6808\u627e\u5230\u95ee\u9898\u539f\u56e0\uff0c\u89e3\u51b3\u6389\u540e\u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u547d\u4ee4\uff0c\u76f4\u5230\u4ee3\u7801\u6d4b\u8bd5\u901a\u8fc7\u3002
\u7136\u540e\u8fd0\u884c isort
\u548c pylint src tests
\u683c\u5f0f\u5316\u4ee3\u7801\u5e76\u68c0\u67e5\u4ee3\u7801\u98ce\u683c\u3002
"},{"location":"quick_start/#224","title":"2.2.4 \u63d0\u4ea4\u4ee3\u7801","text":"\u4e00\u4e2a\u529f\u80fd\u7279\u6027\u5f00\u53d1\u5b8c\u6210\u540e\uff0c\u9700\u8981\u63d0\u4ea4\u4ee3\u7801\u6765\u4fdd\u5b58\u8bb0\u5f55\uff0c\u907f\u514d\u610f\u5916\u64cd\u4f5c\u3002
git add .\ngit commit -m \"feat(counter): \u589e\u52a0 Counter \u903b\u8f91\uff0c\u5e76\u5b8c\u6210\u6d4b\u8bd5\u3002\"\n
"},{"location":"quick_start/#23","title":"2.3 \u7f16\u5199\u547d\u4ee4\u884c\u5165\u53e3","text":"\u5728 src/word_count/
\u76ee\u5f55\u4e0b\uff0c\u521b\u5efa cmdline.py
\u6587\u4ef6\uff0c\u52a0\u5165\u5982\u4e0b\u5185\u5bb9\uff1a
\"\"\"Cmdline\"\"\"\nimport argparse\nimport sys\nfrom word_count.counter import count\ndef init_args() -> argparse.Namespace:\n\"\"\"Init argument and parse\"\"\"\nparser = argparse.ArgumentParser()\nparser.add_argument('-s', '--source', required=True, help='Source file used for count.')\nparser.add_argument('-d', '--dest', required=True, help='Dest file used for count result')\nreturn parser.parse_args(sys.argv[1:])\ndef main():\n\"\"\"Execute\"\"\"\nargs = init_args()\ncount(args.source, args.dest)\nif __name__ == '__main__':\nmain()\n
\u8fd0\u884c isort
\u548c pylint
\u683c\u5f0f\u5316\u4ee3\u7801\u5e76\u68c0\u67e5\u4ee3\u7801\u98ce\u683c\u3002
"},{"location":"quick_start/#231","title":"2.3.1 \u6d4b\u8bd5","text":"\u521b\u5efa tests/test_cmdline.py
\u6587\u4ef6\uff0c\u52a0\u5165\u5982\u4e0b\u5185\u5bb9\uff1a
\"\"\"Test cmdline\"\"\"\nimport sys\nimport pytest\nfrom word_count import cmdline\ndef test_help(mocker, capsys):\n\"\"\"test help command\"\"\"\nargs = ['word_count', '-h']\nmocker.patch.object(sys, 'argv', args)\nwith pytest.raises(SystemExit) as ex:\ncmdline.main()\nassert ex.value.code == 0\nouterr = capsys.readouterr()\nassert '-s SOURCE' in outerr.out\nassert '-d DEST' in outerr.out\ndef test_only_pass_source(mocker, capsys):\n\"\"\"test only pass -s \"\"\"\nargs = ['word_count', '-s', 'foo']\nmocker.patch.object(sys, 'argv', args)\nwith pytest.raises(SystemExit) as ex:\ncmdline.main()\nassert ex.value.code == 2\nouterr = capsys.readouterr()\nassert 'the following arguments are required: -d' in outerr.err\ndef test_only_pass_dest(mocker, capsys):\n\"\"\"test only pass -d\"\"\"\nargs = ['word_count', '-d', 'foo']\nmocker.patch.object(sys, 'argv', args)\nwith pytest.raises(SystemExit) as ex:\ncmdline.main()\nassert ex.value.code == 2\nouterr = capsys.readouterr()\nassert 'the following arguments are required: -s' in outerr.err\ndef test_main(mocker):\n\"\"\"test cmdline, and everything is fine.\"\"\"\nargs = ['word_count', '-s', 'foo', '-d', 'bar']\nmocker.patch.object(sys, 'argv', args)\nmock_count = mocker.patch('word_count.cmdline.count')\ncmdline.main()\nmock_count.assert_called_once()\n
\u8fd0\u884c pytest
\uff0c\u8ba9\u6d4b\u8bd5\u6b63\u786e\u8fd0\u884c\u3002
\u8fd0\u884c isort
\u548c pylint
\u683c\u5f0f\u5316\u4ee3\u7801\u5e76\u68c0\u67e5\u4ee3\u7801\u98ce\u683c\u3002
"},{"location":"quick_start/#232","title":"2.3.2 \u63d0\u4ea4\u4ee3\u7801","text":"git add .\ngit commit -m \"feat(cmdline): \u589e\u52a0 cmdline \u903b\u8f91\uff0c\u5e76\u5b8c\u6210\u6d4b\u8bd5\u3002\"\n
"},{"location":"quick_start/#24","title":"2.4 \u603b\u7ed3","text":"\u81f3\u6b64\uff0c\u6211\u4eec\u7684\u529f\u80fd\u5df2\u7ecf\u5f00\u53d1\u5b8c\u6210\u3002\u5728\u6574\u4e2a\u5f00\u53d1\u8fc7\u7a0b\u4e2d\uff0c\u6211\u4eec\u9075\u5faa\u4e86 \u201c\u6dfb\u52a0\u529f\u80fd\u7279\u6027\u201d => \u201c\u4ee3\u7801\u98ce\u683c\u68c0\u67e5\u201d => \u201c\u5355\u5143\u6d4b\u8bd5\u201d \u7684\u5f00\u53d1\u6d41\u7a0b\u3002
\u5982\u679c\u611f\u89c9\u6bcf\u6b21\u8fd0\u884c\u591a\u4e2a\u547d\u4ee4\u6bd4\u8f83\u7e41\u7410\uff0c\u53ef\u4ee5\u5728\u9879\u76ee\u6839\u76ee\u5f55\u4e2d\u8fd0\u884c tox
\u81ea\u52a8\u5316\u5b8c\u6210\u4ee3\u7801\u6d4b\u8bd5\u3001\u5bfc\u5305\u68c0\u67e5\u548c\u4ee3\u7801\u98ce\u683c\u68c0\u67e5\u3002
tox\n
\u73b0\u5728\u53ef\u4ee5\u5728\u7ec8\u7aef\u4e2d\u8fd0\u884c\u5355\u8bcd\u7edf\u8ba1\uff1a
python src/word_count/cmdline.py -s LICENSE -d /tmp/res.txt\n
"},{"location":"quick_start/#25","title":"2.5 \u6253\u5305\u53d1\u5e03","text":"\u5982\u679c\u5e0c\u671b\u522b\u4eba\u80fd\u66f4\u65b9\u4fbf\u7684\u4f7f\u7528\u9879\u76ee\uff0c\u53ef\u4ee5\u5c06\u9879\u76ee\u6253\u5305\u53d1\u5e03\u5230 pypi \u4e2d\uff0c\u7136\u540e\u5728\u9700\u8981\u4f7f\u7528\u7684\u5730\u65b9\u8fd0\u884c pip install -U word-count
\u3002
\u4f46\u662f\u5b89\u88c5\u5230\u73af\u5883\u540e\u53bb\u8fd0\u884c cmdline.py
\u4f1a\u6bd4\u8f83\u9ebb\u70e6\uff0c\u6240\u4ee5\u9700\u8981\u5c06 cmdline.py
\u6ce8\u518c\u6210\u53ef\u6267\u884c\u547d\u4ee4\u3002
\u4fee\u6539 pyproject.toml
\uff0c\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
[tool.poetry.plugins.console_scripts]\nword_count = \"word_count.cmdline:main\"\n
\u5f53\u4f7f\u7528 pip
\u547d\u4ee4\u5c06\u9879\u76ee\u5305\u5b89\u88c5\u5230\u73af\u5883\u540e\uff0c\u4f1a\u81ea\u52a8\u6ce8\u518c\u4e00\u4e2a word_count
\u7684\u53ef\u6267\u884c\u547d\u4ee4\u3002
\u518d\u6b21\u5c06\u672c\u5730\u9879\u76ee\u4ee5\u53ef\u7f16\u8f91\u65b9\u5f0f\u5b89\u88c5\u5230\u5f53\u524d Python \u73af\u5883\uff1a
poetry install\n
\u7136\u540e\u5c31\u53ef\u4ee5\u6b63\u5e38\u4f7f\u7528 word_count
\u547d\u4ee4\uff1a
$ word_count -h\nusage: word_count [-h] -s SOURCE -d DEST\n\noptional arguments:\n -h, --help show this help message and exit\n -s SOURCE, --source SOURCE\n Source file used for count.\n -d DEST, --dest DEST Dest file used for count result\n
"},{"location":"quick_start/#251","title":"2.5.1 \u6253\u5305","text":"\u8fd0\u884c\u6253\u5305\u547d\u4ee4\uff1a
poetry build
sdist
\u4f1a\u5c06\u9879\u76ee\u6253\u5305\u6210\u6e90\u7801\u5305\uff0c bdist_wheel
\u4f1a\u5c06\u9879\u76ee\u6253\u5305\u6210\u7f16\u8bd1\u540e\u7684\u4e8c\u8fdb\u5236\u5305\u3002
\u6253\u5305\u540e\u7684\u6587\u4ef6\u5728 dist
\u76ee\u5f55\u4e2d\u3002\u53ef\u4ee5\u76f4\u63a5\u5728\u5176\u4ed6\u5730\u65b9\u8fd0\u884c pip install word_count.wheel
\u5b89\u88c5\u3002
"},{"location":"quick_start/#252","title":"2.5.2 \u53d1\u5e03","text":"\u5c06\u5f00\u53d1\u597d\u7684\u9879\u76ee\u53d1\u5e03\u5230\u7d22\u5f15\u4ed3\u5e93\uff0c\u6216\u5185\u7f51\u7684\u79c1\u6709\u4ed3\u5e93\u3002
poetry publish\n
\u9ed8\u8ba4\u4f1a\u5c06\u9879\u76ee\u53d1\u5e03\u5230 pypi \u4e2d\uff0c\u6240\u4ee5\u9700\u8981\u6709\u5bf9\u5e94\u7684\u767b\u5f55\u8d26\u53f7\u3002
"},{"location":"datadevelop/quick_start/etl_develop/","title":"\u5e94\u7528\u5f00\u53d1","text":"\u63d0\u4f9bETL\u5de5\u7a0b\u5316\u7684\u9879\u76ee\u793a\u4f8b\uff0c\u5e2e\u52a9\u521d\u5b66\u8005\u5feb\u901f\u7406\u89e3\u548c\u5b66\u4e60ETL\u5b8c\u6574\u7684\u5de5\u7a0b\u5316\u5f00\u53d1\u3002
"},{"location":"datadevelop/quick_start/etl_develop/#_2","title":"\u4efb\u52a1\u9700\u6c42","text":"\u73b0\u6709\u6c7d\u8f66\u4fe1\u606f\u6570\u636ecar_price.csv
-
\u5bf9CarName
\u5b57\u6bb5\u5305\u542b[dirty data]
\u6570\u636e\u8fdb\u884c\u7ea0\u6b63\uff0c\u53bb\u9664\u5b57\u7b26\u4e32[dirty data]
-
\u5220\u9664price
\u5c0f\u4e8e10000
\u7684\u6c7d\u8f66\u6570\u636e
-
\u6700\u7ec8\u7ed3\u679cSchema\uff1acar_id
, symboling
, car_name
, price
, \u5c06\u7ed3\u679c\u5bfc\u51fajson
\u6587\u4ef6
\u5728\u547d\u4ee4\u884c\u4f7f\u7528cookiecutter
\u521b\u5efa\u9879\u76ee\u9aa8\u67b6:
\u276f cookiecutter https://github.com/pyloong/cookiecutter-pythonic-project-bigdata-etl\nproject_name [My Project]: Automotive Data Etl\nproject_slug [automotive_data_etl]:\nproject_description [My Awesome Project!]: This is my first etl package, i love it.\nauthor_name [Author]: ming\nauthor_email [ming@example.com]: ming@gmail.com\nversion [0.1.0]:\nSelect python_version:\n1 - 3.10\n2 - 3.9\nChoose from 1, 2 [1]:\nuse_src_layout [y]:\nuse_poetry [y]:\nuse_docker [n]:\nSelect ci_tools:\n1 - none\n2 - Gitlab\n3 - Github\nChoose from 1, 2, 3 [1]:\nSelect use_framework:\n1 - none\n2 - pyspark\nChoose from 1, 2 [1]: 2\n
"},{"location":"datadevelop/quick_start/etl_develop/#task","title":"Task\u7c7b","text":"\u521b\u5efa\u6c7d\u8f66\u6570\u636eETL\u4efb\u52a1AutomotiveDataTask
\u7c7b\uff0csrc/automotive_data_etl/tasks/automotive_task/task.py
task.py\"\"\"Processing car data task.\"\"\"\nimport logging\nfrom pyspark.sql import DataFrame\nfrom automotive_data_etl.tasks.abstract.task import AbstractTask\nfrom automotive_data_etl.tasks.automotive_task.automotive_transform import AutomotiveDataTransform\nclass AutomotiveDataTask(AbstractTask):\n\"\"\"Processing car data task.\"\"\"\ndef __init__(self):\nsuper().__init__()\nself.spark = self.ctx.get_spark_session()\ndef _extract(self) -> DataFrame:\n\"\"\"Read CSV file return DataFrame\"\"\"\ndf: DataFrame = self.spark.read.csv(\nself.settings.input_path,\nencoding='utf-8',\nheader=True,\ninferSchema=True\n)\nthis.logger.info(f'Extract data from {self.settings.input_path}')\nreturn df\ndef _transform(self, df: DataFrame) -> DataFrame:\n\"\"\"execute CarTransform transform function\"\"\"\nreturn AutomotiveDataTransform().transform(df)\ndef _load(self, df: DataFrame) -> None:\n\"\"\"Load final data to output path\"\"\"\ndf.write.json(self.settings.output_path, mode='overwrite', encoding='utf-8')\nthis.logger.info(f'Load data to {self.settings.output_path}')\n
\u6bcf\u4e00\u4e2aTask
\u4efb\u52a1\u90fd\u4f1a\u7ecf\u8fc7\u201c\u8f93\u5165\u201d\u3001\u201c\u8f6c\u6362\u201d\u548c\u201c\u8f93\u51fa\u201d\u7684\u8fc7\u7a0b\uff0c\u5b9e\u73b0AbstractTask
\u4e2d\u7684_extract
\u3001_transform
\u3001_load
\u62bd\u8c61\u65b9\u6cd5
_extract
\uff1a\u8bfb\u53d6tmp/input/car_price.csv
CSV\u6587\u4ef6 _transform
\uff1a\u6267\u884c\u5c06\u5b9e\u73b0\u7684Transform
\u7c7b\u7684transform
\u65b9\u6cd5 _load
\uff1a\u5c06DataFrame\u4ee5Json\u683c\u5f0f\u5199\u5165tmp/output
\u76ee\u5f55\u4e0b
"},{"location":"datadevelop/quick_start/etl_develop/#transform","title":"Transform\u7c7b","text":"\u521b\u5efa\u6c7d\u8f66\u6570\u636eAutomotiveDataTransform
\u7c7b\uff0csrc/automotive_data_etl/tasks/automotive_task/automotive_transform.py
automotive_transform.py\"\"\"Car data Transformation.\"\"\"\nfrom functools import reduce\nfrom pyspark.sql import DataFrame\nfrom pyspark.sql.functions import col\nfrom pyspark.sql.functions import udf\nfrom pyspark.sql.types import StringType\nfrom automotive_data_etl.tasks.abstract.transform import AbstractTransform\nclass AutomotiveDataTransform(AbstractTransform):\n\"\"\"Car data Transformation.\"\"\"\ndef transform(self, df: DataFrame) -> DataFrame:\n\"\"\"Execute the transform process\"\"\"\ntransformations = (\nself._filter_price,\nself._process_car_name,\nself._select_final_columns,\n)\nreturn reduce(DataFrame.transform, transformations, df) # type: ignore\n@staticmethod\ndef _filter_price(df: DataFrame) -> DataFrame:\n\"\"\"Filter results price > 10000\"\"\"\nreturn df.filter(col('price') > 10000)\n@staticmethod\ndef _process_car_name(df: DataFrame) -> DataFrame:\n\"\"\"Clean [dirty data] from CarName\"\"\"\nres_df = df.withColumn('CarName', _name_replace_udf(col('CarName')).alias('CarName'))\nreturn res_df\n@staticmethod\ndef _select_final_columns(df: DataFrame) -> DataFrame:\n\"\"\"Car price data dataframe select final columns\"\"\"\nreturn df.select(\ncol('car_ID').alias('car_id'),\ncol('symboling'),\ncol('CarName').alias('car_name'),\ncol('price'),\n)\n@udf(returnType=StringType())\ndef _name_replace_udf(car_name):\n\"\"\"Clean [dirty data] udf\"\"\"\nif not car_name:\nreturn None\nerr_str = '[dirty data]'\nif err_str not in car_name:\nreturn car_name\ncar_name = car_name.replace(err_str, '')\nreturn car_name\n
AutomotiveDataTransform
\u7c7b\uff0c\u5b9e\u73b0\u4ee5\u4e0b\u65b9\u6cd5\uff1a
_filter_price
\u7684\u529f\u80fd\u662f\u7b5b\u9009 price
> 10000 \u7684\u6570\u636e _process_car_name
\u7684\u529f\u80fd\u662f\u4f7f\u7528udf\u65b9\u6cd5_name_replace_udf
\uff0c\u5c06CarName
\u5b57\u6bb5\u4e2d\u5305\u542b\u810f\u6570\u636e[dirty_data]
\u7684\u5185\u5bb9\u8fdb\u884c\u5904\u7406 _select_final_columns
\u65b9\u6cd5\u662f\u67e5\u8be2\u5e76\u8fd4\u56decar_id
,symboling
,car_name
,price
\u6570\u636e transform
\u7684\u529f\u80fd\u662f\u6267\u884c\u5904\u7406\u6d41\u7a0b\uff0c\u8fd4\u56de\u6570\u636e\u7ed3\u679c\uff0c\u81f3\u6b64\u5b8c\u6210\u6c7d\u8f66\u6570\u636e\u8f6c\u6362\u8fc7\u7a0b
"},{"location":"datadevelop/quick_start/etl_develop/#_3","title":"\u914d\u7f6e","text":"\u5c06\u5982\u4e0b\u914d\u7f6e\u66f4\u65b0\u5230\u914d\u7f6e\u6587\u4ef6\u4e2d\uff0c\u56e0\u4e3a\u9879\u76ee\u9ed8\u8ba4\u4f7f\u7528dev\u73af\u5883\u914d\u7f6e\uff0c\u5219\u9700\u8981\u5728configs/dev.toml
\u4e2d\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
# spark configs\nspark_master = 'local[*]'\nspark_config.spark.driver.memory = '3G'\nspark_config.spark.executor.memory = '16G'\nspark_config.spark.sql.debug.maxToStringFields = 100\n
"},{"location":"datadevelop/quick_start/initialization/","title":"\u521d\u59cb\u5316\u9879\u76ee","text":"\u672c\u6587\u901a\u8fc7\u4e00\u4e2a\u5305\u542b\u4e3b\u8981\u77e5\u8bc6\u70b9\u7684\u7b80\u5355\u9879\u76ee\uff0c\u5411\u5f00\u53d1\u8005\u5c55\u793a\u4e00\u4e2a\u901a\u7528\u3001\u89c4\u8303\u548c\u6613\u4e8e\u7406\u89e3\u7684ETL\u7684\u9879\u76ee\u5f00\u53d1\u6d41\u7a0b\u3002 \u793a\u4f8b\u9879\u76ee\u4f7f\u7528Pyspark
\u5c06\u672c\u5730\u6587\u4ef6\u8fdb\u884c\u9884\u5904\u7406\uff0c\u5e76\u5c06\u7ed3\u679c\u5bfc\u51fa\u6587\u4ef6\u7684\u6f14\u793a\u7a0b\u5e8f\u3002
"},{"location":"datadevelop/quick_start/initialization/#_2","title":"\u521b\u5efa\u9879\u76ee\u9aa8\u67b6","text":"\u4f7f\u7528 cookiecutter \u52a0\u8f7d\u9879\u76ee\u6a21\u677f\u3002\u901a\u8fc7\u4ea4\u4e92\u64cd\u4f5c\uff0c\u53ef\u4ee5\u9009\u62e9\u4f7f\u7528\u7684\u529f\u80fd\u3002
cookiecutter https://github.com/pyloong/cookiecutter-pythonic-project-bigdata-etl\n
"},{"location":"datadevelop/quick_start/initialization/#_3","title":"\u521b\u5efa\u865a\u62df\u73af\u5883","text":"\u5207\u6362\u5230\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\uff0c\u9879\u76ee\u4f7f\u7528 poetry \u7ba1\u7406\u865a\u62df\u73af\u5883\uff0c\u8fd0\u884c\u547d\u4ee4\u81ea\u52a8\u521b\u5efa\u865a\u62df\u73af\u5883\uff0c\u540c\u65f6\u5b89\u88c5\u5f00\u53d1\u73af\u5883\u4f9d\u8d56
poetry install\n
"},{"location":"datadevelop/quick_start/initialization/#ide","title":"IDE\u9879\u76ee\u521d\u59cb\u5316","text":""},{"location":"datadevelop/quick_start/initialization/#_4","title":"\u52a0\u8f7d\u865a\u62df\u73af\u5883","text":"\u4f7f\u7528Pycharm\u6253\u5f00\u9879\u76ee File
| Settings
| Project
| Python Interpreter
\u9009\u62e9 Poetry Environment
\u6dfb\u52a0\u521a\u624d\u521b\u5efa\u7684\u865a\u62df\u73af\u5883(\u9009\u62e9Existing
)
"},{"location":"datadevelop/quick_start/initialization/#_5","title":"\u4fee\u6539\u9879\u76ee\u7ed3\u6784","text":"\u4f7f\u7528Pycharm\u6253\u5f00\u9879\u76ee File
| Settings
| Project
| Project Structure
\u5c06src
\u548ctests
\u76ee\u5f55\u8bbe\u7f6e\u4e3aSources
\u6e90\u4ee3\u7801\u8def\u5f84
"},{"location":"datadevelop/quick_start/initialization/#etl","title":"ETL\u9879\u76ee\u6982\u8ff0","text":"\u5728\u5f00\u53d1ETL\u4efb\u52a1\u7684\u65f6\u5019\uff0c\u5efa\u8bae\u5728tasks
\u76ee\u5f55\u4e0b\u65b0\u5efa\u6587\u4ef6\u5939\u6765\u5b58\u653e\u5bf9\u5e94ETL\u4efb\u52a1\u4ee3\u7801\uff0c\u8fd9\u6837\u505a\u7684\u597d\u5904\u662f\u65b9\u4fbf\u7ba1\u7406\u548c\u9605\u8bfb\u3002
"},{"location":"datadevelop/quick_start/initialization/#executor","title":"Executor","text":"\u6267\u884c\u5668executor
\uff0c\u5b83\u53ea\u5173\u5fc3AbstractTask
\u7684run
\u65b9\u6cd5\uff0c\u5f00\u53d1\u8005\u4e0d\u9700\u8981\u91cd\u590d\u5f00\u53d1\u8c03\u7528Task
\u76f8\u5173\u7684\u529f\u80fd\u3002
_load_task
\uff1a\u901a\u8fc7stevedore \u63d2\u4ef6\u6846\u67b6\uff0c\u67e5\u627e\u5728namespace
\u4e2d\u6ce8\u518c\u7684Task
\uff0c\u5e76\u8fdb\u884c\u5b9e\u4f8b\u5316\u3002
run
\uff1a\u8c03\u7528AbstractTask
\u7684run
\u65b9\u6cd5\u3002
\"\"\"\nLoads a Task class and calls its `run()` method.\n\"\"\"\nimport logging\nfrom typing import Callable\nfrom stevedore import ExtensionManager\nfrom etl_project.constants import TASK_NAMESPACE\nfrom etl_project.context import Context\nfrom etl_project.utils.exception import PluginNotFoundError\nclass Executor:\n\"\"\"\n Loads a Task class and calls its `run()` method.\n \"\"\"\n# pylint: disable=too-few-public-methods\ndef __init__(self, ctx: Context, task: str):\nself.ctx = ctx\nself.task = task\ndef run(self) -> None:\n\"\"\"calls its `run()` method in the task class\"\"\"\ntask_class = self._load_task(TASK_NAMESPACE, self.task)\nlogging.info(f\"Running task: {task_class}\")\ntask_class().run()\n@staticmethod\ndef _load_task(namespace: str, name: str) -> Callable:\n\"\"\"Get extension by name from namespace, return task obj\"\"\"\nextension_manager = ExtensionManager(namespace=namespace, invoke_on_load=False)\nfor ext in extension_manager.extensions:\nif ext.name == name:\nreturn ext.plugin\nlogging.warning(f'Load plugin: {ext.plugin} in namespace \"{namespace}\"')\nraise PluginNotFoundError(namespace=namespace, name=name)\n
"},{"location":"datadevelop/quick_start/initialization/#abstracttask","title":"AbstractTask","text":"\u5728\u5f00\u53d1\u65f6\u5efa\u8bae\u5b9e\u73b0AbstractTransform
\u548cAbstractTask
\uff0c\u8fd9\u4e24\u4e2a\u62bd\u8c61\u7c7b\u5c06\u4efb\u52a1\u7684\u901a\u7528\u65b9\u6cd5\u62bd\u8c61\u63d0\u51fa\uff0c\u5f00\u53d1\u8005\u53ea\u9700\u8981\u5173\u5fc3\u4e1a\u52a1\u5c31\u53ef\u4ee5\u4e86\u3002
AbstractTask
: Task\u4efb\u52a1\u62bd\u8c61\u7c7b\uff0cexecutor
\u6267\u884c\u5668\u5b9e\u4f8b\u5316Task
\uff0c\u6267\u884cAbstractTask
\u62bd\u8c61\u7236\u7c7b\u7684run
\u65b9\u6cd5
run
: \u6267\u884cTask\u4efb\u52a1\u6d41\u7a0b _extract
: \u6570\u636e\u6e90\u62bd\u53d6(\u62bd\u8c61\u65b9\u6cd5) _transform
: \u6570\u636e\u8f6c\u6362(\u62bd\u8c61\u65b9\u6cd5)\uff0c\u6267\u884c\u8f6c\u6362\u6d41\u7a0b\uff0c\u8c03\u7528AbstractTransform
\u5b50\u7c7b _load
: \u6570\u636e\u52a0\u8f7d(\u62bd\u8c61\u65b9\u6cd5)
\"\"\"Base Task\"\"\"\nfrom abc import ABC, abstractmethod\nfrom etl_project.context import Context\nclass AbstractTask(ABC):\n\"\"\"\n Base class to read a dataset, transform it, and save it to a table.\n \"\"\"\n# pylint: disable=[too-few-public-methods]\ndef __init__(self):\nself.ctx = Context()\nself.settings = self.ctx.settings\ndef run(self) -> None:\n\"\"\"Execute task module\"\"\"\ndata = self._extract()\ndata_transformed = self._transform(data)\nself._load(data_transformed)\n@abstractmethod\ndef _extract(self):\n\"\"\"extract tmp from file/database/other.\"\"\"\nraise NotImplementedError\n@abstractmethod\ndef _transform(self, data):\n\"\"\"Transform incoming tmp, and output the transform result\"\"\"\nraise NotImplementedError\n@abstractmethod\ndef _load(self, data) -> None:\n\"\"\"Load tmp to file/database/other.\"\"\"\nraise NotImplementedError\n
"},{"location":"datadevelop/quick_start/initialization/#abstracttransform","title":"AbstractTransform","text":"AbstractTransform
: Transform\u62bd\u8c61\u7c7b\uff0c\u63d0\u4f9bAbstractTask
\u4e2d_transform
\u8fdb\u884c\u8c03\u7528\uff0c\u540c\u4e00\u4e2aTask\u53ef\u4ee5\u5b9e\u73b0\u591a\u4e2a_transform
_transform
: \u6570\u636e\u8f6c\u6362(\u62bd\u8c61\u65b9\u6cd5)\uff0c\u6307\u5b9a\u8f6c\u6362\u6d41\u7a0b\uff0c\u5904\u7406\u8f93\u5165\u6570\u636e(data
)
\"\"\"Base Transform\"\"\"\nfrom abc import ABC, abstractmethod\nfrom etl_project.context import Context\nclass AbstractTransform(ABC):\n\"\"\"\n Base class to define a DataFrame transformation.\n \"\"\"\n# pylint: disable=[too-few-public-methods]\ndef __init__(self):\nself.ctx = Context()\nself.settings = self.ctx.settings\n@abstractmethod\ndef transform(self, data):\n\"\"\"Transform original dataset.\"\"\"\nraise NotImplementedError\n
"},{"location":"datadevelop/quick_start/initialization/#context","title":"Context","text":"context.py
\u8d1f\u8d23\u6574\u4e2a\u9879\u76ee\u7684\u4e0a\u4e0b\u6587\u5185\u5bb9\u7ba1\u7406\uff0c\u5355\u4f8b\u6a21\u5f0f\u5b9e\u73b0\uff0c\u5f00\u53d1\u8005\u53ef\u4ee5\u5c06\u516c\u5171\u7684\u5c5e\u6027\u6216\u65b9\u6cd5\u5728Context
\u4e2d\u8fdb\u884c\u5b9e\u73b0\u3002\u8fd9\u6837\u53ef\u4ee5\u907f\u514d\u5bf9\u8c61\u7684\u91cd\u590d\u5b9e\u4f8b\u5316\uff0c\u59cb\u7ec8\u4f7f\u7528\u540c\u4e00\u4e2aContext
\u5e76\u8c03\u7528\u5176\u4e2d\u7684\u5c5e\u6027\u548c\u65b9\u6cd5\u3002
\"\"\"Context\"\"\"\nfrom dynaconf.base import Settings\nfrom etl_project.constants import ENV_DEVELOPMENT\nfrom etl_project.dependencies.config import config_manager\nfrom etl_project.dependencies.logger import LoggerManager\n@singleton\nclass Context:\n\"\"\"\n Context for project, Provide properties and methods\n \"\"\"\nenvironment = ENV_DEVELOPMENT\ndef __init__(self):\n\"\"\"Context Parameters\"\"\"\nself.settings = config_manager.from_env(self.environment)\nself.logger = LoggerManager(self.settings).get_logger()\n
Context
\u6240\u4f7f\u7528\u7684@singleton
\u5355\u4f8b\u6a21\u5f0f\u88c5\u9970\u5668\u5b9e\u73b0\u5982\u4e0b\uff1a
\"\"\"Singleton pattern decorator\"\"\"\n_instance = {}\ndef singleton(cls):\n# \u521b\u5efa\u4e00\u4e2a\u5b57\u5178\u7528\u6765\u4fdd\u5b58\u88ab\u88c5\u9970\u7c7b\u7684\u5b9e\u4f8b\u5bf9\u8c61 _instance = {}\ndef _singleton(*args, **kwargs):\n# \u5224\u65ad\u8fd9\u4e2a\u7c7b\u6709\u6ca1\u6709\u521b\u5efa\u8fc7\u5bf9\u8c61\uff0c\u6ca1\u6709\u65b0\u521b\u5efa\u4e00\u4e2a\uff0c\u6709\u5219\u8fd4\u56de\u4e4b\u524d\u521b\u5efa\u7684\nif cls not in _instance:\n_instance[cls] = cls(*args, **kwargs)\nreturn _instance[cls]\nreturn _singleton\n
"},{"location":"datadevelop/quick_start/initialization/#_6","title":"\u6ce8\u518c\u63d2\u4ef6","text":"ETL\u4efb\u52a1\u5b8c\u6210\u540e\u9700\u8981\u6ce8\u518c\u63d2\u4ef6\uff1a
\u56e0\u4e3a\u9879\u76ee\u9ed8\u8ba4\u4f7f\u7528poetry \u7ba1\u7406\u865a\u62df\u73af\u5883\uff0c\u5219\u9700\u8981\u5728pyproject.toml
\u6587\u4ef6\u589e\u52a0\u4e2d\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
[tool.poetry.plugins.\"etl_tasks\"]\ntask_name = \"{task_class_path}:TaskExample\"\n
\u4f7f\u7528\u5982\u4e0b\u547d\u4ee4\u5c06\u9879\u76ee\u63d2\u4ef6\u66f4\u65b0\u5230\u73af\u5883\u4e2d\uff1a
poetry install\n
"},{"location":"datadevelop/quick_start/preparation/","title":"\u73af\u5883\u51c6\u5907","text":""},{"location":"datadevelop/quick_start/preparation/#_2","title":"\u5f00\u53d1\u73af\u5883","text":"\u672c\u9875\u603b\u7ed3\u4e86\u6570\u636e\u5f00\u53d1\u9879\u76ee\u6240\u9700\u7684\u51c6\u5907\u5de5\u4f5c\uff0c\u524d\u63d0\u5df2\u7ecf\u5b8c\u6210\u5f00\u53d1\u524d\u51c6\u5907 \u548c\u5b89\u88c5Cookiecutter\u3002
\u5feb\u901f\u4e0a\u624b\u793a\u4f8b\u9879\u76ee\u4f7f\u7528Pyspark\u8fdb\u884c\u5f00\u53d1\uff0c\u53ef\u4ee5\u901a\u8fc7Pypi \u5b89\u88c5PySpark\u5982\u4e0b\uff1a
pip install pyspark\n
\u672c\u5730\u8fd0\u884cPySpark\u9879\u76ee\u65f6\u73af\u5883\u4f9d\u8d56Hadoop \u548cJDK\uff0c\u9700\u8981\u5b89\u88c5\u5e76\u914d\u7f6e\u73af\u5883\u53d8\u91cf
\u73af\u5883\u53d8\u91cf\u8def\u5f84\u95ee\u9898
\u5982JAVA_HOME\uff0cHADOOP_HOME\u73af\u5883\u53d8\u91cf\uff0c\u8def\u5f84\u4e2d\u4e0d\u8981\u5e26\u6709\u7a7a\u683c\u6216\u4e2d\u6587\uff0c\u907f\u514d\u52a0\u8f7d\u65f6\u62a5\u9519
"},{"location":"datadevelop/quick_start/preparation/#hadoop","title":"\u5b89\u88c5Hadoop","text":"\u4e0b\u8f7dHadoop\u4e8c\u8fdb\u5236\u5305
\u5efa\u8bae\u4f7f\u7528\u89e3\u538b\u5de5\u5177\u5bf9.tar.gz
\u6587\u4ef6\u683c\u5f0f\u8fdb\u884c\u89e3\u538b\uff0c\u5982Bandizip
\u5728Windows PowerShell \u8fd0\u884ctar -zxvf
\u4e2d\u53ef\u80fd\u53d1\u751fMaximum Path Length Limitation
"},{"location":"datadevelop/quick_start/preparation/#jdk","title":"\u5b89\u88c5JDK","text":"\u4e0b\u8f7dJDK\u5b89\u88c5\u5305\uff0c\u5efa\u8bae\u7edf\u4e00\u7ba1\u7406\u5f00\u53d1\u73af\u5883\uff0c\u66f4\u6539\u5b89\u88c5\u8def\u5f84\u3002
"},{"location":"datadevelop/quick_start/preparation/#_3","title":"\u914d\u7f6e\u73af\u5883\u53d8\u91cf","text":"\u914d\u7f6eJAVA_HOME
\u914d\u7f6eHADOOP_HOME
\u914d\u7f6e%JAVA_HOME%/bin
\uff0c%HADOOP_HOME%/bin
"},{"location":"datadevelop/quick_start/preparation/#_4","title":"\u5e38\u89c1\u95ee\u9898\u603b\u7ed3","text":""},{"location":"datadevelop/quick_start/preparation/#1-winutilsexe-hadoopdll","title":"\u95ee\u98981 \uff08\u7f3a\u5c11winutils.exe
, hadoop.dll
\uff09","text":"22/08/25 13:51:47 tid: [main] WARN org.apache.hadoop.util.Shell - Did not find winutils.exe: {}\njava.io.FileNotFoundException: java.io.FileNotFoundException: HADOOP_HOME and hadoop.home.dir are unset. -see https://wiki.apache.org/hadoop/WindowsProblems\n at org.apache.hadoop.util.Shell.fileNotFoundException(Shell.java:548)\nat org.apache.hadoop.util.Shell.getHadoopHomeDir(Shell.java:569)\nat org.apache.hadoop.util.Shell.getQualifiedBin(Shell.java:592)\nat org.apache.hadoop.util.Shell.<clinit>(Shell.java:689)\nat org.apache.hadoop.util.StringUtils.<clinit>(StringUtils.java:78)\nat org.apache.hadoop.fs.FileSystem$Cache$Key.<init>(FileSystem.java:3609)\nat org.apache.hadoop.fs.FileSystem$Cache$Key.<init>(FileSystem.java:3604)\nat org.apache.hadoop.fs.FileSystem$Cache.get(FileSystem.java:3441)\nat org.apache.hadoop.fs.FileSystem.get(FileSystem.java:524)\nat org.puppy.hadoop.app.HDFSApplication.main(HDFSApplication.java:26)\nCaused by: java.io.FileNotFoundException: HADOOP_HOME and hadoop.home.dir are unset.\n at org.apache.hadoop.util.Shell.checkHadoopHomeInner(Shell.java:468)\nat org.apache.hadoop.util.Shell.checkHadoopHome(Shell.java:439)\nat org.apache.hadoop.util.Shell.<clinit>(Shell.java:516)\n... 6 common frames omitted\n
\u89e3\u51b3\u65b9\u6848:
Windows\u5728\u5b89\u88c5Hadoop\u73af\u5883\u65f6\u53ef\u80fd\u4f1a\u9047\u5230\u7f3a\u5c11\u6587\u4ef6winutils.exe
\u548chadoop.dll
\uff0c\u53ef\u4ee5\u901a\u8fc7github\u4e0b\u8f7dHadoop\u6587\u4ef6 \uff0c\u5c06\u5b89\u88c5\u65f6\u7f3a\u5c11\u7684\u6587\u4ef6\uff0c\u653e\u5165%Hadoop%/bin\u76ee\u5f55\u4e0b\uff0c\u91cd\u542fIDE\u3002
\uff08\u5982\u679c\u8fd8\u4e0d\u6210\u529f\u7684\u8bdd\u53ef\u4ee5\u5c1d\u8bd5\uff09\u5c06hadoop.dll\u590d\u5236\u5230C:\\Window\\System32\u4e0b
"},{"location":"datadevelop/quick_start/preparation/#2-python-worker-failed-to-connect-back","title":"\u95ee\u98982 \uff08Python worker failed to connect back.\uff09","text":"22/08/25 13:51:47 ERROR Executor: Exception in task 0.0 in stage 2.0 (TID 2)\norg.apache.spark.SparkException: Python worker failed to connect back.\n at org.apache.spark.api.python.PythonWorkerFactory.createSimpleWorker(PythonWorkerFactory.scala:189)\nat org.apache.spark.api.python.PythonWorkerFactory.create(PythonWorkerFactory.scala:109)\nat org.apache.spark.SparkEnv.createPythonWorker(SparkEnv.scala:124)\nat org.apache.spark.api.python.BasePythonRunner.compute(PythonRunner.scala:164)\nat org.apache.spark.sql.execution.python.BatchEvalPythonExec.evaluate(BatchEvalPythonExec.scala:81)\nat org.apache.spark.sql.execution.python.EvalPythonExec.$anonfun$doExecute$2(EvalPythonExec.scala:130)\nat org.apache.spark.rdd.RDD.$anonfun$mapPartitions$2(RDD.scala:855)\nat org.apache.spark.rdd.RDD.$anonfun$mapPartitions$2$adapted(RDD.scala:855)\nat org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:52)\nat org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:365)\nat org.apache.spark.rdd.RDD.iterator(RDD.scala:329)\nat org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:52)\nat org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:365)\nat org.apache.spark.rdd.RDD.iterator(RDD.scala:329)\nat org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:52)\nat org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:365)\nat org.apache.spark.rdd.RDD.iterator(RDD.scala:329)\nat org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:90)\nat org.apache.spark.scheduler.Task.run(Task.scala:136)\nat org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$3(Executor.scala:548)\nat org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1504)\nat org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:551)\nat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\nat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\nat java.lang.Thread.run(Thread.java:748)\nCaused by: java.net.SocketTimeoutException: Accept timed out\n at java.net.DualStackPlainSocketImpl.waitForNewConnection(Native Method)\nat java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:135)\nat java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)\nat java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)\nat java.net.ServerSocket.implAccept(ServerSocket.java:545)\nat java.net.ServerSocket.accept(ServerSocket.java:513)\nat org.apache.spark.api.python.PythonWorkerFactory.createSimpleWorker(PythonWorkerFactory.scala:176)\n... 24 more\n
\u89e3\u51b3\u65b9\u6848:
\u5168\u5c40\u589e\u52a0\u73af\u5883\u53d8\u91cf\u6216IDE\u589e\u52a0\u73af\u5883\u53d8\u91cf\uff0c\u589e\u52a0\u5185\u5bb9\u5982\u4e0b\uff1a
PYSPARK_DRIVER_PYTHON=jupyter;\nPYSPARK_PYTHON=python\n
"},{"location":"datadevelop/quick_start/preparation/#3-poetrygbk","title":"\u95ee\u98983 \uff08Poetry\u4e0b\u8f7d\u8d44\u6e90\"gbk\" \u683c\u5f0f\u5f02\u5e38\uff09","text":"The following packages are already present in the pyproject.toml and will be skipped: 'gbk' codec can't encode character '\\u2022' in position 2: illegal multibyte sequence\n
\u89e3\u51b3\u65b9\u6848:
Windows\u7cfb\u7edf\u8bed\u8a00\u8bbe\u7f6e\u4e3autf-8
\u683c\u5f0f
"},{"location":"datadevelop/quick_start/release/","title":"\u53d1\u5e03","text":""},{"location":"datadevelop/quick_start/release/#_2","title":"\u672c\u5730\u8fd0\u884c","text":""},{"location":"datadevelop/quick_start/release/#task","title":"\u6ce8\u518cTask","text":"\u5c06main
\u547d\u4ee4\u884c\u5165\u53e3\u548c\u4e0a\u8ff0\u5b9e\u73b0\u7684Task
\u7c7b\u6ce8\u518c\u5230\u547d\u540d\u7a7a\u95f4\u4e2d\u3002
\u7f16\u8f91pyproject.toml
\u6587\u4ef6\uff0c\u589e\u52a0poetry\u63d2\u4ef6\u5982\u4e0b\u5185\u5bb9\uff1a
[tool.poetry.plugins.console_scripts]\nautomotive_data_etl = \"automotive_data_etl.cmdline:main\"\n[tool.poetry.plugins.\"etl_tasks\"]\nautomotive_task = \"automotive_data_etl.tasks.automotive_task.task:AutomotiveDataTask\"\n
\u8fd9\u4e48\u505a\u7684\u76ee\u7684\u662f\u5c06AutomotiveDataTask
\u6ce8\u518c\u5230entry_points
\u4e2d\uff0c \u7136\u540e\u5728\u7a0b\u5e8f\u4e2d\u4f7f\u7528importlib.metadata
\u6839\u636e\u540d\u79f0\u7a7a\u95f4\u67e5\u627e\u3002\u800c stevedore
\u5219\u662f\u5c01\u88c5\u4e86\u67e5\u627e\u7684\u590d\u6742\u903b\u8f91\uff0c\u8ba9\u4f7f\u7528\u63d2\u4ef6\u66f4\u7b80\u5355\u3002
\u5c06\u9879\u76ee\u4ee5\u53ef\u7f16\u8f91\u6a21\u5f0f\u5b89\u88c5\u5230\u5f53\u524d\u73af\u5883\uff1a
poetry install\n
\u53ef\u4ee5\u5728 Python Console
\u4e0b\u67e5\u770b\u6ce8\u518c\u63d2\u4ef6\u4fe1\u606f\uff1a
>>> from importlib.metadata import entry_points\n\n>>> entry_points(group='etl_tasks')\n[EntryPoint(name='automotive_task', value='automotive_data_etl.tasks.automotive_task.task:AutomotiveDataTask', group='etl_tasks')]\n
\u5c06\u672c\u5730\u9879\u76ee\u4ee5\u53ef\u7f16\u8f91\u65b9\u5f0f\u5b89\u88c5\u5230\u5f53\u524d Python \u73af\u5883\uff1a
pip install -e .\n
"},{"location":"datadevelop/quick_start/release/#task_1","title":"\u8fd0\u884cTask","text":"\u7136\u540e\u901a\u8fc7\u547d\u4ee4\u884c\u7684\u65b9\u5f0f\u8fd0\u884cTask
\uff0c\u901a\u8fc7\u547d\u4ee4\u884c\u53c2\u6570\u7684\u65b9\u5f0f\u66f4\u65b0\u8f93\u5165\u8f93\u51fa\u8def\u5f84\uff1a
automotive_data_etl \\\n--env=development \\\n--task=automotive_task \\\n--input=tmp/input/car_price.csv \\\n--output=tmp/output/\n
"},{"location":"datadevelop/quick_start/tests/","title":"\u6d4b\u8bd5","text":""},{"location":"datadevelop/quick_start/tests/#_2","title":"\u5355\u5143\u6d4b\u8bd5","text":"\u5355\u5143\u6d4b\u8bd5\uff08unit test\uff09\u5c31\u662f\u7f16\u5199\u6d4b\u8bd5\u6765\u9a8c\u8bc1\u67d0\u4e00\u6a21\u5757\u7684\u529f\u80fd\u6b63\u786e\u6027\u3002\u4e00\u822c\u4f1a\u6307\u5b9a\u8f93\u5165\uff0c\u9a8c\u8bc1\u8f93\u51fa\u662f\u5426\u7b26\u5408\u9884\u671f\uff0c\u53ef\u4ee5\u5e2e\u52a9\u6211\u4eec\u5f88\u5feb\u51c6\u786e\u7684\u5b9a\u4f4d\u5230\u95ee\u9898\u7684\u4f4d\u7f6e\uff0c\u51fa\u73b0\u95ee\u9898\u7684\u6a21\u5757\u548c\u5355\u5143\u3002 \u6211\u4eec\u5c06\u4f7f\u7528Pytest\u8fdb\u884c\u6d4b\u8bd5\u3002
"},{"location":"datadevelop/quick_start/tests/#_3","title":"\u914d\u7f6e\u6587\u4ef6\u6d4b\u8bd5","text":"test_settings
\uff1a\u6d4b\u8bd5dynaconf
\u914d\u7f6e\u662f\u5426\u4f7f\u7528testing
\u73af\u5883\u914d\u7f6e
@pytest.fixture()\ndef context():\n\"\"\"Fixture context for the tests\"\"\"\nContext().environment = 'testing'\nctx = Context()\nreturn ctx\ndef test_settings(context):\n\"\"\"Test: Setting init by \"testing\" env\"\"\"\nsettings = context.settings\nassert settings.message == 'This is in testing env'\n# tmp path\nassert settings.input_path == '../tmp/input/car_price.csv'\nassert settings.output_path == '../tmp/output'\n# spark configs\nassert settings.spark_master == 'local[*]'\nassert settings.spark_config.spark.driver.memory == '3G'\nassert settings.spark_config.spark.executor.memory == '16G'\nassert settings.spark_config.spark.sql.debug.maxToStringFields == 100\n
"},{"location":"datadevelop/quick_start/tests/#extract","title":"\u4efb\u52a1Extract\u6d4b\u8bd5","text":"test_extract
\uff1a\u6d4b\u8bd5car_price.csv
\u6587\u4ef6\u662f\u5426\u88ab\u6b63\u786e\u8bfb\u53d6
def test_extract(context):\n\"\"\"Test: Read CSV file return DataFrame\"\"\"\ntask = AutomotiveDataTask()\ndf = task._extract()\nassert df.count() == 205\nreturn df\n
"},{"location":"datadevelop/quick_start/tests/#transform","title":"\u4efb\u52a1Transform\u6d4b\u8bd5","text":"test_transform_filter_price
\uff1a\u6d4b\u8bd5automotive_data_etl
\u8f6c\u6362\u8fc7\u7a0b\uff0c\u662f\u5426\u8fc7\u6ee4price
\u5b57\u6bb510000
\u4ee5\u4e0a\u7684\u6570\u636e
def test_transform_filter_price(test_extract):\n\"\"\"Test: Filter results price > 10000\"\"\"\ntransform = AutomotiveDataTransform()\ndf = transform._filter_price(test_extract)\nassert df.filter(col('price') <= 10000).count() == 0\nreturn df\n
test_transform_process_car_name
\uff1a\u6d4b\u8bd5CarName
\u5217\u662f\u5426\u5c06[dirty tmp]
\u5b57\u7b26\u4e32\u6e05\u9664
def test_transform_process_car_name(test_transform_filter_price):\n\"\"\"Test: Clean [dirty tmp] from CarName\"\"\"\ntransform = AutomotiveDataTransform()\ndf = transform._process_car_name(test_transform_filter_price)\nassert df.filter(col('CarName').contains('[dirty tmp]')).count() == 0\nreturn df\n
test_transform_select_final_columns
\uff1a\u6d4b\u8bd5\u6700\u7ec8\u7ed3\u679cColumns
\u662f\u5426\u4e3a\uff1acar_id
, car_name
, symboling
, price
def test_transform_select_final_columns(test_transform_process_car_name):\n\"\"\"Test: Final columns is ['car_id', 'car_name', 'symboling', 'price']\"\"\"\nfinal_columns = ['car_id', 'car_name', 'symboling', 'price']\ntransform = AutomotiveDataTransform()\ndf = transform._select_final_columns(test_transform_process_car_name)\nnames = df.schema.names\nassert names.sort() == final_columns.sort()\nreturn df\n
"},{"location":"datadevelop/quick_start/tests/#load","title":"\u4efb\u52a1Load\u6d4b\u8bd5","text":"test_load
\uff1a\u6d4b\u8bd5\u7ed3\u679c\u6570\u636e\u662f\u5426\u53ef\u4ee5\u6b63\u786e\u88ab\u5199\u5165JSON
\u6587\u4ef6
def test_load(test_transform_select_final_columns):\n\"\"\"Test: Load csv file\"\"\"\ntask = AutomotiveDataTask()\ntask._load(test_transform_select_final_columns)\n
"},{"location":"datadevelop/quick_start/tests/#_4","title":"\u4efb\u52a1\u5b8c\u6574\u6d41\u7a0b\u6d4b\u8bd5","text":"\u521b\u5efa\u6d4b\u8bd5\u6587\u4ef6tests/test_task.py
\"\"\"Test log\"\"\"\nimport pytest\nfrom pyspark.sql.functions import col\nfrom automotive_data_etl.context import Context\nfrom automotive_data_etl.tasks.automotive_task.automotive_transform import AutomotiveDataTransform\nfrom automotive_data_etl.tasks.automotive_task.task import AutomotiveDataTask\n@pytest.fixture()\ndef context():\n\"\"\"Fixture context for the tests\"\"\"\nContext().environment = 'testing'\nctx = Context()\nreturn ctx\ndef test_settings(context):\n\"\"\"Test: Setting init by \"testing\" env\"\"\"\nsettings = context.settings\nassert settings.message == 'This is in testing env'\n# tmp path\nassert settings.input_path == '../tmp/input/car_price.csv'\nassert settings.output_path == '../tmp/output'\n# spark configs\nassert settings.spark_master == 'local[*]'\nassert settings.spark_config.spark.driver.memory == '3G'\nassert settings.spark_config.spark.executor.memory == '16G'\nassert settings.spark_config.spark.sql.debug.maxToStringFields == 100\n@pytest.fixture()\ndef test_extract(context):\n\"\"\"Test: Read CSV file return DataFrame\"\"\"\ntask = AutomotiveDataTask()\ndf = task._extract()\nassert df.count() == 205\nreturn df\n@pytest.fixture()\ndef test_transform_filter_price(test_extract):\n\"\"\"Test: Filter results price > 10000\"\"\"\ntransform = AutomotiveDataTransform()\ndf = transform._filter_price(test_extract)\nassert df.filter(col('price') <= 10000).count() == 0\nreturn df\n@pytest.fixture()\ndef test_transform_process_car_name(test_transform_filter_price):\n\"\"\"Test: Clean [dirty tmp] from CarName\"\"\"\ntransform = AutomotiveDataTransform()\ndf = transform._process_car_name(test_transform_filter_price)\nassert df.filter(col('CarName').contains('[dirty tmp]')).count() == 0\nreturn df\n@pytest.fixture()\ndef test_transform_select_final_columns(test_transform_process_car_name):\n\"\"\"Test: Final columns is ['car_id', 'car_name', 'symboling', 'price']\"\"\"\nfinal_columns = ['car_id', 'car_name', 'symboling', 'price']\ntransform = AutomotiveDataTransform()\ndf = transform._select_final_columns(test_transform_process_car_name)\nnames = df.schema.names\nassert names.sort() == final_columns.sort()\nreturn df\ndef test_load(test_transform_select_final_columns):\n\"\"\"Test: Load csv file\"\"\"\ntask = AutomotiveDataTask()\ntask._load(test_transform_select_final_columns)\n
"},{"location":"datadevelop/quick_start/tests/#_5","title":"\u6570\u636e\u6d4b\u8bd5","text":""},{"location":"datadevelop/quick_start/tests/#step-1","title":"Step 1: \u6570\u636e\u8f93\u5165\u6d4b\u8bd5","text":" -
\u6570\u636e\u8d44\u6e90\u5e94\u8be5\u88ab\u9a8c\u8bc1\uff0c\u6765\u786e\u4fdd\u6b63\u786e\u7684\u6570\u636e\u88ab\u52a0\u8f7d\u8fdb\u7cfb\u7edf
-
\u4efb\u52a1\u9700\u6c42\u4f7f\u7528CSV\u6587\u4ef6\u6570\u636e\u6e90\uff0c\u52a0\u8f7d\u5185\u5bb9\u4e0e\u6e90\u6570\u636e\u8fdb\u884c\u5339\u914d
"},{"location":"datadevelop/quick_start/tests/#step-2-transform","title":"Step 2: Transform \u9636\u6bb5\u6d4b\u8bd5","text":" -
\u6d4b\u8bd5\u8f6c\u6362\u89c4\u5219\u53ef\u6b63\u786e\u6267\u884c
-
\u8f6c\u6362\u6570\u636e\u91cf\u4e0e\u88ab\u8f6c\u6362\u6570\u636e\u91cf\u662f\u5426\u5339\u914d
"},{"location":"datadevelop/quick_start/tests/#step-3","title":"Step 3: \u8f93\u51fa\u7ed3\u679c\u6d4b\u8bd5","text":" -
\u68c0\u67e5\u8f6c\u6362(Transformation)\u89c4\u5219\u88ab\u6b63\u786e\u5e94\u7528
-
\u901a\u8fc7\u5176\u4ed6\u65b9\u5f0f\u8ba1\u7b97\u6e90\u6570\u636e\u91cf\u8fdb\u884c\u6bd4\u8f83
"},{"location":"guidelines/advanced/configuration/","title":"\u914d\u7f6e","text":"\u914d\u7f6e\u662f\u4e00\u4e2a\u9879\u76ee\u7684\u6838\u5fc3\u9a71\u52a8\uff0c\u53ef\u4ee5\u5728\u4e0d\u66f4\u6539\u6e90\u4ee3\u7801\u6216\u51cf\u5c11\u6e90\u4ee3\u7801\u4fee\u6539\u7684\u60c5\u51b5\u4e0b\u5feb\u901f\u8c03\u6574\u9879\u76ee\u7684\u8fd0\u884c\u3002 \u4f7f\u7528\u4e2d\u5fc3\u914d\u7f6e\u9a71\u52a8\u9879\u76ee\uff0c\u80fd\u8ba9\u9879\u76ee\u7684\u4f7f\u7528\u66f4\u52a0\u7075\u6d3b\uff0c\u8fd0\u7ef4\u5de5\u4f5c\u66f4\u8f7b\u677e\u3002
\u4f8b\u5982 Django \u6846\u67b6\u4f1a\u81ea\u5e26\u4e00\u4e2a settings.py
\u6587\u4ef6\uff0c\u5728 settings.py
\u4e2d\u7684\u914d\u7f6e\u9879\u90fd\u4f1a\u8986\u76d6\u6846\u67b6 \u7ea7\u522b\u7684\u9ed8\u8ba4\u914d\u7f6e\uff0c\u65b9\u4fbf\u7528\u6237\u81ea\u5b9a\u4e49\u4fee\u6539\u3002\u5728\u4ee3\u7801\u4e2d\uff0c\u53ef\u4ee5\u4f7f\u7528 django.settings
\u5bf9\u8c61\u83b7\u53d6\u6240\u6709 \u914d\u7f6e\u9879\u3002 Scrapy \u6846\u67b6\u540c\u6837\u4e5f\u6709\u8fd9\u79cd\u673a\u5236\u3002
\u8fd9\u4e9b\u6210\u719f\u7684\u6846\u67b6\u589e\u52a0\u4e86\u4e2d\u5fc3\u914d\u7f6e\uff0c\u5c31\u662f\u4e3a\u4e86\u901a\u8fc7\u5f00\u653e\u51fa\u6765\u7684\u914d\u7f6e\u9879\u6765\u7075\u6d3b\u63a7\u5236\u6846\u67b6\u6240\u652f\u6301\u7684\u5185\u5bb9\u3002 \u800c\u5728\u4e00\u822c\u9879\u76ee\u4e2d\uff0c\u4e5f\u53ef\u4ee5\u53c2\u7167\u8fd9\u79cd\u8bbe\u8ba1\uff0c\u8ba9\u9879\u76ee\u90e8\u7f72\u66f4\u52a0\u7075\u6d3b\u3002
"},{"location":"guidelines/advanced/configuration/#1","title":"1. \u4e00\u822c\u505a\u6cd5","text":"\u5e38\u89c1\u589e\u52a0\u4e2d\u5fc3\u914d\u7f6e\u7684\u505a\u6cd5\u662f\u5728\u9879\u76ee\u4e2d\u589e\u52a0\u4e00\u4e2a settings.py
\u6587\u4ef6\uff0c\u8be5\u6587\u4ef6\u4e2d\u6a21\u5757\u7ea7\u522b\u5e38\u91cf\u5b9a\u4e49\u914d\u7f6e\u9879\u3002 \u5728\u4f7f\u7528\u65f6\uff0c\u901a\u8fc7\u5bfc\u5165\u6a21\u5757\u4e2d\u7684\u5185\u5bb9\u4f7f\u7528\u3002
\u4f8b\u5982\uff1a
\u5728\u9879\u76ee\u4e2d\u521b\u5efa\u4e00\u4e2a settings.py
\u6587\u4ef6\uff0c\u5728\u6587\u4ef6\u4e2d\u5b9a\u4e49\u6a21\u5757\u7ea7\u5e38\u91cf
## Settings\n# File config\nSOURCE_FILE = '/tmp/foo.txt'\n# Log config\nLOG_LEVEL = 'DEBUG'\nLOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n
\u521b\u5efa app.py
\u6587\u4ef6\uff0c\u5728\u6587\u4ef6\u4e2d\u5bfc\u5165 settings
\u6a21\u5757\uff0c\u5e76\u4f7f\u7528\u8be5\u6a21\u5757\u4e2d\u7684\u5e38\u91cf\u3002
\"\"\"Count a file \"\"\"\nimport logging\nfrom pathlib import Path \nimport settings\n# Config root logger\nlogging.basicConfig(\nlevel=settings.LOG_LEVEL,\nformat=settings.LOG_FORMAT,\n)\ndef count_word(source_file: Path) -> None:\n\"\"\"\n :param source_file:\n :return: None\n \"\"\"\ntotal_words = 0\n# Read source_file\nlogging.debug('Read file: %s', source_file)\nwith open(source_file, mode='r', encoding='utf-8') as source_obj:\nfor line in source_obj.readlines():\ntotal_words += len(line.split(' '))\nlogging.info('File has %s words', total_words)\ndef main():\ncount_word(Path(settings.SOURCE_FILE))\nif __name__ == '__main__':\nmain()\n
"},{"location":"guidelines/advanced/configuration/#2","title":"2. \u52a8\u6001\u914d\u7f6e\u793a\u4f8b","text":"Dynaconf \u662f\u4e00\u4e2a\u7075\u6d3b\u7684\u4e2d\u5fc3\u914d\u7f6e\u7ba1\u7406\u5de5\u5177\uff0c\u5e95\u5c42\u8bbe\u8ba1\u548c Django \u4e00\u81f4\uff0c\u4f1a\u5ef6\u8fdf\u52a0\u8f7d\u914d\u7f6e\u3002
\u5176\u5177\u6709\u5982\u4e0b\u7279\u70b9\uff1a
- \u52a0\u8f7d\u591a\u4e2a\u914d\u7f6e\u6e90
- \u914d\u7f6e\u5206\u5c42
- Django Flask \u6269\u5c55
- \u652f\u6301 Redis \u548c Vault
\u5728\u9879\u76ee\u4e2d\u65b0\u5efa\u914d\u7f6e\u6587\u4ef6 settings.yml
settings.yml \uff1a
foo: 1\nbar: 2\n
\u65b0\u5efa\u914d\u7f6e\u6a21\u5757 config.py
config.py \uff1a
from dynaconf import Dynaconf\nsettings = Dynaconf(\nsettings_files=['settings.yml'],\n)\n
\u65b0\u5efa\u4e00\u4e2a app.py
\u6587\u4ef6\uff0c\u4f7f\u7528\u914d\u7f6e
app.py \uff1a
from config import settings\nprint(settings.FOO)\nprint(settings.BAR)\n
\u7136\u540e\u8fd0\u884c python app.py
\u53ef\u4ee5\u770b\u5230\u5df2\u7ecf\u80fd\u591f\u81ea\u52a8\u83b7\u53d6 settings.yml
\u914d\u7f6e\u6587\u4ef6\u4e2d\u7684\u503c\u3002
\u589e\u52a0\u672c\u5730\u914d\u7f6e\u6587\u4ef6 settings.local.yml
settings.local.yml :
foo: 10\nbar: 20\n
\u518d\u6b21\u8fd0\u884c python app.py
\uff0c\u7a0b\u5e8f\u4f1a\u81ea\u52a8\u83b7\u53d6 settings.local.yml
\u3002
\u8fd9\u662f\u56e0\u4e3a Dynaconf
\u5728\u521d\u59cb\u5316\u662f\u4f20\u5165\u4e86\u914d\u7f6e\u6587\u4ef6\u683c\u5f0f\u4e3a settings.yml
\uff0c\u5728\u52a0\u8f7d\u914d\u7f6e\u65f6\uff0c\u4f1a\u540c\u65f6\u67e5\u627e settings.local.yml
\u7684\u914d\u7f6e\u6587\u4ef6\u3002 \u5e76\u5c06\u4e24\u4e2a\u914d\u7f6e\u6587\u4ef6\u7684\u5185\u5bb9\u5408\u5e76\uff0c\u5982\u679c\u5b58\u5728\u76f8\u540c\u53d8\u91cf\uff0c settings.local.yml
\u4f1a\u8986\u76d6 settings.yml
\u4e2d\u7684\u914d\u7f6e\u3002
"},{"location":"guidelines/advanced/configuration/#_2","title":"\u9879\u76ee\u5b9e\u8df5","text":""},{"location":"guidelines/advanced/configuration/#django","title":"Django \u9879\u76ee","text":"Dynaconf \u53ef\u4ee5\u642d\u914d Django \u4e00\u8d77\u4f7f\u7528\u3002\u867d\u7136 Django \u6709\u81ea\u5df1\u7684\u914d\u7f6e\u6a21\u5757\uff0c\u4f46\u662f\u5e76\u4e0d\u7075\u6d3b\u3002
\u642d\u914d Dynaconf \uff0c\u53ef\u4ee5\u542f\u52a8\u5c42\u7ea7\u914d\u7f6e\uff0c\u4f8b\u5982\u652f\u6301 Dev
\u3001 prod
\u548c test
\u591a\u79cd\u73af\u5883\u7684\u914d\u7f6e\uff0c\u800c\u4e14\u53ef\u4ee5\u901a\u8fc7\u73af\u5883\u53d8\u91cf\u5f88\u65b9\u4fbf\u7684 \u4fee\u6539\u914d\u7f6e\uff0c\u5305\u62ec\u52a0\u8f7d\u5176\u4ed6\u5730\u65b9\u7684\u914d\u7f6e\u3002
\u5728 Django \u9879\u76ee\u7684 settings.py
\u6587\u4ef6\u6700\u540e\u6dfb\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
import dynaconf # pylint: disable=wrong-import-position\nsettings = dynaconf.DjangoDynaconf(\n__name__,\nenvvar_prefix='BLOG',\nsettings_files=[\nBASE_DIR / 'settings.local.yml'\n],\nenvironments=False,\nload_dotenv=True,\nENVVAR_FOR_DYNACONF='BLOG_SETTINGS',\nincludes=[\nPath(sys.prefix, 'etc', 'blog', 'settings.yml'),\n]\n)\n
\u5f53 Django \u52a0\u8f7d settings.py
\u6a21\u5757\u7684\u65f6\u5019\uff0c\u4f1a\u521d\u59cb\u5316 Dynaconf \u3002 Dynaconf \u4f1a\u5c06 Django \u7684 settings
\u5bf9\u8c61\u4e2d\u7684\u914d\u7f6e\u52a0\u8f7d\u5230 Dynaconf \u4e2d\uff0c \u7136\u540e\u5c06\u81ea\u8eab\u7684\u6240\u6709\u914d\u7f6e\u518d\u91cd\u65b0\u52a0\u8f7d\u5230 Django \u7684 settings
\u5bf9\u8c61\u4e2d\u3002
Dynaconf \u4e0d\u4ec5\u4f1a\u52a0\u8f7d\u914d\u7f6e\u6587\u4ef6\uff0c\u4e5f\u4f1a\u52a0\u8f7d\u4ee5 BLOG_
\u5f00\u5934\u7684\u73af\u5883\u53d8\u91cf\u3002
"},{"location":"guidelines/advanced/configuration/#_3","title":"\u4f7f\u7528\u914d\u7f6e\u6587\u4ef6","text":"\u5728\u521d\u59cb\u5316 Dynaconf \u65f6\uff0c\u4f1a\u52a0\u8f7d\u9879\u76ee\u6839\u76ee\u5f55\u7684 settings.local.yml
\u914d\u7f6e\u6587\u4ef6\uff0c\u6b64\u6587\u4ef6\u4e00\u822c\u662f\u5f00\u53d1\u65f6\u4f7f\u7528\u7684\u672c\u5730\u914d\u7f6e\uff0c\u5e76\u4e14\u4e0d\u5e94\u8be5\u88ab Git \u8ffd\u8e2a\u3002 \u5728\u4e0d\u540c\u7684\u5f00\u53d1\u4eba\u5458\u4f7f\u7528\u6216\u8005\u4e0d\u540c\u7684\u73af\u5883\u4e2d\uff0c\u53ef\u4ee5\u4f7f\u7528\u591a\u6837\u5316\u7684\u672c\u5730\u914d\u7f6e\u3002\u5bf9\u4e8e\u9700\u8981\u7edf\u4e00\u7684\u9ed8\u8ba4\u914d\u7f6e\uff0c\u76f4\u63a5\u653e\u5728 settings.py
\u4e2d\u5c31\u53ef\u4ee5\u4e86\u3002
\u540c\u65f6\u8fd8\u4f1a\u8bfb\u53d6 <sys.prefix>/etc/blog/settings.yml
\u3002\u8fd9\u4e2a\u4e00\u822c\u4f5c\u4e3a\u751f\u4ea7\u73af\u5883\u7684\u7cfb\u7edf\u914d\u7f6e\u3002\u5982\u679c\u4f7f\u7528\u7684\u662f\u7cfb\u7edf Python \u73af\u5883\uff0c\u53ef\u80fd\u76ee\u5f55\u662f\u5728 /usr/local/etc/blog/settings.yml
\uff0c\u5982\u679c\u662f\u865a\u62df\u73af\u5883\uff0c\u5219\u53ef\u80fd\u662f /home/foo/.virtualenvs/blog-fxage/etc/blog/settings.yml
\u3002
\u5982\u679c\u60f3\u81ea\u5b9a\u4e49\u914d\u7f6e\u6587\u4ef6\u4f4d\u7f6e\uff0c\u53ef\u4ee5\u901a\u8fc7\u8bbe\u7f6e\u73af\u5883\u53d8\u91cf BLOG_SETTINGS=/tmp/settings.yml
\u6307\u5b9a Dynaconf \u52a0\u8f7d\u6587\u4ef6\u7684\u4f4d\u7f6e\u3002
DEBUG: true\nALLOWED_HOSTS:\n- '*'\nINSTALLED_APPS:\n- dynaconf_merge_unique # \u6307\u793a Dynaconf \u5c06 INSTALLED_APPS \u4e0e\u9ed8\u8ba4\u914d\u7f6e\u5408\u5e76\u800c\u4e0d\u662f\u8986\u76d6\uff0c\u5e76\u4e14\u8fdb\u884c\u53bb\u91cd\n- debug_toolbar # \u6307\u793a Dynaconf \u5c06 debug_toolbar \u6dfb\u52a0\u5230 INSTALLED_APPS \u5217\u8868\u4e2d\nMIDDLEWARE:\n- dynaconf_merge_unique\n- debug_toolbar.middleware.DebugToolbarMiddleware\nDATABASES:\ndefault:\nENGINE: 'django.db.backends.mysql'\nNAME: blo\nUSER: root\nPASSWORD: '000000'\nHOST: 127.0.0.1\nPORT: 3306\nREST_FRAMEWORK:\n# \u6307\u793a Dynaconf \u5c06 REST_FRAMEWORK \u4e0e\u9ed8\u8ba4\u914d\u7f6e\u5408\u5e76\uff0c\u800c\u4e0d\u662f\u8986\u76d6\ndynaconf_merge_unique: true\n# \u6307\u793a Dynaconf \u53ea\u4fee\u6539 PAGE_SIZE \u7684\u503c\uff0c\u5176\u4ed6\u4e0d\u53d8\nPAGE_SIZE: 10\n}\n
\u4e0a\u8ff0\u914d\u7f6e\u5728 Dynaconf \u8bfb\u53d6\u540e\uff0c\u53ef\u4ee5\u8986\u76d6 settings.py
\u4e2d\u7684\u9ed8\u8ba4\u914d\u7f6e\u3002\u5176\u4e2d\u6709\u51e0\u4e2a\u70b9\u9700\u8981\u6ce8\u610f\uff1a
- \u5982\u679c\u76f4\u63a5\u6587\u4ef6\u4e2d\u5b9a\u4e49\u914d\u7f6e\uff0c\u4f1a\u8986\u76d6\u9ed8\u8ba4\u914d\u7f6e\u3002
- \u5982\u679c\u9700\u8981\u548c\u9ed8\u8ba4\u914d\u7f6e\u5408\u5e76\uff0c\u53ef\u4ee5\u4f7f\u7528
dynaconf_merge
\u3002
"},{"location":"guidelines/advanced/configuration/#_4","title":"\u4f7f\u7528\u73af\u5883\u53d8\u91cf","text":"Dynaconf \u652f\u6301\u52a0\u8f7d\u73af\u5883\u53d8\u91cf\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528 .env \u6587\u4ef6\u3002
\u5728\u4f7f\u7528\u73af\u5883\u53d8\u91cf\u65f6\uff0c\u540c\u6837\u548c\u914d\u7f6e\u6587\u4ef6\u4e00\u6837\uff0c\u652f\u6301\u5b8c\u5168\u8986\u76d6\uff0c\u548c\u81ea\u52a8\u5408\u5e76\u3002
\u9700\u8981\u989d\u5916\u5f3a\u8c03\u4e00\u70b9\u7684\u662f\uff0c Dynaconf \u521d\u59cb\u5316\u7684\u65f6\uff0c\u4f7f\u7528\u4e86 envvar_prefix=BLOG
\u3002 Dynaconf \u4f1a\u81ea\u52a8\u52a0\u8f7d\u4ee5 BLOG_
\u5f00\u5934\u7684 \u73af\u5883\u53d8\u91cf\u3002\u5305\u62ec ENVVAR_FOR_DYNACONF='BLOG_SETTINGS'
\u914d\u7f6e\u7684 Dynaconf \u52a0\u8f7d\u914d\u7f6e\u6587\u4ef6\u7684\u73af\u5883\u53d8\u91cf BLOG_SETTINGS
\u3002
\u6240\u4ee5\u5728\u4f7f\u7528\u73af\u5883\u53d8\u91cf\u7684\u65f6\u5019\uff0c\u4e0d\u8981\u9519\u8bef\u7684\u5c06 BLOG_SETTINGS
\u73af\u5883\u53d8\u91cf\u6307\u5b9a\u5176\u4ed6\u5185\u5bb9\uff0c\u800c\u9020\u6210\u4e0d\u5fc5\u8981\u7684\u9519\u8bef\u3002
# \u4f7f\u7528\u73af\u5883\u53d8\u91cf\u914d\u7f6e\u5355\u503c\nexport BLOG_DEBUG='True'\n# \u4f7f\u7528\u73af\u5883\u53d8\u91cf\u914d\u7f6e\u5bf9\u8c61\nexport BLOG_DATABASES=\"{'default'={'ENGINE'='django.db.backends.mysql', 'NAME'='blog', 'USER'='root', 'PASSWORD'='000000', 'HOST'='localhost', 'POST'=3306}}\"\n# \u4f7f\u7528\u73af\u5883\u53d8\u91cf\u914d\u7f6e\u5408\u5e76\u5185\u5bb9\nexport BLOG_MIDDLEWARE='[\"dynaconf_merge_unique\", \"debug_toolbar.middleware.DebugToolbarMiddleware\"]' # \u4f7f\u7528 dynaconf_merge_unique \u5408\u5e76\u5e76\u53bb\u91cd\nexport BLOG_MIDDLEWARE='@merge [\"debug_toolbar.middleware.DebugToolbarMiddleware\"]' # \u4f7f\u7528 merge \u5173\u952e\u5b57\nexport BLOG_MIDDLEWARE='@merge debug_toolbar.middleware.DebugToolbarMiddleware' # \u7b80\u5199\nexport BLOG_REST_FRAMEWORK='{PAGE_SIZE=10, dynaconf_merge=true}' # \u4f7f\u7528 dynaconf_merge \u5408\u5e76\nexport BLOG_REST_FRAMEWORK='@merge {PAGE_SIZE=10}' # \u4f7f\u7528 merge \u5173\u952e\u5b57\nexport BLOG_REST_FRAMEWORK='@merge PAGE_SIZE=10' # \u7b80\u5199\nexport BLOG_DATABASES__default__PASSWORD='123456' # \u4f7f\u7528\u4e24\u4e2a\u4e0b\u5212\u7ebf (__) \u4f5c\u4e3a\u5b50\u7ea7\n
"},{"location":"guidelines/advanced/exception/","title":"\u5f02\u5e38\u7ba1\u7406","text":"\u51e0\u4e4e\u6240\u6709\u7f16\u7a0b\u8bed\u8a00\u4e2d\u90fd\u6709\u5f02\u5e38\u3002\u5f02\u5e38\u53ef\u4ee5\u5feb\u901f\u6307\u51fa\u7a0b\u5e8f\u51fa\u73b0\u7684\u95ee\u9898\uff0c\u4fbf\u4e8e\u6392\u67e5\u3002\u5f00\u53d1\u4eba\u5458\u4e5f\u53ef\u4ee5\u6839\u636e\u60c5\u51b5\u629b\u51fa\u81ea\u5b9a\u4e49\u5f02\u5e38\uff0c \u4ee5\u6307\u793a\u671f\u671b\u7684\u5185\u5bb9\u548c\u5b9e\u9645\u4e0d\u76f8\u7b26\u3002\u826f\u597d\u7684\u5f02\u5e38\u8bbe\u8ba1\u548c\u4f7f\u7528\u4e60\u60ef\uff0c\u53ef\u4ee5\u63d0\u9ad8\u7a0b\u5e8f\u7684\u8d28\u91cf\u3002
"},{"location":"guidelines/advanced/exception/#_2","title":"\u4ecb\u7ecd","text":"Python \u4e2d\u7684\u5f02\u5e38\u5206\u4e3a\u4e24\u7c7b\uff0c\u4e00\u662f\u53e5\u6cd5\u9519\u8bef\uff0c\u4e00\u7c7b\u662f\u5f02\u5e38\u3002
"},{"location":"guidelines/advanced/exception/#_3","title":"\u53e5\u6cd5\u9519\u8bef","text":"\u53e5\u6cd5\u9519\u8bef\u662f\u7528\u6765\u6307\u793a Python \u7f16\u7801\u4e0d\u7b26\u5408\u53e5\u6cd5\u89c4\u8303\u7684\uff1a
>>> while True print('Hello world')\nFile \"<stdin>\", line 1\nwhile True print('Hello world')\n^\nSyntaxError: invalid syntax\n
\u5982\u4e0a\u6240\u793a\uff0c\u4f7f\u7528 ^
\u6307\u793a\u9519\u8bef\u7684\u4f4d\u7f6e\u3002
"},{"location":"guidelines/advanced/exception/#_4","title":"\u5f02\u5e38","text":"\u5373\u4f7f\u8bed\u53e5\u6216\u8868\u8fbe\u5f0f\u4f7f\u7528\u4e86\u6b63\u786e\u7684\u8bed\u6cd5\uff0c\u6267\u884c\u65f6\u4ecd\u53ef\u80fd\u89e6\u53d1\u9519\u8bef\u3002\u6267\u884c\u65f6\u68c0\u6d4b\u5230\u7684\u9519\u8bef\u79f0\u4e3a \u5f02\u5e38\uff0c \u5f02\u5e38\u4e0d\u4e00\u5b9a\u5bfc\u81f4\u4e25\u91cd\u7684\u540e\u679c\uff1a\u5f88\u5feb\u6211\u4eec\u5c31\u80fd\u5b66\u4f1a\u5982\u4f55\u5904\u7406 Python \u7684\u5f02\u5e38\u3002\u5927\u591a\u6570\u5f02\u5e38\u4e0d\u4f1a\u88ab\u7a0b\u5e8f\u5904\u7406\uff0c \u800c\u662f\u663e\u793a\u4e0b\u5217\u9519\u8bef\u4fe1\u606f\uff1a
>>> 10 * (1/0)\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in <module>\nZeroDivisionError: division by zero\n>>> 4 + spam*3\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in <module>\nNameError: name 'spam' is not defined\n>>> '2' + 2\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in <module>\nTypeError: can only concatenate str (not \"int\") to str\n
\u5185\u7f6e\u5f02\u5e38\u7ed3\u6784\u5982\u4e0b\uff1a
BaseException\n+-- SystemExit\n+-- KeyboardInterrupt\n+-- GeneratorExit\n+-- Exception\n+-- StopIteration\n+-- StopAsyncIteration\n+-- ArithmeticError\n| +-- FloatingPointError\n| +-- OverflowError\n| +-- ZeroDivisionError\n+-- AssertionError\n+-- AttributeError\n+-- BufferError\n+-- EOFError\n+-- ImportError\n| +-- ModuleNotFoundError\n+-- LookupError\n| +-- IndexError\n| +-- KeyError\n+-- MemoryError\n+-- NameError\n| +-- UnboundLocalError\n+-- OSError\n| +-- BlockingIOError\n| +-- ChildProcessError\n| +-- ConnectionError\n| | +-- BrokenPipeError\n| | +-- ConnectionAbortedError\n| | +-- ConnectionRefusedError\n| | +-- ConnectionResetError\n| +-- FileExistsError\n| +-- FileNotFoundError\n| +-- InterruptedError\n| +-- IsADirectoryError\n| +-- NotADirectoryError\n| +-- PermissionError\n| +-- ProcessLookupError\n| +-- TimeoutError\n+-- ReferenceError\n+-- RuntimeError\n| +-- NotImplementedError\n| +-- RecursionError\n+-- SyntaxError\n| +-- IndentationError\n| +-- TabError\n+-- SystemError\n+-- TypeError\n+-- ValueError\n| +-- UnicodeError\n| +-- UnicodeDecodeError\n| +-- UnicodeEncodeError\n| +-- UnicodeTranslateError\n+-- Warning\n+-- DeprecationWarning\n+-- PendingDeprecationWarning\n+-- RuntimeWarning\n+-- SyntaxWarning\n+-- UserWarning\n+-- FutureWarning\n+-- ImportWarning\n+-- UnicodeWarning\n+-- BytesWarning\n+-- EncodingWarning\n+-- ResourceWarning\n
"},{"location":"guidelines/advanced/exception/#_5","title":"\u4f7f\u7528","text":""},{"location":"guidelines/advanced/exception/#_6","title":"\u6355\u83b7\u5f02\u5e38","text":"\u5728\u903b\u8f91\u4e2d\uff0c\u53ef\u80fd\u51fa\u73b0\u4e0d\u7b26\u5408\u9884\u671f\u7684\u903b\u8f91\uff0c\u4f1a\u629b\u51fa\u76f8\u5173\u5f02\u5e38\u3002\u6b64\u65f6\u5728\u7f16\u7801\u65f6\uff0c\u4e3a\u4e86\u903b\u8f91\u7684\u6b63\u5e38\u8fd0\u884c\uff0c\u9700\u8981\u5bf9\u903b\u8f91\u8fdb\u884c\u5904\u7406\uff1a
import sys\ntry:\nf = open('myfile.txt')\ns = f.readline()\ni = int(s.strip())\nexcept OSError as err:\nprint(\"OS error: {0}\".format(err))\nexcept ValueError:\nprint(\"Could not convert data to an integer.\")\nexcept BaseException as err:\nprint(f\"Unexpected {err=}, {type(err)=}\")\nraise\n
\u5982\u4e0a\u8ff0\u903b\u8f91\uff0c\u5bf9\u4e8e\u5df2\u77e5\u80fd\u5224\u65ad\u7684\u60c5\u51b5\uff0c\u53ef\u4ee5\u901a\u8fc7\u65e5\u5fd7\u8f93\u51fa\u663e\u793a\u53cb\u597d\u4fe1\u606f\uff0c\u907f\u514d\u7a0b\u5e8f\u7acb\u5373\u505c\u6b62\u3002\u5f53\u65e0\u6cd5\u5224\u65ad\u5f02\u5e38\u65f6\uff0c\u5219 \u7ee7\u7eed\u629b\u51fa\u5f02\u5e38\u3002
\u6355\u83b7\u5f02\u5e38\u662f\uff0c\u4f7f\u7528 try...except
\u4ee3\u7801\u5757\u5305\u88f9\u9700\u8981\u5904\u7406\u5f02\u5e38\u7684\u4ee3\u7801\u3002 expect
\u6355\u83b7\u6307\u5b9a\u7684\u5f02\u5e38\u7c7b\u578b\uff0c\u5982\u679c\u51fa\u73b0\uff0c\u8fdb\u5165 \u5bf9\u5e94\u7684\u4ee3\u7801\u903b\u8f91\u3002\u5bf9\u4e8e\u4e00\u4e9b\u4e0d\u60f3\u5904\u7406\u7684\uff0c\u901a\u8fc7 raise
\u629b\u51fa\u5f02\u5e38\u3002
"},{"location":"guidelines/advanced/exception/#_7","title":"\u5f02\u5e38\u94fe","text":"\u5f53\u629b\u51fa\u5f02\u5e38\u65f6\uff0c raise
\u8bed\u53e5\u652f\u6301 from
\u5b50\u53e5\u542f\u7528\u94fe\u5f0f\u5f02\u5e38\u3002
>>> def func():\n... raise ConnectionError\n...\n>>> try:\n... func()\n... except ConnectionError as exc:\n... raise RuntimeError('Failed to open database') from exc\n...\nTraceback (most recent call last):\nFile \"<stdin>\", line 2, in <module>\nFile \"<stdin>\", line 2, in func\nConnectionError\nThe above exception was the direct cause of the following exception:\nTraceback (most recent call last):\nFile \"<stdin>\", line 4, in <module>\nRuntimeError: Failed to open database\n
\u4e0a\u8ff0\u793a\u4f8b\u4e2d\uff0c\u5f02\u5e38\u4fe1\u606f\u4e2d\u542b\u6709\u4e24\u6b21\u629b\u51fa\u7684\u5f02\u5e38\u3002\u8fd9\u5bf9\u4e8e\u8c03\u8bd5\u5f88\u6709\u5e2e\u52a9\u3002
\u5982\u679c\u4e0d\u60f3\u629b\u51fa\u94fe\u5f0f\u5f02\u5e38\uff0c\u53ef\u4ee5\u4f7f\u7528 from None
\uff1a
>>> try:\n... open('database.sqlite')\n... except OSError:\n... raise RuntimeError from None\n...\nTraceback (most recent call last):\nFile \"<stdin>\", line 4, in <module>\nRuntimeError\n
"},{"location":"guidelines/advanced/exception/#_8","title":"\u81ea\u5b9a\u4e49\u5f02\u5e38","text":"\u7a0b\u5e8f\u53ef\u4ee5\u901a\u8fc7\u521b\u5efa\u65b0\u7684\u5f02\u5e38\u7c7b\u547d\u540d\u81ea\u5df1\u7684\u5f02\u5e38\uff08Python \u7c7b\u7684\u5185\u5bb9\u8be6\u89c1 \u7c7b\uff09\u3002\u4e0d\u8bba\u662f\u4ee5\u76f4\u63a5\u8fd8\u662f\u95f4\u63a5\u7684\u65b9\u5f0f\uff0c\u5f02\u5e38\u90fd\u5e94\u4ece Exception \u7c7b\u6d3e\u751f\u3002
\u5f02\u5e38\u7c7b\u548c\u5176\u4ed6\u7c7b\u4e00\u6837\uff0c\u53ef\u4ee5\u6267\u884c\u4efb\u4f55\u64cd\u4f5c\u3002\u4f46\u901a\u5e38\u4f1a\u6bd4\u8f83\u7b80\u5355\uff0c\u53ea\u63d0\u4f9b\u8ba9\u5904\u7406\u5f02\u5e38\u7684\u7a0b\u5e8f\u63d0\u53d6\u9519\u8bef\u4fe1\u606f\u7684\u4e00\u4e9b\u5c5e\u6027\u3002 \u521b\u5efa\u80fd\u89e6\u53d1\u591a\u4e2a\u4e0d\u540c\u9519\u8bef\u7684\u6a21\u5757\u65f6\uff0c\u4e00\u822c\u53ea\u4e3a\u8be5\u6a21\u5757\u5b9a\u4e49\u5f02\u5e38\u57fa\u7c7b\uff0c\u7136\u540e\u518d\u6839\u636e\u4e0d\u540c\u7684\u9519\u8bef\u6761\u4ef6\uff0c\u521b\u5efa\u6307\u5b9a\u5f02\u5e38\u7c7b\u7684\u5b50\u7c7b\uff1a
class Error(Exception):\n\"\"\"Base class for exceptions in this module.\"\"\"\npass\nclass InputError(Error):\n\"\"\"Exception raised for errors in the input.\n Attributes:\n expression -- input expression in which the error occurred\n message -- explanation of the error\n \"\"\"\ndef __init__(self, expression, message):\nself.expression = expression\nself.message = message\nclass TransitionError(Error):\n\"\"\"Raised when an operation attempts a state transition that's not\n allowed.\n Attributes:\n previous -- state at beginning of transition\n next -- attempted new state\n message -- explanation of why the specific transition is not allowed\n \"\"\"\ndef __init__(self, previous, next, message):\nself.previous = previous\nself.next = next\nself.message = message\n
\u5927\u591a\u6570\u5f02\u5e38\u547d\u540d\u90fd\u4ee5 \u201cError\u201d \u7ed3\u5c3e\uff0c\u7c7b\u4f3c\u6807\u51c6\u5f02\u5e38\u7684\u547d\u540d\u3002
\u8bb8\u591a\u6807\u51c6\u6a21\u5757\u90fd\u9700\u8981\u81ea\u5b9a\u4e49\u5f02\u5e38\uff0c\u4ee5\u62a5\u544a\u7531\u5176\u5b9a\u4e49\u7684\u51fd\u6570\u4e2d\u51fa\u73b0\u7684\u9519\u8bef\u3002
"},{"location":"guidelines/advanced/exception/#_9","title":"\u5f02\u5e38\u6e05\u7406","text":"\u5bf9\u4e8e\u50cf\u6587\u4ef6\u6216\u8005\u8fde\u63a5\u5bf9\u8c61\u7684\u64cd\u4f5c\uff0c\u5728\u6253\u5f00\u540e\uff0c\u9700\u8981\u5728\u5f02\u5e38\u6700\u540e\u5173\u95ed\uff0c\u5c31\u9700\u8981\u7528\u5230\u5f02\u5e38\u6e05\u7406\u3002
import sys\ntry:\nf = open('myfile.txt')\ns = f.readline()\ni = int(s.strip())\nexcept OSError as err:\nprint(\"OS error: {0}\".format(err))\nraise\nfinally:\nf.close()\n
\u4e0a\u8ff0\u903b\u8f91\u4e2d\uff0c\u4f7f\u7528 try...expect...finally
\u505a\u629b\u51fa\u5f02\u5e38\u540e\u7684\u6e05\u7406\u5de5\u4f5c\u3002\u5176\u4e2d finally
\u4ee3\u7801\u5757\u4e2d\uff0c\u5173\u95ed\u4e86\u524d\u9762 \u6253\u5f00\u7684\u6587\u4ef6\u5bf9\u8c61\u3002
def divide(x, y):\ntry:\nresult = x / y\nexcept ZeroDivisionError:\nprint(\"division by zero!\")\nelse:\nprint(\"result is\", result)\nfinally:\nprint(\"executing finally clause\")\n
\u4e0a\u8ff0\u793a\u4f8b\u4ee3\u7801\u901a\u8fc7 else
\u903b\u8f91\u5757\u6267\u884c\u6ca1\u6709\u89e6\u53d1\u5f02\u5e38\u65f6\u7684\u903b\u8f91\u3002
\u5bf9\u4e8e\u4e00\u4e9b\u6e05\u7406\u6027\u7684\u5de5\u4f5c\uff0c\u63a8\u8350\u4f7f\u7528 with \u8bed\u53e5\u81ea\u52a8\u7ba1\u7406\u4e0a\u4e0b\u6587\u3002
"},{"location":"guidelines/advanced/exception/#_10","title":"\u5b9e\u8df5","text":"\u5f00\u53d1\u5b9e\u8df5\u4e2d\uff0c\u5f02\u5e38\u4fe1\u606f\u5bf9\u8bca\u65ad\u7a0b\u5e8f\u975e\u5e38\u91cd\u8981\u3002\u6240\u4ee5\u5728\u4f7f\u7528\u548c\u5904\u7406\u5f02\u5e38\u65f6\uff0c\u8bf7\u9075\u5faa\u5982\u4e0b\u51e0\u70b9\uff1a
- \u9700\u8981\u5904\u7406\u5f02\u5e38\u65f6\u4f7f\u7528
try...except...finally
\u6355\u83b7 - \u5904\u7406\u5f02\u5e38\u65f6\uff0c\u5982\u679c\u6ca1\u6709\u7ee7\u7eed\u629b\u51fa\u5f02\u5e38\uff0c\u9700\u8981\u8f93\u5165\u65e5\u5fd7\u4fe1\u606f\u3002\u9664\u975e\u4f60\u77e5\u9053\u4e0d\u8f93\u51fa\u4efb\u4f55\u4fe1\u606f\u4e0d\u4f1a\u9020\u6210\u62cd\u9519\u56f0\u96be\u3002
- \u9879\u76ee\u7ea7\u522b\uff0c\u4e00\u5b9a\u8981\u5b9a\u4e49\u4e00\u4e2a\u9879\u76ee\u7684\u57fa\u7c7b\u5f02\u5e38\u3002\u9879\u76ee\u4e2d\u5176\u4ed6\u81ea\u5b9a\u4e49\u5f02\u5e38\u5fc5\u987b\u7ee7\u627f\u8be5\u57fa\u7c7b\u5f02\u5e38\u3002\u8fd9\u4e48\u505a\u7684\u76ee\u7684\u662f\u53ef\u4ee5\u5728\u5916\u5c42\u903b\u8f91\u901a\u8fc7\u6355\u83b7\u57fa\u7c7b \u5f02\u5e38\u6765\u53ea\u6355\u83b7\u629b\u51fa\u7684\u81ea\u5b9a\u4e49\u5f02\u5e38\u3002
- \u9879\u76ee\u5f02\u5e38\u8981\u4ee5
ERROR
\u7ed3\u5c3e\u3002\u548c\u6807\u51c6\u5f02\u5e38\u547d\u540d\u7c7b\u4f3c\u3002
"},{"location":"guidelines/advanced/logging/","title":"Logging","text":"\u5728\u5f00\u53d1\u4e2d\uff0c\u901a\u5e38\u4f1a\u4f7f\u7528 print
\u8f93\u51fa\u4e00\u4e9b\u4fe1\u606f\uff0c\u6216\u8005\u8bca\u65ad\u4fe1\u606f\u3002\u5728\u4fe1\u606f\u7684\u5b8c\u6574\u6027\u548c\u4fe1\u606f\u683c\u5f0f\u4e0a\u90fd\u4e0d\u80fd\u7b80\u4fbf\u4e14\u7075\u6d3b\u63a7\u5236\u3002\u6b64\u65f6\u4f7f\u7528 logging
\u662f\u4e2a\u66f4\u597d\u7684\u9009\u62e9\uff0c \u800c\u4e14\u4e5f\u9f13\u52b1\u5f00\u53d1\u4eba\u5458\u5c3d\u53ef\u80fd\u7684\u4f18\u5148\u9009\u7528\u6253\u5370\u65e5\u5fd7\u7684\u65b9\u5f0f\u5728\u63a7\u5236\u53f0\u8f93\u51fa\u4fe1\u606f\u3002
\u65e5\u5fd7\u662f\u5bf9\u8f6f\u4ef6\u6267\u884c\u65f6\u6240\u53d1\u751f\u4e8b\u4ef6\u7684\u4e00\u79cd\u8ffd\u8e2a\u65b9\u5f0f\u3002\u8f6f\u4ef6\u5f00\u53d1\u4eba\u5458\u5bf9\u4ed6\u4eec\u7684\u4ee3\u7801\u6dfb\u52a0\u65e5\u5fd7\u8c03\u7528\uff0c\u501f\u6b64\u6765\u6307\u793a\u67d0\u4e8b\u4ef6\u7684\u53d1\u751f\u3002 \u4e00\u4e2a\u4e8b\u4ef6\u901a\u8fc7\u4e00\u4e9b\u5305\u542b\u53d8\u91cf\u6570\u636e\u7684\u63cf\u8ff0\u4fe1\u606f\u6765\u63cf\u8ff0\uff08\u6bd4\u5982\uff1a\u6bcf\u4e2a\u4e8b\u4ef6\u53d1\u751f\u65f6\u7684\u6570\u636e\u90fd\u662f\u4e0d\u540c\u7684\uff09\u3002\u5f00\u53d1\u8005\u8fd8\u4f1a\u533a\u5206\u4e8b\u4ef6\u7684\u91cd\u8981\u6027\uff0c \u91cd\u8981\u6027\u4e5f\u88ab\u79f0\u4e3a \u7b49\u7ea7 \u6216 \u4e25\u91cd\u6027\u3002\u6709\u4e00\u4e2a\u597d\u7684\u65e5\u5fd7\u5b9e\u8df5\uff0c\u80fd\u8ba9\u5f00\u53d1\u8c03\u8bd5\u6d41\u7a0b\u66f4\u987a\u7545\uff0c\u51fa\u73b0\u95ee\u9898\u80fd\u66f4\u5feb\u901f\u7cbe\u51c6\u5b9a\u4f4d\u3002
\u672c\u6587\u4e0d\u4f1a\u4ee5\u6700\u57fa\u7840\u7684\u65b9\u5f0f\u8bb2\u8ff0 Python logging \u7684\u4f7f\u7528\uff0c\u800c\u662f\u4ee5\u5f53\u524d\u603b\u7ed3\u7684\u5b9e\u8df5\u65b9\u5f0f\u7ed3\u5408\u5b9e\u9645\u64cd\u4f5c\u6848\u4f8b\u5c55\u793a Logging \u7684\u4f7f\u7528\uff0c\u6240\u4ee5\u5728\u9605\u8bfb\u6587\u7ae0\u94b1\uff0c \u4f60\u5e94\u8be5\u63d0\u524d\u4e86\u89e3 \u65e5\u5fd7 HOWTO \u548c Python \u7684\u65e5\u5fd7\u8bb0\u5f55\u5de5\u5177 \u4e24\u7bc7\u6587\u6863\u3002
"},{"location":"guidelines/advanced/logging/#1","title":"1. \u7b80\u5355\u4f7f\u7528","text":"\u5728\u4e00\u822c\u5f00\u53d1\u4e2d\uff0c\u5bf9\u4e8e\u4e34\u65f6\u5f00\u53d1\u7684\u9879\u76ee\uff0c\u53ef\u80fd\u4e3a\u4e86\u5feb\u901f\u5b8c\u6210\u4efb\u52a1\uff0c\u9879\u76ee\u4e2d\u5927\u91cf\u4f7f\u7528\u4e86 print
\u5c06\u8c03\u8bd5\u4fe1\u606f\u8f93\u51fa\u5230\u63a7\u5236\u53f0\u3002 \u9879\u76ee\u540e\u671f\u5c31\u4f1a\u51fa\u73b0\u8c03\u8bd5\u56f0\u96be\u7b49\u95ee\u9898\u3002\u672c\u8282\u4f1a\u63d0\u4f9b\u5728\u7b80\u5355\u73af\u5883\u4e0b\u5feb\u901f\u4f7f\u7528\u65e5\u5fd7\u65b9\u5f0f\u3002
"},{"location":"guidelines/advanced/logging/#11","title":"1.1 \u5355\u6587\u4ef6\u4f7f\u7528","text":"\u5bf9\u4e8e\u5355\u6587\u4ef6\u7684\u4f7f\u7528\uff0c\u76f4\u63a5\u4f7f\u7528\u6839\u65e5\u5fd7\u5bf9\u8c61\u5373\u53ef\u3002\u7531\u4e8e\u9ed8\u8ba4\u7684\u65e5\u5fd7\u7ea7\u522b\u4e3a WARNING
\uff0c\u6240\u4ee5\u9700\u8981\u4f7f\u7528\u66f4\u4f4e\u7ea7\u522b\u7684\u65e5\u5fd7\u662f\u65e0\u6cd5\u663e\u793a\u7684\u3002
\"\"\"Simple logging\"\"\"\nimport logging\nlogging.warning('I love you ~')\n
\u5982\u679c\u540e\u7eed\u5f00\u53d1\u6709\u8981\u63a7\u5236\u65e5\u5fd7\u7ea7\u522b\u7684\u9700\u6c42\uff0c\u76f4\u63a5\u5728\u5f00\u59cb\u521d\u59cb\u5316\u65e5\u5fd7\u914d\u7f6e\u5c31\u53ef\u4ee5\u4e86\uff1b
\"\"\"Simple logging\"\"\"\nimport logging\nlogging.basicConfig(level=logging.DEBUG)\nlogging.warning('I love you ~')\nlogging.debug('I love you too ~')\n
\u5f53\u9700\u8981\u8f93\u51fa\u66f4\u8be6\u7ec6\u7684\u65e5\u5fd7\u4fe1\u606f\uff0c\u5982\u6267\u884c\u65f6\u95f4 \u3001 \u65e5\u5fd7\u7ea7\u522b \u3001 \u7ebf\u7a0b\u6216\u8fdb\u7a0b\u4fe1\u606f\uff0c\u90fd\u53ef\u4ee5\u5f88\u65b9\u4fbf\u7684\u63a7\u5236\u3002
\"\"\"Simple logging\"\"\"\nimport logging\nlogging.basicConfig(\nlevel=logging.DEBUG,\n# format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',\n# format='%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(process)d %(thread)d - %(message)s',\nformat='%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(process)d %(thread)d - %(pathname)s:%(lineno)d %(message)s',\ndatefmt='%Y-%m-%dT%H:%M:%S.%s+0800',\n)\nlogging.warning('I love you ~')\nlogging.debug('I love you too ~')\n
\u867d\u7136\u4f7f\u7528 print
\u80fd\u66f4\u5feb\u901f\u7684\u5728\u63a7\u5236\u592a\u8f93\u51fa\u60f3\u8981\u770b\u5230\u7684\u5185\u5bb9\uff0c\u4f46\u4ece\u4e0a\u9762\u7684\u793a\u4f8b\u6765\u770b\uff0c\u76f4\u63a5\u4f7f\u7528\u9ed8\u8ba4\u7684\u65e5\u5fd7\u8f93\u51fa\u4e5f\u662f\u5f88\u65b9\u4fbf\u7684\uff0c\u552f\u4e00\u7684\u533a\u522b\u53ef\u80fd \u5c31\u662f\u9700\u8981\u5bfc\u5165\u4e86\u3002\u800c\u4f7f\u7528\u65e5\u5fd7\u7684\u8bdd\uff0c\u60f3\u8981\u5728\u540e\u7eed\u589e\u52a0\u8f93\u51fa\u66f4\u7cbe\u786e\u7684\u4fe1\u606f\u5c31\u663e\u5f97\u6bd4\u8f83\u7075\u6d3b\u3002
\u65e5\u5fd7\u683c\u5f0f\u6240\u652f\u6301\u7684\u5b57\u6bb5\u8bf7\u53c2\u8003 LogRecord \u5c5e\u6027 \u3002
\u8fd9\u4e5f\u662f\u4e3a\u4ec0\u4e48\u5efa\u8bae\u4f18\u5148\u4f7f\u7528\u65e5\u5fd7\u7684\u539f\u56e0\u3002
"},{"location":"guidelines/advanced/logging/#12","title":"1.2 \u4e00\u822c\u9879\u76ee\u4e2d\u4f7f\u7528","text":"\u5bf9\u4e8e\u4e00\u822c\u7684\u9879\u76ee\uff0c\u53ef\u80fd\u4f1a\u6709\u591a\u4e2a\u6a21\u5757\u6216\u8005\u5305\uff0c\u5728\u6bcf\u4e2a\u6a21\u5757\u4e2d\u90fd\u521d\u59cb\u5316\u4e00\u4e0b\u65e5\u5fd7\u914d\u7f6e\u663e\u7136\u8fdd\u53cd\u4e86 DRY \u539f\u5219\u3002 \u6240\u4ee5\u6700\u597d\u662f\u6709\u4e00\u4e2a\u516c\u5171\u7684\u65e5\u5fd7\u6a21\u5757\uff0c\u5728\u8be5\u6a21\u5757\u4e2d\u521d\u59cb\u5316\u65e5\u5fd7\u3002
log.py \uff1a
\"\"\"Log config\"\"\"\nimport logging\nlogging.basicConfig(\nlevel=logging.DEBUG,\nformat='%(asctime)s - %(name)s - %(levelname)s - %(message)s',\ndatefmt='%Y-%m-%dT%H:%M:%S.%s+0800',\n)\nlogger = logging.getLogger()\n
\u5728\u5176\u4ed6\u6a21\u5757\u4e2d\u76f4\u63a5\u5bfc\u5165 from log import logger
\u5373\u53ef\u4f7f\u7528\u3002
\u63d0\u793a \u5728 log.py
\u4e2d\u521d\u59cb\u5316\u65e5\u5fd7\u914d\u7f6e\u540e\uff0c\u867d\u7136\u53ef\u4ee5\u5728\u5176\u4ed6\u6a21\u5757\u4e2d\u76f4\u63a5\u4f7f\u7528 logging
\u6253\u5370\u65e5\u5fd7\uff0c\u4f46\u5982\u679c\u542f\u52a8\u811a\u672c\u7684\u65f6\u5019\u6ca1\u6709\u5bfc\u5165\u8be5\u6a21\u5757\uff0c \u8be5\u6a21\u5757\u4e2d\u7684\u5185\u5bb9\u662f\u4e0d\u4f1a\u52a0\u8f7d\u7684\uff0c\u4e5f\u5c31\u662f\u6240\u65e5\u5fd7\u914d\u7f6e\u5e76\u6ca1\u6709\u5b9e\u9645\u6267\u884c\u3002
\u6b64\u65f6\u53ef\u4ee5\u91cd\u6784\u4ee3\u7801\uff1a
\"\"\"Log config\"\"\"\nimport logging\ndef config_logging() -> None:\n\"\"\"Config logging\"\"\"\nlogging.basicConfig(\nlevel=logging.DEBUG,\nformat='%(asctime)s - %(name)s - %(levelname)s - %(message)s',\ndatefmt='%Y-%m-%dT%H:%M:%S.%s+0800',\n)\n
\u7136\u540e\u5728\u9879\u76ee\u5b9e\u9645\u6267\u884c\u4e4b\u524d\u8c03\u7528\u65b9\u6cd5\u521d\u59cb\u5316\u914d\u7f6e\u5c31\u53ef\u4ee5\u4e86\u3002
\u5bf9\u4e8e\u9700\u8981\u7075\u6d3b\u63a7\u5236\u65e5\u5fd7\u914d\u7f6e\u7684\uff0c\u53ef\u4ee5\u5c06\u65e5\u5fd7\u7ea7\u522b\u653e\u5230\u914d\u7f6e\u4e2d\uff0c\u901a\u8fc7\u5de5\u5382\u65b9\u5f0f\u521d\u59cb\u5316\uff1a
\"\"\"Log config\"\"\"\nimport logging\nfrom typing import Optional\ndef config_logging(\nlevel: Optional[int, str] = logging.DEBUG,\nlog_format: Optional[str] = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n) -> None:\n\"\"\"\n Config logging\n :param level:\n :param log_format:\n :return:\n \"\"\"\nlogging.basicConfig(\nlevel=level,\nformat=log_format,\ndatefmt='%Y-%m-%dT%H:%M:%S.%s+0800',\n)\n
\u5728\u903b\u8f91\u6267\u884c\u4e4b\u524d\u8c03\u7528\u65b9\u6cd5\u901a\u8fc7\u53c2\u6570\u5de5\u5382\u5316\u65e5\u5fd7\u914d\u7f6e\u3002
"},{"location":"guidelines/advanced/logging/#2","title":"2. \u901a\u7528\u5b9e\u8df5","text":"\u672c\u8282\u5185\u5bb9\u662f\u5728\u901a\u7528\u9879\u76ee\u4e2d\u53ef\u4ee5\u4f7f\u7528\u7684\u4e00\u822c\u5b9e\u8df5\u65b9\u6cd5\u3002
"},{"location":"guidelines/advanced/logging/#21-ini","title":"2.1 \u4f7f\u7528 ini
\u683c\u5f0f\u6587\u4ef6\u914d\u7f6e\u65e5\u5fd7","text":"\u5728\u9879\u76ee\u4e2d\u65b0\u5efa\u4e00\u4e2a log.ini
\u7684\u914d\u7f6e\u6587\u4ef6\uff1a
log.ini \uff1a
[loggers]\nkeys = root,simpleExample\n[handlers]\nkeys = consoleHandler\n[formatters]\nkeys = simpleFormatter\n[logger_root]\nlevel = DEBUG\nhandlers = consoleHandler\n[logger_simpleExample]\nlevel = DEBUG\nhandlers = consoleHandler\nqualname = simpleExample\npropagate = 0\n[handler_consoleHandler]\nclass = StreamHandler\nlevel = DEBUG\nformatter = simpleFormatter\nargs = (sys.stdout,)\n[formatter_simpleFormatter]\nformat = %(asctime)s - %(name)s - %(levelname)s - %(message)s\ndatefmt =\n
\u65b0\u5efa\u4e00\u4e2a log.py
\u7684\u5305\uff1a
log.py \uff1a
from logging.config import fileConfig\ndef init_ini_log() -> None:\nfileConfig('log.ini')\n
\u6700\u540e\u5728\u7a0b\u5e8f\u6267\u884c\u524d\uff0c\u521d\u59cb\u5316\u65e5\u5fd7\u914d\u7f6e\u3002
logging \u9ed8\u8ba4\u4f7f\u7528 configparser \u89e3\u6790 ini
\u683c\u5f0f\u6587\u4ef6\u3002 \u5982\u679c\u4f60\u60f3\u4f7f\u7528\u5176\u4ed6\u683c\u5f0f\uff0c\u5982 toml
\u6216\u8005 yaml
\uff0c\u5219\u9700\u8981\u81ea\u5df1\u624b\u52a8\u8bfb\u53d6\u5e76\u89e3\u6790\u6587\u4ef6\u5185\u5bb9\u4e3a Dict \u5373\u53ef\u3002
"},{"location":"guidelines/advanced/logging/#22-yaml","title":"2.2 \u4f7f\u7528 yaml
\u683c\u5f0f\u6587\u4ef6\u914d\u7f6e\u65e5\u5fd7","text":"\u65b0\u5efa log.yml
\u6587\u4ef6\uff1a
log.yml \uff1a
version: 1\nformatters:\nsimple:\nformat: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'\nhandlers:\nconsole:\nclass: logging.StreamHandler\nlevel: DEBUG\nformatter: simple\nstream: ext://sys.stdout\nloggers:\nsimpleExample:\nlevel: DEBUG\nhandlers:\n- console\npropagate: false\nroot:\nlevel: DEBUG\nhandlers:\n- console\n
YAML \u683c\u5f0f\u89e3\u6790\u5de5\u5177\u6709\u4e09\u4e2a\uff0c\u90fd\u5728\u6587\u6863\u4e2d\u6709\u76f8\u5e94\u5730\u5740\u3002\u5728\u8fd9\u91cc\u9009\u7528 PyYAML \u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
pip install pyyaml\n
\u65b0\u5efa log.py
\u6a21\u5757\uff1a
log.py \uff1a
from logging.config import dictConfig\nfrom yaml import load\ntry:\nfrom yaml import CLoader as Loader, CDumper as Dumper\nexcept ImportError:\nfrom yaml import Loader, Dumper\ndef init_yml_log() -> None:\nwith open('log.yml', mode='r') as obj:\nlogging_config = load(obj, Loader=Loader)\ndictConfig(logging_config)\n
\u6700\u540e\u5728\u7a0b\u5e8f\u6267\u884c\u524d\uff0c\u521d\u59cb\u5316\u65e5\u5fd7\u914d\u7f6e\u3002
"},{"location":"guidelines/advanced/logging/#23","title":"2.3 \u6ce8\u610f\u4e8b\u9879","text":""},{"location":"guidelines/advanced/logging/#231","title":"2.3.1 \u4f18\u5316","text":"\u6839\u636e\u6587\u6863\u4e2d \u4f18\u5316 \u4e00\u8282\u5185\u5bb9\u63cf\u8ff0\uff0c\u65e5\u5fd7\u4e2d\u7684\u53c2\u6570\u5316\u6d88\u606f\uff0c \u5e94\u8be5\u5ef6\u8fdf\u52a0\u8f7d\u3002\u8fd9\u4e48\u505a\u662f\u4e3a\u4e86\u51cf\u5c11\u5728\u8ba1\u7b97\u65e5\u5fd7\u53c2\u6570\u65f6\u6240\u6d88\u8017\u7684\u8d44\u6e90\uff0c\u56e0\u4e3a\u5982\u679c\u65e5\u5fd7\u8bb0\u5f55\u975e\u4e22\u5f03\uff0c\u5219\u4e0d\u9700\u8981\u6d88\u8017\u8fd9\u90e8\u5206\u8d44\u6e90\u3002\u6240\u4ee5\u5728 \u65e5\u5fd7\u8bb0\u5f55\u4e0a\uff0c\u5e94\u91c7\u7528 %
\u7684\u65b9\u5f0f\uff0c\u800c\u4e0d\u662f\u5176\u4ed6\u5b57\u7b26\u4e32\u683c\u5f0f\u5316\u3002
\u5173\u4e8e\u6027\u80fd\u7684\u8ba8\u8bba\u53ef\u4ee5\u53c2\u8003 W1202 - logging-fstring-interpolation is not useful \u3002
"},{"location":"guidelines/advanced/plugin/","title":"\u63d2\u4ef6","text":"plug-in \u5728\u7ef4\u57fa\u767e\u79d1\u4e2d\u662f\u8fd9\u4e48\u5b9a\u4e49\u7684\uff1a\u201c\u5728\u8ba1\u7b97\u4e2d\uff0c\u63d2\u4ef6\u662f\u8f6f\u4ef6\u7ec4\u4ef6\uff0c\u4e3a\u73b0\u6709\u8ba1\u7b97\u673a\u7a0b\u5e8f\u589e\u52a0\u4e00\u4e2a\u7279\u5b9a\u7684\u7279\u5f81\u3002\u201d \u6240\u4ee5\u63d2\u4ef6\u5e94\u8be5\u662f\u4e00\u4e2a\u80fd\u591f\u7075\u6d3b\u914d\u7f6e\uff0c\u5e76\u5f88\u65b9\u4fbf\u7684\u8f7d\u5165\u914d\u7f6e\u4e2d\u7684\u5185\u5bb9\u3002
\u7531\u4e8e Python \u672c\u8eab\u7684\u52a8\u6001\u7279\u6027\uff0c\u63d2\u4ef6\u5316\u7684\u5b9e\u73b0\u5c31\u66f4\u7075\u6d3b\u3002\u73b0\u6709\u7684\u52a8\u6001\u63d2\u4ef6\u90fd\u662f\u57fa\u4e8e Python \u7684\u547d\u540d\u7a7a\u95f4\u548c\u52a8\u6001\u5bfc\u5165\u529f\u80fd\u6765\u67e5\u627e\u5e76\u5bfc\u5165\u5916\u90e8\u4f9d\u8d56\u3002 \u5177\u4f53\u539f\u7406\u53ef\u4ee5\u67e5\u770b Creating and discovering plugins \u3002
"},{"location":"guidelines/advanced/plugin/#_2","title":"\u63d2\u4ef6\u6846\u67b6","text":""},{"location":"guidelines/advanced/plugin/#pluggy","title":"pluggy","text":"pluggy \u662f\u4ece pytest \u4e2d\u6f14\u5316\u51fa\u6765\u7684\u4e00\u4e2a\u63d2\u4ef6\u5de5\u5177\u3002\u5b83\u4e3a pytest \u63d0\u4f9b\u5916\u56f4\u63d2\u4ef6\u652f\u6301\uff0c\u5f53\u5f00\u53d1\u4eba\u5458\u9700\u8981\u6269\u5c55 pytest \u7684\u529f\u80fd\u65f6\uff0c\u57fa\u4e8e pytest \u7684\u89c4\u8303\u505a\u51fa\u5bf9\u5e94\u7684\u63d2\u4ef6\u7136\u540e\u5c06\u5176\u5b89\u88c5\u5230\u73af\u5883\u4e2d\u540e\uff0c pytest \u5c31\u53ef\u4ee5\u81ea\u52a8\u8bc6\u522b\u5df2\u6709\u63d2\u4ef6\u3002
\u5176\u5177\u4f53\u539f\u7406\u662f\u901a\u8fc7\u521b\u5efa\u4e00\u4e2a hookspec = pluggy.HookspecMarker(\"eggsample\")
\u6765\u6807\u8bb0\u63d2\u4ef6\u4e8b\u5148\u7684\u89c4\u8303\uff0c\u7136\u540e\u4f7f\u7528 hookimpl = pluggy.HookimplMarker(\"eggsample\")
\u6807\u8bb0 \u63d2\u4ef6\u7684\u5b9e\u73b0\u3002
\u89c4\u8303\uff1a
import pluggy\nhookspec = pluggy.HookspecMarker(\"eggsample\")\n@hookspec\ndef eggsample_add_ingredients(ingredients: tuple):\n\"\"\"Have a look at the ingredients and offer your own.\n :param ingredients: the ingredients, don't touch them!\n :return: a list of ingredients\n \"\"\"\n@hookspec\ndef eggsample_prep_condiments(condiments: dict):\n\"\"\"Reorganize the condiments tray to your heart's content.\n :param condiments: some sauces and stuff\n :return: a witty comment about your activity\n
\u5b9e\u73b0\uff1a
import pluggy\nhookimpl = pluggy.HookimplMarker(\"eggsample\")\n\"\"\"Marker to be imported and used in plugins (and for own implementations)\"\"\"\nclass ExamplePluggy:\n@hookimpl\ndef eggsample_add_ingredients(self):\nspices = [\"salt\", \"pepper\"]\nyou_can_never_have_enough_eggs = [\"egg\", \"egg\"]\ningredients = spices + you_can_never_have_enough_eggs\nreturn ingredients\n@hookimpl\ndef eggsample_prep_condiments(self, condiments):\ncondiments[\"mint sauce\"] = 1\n
\u7136\u540e\u5c06\u63d2\u4ef6\u89c4\u8303\u548c\u5b9e\u73b0\u88c5\u8f7d\u5230\u63d2\u4ef6\u7ba1\u7406\u7c7b\u4e2d\uff0c\u4e3a\u4e86\u53ef\u4ee5\u627e\u5230\u5176\u4ed6\u4eba\u5f00\u53d1\u7684\u63d2\u4ef6\uff0c\u9700\u8981\u8c03\u7528 load_setuptools_entrypoints
\u65b9\u6cd5\u4ece\u547d\u540d\u7a7a\u95f4 \u67e5\u627e\u5df2\u7ecf\u5728\u6307\u5b9a\u547d\u540d\u7a7a\u95f4\u4e0b\u7684\u5176\u4ed6\u63d2\u4ef6\u3002
import itertools\nimport random\nimport pluggy\ndef get_plugin_manager():\npm = pluggy.PluginManager(\"eggsample\")\npm.add_hookspecs(hookspecs)\npm.load_setuptools_entrypoints(\"eggsample\")\npm.register(ExamplePluggy)\nreturn pm\n
\u5728\u4f7f\u7528\u65f6\uff0c\u8c03\u7528 pm.hook.eggsample_add_ingredients
\u4f20\u9012\u53c2\u6570\u5373\u53ef\u3002
\u5916\u90e8\u5f00\u53d1\u7684\u63d2\u4ef6\uff0c\u53ea\u9700\u8981\u9075\u5faa\u63d2\u4ef6\u89c4\u8303\u505a\u5b9e\u73b0\uff1a
import eggsample\n@eggsample.hookimpl\ndef eggsample_add_ingredients(ingredients):\n\"\"\"Here the caller expects us to return a list.\"\"\"\nif \"egg\" in ingredients:\nspam = [\"lovely spam\", \"wonderous spam\"]\nelse:\nspam = [\"splendiferous spam\", \"magnificent spam\"]\nreturn spam\n@eggsample.hookimpl\ndef eggsample_prep_condiments(condiments):\n\"\"\"Here the caller passes a mutable object, so we mess with it directly.\"\"\"\ntry:\ndel condiments[\"steak sauce\"]\nexcept KeyError:\npass\ncondiments[\"spam sauce\"] = 42\nreturn \"Now this is what I call a condiments tray!\"\n
\u5e76\u5728\u6253\u5305\u4fe1\u606f\u4e2d\u6807\u6ce8\u76f8\u540c\u7684\u547d\u540d\u7a7a\u95f4\uff1a
from setuptools import setup\nsetup(\nname=\"eggsample-spam\",\ninstall_requires=\"eggsample\",\nentry_points={\"eggsample\": [\"spam = eggsample_spam\"]},\npy_modules=[\"eggsample_spam\"],\n)\n
\u5176\u539f\u7406\u4e5f\u662f\u901a\u8fc7 Python \u7684 from importlib.metadata import entry_points
\u627e\u5230\u6ce8\u518c\u5230 Python \u89e3\u91ca\u5668 entry_points
\u4e2d\u7684\u5305\uff0c\u5e76 \u6839\u636e\u547d\u540d\u7a7a\u95f4\u83b7\u53d6\u9700\u8981\u7684\u5185\u5bb9\u3002
"},{"location":"guidelines/advanced/plugin/#stevedore","title":"stevedore","text":"stevedore \u662f Openstack \u5f00\u53d1\u548c\u7ef4\u62a4\u7684\u4e00\u4e2a\u63d2\u4ef6\u5de5\u5177\u3002\u8be5 \u63d2\u4ef6\u4e3a Openstack \u7684 ceilometer \u63d0\u4f9b\u63d2\u4ef6\u529f\u80fd\u3002
stevedore \u5219\u662f\u63a8\u8350\u4f7f\u7528\u7ee7\u627f\u7684\u65b9\u5f0f\u89c4\u8303\u63d2\u4ef6\u63a5\u53e3\u3002
\u9996\u5148\u521b\u5efa\u4e00\u4e2a\u63d2\u4ef6\u57fa\u7c7b\uff1a
# stevedore/example/base.py\n# -*- coding: utf-8 -*-\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport abc\nclass FormatterBase(metaclass=abc.ABCMeta):\n\"\"\"Base class for example plugin used in the tutorial.\n \"\"\"\ndef __init__(self, max_width=60):\nself.max_width = max_width\n@abc.abstractmethod\ndef format(self, data):\n\"\"\"Format the data and return unicode text.\n :param data: A dictionary with string keys and simple types as\n values.\n :type data: dict(str:?)\n :returns: Iterable producing the formatted text.\n \"\"\"\n
\u7136\u540e\u5b9e\u73b0\u4e00\u4e2a\u7b80\u5355\u7684\u63d2\u4ef6\uff1a
# stevedore/example/simple.py\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\nfrom stevedore.example import base\nclass Simple(base.FormatterBase):\n\"\"\"A very basic formatter.\"\"\"\ndef format(self, data):\n\"\"\"Format the data and return unicode text.\n :param data: A dictionary with string keys and simple types as\n values.\n :type data: dict(str:?)\n \"\"\"\nfor name, value in sorted(data.items()):\nline = '{name} = {value}\\n'.format(\nname=name,\nvalue=value,\n)\nyield line\n
\u6700\u540e\u6253\u5305\u3002\u6253\u5305\u7684\u65f6\u5019\uff0c\u5c06\u63d2\u4ef6\u6ce8\u518c\u5230 entry_points
\u4e2d\u3002
# stevedore/example/setup.py\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom setuptools import find_packages\nfrom setuptools import setup\nsetup(\nname='stevedore-examples',\nversion='1.0',\ndescription='Demonstration package for stevedore',\nauthor='Doug Hellmann',\nauthor_email='doug@doughellmann.com',\nurl='http://opendev.org/openstack/stevedore',\nclassifiers=['Development Status :: 3 - Alpha',\n'License :: OSI Approved :: Apache Software License',\n'Programming Language :: Python',\n'Programming Language :: Python :: 2',\n'Programming Language :: Python :: 2.7',\n'Programming Language :: Python :: 3',\n'Programming Language :: Python :: 3.5',\n'Intended Audience :: Developers',\n'Environment :: Console',\n],\nplatforms=['Any'],\nscripts=[],\nprovides=['stevedore.examples',\n],\npackages=find_packages(),\ninclude_package_data=True,\nentry_points={\n'stevedore.example.formatter': [\n'simple = stevedore.example.simple:Simple',\n'plain = stevedore.example.simple:Simple',\n],\n},\nzip_safe=False,\n)\n
\u521b\u5efa\u4e00\u4e2a\u63d2\u4ef6\u9879\u76ee\uff0c\u5e76\u5b9e\u73b0\u63d2\u4ef6\uff1a
# stevedore/example2/fields.py\n# -*- coding: utf-8 -*-\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport textwrap\nfrom stevedore.example import base\nclass FieldList(base.FormatterBase):\n\"\"\"Format values as a reStructuredText field list.\n For example::\n : name1 : value\n : name2 : value\n : name3 : a long value\n will be wrapped with\n a hanging indent\n \"\"\"\ndef format(self, data):\n\"\"\"Format the data and return unicode text.\n :param data: A dictionary with string keys and simple types as\n values.\n :type data: dict(str:?)\n \"\"\"\nfor name, value in sorted(data.items()):\nfull_text = ': {name} : {value}'.format(\nname=name,\nvalue=value,\n)\nwrapped_text = textwrap.fill(\nfull_text,\ninitial_indent='',\nsubsequent_indent=' ',\nwidth=self.max_width,\n)\nyield wrapped_text + '\\n'\n
\u540c\u6837\u6253\u5305\uff0c\u5e76\u914d\u7f6e\u6ce8\u518c\u4fe1\u606f\uff0c\u5c06\u63d2\u4ef6\u6ce8\u518c\u5230\u540c\u4e00\u4e2a\u547d\u540d\u7a7a\u95f4\u4e2d\uff1a
# stevedore/example2/setup.py\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom setuptools import find_packages\nfrom setuptools import setup\nsetup(\nname='stevedore-examples2',\nversion='1.0',\ndescription='Demonstration package for stevedore',\nauthor='Doug Hellmann',\nauthor_email='doug@doughellmann.com',\nurl='http://opendev.org/openstack/stevedore',\nclassifiers=['Development Status :: 3 - Alpha',\n'License :: OSI Approved :: Apache Software License',\n'Programming Language :: Python',\n'Programming Language :: Python :: 2',\n'Programming Language :: Python :: 2.7',\n'Programming Language :: Python :: 3',\n'Programming Language :: Python :: 3.5',\n'Intended Audience :: Developers',\n'Environment :: Console',\n],\nplatforms=['Any'],\nscripts=[],\nprovides=['stevedore.examples2',\n],\npackages=find_packages(),\ninclude_package_data=True,\nentry_points={\n'stevedore.example.formatter': [\n'field = stevedore.example2.fields:FieldList',\n],\n},\nzip_safe=False,\n)\n
\u5728\u4f7f\u7528\u662f\uff0c\u53ef\u4ee5\u901a\u8fc7 driver \u65b9\u5f0f\u8c03\u7528\uff1a
# stevedore/example/load_as_driver.py\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport argparse\nfrom stevedore import driver\nif __name__ == '__main__':\nparser = argparse.ArgumentParser()\nparser.add_argument(\n'format',\nnargs='?',\ndefault='simple',\nhelp='the output format',\n)\nparser.add_argument(\n'--width',\ndefault=60,\ntype=int,\nhelp='maximum output width for text',\n)\nparsed_args = parser.parse_args()\ndata = {\n'a': 'A',\n'b': 'B',\n'long': 'word ' * 80,\n}\nmgr = driver.DriverManager(\nnamespace='stevedore.example.formatter',\nname=parsed_args.format,\ninvoke_on_load=True,\ninvoke_args=(parsed_args.width,),\n)\nfor chunk in mgr.driver.format(data):\nprint(chunk, end='')\n
\u6216\u8005\u901a\u8fc7 extensions \u7684\u65b9\u5f0f\u8c03\u7528\uff1a
# stevedore/example/load_as_extension.py\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport argparse\nfrom stevedore import extension\nif __name__ == '__main__':\nparser = argparse.ArgumentParser()\nparser.add_argument(\n'--width',\ndefault=60,\ntype=int,\nhelp='maximum output width for text',\n)\nparsed_args = parser.parse_args()\ndata = {\n'a': 'A',\n'b': 'B',\n'long': 'word ' * 80,\n}\nmgr = extension.ExtensionManager(\nnamespace='stevedore.example.formatter',\ninvoke_on_load=True,\ninvoke_args=(parsed_args.width,),\n)\ndef format_data(ext, data):\nreturn (ext.name, ext.obj.format(data))\nresults = mgr.map(format_data, data)\nfor name, result in results:\nprint('Formatter: {0}'.format(name))\nfor chunk in result:\nprint(chunk, end='')\nprint('')\n
"},{"location":"guidelines/advanced/plugin/#_3","title":"\u5b9e\u8df5","text":"\u901a\u8fc7\u5b9e\u9645\u5f00\u53d1\u4f53\u9a8c\uff0c\u63a8\u8350\u4f7f\u7528 stevedore \u3002\u603b\u7ed3\u4f18\u70b9\u5982\u4e0b\uff1a
- \u901a\u8fc7\u8bbe\u8ba1\u57fa\u7c7b\u5b9a\u4e49\u63a5\u53e3\u89c4\u8303
- \u63d2\u4ef6\u8c03\u7528\u66f4\u52a0\u7075\u6d3b\u3002
- \u4f7f\u7528\u590d\u6742\u5ea6\u4e0a stevedore \u66f4\u80dc\u4e00\u7b79
"},{"location":"guidelines/advanced/signal_decouple/","title":"\u4fe1\u53f7\u89e3\u8026","text":"\u8fd9\u91cc\u6240\u8bf4\u7684\u4fe1\u53f7\u5e76\u4e0d\u662f\u64cd\u4f5c\u7cfb\u7edf\u7684\u4fe1\u53f7\uff0c\u800c\u662f \u4e8b\u4ef6\u9a71\u52a8\u67b6\u6784 \u7684\u4e00\u79cd\u7b80\u5355\u5b9e\u73b0\u3002
\u4e8b\u4ef6\u9a71\u52a8\u67b6\u6784\u53ef\u4ee5\u57fa\u4e8e\u53d1\u5e03/\u8ba2\u9605\u6a21\u578b\u6216\u8005\u4e8b\u4ef6\u6d41\u6a21\u578b\u3002
\u540e\u9762\u8c08\u5230\u7684\u90fd\u662f\u57fa\u4e8e\u53d1\u5e03/\u8ba2\u9605\u6a21\u578b\u5b9e\u73b0\u7684\u3002
"},{"location":"guidelines/advanced/signal_decouple/#_2","title":"\u5386\u53f2","text":"Python \u4e2d\u7684\u4fe1\u53f7\u89e3\u8026\u673a\u5236\u53ef\u4ee5\u901a\u8fc7 pydispatcher \u5b9e\u73b0\u3002\u800c\u4e14 Django Web \u6846\u67b6\u4e2d\u7684\u4fe1\u53f7\u673a\u5236\u4e5f\u662f \u57fa\u4e8e\u8fd9\u4e2a\u9879\u76ee\u884d\u751f\u7684\u3002
\u8be5\u9879\u76ee\u7684\u6838\u5fc3\u903b\u8f91 ---- \u5f31\u5f15\u7528\uff0c\u4e5f\u5728\u540e\u6765\u5f15\u5165\u5230 Python \u5b98\u65b9\u5e93\u4e2d\u3002\u6b64\u540e\u8be5\u9879\u76ee\u4e5f\u5728 2015 \u5e74\u4e0d\u518d\u66f4\u65b0\u3002
\u800c\u4e4b\u540e\u793e\u533a\u4e5f\u51fa\u73b0\u4e00\u4e9b\u4fe1\u53f7\u6846\u67b6\uff0c\u548c\u5728\u5e95\u5c42\u5b9e\u73b0\u7c7b\u4f3c\u4e8e pydispatcher \u529f\u80fd\u7684\u903b\u8f91\u3002
"},{"location":"guidelines/advanced/signal_decouple/#_3","title":"\u4fe1\u53f7\u6846\u67b6","text":""},{"location":"guidelines/advanced/signal_decouple/#pydispatcher","title":"pydispatcher","text":"pydispatcher \u63d0\u4f9b\u591a\u751f\u4ea7\u8005-\u591a\u6d88\u8d39\u8005\u4fe1\u53f7\u6ce8\u518c\u548c\u8def\u7531\u57fa\u7840\u8bbe\u65bd\uff0c\u4ee5\u5728\u591a\u4e2a\u4e0a\u4e0b\u6587\u4e2d\u4f7f\u7528\u3002
"},{"location":"guidelines/advanced/signal_decouple/#pydispatcher_1","title":"pydispatcher \u4f7f\u7528\u793a\u4f8b","text":"from pydispatch import dispatcher\nstart_process = 'process'\ndef audit(name):\nprint(f'{name} processing ......')\ndispatcher.connect(audit, signal=start_process, sender=dispatcher.Any)\nclass ETL:\nname = 'foo'\ndef process(self):\n\"\"\"\"\"\"\ndispatcher.send(signal=start_process, sender=self, name=self.name)\nif __name__ == '__main__':\nETL().process()\n
\u4e0a\u8ff0\u793a\u4f8b\u4e2d start_process
\u8ba2\u9605\u4e86 audit
\u4e8b\u4ef6\uff0c\u7136\u540e\u5728\u6267\u884c ETL.process
\u7684\u65f6\u5019\uff0c\u901a\u8fc7 dispatcher.send
\u4e00\u6761\u8bb0\u5f55\uff0c \u540c\u65f6\u89e6\u53d1\u8be5\u4e8b\u4ef6\u6267\u884c\u3002
pydispatcher \u652f\u6301\u6307\u5b9a\u7279\u5b9a\u4fe1\u53f7\uff0c\u548c\u53d1\u9001\u8005\u6216\u533f\u540d\u3002\u4fe1\u53f7\u53ef\u4ee5\u662f\u7279\u5b9a\u6216\u8005\u533f\u540d\u3002\u5bf9\u8c61\u7531 Python \u89e3\u91ca\u5668 \u89e3\u91ca\u5668\u7ba1\u7406\uff0c\u5982\u679c\u5bf9\u8c61\u88ab\u56de\u6536\uff0c\u5219\u4e0d\u4f1a\u5728\u89e6\u53d1\u3002
"},{"location":"guidelines/advanced/signal_decouple/#blinker","title":"blinker","text":"blinker \u4e3aPython\u5bf9\u8c61\u63d0\u4f9b\u5feb\u901f\u548c\u7b80\u5355\u7684\u5bf9\u8c61\u548c\u5e7f\u64ad\u4fe1\u53f7\u3002\u5176\u5185\u90e8\u903b\u8f91\u4f9d\u7136\u4f7f\u7528\u7684\u662f\u5f31\u5f15\u7528\u3002\u4f7f\u7528\u8d77\u6765\u548c pydispatcher \u7c7b\u4f3c\u3002
"},{"location":"guidelines/advanced/signal_decouple/#blinker_1","title":"blinker \u4f7f\u7528\u793a\u4f8b","text":"from blinker import Signal\nclass AltProcessor:\non_ready = Signal()\non_complete = Signal()\ndef __init__(self, name):\nself.name = name\ndef go(self):\nself.on_ready.send(self)\nprint(\"Alternate processing.\")\nself.on_complete.send(self)\ndef __repr__(self):\nreturn '<AltProcessor %s>' % self.name\napc = AltProcessor('c')\n@apc.on_complete.connect\ndef completed(sender):\nprint \"AltProcessor %s completed!\" % sender.name\nif __name__ == '__main__':\napc.go()\n
blinker \u540c\u6837\u652f\u6301\u533f\u540d\u4fe1\u53f7\uff0c\u5e95\u5c42\u7684\u5f31\u5f15\u7528\u673a\u5236\u53ef\u4ee5\u51cf\u5c11\u5bf9\u8c61\u7684\u5f15\u7528\u3002\u5b83\u6709\u4e00\u4e2a\u597d\u5904\u662f\u652f\u6301 \u88c5\u9970\u5668\u8ba2\u9605\u4e8b\u4ef6\uff0c\u4f7f\u7528\u8d77\u6765\u6bd4\u8f83\u65b9\u4fbf\u3002
"},{"location":"guidelines/advanced/signal_decouple/#aiosignal","title":"aiosignal","text":"aiosignal \u662f\u4ece aiohttp \u4e2d\u72ec\u7acb\u51fa\u6765\u7684\u5f02\u6b65\u4fe1\u53f7\u6846\u67b6\u3002 \u5b83\u548c\u4e0a\u8ff0\u4e24\u4e2a\u4fe1\u53f7\u6846\u67b6\u533a\u522b\u6709\uff1a\u4e00\uff0c\u5b83\u662f\u4e00\u4e2a\u5f02\u6b65\u4fe1\u53f7\u6846\u67b6\uff0c\u53ef\u4ee5\u8ba2\u9605\u5f02\u6b65\u4e8b\u4ef6\uff1b\u4e8c\uff0c\u5728\u8ba2\u9605\u4e8b\u4ef6\u65f6\uff0c\u5c5e\u4e8e\u5f3a\u5f15\u7528\u3002
"},{"location":"guidelines/advanced/signal_decouple/#aiosignal_1","title":"aiosignal \u4f7f\u7528\u793a\u4f8b","text":"import asyncio\nfrom aiosignal import Signal\nsignal = Signal('signal')\nasync def receiver(message: str):\nprint(f'I receive message: {message}')\nsignal.append(receiver)\nsignal.freeze()\nasync def main():\nawait signal.send('I am god!')\nif __name__ == '__main__':\nasyncio.run(main())\n
\u5728\u5e95\u5c42\uff0c Signal
\u662f\u7ee7\u627f\u4e86 MutableSequence
\u7c7b\uff0c\u4f7f\u7528 Signal.append
\u65b9\u6cd5\u5c06\u8ba2\u9605\u7684\u4e8b\u4ef6\u4fdd\u5b58\u5728\u5bf9\u8c61\u7684\u5c5e\u6027\u4e2d\u3002 \u5f53\u8c03\u7528 Signal.send
\u65b9\u6cd5\u65f6\uff0c\u4f1a\u904d\u5386\u8ba2\u9605\u7684\u4e8b\u4ef6\u5217\u8868\uff0c\u7136\u540e\u6267\u884c\u3002
"},{"location":"guidelines/advanced/signal_decouple/#_4","title":"\u5b9e\u73b0\u81ea\u5b9a\u4e49\u7684\u5f02\u6b65\u4fe1\u53f7","text":"aio-pydispatch
"},{"location":"guidelines/advanced/signal_decouple/#_5","title":"\u6e90\u4ee3\u7801","text":"aio_signal.signal.py
\"\"\"\nAsyncio pydispatch (Signal Manager)\nThis is based on [pyDispatcher](http://pydispatcher.sourceforge.net/) reference\n[scrapy SignalManager](https://docs.scrapy.org/en/latest/topics/signals.html) implementation on\n[Asyncio](https://docs.python.org/3/library/asyncio.html)\n\"\"\"\nimport asyncio\nimport functools\nimport logging\nimport threading\nimport weakref\nfrom typing import (Any, Awaitable, Callable, List, Optional, Tuple, TypeVar,\nUnion)\nfrom aio_pydispatch.utils import id_maker, safe_ref\nT = TypeVar('T') # pylint: disable=invalid-name\nlogger = logging.getLogger(__name__)\nclass _IgnoredException(Exception):\n\"\"\"Ignore exception\"\"\"\nclass Signal:\n\"\"\"\n The signal manager, you can register functions to a signal,\n and store in it.\n Then you can touch off all function that registered on the\n signal where you want.\n example:\n import asyncio\n from aio_pydispatch import Signal\n server_start = Signal('server_start')\n server_stop = Signal('server_stop')\n def ppp(value: str) -> None:\n print(value)\n async def main():\n server_start.connect(ppp)\n server_stop.connect(ppp)\n await server_start.send('server start')\n await asyncio.sleep(1)\n await server_stop.send('server stop')\n if __name__ == '__main__':\n asyncio.run(main())\n \"\"\"\ndef __init__(\nself,\nname: Optional[str] = None,\ndoc: Optional[str] = None,\n):\nself._name = name\nself._doc = doc\nself.__lock = threading.Lock()\nself.__should_clear_receiver = False\nself._receivers = {}\n@property\ndef receivers(self):\n\"\"\"Receivers\"\"\"\nreturn self._receivers\ndef connect(\nself,\nreceiver: Callable[..., Union[T, Awaitable]],\n) -> Callable[..., Union[T, Awaitable]]:\n\"\"\"\n Connect a receiver on this signal.\n :param receiver:\n :return:\n \"\"\"\nassert callable(receiver), \"Signal receivers must be callable.\"\nreferenced_receiver = safe_ref(receiver, self._set_should_clear_receiver, value=True)\nlookup_key = id_maker(receiver)\nwith self.__lock:\nself._clear_dead_receivers()\nif lookup_key not in self._receivers:\nself._receivers.setdefault(lookup_key, referenced_receiver)\nself._set_should_clear_receiver(False)\nreturn receiver\nasync def send(self, *args, **kwargs) -> List[Tuple[Any, Any]]:\n\"\"\"Send signal, touch off all registered function.\"\"\"\n_dont_log = kwargs.pop('_ignored_exception', _IgnoredException)\nresponses = []\nloop = asyncio.get_running_loop()\nfor receiver in self.live_receivers:\nfunc = functools.partial(\nreceiver,\n*args,\n**kwargs\n)\ntry:\nif asyncio.iscoroutinefunction(receiver):\nresponse = await func()\nelse:\nresponse = await loop.run_in_executor(None, func)\nexcept _dont_log as ex:\nresponse = ex\nexcept Exception as ex: # pylint: disable=broad-except\nresponse = ex\nlogger.error('Caught an error on %s', receiver, exc_info=True)\nresponses.append((receiver, response))\nreturn responses\ndef sync_send(self, *args, **kwargs) -> List[Tuple[Any, Any]]:\n\"\"\"\n Can only trigger sync func. If func is coroutine function,\n it will return a awaitable object.\n :param args:\n :param kwargs:\n :return:\n \"\"\"\n_dont_log = kwargs.pop('_ignored_exception', _IgnoredException)\nresponses = []\nfor receiver in self.live_receivers:\ntry:\nif asyncio.iscoroutinefunction(receiver):\nlogger.warning('%s is coroutine, but it not awaited', receiver)\nresponse = receiver(*args, **kwargs)\nexcept _dont_log as ex:\nresponse = ex\nexcept Exception as ex: # pylint: disable=broad-except\nresponse = ex\nlogger.error('Caught an error on %s', receiver, exc_info=True)\nresponses.append((receiver, response))\nreturn responses\n@property\ndef live_receivers(self) -> List[Callable[..., Union[T, Awaitable]]]:\n\"\"\"Get all live receiver.\"\"\"\nwith self.__lock:\nreceivers = []\n_receiver = self._receivers.copy()\nfor lookup_key, receiver in _receiver.items():\nif isinstance(receiver, weakref.ReferenceType):\nreal_receiver = receiver()\nif real_receiver is None:\nself._receivers.pop(lookup_key)\nelse:\nreceivers.append(real_receiver)\nreturn receivers\ndef _set_should_clear_receiver(self, value: bool) -> None:\n\"\"\"Register to the receiver weakerf finalize callback\"\"\"\nself.__should_clear_receiver = value\ndef _clear_dead_receivers(self) -> None:\nif self.__should_clear_receiver:\n_receiver = self._receivers.copy()\nfor lookup_key, receiver in _receiver.items():\nif isinstance(receiver, weakref.ReferenceType) and receiver() is not None:\ncontinue\nself._receivers.pop(lookup_key)\ndef disconnect(self, receiver) -> None:\n\"\"\"clean a receiver\"\"\"\nself._receivers.pop(id_maker(receiver))\ndef disconnect_all(self) -> None:\n\"\"\"Clean all receiver.\"\"\"\nself._receivers.clear()\n
aio_signal.utils.py
\"\"\"Utils\"\"\"\nimport weakref\nfrom typing import Any, Callable, Tuple\nfrom weakref import ReferenceType, WeakMethod\ndef ref_adapter(receiver: Any) -> Tuple[Any, ReferenceType]:\n\"\"\"\n Adapt a receiver to ref object.\n :param receiver:\n :return:\n \"\"\"\nref = weakref.ref\nreceiver_obj = receiver\n# Check if receiver is a ref.\nif hasattr(receiver, '__self__') and hasattr(receiver, '__func__'):\nref = WeakMethod\nreceiver_obj = receiver.__self__\nreferenced_receiver = ref(receiver)\nreturn receiver_obj, referenced_receiver\ndef safe_ref(receiver: Any, callback: Callable[..., None], *args, **kwargs) -> ReferenceType:\n\"\"\"\n Save ref a receiver.\n Register a callback function to the object finalizer\n :param receiver: A ref object\n :param callback: Register the callback function to the object finalizer\n :param args: Callback args\n :param kwargs: Callback kwargs\n :return:\n \"\"\"\nreceiver_obj, receiver = ref_adapter(receiver)\nweakref.finalize(receiver_obj, callback, *args, **kwargs)\nreturn receiver\ndef id_maker(receiver: Any) -> int:\n\"\"\"\n Get receiver id.\n If receiver is ref object, will return a ref object id.\n :param receiver:\n :return: Any\n \"\"\"\nif not isinstance(receiver, ReferenceType):\n_, receiver = ref_adapter(receiver)\nreturn id(receiver)\n
\u81ea\u5b9a\u4e49\u7684 aio_signal
\u652f\u6301\u8ba2\u9605\u540c\u6b65\u4e8b\u4ef6\u548c\u5f02\u6b65\u4e8b\u4ef6\u3002\u53d1\u5e03\u65f6\u652f\u6301\u540c\u6b65\u53d1\u5e03\u548c\u5f02\u6b65\u53d1\u5e03\u3002\u5f02\u6b65\u53d1\u5e03\u652f\u6301\u89e6\u53d1\u540c\u6b65\u4e8b\u4ef6\u548c \u5f02\u6b65\u4e8b\u4ef6\uff0c\u540c\u6b65\u53d1\u5e03\u53ea\u652f\u6301\u89e6\u53d1\u540c\u6b65\u4e8b\u4ef6\u3002
"},{"location":"guidelines/advanced/signal_decouple/#_6","title":"\u4f7f\u7528","text":"import asyncio\nfrom aio_signal import Signal\nserver_start = Signal('server_start')\nserver_stop = Signal('server_stop')\ndef ppp(value: str) -> None:\nprint(value)\nasync def main():\nserver_start.connect(ppp)\nserver_stop.connect(ppp)\nawait server_start.send('server start')\nawait asyncio.sleep(1)\nawait server_stop.send('server stop')\nif __name__ == '__main__':\nasyncio.run(main())\n
"},{"location":"guidelines/advanced/signal_decouple/#_7","title":"\u5b9e\u8df5","text":"\u5728\u5f00\u53d1\u5b9e\u8df5\u4e2d\uff0c\u63a8\u8350\u4f7f\u7528\u5e26\u6709\u5f31\u5f15\u7528\u7684\u4fe1\u53f7\u5e93\u3002\u8fd9\u6837\u53ef\u4ee5\u907f\u514d\u8d44\u6e90\u5360\u7528\u3002
"},{"location":"guidelines/advanced/test/","title":"Test - \u6d4b\u8bd5","text":"\u6d4b\u8bd5\u662f\u8f6f\u4ef6\u5f00\u53d1\u4e2d\u4e00\u4e2a\u4e0d\u53ef\u907f\u514d\u7684\u73af\u8282\uff0c\u5728\u4ee3\u7801\u7ea7\u522b\u8fdb\u884c\u6d4b\u8bd5\uff0c\u80fd\u591f\u5728\u90e8\u7f72\u94b1\u5c3d\u65e9\u53d1\u73b0\u7a0b\u5e8f\u4e2d\u7684\u5f02\u5e38\uff0c\u589e\u5f3a\u8f6f\u4ef6\u7684\u5065\u58ee\u6027\u3002
\u5728\u9075\u5faa TDD \u539f\u5219\u6765\u5f00\u53d1\uff0c\u80fd\u6709\u6548\u63d0\u9ad8\u4ee3\u7801\u7684\u8bbe\u8ba1\u3002
"},{"location":"guidelines/advanced/test/#1","title":"1. \u6d4b\u8bd5\u5de5\u5177","text":"\u5728 Python \u4e2d\u9664\u4e86\u6709\u8bed\u8a00\u5185\u7f6e\u7684\u6d4b\u8bd5\u6846\u67b6\u4e4b\u5916\uff0c\u8fd8\u6709\u8bb8\u591a\u7b2c\u4e09\u65b9\u6d4b\u8bd5\u6846\u67b6\uff0c\u4e00\u4e9b\u975e\u6d4b\u8bd5\u6846\u67b6\u5185\u90e8\u4e5f\u4f1a\u5185\u7f6e\u6d4b\u8bd5\u6846\u67b6\u3002\u5176\u76ee\u7684\u90fd\u662f\u5728\u5185\u7f6e\u6d4b\u8bd5\u6846\u67b6\u7684\u57fa\u7840\u4e0a \u589e\u52a0\u4e86\u4e00\u4e9b\u7279\u6027\uff0c\u8ba9\u7f16\u5199\u6d4b\u8bd5\u66f4\u52a0\u65b9\u4fbf\uff0c\u6d4b\u8bd5\u8fc7\u7a0b\u66f4\u52a0\u987a\u7545\u3002
\u4e3a\u4e86\u65b9\u4fbf\u6d4b\u8bd5\u6846\u67b6\u67e5\u627e\u6d4b\u8bd5\u7528\u4f8b\uff0c\u5728\u7f16\u5199\u6d4b\u8bd5\u65f6\u5e94\u9075\u5faa\u4e00\u5b9a\u7684\u89c4\u8303\uff1a
- \u6d4b\u8bd5\u6a21\u5757\u8981\u4ee5
test_
\u5f00\u5934 - \u6d4b\u8bd5\u65b9\u6cd5\u8981\u4ee5
test_
\u5f00\u5934 - \u6d4b\u8bd5\u7c7b\u540d\u8981\u4ee5
Test
\u5f00\u5934
"},{"location":"guidelines/advanced/test/#11","title":"1.1 \u5185\u7f6e\u6d4b\u8bd5\u6846\u67b6","text":"Python \u5185\u7f6e\u6d4b\u8bd5\u6846\u67b6\u662f unittest \uff0c\u662f\u53d7\u5230\u4e86 JUnit \u7684\u542f\u53d1\uff0c\u5e76\u4e14\u5728\u4f7f\u7528\u4e0a\u4e0e\u5176\u4ed6\u8bed\u8a00\u7684 \u5355\u5143\u6d4b\u8bd5\u6846\u67b6\u7c7b\u4f3c\u3002
\u9762\u5411\u5bf9\u8c61\u7684\u65b9\u5f0f\u6240\u652f\u6301\u7684\u51e0\u4e2a\u6982\u5ff5\uff1a
- \u6d4b\u8bd5\u811a\u624b\u67b6\uff1a
test fixture
\u8868\u793a\u4e3a\u4e86\u5c55\u5f00\u4e00\u9879\u6216\u591a\u9879\u6d4b\u8bd5\u6240\u9700\u8981\u51c6\u5907\u7684\u5de5\u4f5c\uff0c\u4ee5\u53ca\u76f8\u5173\u7684\u6e05\u7406\u5de5\u4f5c - \u6d4b\u8bd5\u7528\u4f8b\uff1a\u4e00\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u662f\u4e00\u4e2a\u72ec\u7acb\u7684\u5355\u5143\u6d4b\u8bd5\u3002
- \u6d4b\u8bd5\u5957\u4ef6\uff1a\u662f\u4e00\u7cfb\u5217\u7684\u6d4b\u8bd5\u7528\u4f8b\uff0c\u6216\u6d4b\u8bd5\u5957\u4ef6\u3002
- \u6d4b\u8bd5\u8fd0\u884c\u5668\uff1a\u7528\u4e8e\u6267\u884c\u548c\u8f93\u51fa\u6d4b\u8bd5\u7ed3\u679c\u3002
\u4e0b\u9762\u662f\u4e00\u4e2a\u6700\u7b80\u5355\u7684\u6d4b\u8bd5\u7528\u4f8b\uff1a
# Test\nimport unittest\nfrom unittest import TestCase\nclass TestSum(TestCase):\ndef test_sum(self):\n\"\"\"Test sum\"\"\"\nassert sum([1, 1]) == 2\nif __name__ == '__main__':\nunittest.main()\n
\u53ef\u4ee5\u901a\u8fc7\u8fd0\u884c\u8be5\u6587\u4ef6\u8fd0\u884c\u6d4b\u8bd5\uff0c\u4e5f\u53ef\u4ee5\u7528 python -m test_xxx.py
\u8fd0\u884c\u6d4b\u8bd5\u6a21\u5757\u3002
\u7ec4\u7ec7\u6d4b\u8bd5\u7684\u6d4b\u8bd5\u4ee3\u7801\uff1a
# Test\nfrom csv import DictReader\nimport unittest\nfrom unittest import TestCase\nfrom tempfile import NamedTemporaryFile\nclass TestSum(TestCase):\ndef test_sum(self):\n\"\"\"Test sum\"\"\"\nassert sum([1, 1]) == 2\nclass TestCsv(TestCase):\ndef setUp(self) -> None:\nself.temp_file = NamedTemporaryFile(suffix='csv')\nself.filename = self.temp_file.name\nwith open(self.filename, 'w') as obj:\nobj.writelines([\n'name,age\\n',\n'foo,12\\n',\n'bar,15\\n'\n])\ndef test_csv(self):\nwith open(self.filename, 'r') as obj:\nreader = DictReader(obj)\ndata = list(reader)\nself.assertEqual(len(data), 2)\ndef tearDown(self) -> None:\nself.temp_file.close()\ndef suite():\n_suite = unittest.TestSuite()\n_suite.addTest(TestSum())\n_suite.addTest(TestCsv())\nreturn _suite\nif __name__ == '__main__':\nrunner = unittest.TextTestRunner()\nrunner.run(suite())\n
\u4f7f\u7528 TestSuite
\u548c TextTestRunner
\u7ec4\u7ec7\u6d4b\u8bd5\uff0c\u53ef\u4ee5\u8ba9\u4ee3\u7801\u7684\u903b\u8f91\u66f4\u52a0\u6e05\u6670\u3002
"},{"location":"guidelines/advanced/test/#111-mock","title":"1.1.1 Mock \u5bf9\u8c61","text":"\u5728\u8fdb\u884c\u5355\u5143\u6d4b\u8bd5\u7684\u65f6\u5019\uff0c\u96be\u514d\u4f1a\u9047\u5230\u4f9d\u8d56\u5177\u4f53\u7684\u5bf9\u8c61\u6216\u8d44\u6e90\u7684\u60c5\u51b5\u3002\u4e3a\u4e86\u53ea\u6d4b\u8bd5\u5177\u4f53\u5355\u5143\u7684\u903b\u8f91\uff0c\u5c31\u9700\u8981\u6a21\u62df\u5355\u5143\u903b\u8f91\u6240\u4f9d\u8d56\u7684\u5185\u5bb9\u3002
\u4f7f\u7528 unittest.mock \u53ef\u4ee5\u5f88\u597d\u89e3\u51b3\u8fd9\u4e2d\u95ee\u9898\u3002
\u5982\u4e0b\u4f8b\u5b50\uff1a
# Test\nimport unittest\nfrom typing import Any\nfrom unittest import TestCase\nfrom unittest.mock import Mock\nclass Session:\ndef close(self, connection: Any):\nconnection.close()\nclass TestSession(TestCase):\ndef setUp(self) -> None:\nself.session = Session()\ndef test_close(self):\nmock = Mock()\nself.session.close(mock)\nmock.close.assert_called_with()\nif __name__ == '__main__':\nunittest.main()\n
\u5728\u6d4b\u8bd5 Session.close
\u8fd9\u4e2a\u5355\u5143\u903b\u8f91\u7684\u65f6\u5019\uff0c\u4f9d\u8d56\u4e00\u4e2a connection
\u5bf9\u8c61\u3002\u56e0\u4e3a\u5355\u5143\u6d4b\u8bd5\u4ec5\u5173\u6ce8\u5355\u5143\u5185\u90e8\u903b\u8f91\u662f\u5426\u6b63\u786e\uff0c\u5373\u7ed9\u5b9a\u8f93\u5165\uff0c\u6d4b\u8bd5\u5176\u5185\u90e8\u903b\u8f91\u3002 \u6240\u4ee5\u4f7f\u7528\u4e00\u4e2a Mock
\u5bf9\u8c61\u6a21\u62df\u5165\u53c2\uff0c\u7136\u540e\u5224\u65ad\u5165\u53c2\u662f\u5426\u5728\u903b\u8f91\u5185\u88ab\u8c03\u7528\u3002
\u9664\u4e86\u6a21\u62df\u5bf9\u8c61\uff0c\u8fd8\u53ef\u4ee5\u6a21\u62df\u7c7b\uff1a
# Test\nimport unittest\nfrom unittest import TestCase\nfrom unittest.mock import patch\nclass Session:\ndef exist(self):\n\"\"\"\"\"\"\ndef delete(self):\nif self.exist():\nreturn True\nreturn False\nclass TestSession(TestCase):\ndef setUp(self) -> None:\nself.session = Session()\ndef test_delete(self):\nwith patch.object(Session, 'exist', return_value=True) as mock_session:\nsession = Session()\nself.assertTrue(session.delete())\nmock_session.assert_called_once()\nif __name__ == '__main__':\nunittest.main()\n
\u6848\u4f8b\u4e2d\uff0c\u6d4b\u8bd5\u7684\u662f Session.delete
\u65b9\u6cd5\uff0c\u5185\u90e8\u903b\u8f91\u4f9d\u8d56 Session.exist
\u3002\u56e0\u4e3a\u4ec5\u6d4b\u8bd5\u5355\u5143\u903b\u8f91\u6240\u4ee5\u5c06\u5b83\u4f9d\u8d56 \u8c03\u7528\u7684 Session.exist
\u6a21\u62df\u6389\u3002
"},{"location":"guidelines/advanced/test/#12-pytest","title":"1.2 Pytest","text":"Pytest \u662f\u5728 unittest \u7684\u57fa\u7840\u4e0a \u589e\u52a0\u4e86\u5927\u91cf\u8bed\u6cd5\u7cd6\uff0c\u8ba9\u6d4b\u8bd5\u66f4\u52a0\u7b80\u4fbf\u548c\u7075\u6d3b\u3002\u5e76\u4e14\u5e26\u6709\u63d2\u4ef6\u529f\u80fd\uff0c\u65b9\u4fbf\u96c6\u6210\u5176\u4ed6\u529f\u80fd\u3002
\u7531\u4e8e Pytest \u80fd\u517c\u5bb9\u5176\u4ed6\u5927\u591a\u6570\u6d4b\u8bd5\u6846\u67b6\uff0c\u800c\u4e14\u5b83\u4e5f\u5177\u6709\u5f3a\u5927\u7684\u529f\u80fd\uff0c\u6240\u4ee5\u63a8\u8350\u4f7f\u7528 Pytest \u4f5c\u4e3a\u4e3b\u8981\u6d4b\u8bd5\u6846\u67b6\u4f7f\u7528\u3002
\u5b89\u88c5\uff1a
pip install pytest\n
\u7f16\u5199\u6d4b\u8bd5\u6587\u4ef6\uff1a
# content of test_sample.py\ndef inc(x):\nreturn x + 1\ndef test_answer():\nassert inc(3) == 5\n
\u5728\u7ec8\u7aef\u8fd0\u884c pytest
\u5373\u53ef\u81ea\u52a8\u53d1\u73b0\u6d4b\u8bd5\uff0c\u5e76\u8fd0\u884c\u3002
\u63d0\u793a pytest
\u4f1a\u81ea\u52a8\u53d1\u73b0\u6240\u6709\u4ee5 test_
\u5f00\u5934\u548c _test.py
\u7ed3\u5c3e\u7684\u6587\u4ef6\uff0c\u5e76\u52a0\u8f7d\u6240\u6709\u4ee5 test_
\u5f00\u5934\u7684\u65b9\u6cd5\u548c Test
\u5f00\u5934\u7684\u7c7b\u3002
"},{"location":"guidelines/advanced/test/#122","title":"1.2.2 \u76ee\u5f55\u7ed3\u6784\u7684\u9009\u62e9","text":"\u5728\u9879\u76ee\u7ed3\u6784\u4e0a\uff0c\u63a8\u8350\u4f7f\u7528 src
\u76ee\u5f55\u653e\u6e90\u4ee3\u7801\uff0c\u540c\u7ea7\u7684 tests
\u653e\u6d4b\u8bd5\u6a21\u5757\uff0c\u6d4b\u8bd5\u6a21\u5757\u7684\u7ec4\u7ec7\u548c src
\u7684\u5305\u7ed3\u6784\u4e00\u81f4\uff0c\u6d4b\u8bd5\u7684\u529f\u80fd \u76f8\u5bf9\u5e94\u3002
setup.py\nsrc/\n mypkg/\n __init__.py\n app.py\n view.py\ntests/\n __init__.py\n foo/\n __init__.py\n test_view.py\n bar/\n __init__.py\n test_view.py\n
\u5176\u4ed6\u98ce\u683c\u7684\u4f7f\u7528\u53ef\u4ee5\u53c2\u8003 Choosing a test layout / import rules
"},{"location":"guidelines/advanced/test/#121-fixture","title":"1.2.1 fixture","text":"Pytest \u7684 fixture \u53ef\u4ee5\u4e3a\u6d4b\u8bd5\u63d0\u4f9b\u4e00\u5b9a\u7684\u73af\u5883\u3002
import pytest\nclass Fruit:\ndef __init__(self, name):\nself.name = name\ndef __eq__(self, other):\nreturn self.name == other.name\n@pytest.fixture\ndef my_fruit():\nreturn Fruit(\"apple\")\n@pytest.fixture\ndef fruit_basket(my_fruit):\nreturn [Fruit(\"banana\"), my_fruit]\ndef test_my_fruit_in_basket(my_fruit, fruit_basket):\nassert my_fruit in fruit_basket\n
\u5728\u6d4b\u8bd5\u662f\uff0c\u516c\u5171\u7684 fixture
\u4e00\u822c\u63a8\u8350\u653e\u5728 conftest.py
\u4e2d\u3002
\u66f4\u590d\u6742\u7684 fixture
\uff1a
import pytest\n@pytest.fixture\ndef order():\nreturn []\n@pytest.fixture\ndef a(order):\norder.append(\"a\")\n@pytest.fixture\ndef b(a, order):\norder.append(\"b\")\n@pytest.fixture\ndef c(a, b, order):\norder.append(\"c\")\n@pytest.fixture\ndef d(c, b, order):\norder.append(\"d\")\n@pytest.fixture\ndef e(d, b, order):\norder.append(\"e\")\n@pytest.fixture\ndef f(e, order):\norder.append(\"f\")\n@pytest.fixture\ndef g(f, c, order):\norder.append(\"g\")\ndef test_order(g, order):\nassert order == [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\"]\n
"},{"location":"guidelines/advanced/test/#123","title":"1.2.3 \u53c2\u6570\u5316\u6d4b\u8bd5","text":"\u5728\u9488\u5bf9\u540c\u4e00\u4e2a\u903b\u8f91\u6709\u591a\u79cd\u4e0d\u540c\u8f93\u5165\u8fdb\u884c\u6d4b\u8bd5\u65f6\uff0c\u76f4\u63a5\u60f3\u5230\u7684\u5904\u7406\u65b9\u5f0f\u5c31\u662f\u505a\u6210\u5de5\u5382\uff0c\u7136\u540e\u5728\u6d4b\u8bd5\u65b9\u6cd5\u4e2d \u6784\u9020\u53c2\u6570\u5217\u8868\u4f20\u9012\u7ed9\u5de5\u5382\u3002\u8fd9\u5728 unittest \u4e2d\u79f0\u4f5c\u590d\u7528\u6d4b\u8bd5\u903b\u8f91\u3002\u5426\u5219\u5c31\u9700\u8981\u4e3a\u6d4b\u8bd5\u903b\u8f91\u7f16\u5199\u4e0d\u540c\u53c2\u6570 \u7684\u6d4b\u8bd5\u65b9\u6cd5\u3002
\u4f46\u5728 Pytest \u4e2d\u53ef\u4ee5\u4f7f\u7528\u53c2\u6570\u5316\u6d4b\u8bd5\uff0c \u8f7b\u677e\u89e3\u51b3\u8fd9\u79cd\u95ee\u9898\u3002
\"\"\"Test log\"\"\"\nimport pytest\ndef update_log_level(debug: bool, level: str) -> str:\nif debug:\nreturn 'DEBUG'\nreturn level\n@pytest.mark.parametrize(\n['debug', 'level', 'expect_value'],\n[\n(True, '', 'DEBUG'),\n(True, 'INFO', 'DEBUG'),\n(False, 'DEBUG', 'DEBUG'),\n(False, 'INFO', 'INFO'),\n]\n)\ndef test_log_level(debug: bool, level: str, expect_value):\n\"\"\"Test log level\"\"\"\nlog_level_name = update_log_level(debug, level)\nassert log_level_name == expect_value\n
\u53c2\u6570\u5316\u6d4b\u8bd5\u5e26\u6765\u7684\u597d\u5904\u975e\u5e38\u76f4\u89c2\uff0c\u800c\u4e14\u6d4b\u8bd5\u7f16\u5199\u4e5f\u53d8\u5f97\u7b80\u5355\u3002
"},{"location":"guidelines/advanced/test/#124","title":"1.2.4 \u63d2\u4ef6","text":"Pytest \u62e5\u6709\u5927\u91cf\u7684\u63d2\u4ef6 \uff0c\u800c\u4e14\u57fa\u672c\u4e0a\u662f\u5b89\u88c5\u5373\u53ef\u548c Pytest \u65e0\u7f1d \u96c6\u6210\uff0c\u8f7b\u677e\u4f7f\u7528\u3002
\u4e0b\u9762\u5217\u4e3e\u51e0\u4e2a\u5e38\u7528\u7684\u63d2\u4ef6
"},{"location":"guidelines/advanced/test/#1241-pytest-asyncio","title":"1.2.4.1 pytest-asyncio","text":"pytest-asyncio \u53ef\u4ee5\u8f7b\u677e\u6d4b\u8bd5 asyncio
\u65b9\u6cd5
@pytest.mark.asyncio\nasync def test_some_asyncio_code():\nres = await library.do_something()\nassert b'expected result' == res\n
"},{"location":"guidelines/advanced/test/#1242-pytest-mock","title":"1.2.4.2 pytest-mock","text":"pytest-mock \u53ef\u4ee5\u50cf\u4f7f\u7528 fixture
\u4e00\u6837\u4f7f\u7528 mock
\uff0c\u800c\u4e0d\u5fc5\u5bfc\u5165 unittest.mock
import os\nclass UnixFS:\n@staticmethod\ndef rm(filename):\nos.remove(filename)\ndef test_unix_fs(mocker):\nmocker.patch('os.remove')\nUnixFS.rm('file')\nos.remove.assert_called_once_with('file')\n
"},{"location":"guidelines/advanced/test/#1243-pytest-cov","title":"1.2.4.3 pytest-cov","text":"pytest-cov \u8ba9 coverage \u548c Pytest \u96c6\u6210\uff0c \u65b9\u4fbf\u4f7f\u7528\u3002
"},{"location":"guidelines/advanced/test/#13","title":"1.3 \u6846\u67b6\u81ea\u5e26\u6d4b\u8bd5","text":"\u672c\u8282\u5185\u5bb9\u4e3b\u8981\u7b80\u5355\u63cf\u8ff0\u4e00\u4e9b\u6846\u67b6\u81ea\u5e26\u7684\u6d4b\u8bd5\u7684\u4f7f\u7528\u3002 Pytest \u4e5f\u90fd\u80fd\u517c\u5bb9\u8fd9\u4e9b\u6d4b\u8bd5\u3002\u6240\u4ee5\u5982\u679c\u4f7f\u7528\u6846\u67b6\u63a8\u8350\u7684\u5199\u6cd5\u6765\u5199\u6d4b\u8bd5\uff0c\u5728\u4f7f\u7528 Pytest \u8fd0\u884c \u4e5f\u662f\u6ca1\u6709\u95ee\u9898\u7684\u3002
"},{"location":"guidelines/advanced/test/#131-django","title":"1.3.1 Django","text":"Django \u7684\u5355\u5143\u6d4b\u8bd5\u4e5f\u662f\u57fa\u4e8e unittest
\u6765\u505a\u7684\uff0c \u53ea\u4e0d\u8fc7\u589e\u52a0\u4e86\u4e00\u4e9b\u6846\u67b6\u4e0a\u7684\u5185\u5bb9\uff0c\u4fbf\u4e8e\u5728\u6d4b\u8bd5\u65f6\uff0c\u9644\u5e26\u6846\u67b6\u529f\u80fd\u3002
\u6d4b\u8bd5\u7528\u4f8b\uff1a
from django.test import TestCase\nfrom myapp.models import Animal\nclass AnimalTestCase(TestCase):\ndef setUp(self):\nAnimal.objects.create(name=\"lion\", sound=\"roar\")\nAnimal.objects.create(name=\"cat\", sound=\"meow\")\ndef test_animals_can_speak(self):\n\"\"\"Animals that can speak are correctly identified\"\"\"\nlion = Animal.objects.get(name=\"lion\")\ncat = Animal.objects.get(name=\"cat\")\nself.assertEqual(lion.speak(), 'The lion says \"roar\"')\nself.assertEqual(cat.speak(), 'The cat says \"meow\"')\n
\u8fd0\u884c\u6d4b\u8bd5 ./manage.py test
\u3002
\u5728\u8fd0\u884c\u65f6\uff0c\u5185\u90e8\u903b\u8f91\u4f9d\u7136\u662f\u901a\u8fc7 unittest
\u6765\u81ea\u52a8\u67e5\u627e\u6d4b\u8bd5\u7c7b\u3002
"},{"location":"guidelines/advanced/test/#132-fastapi","title":"1.3.2 Fastapi","text":"Fastapi \u4ec5\u4ec5\u63d0\u4f9b\u4e86\u5e26\u6709\u6846\u67b6\u529f\u80fd\u7684 TestClient
\u3002\u521d\u59cb\u5316\u7684\u5b9e\u4f8b \u65b9\u4fbf\u6d4b\u8bd5 API \u63a5\u53e3\uff0c\u800c\u4e0d\u662f\u771f\u6b63\u542f\u52a8\u4e00\u4e2a Web \u670d\u52a1\u3002
from fastapi import FastAPI\nfrom fastapi.testclient import TestClient\napp = FastAPI()\n@app.get(\"/\")\nasync def read_main():\nreturn {\"msg\": \"Hello World\"}\nclient = TestClient(app)\ndef test_read_main():\nresponse = client.get(\"/\")\nassert response.status_code == 200\nassert response.json() == {\"msg\": \"Hello World\"}\n
\u8fd0\u884c pytest
\u3002
"},{"location":"guidelines/advanced/type_hint/","title":"\u7c7b\u578b\u63d0\u793a","text":"Python \u4f5c\u4e3a\u4e00\u4e2a\u52a8\u6001\u7c7b\u578b\u8bed\u8a00\uff0c\u5728\u7f16\u7801\u8fc7\u7a0b\u4e2d\u51fa\u73b0\u7684\u4e00\u4e9b\u5c0f\u95ee\u9898\uff0c\u76f4\u5230\u8fd0\u884c\u65f6\u624d\u88ab\u53d1\u73b0\u3002\u76f8\u6bd4\u4e8e\u9759\u6001\u8bed\u8a00\uff0c \u50cf Java \u3001 C/C++ \u7b49\uff0c\u5728\u7f16\u8bd1\u671f\u95f4\u5c31\u80fd\u53d1\u73b0\u5e76\u6539\u8fdb\u4ee3\u7801\u95ee\u9898\u3002 \u6240\u4ee5\u4e3a\u4e86\u5728\u8fd0\u884c\u65f6\u4e4b\u524d\u5c3d\u53ef\u80fd\u907f\u514d\u51fa\u95ee\u9898\uff0c \u5728 2014 \u5e74 Guido van Rossum \u7b49\u4eba\u4e3a Python \u63d0\u51fa\u4e86 \u7c7b\u578b\u63d0\u793a\u7406\u8bba \u3002 \u5728 2015 \u5e74\u7684 Pycon \u505a\u4e86\u8be5\u4e3b\u9898\u7684\u6f14\u8bb2\u3002 \u76f4\u5230\u73b0\u5728\uff0c\u5173\u4e8e\u9759\u6001\u7c7b\u578b\u76f8\u5173\u7684 PEP \u6709\uff1a
- PEP 484 -- Type Hints
- PEP 526 -- Syntax for Variable Annotations
- PEP 544 -- Protocols: Structural subtyping (static duck typing)
- PEP 586 -- Literal Types
- PEP 589 -- TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys
- PEP 591 -- Adding a final qualifier to typing
\u5230\u73b0\u5728\u7684 python 3.9 \u7248\u672c\uff0c\u7c7b\u578b\u63d0\u793a\u7684\u652f\u6301\u5df2\u7ecf\u5f88\u4e30\u5bcc\u4e86\u3002\u540c\u65f6\u4e0e\u7c7b\u578b\u63d0\u793a\u76f8\u5173\u7684\u68c0\u6d4b\u5de5\u5177\uff0c\u5de5\u5177\u5728 IDE \u4e0a\u7684\u96c6\u6210\u529f\u80fd\u4e5f\u5f88\u5b8c\u5584\uff0c \u5728\u5f00\u53d1\u4f53\u9a8c\u4e0a\u6709\u4e86\u5f88\u5927\u7684\u63d0\u5347\u3002\u540c\u65f6\u7c7b\u578b\u68c0\u67e5\u4e5f\u6210\u4e3a\u4e86 CI \u7684\u91cd\u8981\u73af\u8282\uff0c\u6709\u52a9\u4e8e\u66f4\u65e9\u66f4\u53ca\u65f6\u7684\u89c4\u907f Bug \u7684\u51fa\u73b0\u3002
"},{"location":"guidelines/advanced/type_hint/#1","title":"1. \u521d\u8bc6\u7c7b\u578b\u63d0\u793a","text":"\u7c7b\u578b\u63d0\u793a\u53ef\u4ee5\u5728\u7c7b\u3001\u65b9\u6cd5\u6216\u53d8\u91cf\u4e0a\u6807\u6ce8\u76f8\u5e94\u7684\u7c7b\u578b\uff0c\u5728\u8c03\u7528\u7684\u65f6\u5019\u901a\u8fc7\u9759\u6001\u7c7b\u578b\u68c0\u67e5\u5de5\u5177\u68c0\u6d4b\u8c03\u7528\u662f\u5426\u5b58\u5728\u95ee\u9898\u3002
\u5982\u4e0b\u9762\u7684\u4f8b\u5b50\uff1a
def greeting(name: str) -> str:\nreturn f'Hello {name}'\n
\u5728\u5b9a\u4e49\u65b9\u6cd5 greeting
\u7684\u65f6\u5019\uff0c\u58f0\u660e\u53c2\u6570 name
\u662f str
\u7c7b\u578b\uff0c\u8fd4\u56de\u503c\u662f str
\u7c7b\u578b\u3002\u5f53\u8c03\u7528 greeting
\u51fd\u6570\u65f6\uff0c\u5982\u679c\u4f20\u9012\u4e00\u4e2a int
\u7c7b\u578b \u7684\u503c\uff0c \u8fd0\u884c\u7c7b\u578b\u68c0\u67e5\u4f1a\u5931\u8d25\uff0c\u540c\u65f6\u53d1\u51fa\u8b66\u544a\u63d0\u793a\u3002\u5982\u679c IDE \u5df2\u7ecf\u652f\u6301\u7c7b\u578b\u68c0\u67e5\uff0c\u5219\u5728\u8c03\u7528\u7684\u65f6\u5019\uff0c\u4f1a\u63d0\u793a\u8be5\u65b9\u6cd5\u7684\u53c2\u6570\u7c7b\u578b\uff0c \u5982\u679c\u4f20\u9012\u9519\u8bef\u7c7b\u578b\u7684\u53c2\u6570 IDE \u4f1a\u53ca\u65f6\u53d1\u51fa\u8b66\u544a\uff0c\u63d0\u793a\u6211\u4eec\u4fee\u590d\u3002
import logging\nfrom pathlib import Path # Config root logger\nlogging.basicConfig(\nlevel=logging.DEBUG,\nformat='%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n)\ndef count(source_file: str, dest_file: str) -> None:\n\"\"\"\n Count source\n :param source_file:\n :param dest_file:\n :return:\n \"\"\"\ntotal = read_from_file(Path(source_file))\nwrite_to_file(Path(dest_file), total)\ndef read_from_file(source_file: Path) -> int:\n\"\"\"\n Read file\n :param source_file:\n :return:\n \"\"\"\ntotal_words = 0\n# Read source_file\nlogging.debug('Read file: %s', source_file)\nwith open(source_file, 'r') as source_obj:\nfor line in source_obj.readlines():\ntotal_words += len(line.split(' '))\nreturn total_words\ndef write_to_file(dest_file: Path, total_words: int) -> None:\n\"\"\"\n Write result to file\n :param dest_file:\n :param total_words:\n :return:\n \"\"\"\nlogging.debug('Count %s words, write to %d', dest_file, total_words)\nwith open(dest_file, 'w') as dest_obj:\ndest_obj.write(f'Total count: {total_words}')\n
\u4e0a\u8ff0\u4ee3\u7801\u4e2d\uff0c\u6240\u6709\u65b9\u6cd5\u7684\u53c2\u6570\u548c\u8fd4\u56de\u503c\u90fd\u8fdb\u884c\u4e86\u7c7b\u578b\u6807\u6ce8\u3002
"},{"location":"guidelines/advanced/type_hint/#2","title":"2. \u4f7f\u7528\u7c7b\u578b\u63d0\u793a","text":""},{"location":"guidelines/advanced/type_hint/#21","title":"2.1 \u4e00\u822c\u7c7b\u578b\u63d0\u793a","text":"\u5728\u8fdb\u884c\u7c7b\u578b\u6807\u6ce8\u7684\u8fc7\u7a0b\u4e2d\uff0c\u4e00\u822c\u76f4\u63a5\u901a\u8fc7\u6807\u6ce8\u53d8\u91cf\u672c\u8eab\u7684\u7c7b\u578b\u5c31\u53ef\u4ee5\u4e86\u3002
\u4f8b\u5982\uff1a
\"\"\"Example\"\"\"\nimport logging\nlogging.basicConfig(level=logging.DEBUG)\nclass User:\n\"\"\"User\"\"\"\ndef __init__(self, name: str):\nself._name = name\n@property\ndef name(self) -> str:\n\"\"\"User's name\"\"\"\nreturn self._name\ndef __repr__(self):\n\"\"\"repr\"\"\"\nreturn f'<User(name=\"{self.name}\")>'\ndef save(user: User):\n\"\"\"Mock to save a user\"\"\"\nlogging.info('Save object: %s', user)\nif __name__ == '__main__':\nsave(User('Jim'))\n
\u5982\u4e0a\u8ff0\u4f8b\u5b50\u4e2d\uff0c save
\u65b9\u6cd5\u4f20\u5165\u4e00\u4e2a User
\u7c7b\u578b\u7684\u53c2\u6570\uff0c\u76f4\u63a5\u4f7f\u7528\u8be5\u7c7b\u6807\u6ce8\u5c31\u53ef\u4ee5\u4e86\u3002
\u9488\u5bf9\u4e00\u822c\u6570\u636e\u7c7b\u578b\uff0c\u5982 int
\u3001 str
\u3001 float
\u3001 bytes
\u7b49\uff0c\u53ef\u4ee5\u76f4\u63a5\u6807\u6ce8\u3002
"},{"location":"guidelines/advanced/type_hint/#22","title":"2.2 \u6cdb\u578b\u5177\u8c61\u5bb9\u5668","text":"\"\"\"Example\"\"\"\nfrom typing import Dict, List\ndef count_words(records: List[str]) -> Dict[str, int]:\n\"\"\"Count word of all lines.\"\"\"\nresult: Dict[str, int] = {}\nfor record in records:\nfor word in record.split(' '):\ncount = result.get(word, 0)\nresult.update({word: count + 1})\nreturn result\n
count_words
\u65b9\u6cd5\u63a5\u6536\u4e00\u4e2a\u5185\u542b str
\u7684 list
\u53c2\u6570\uff0c\u540c\u65f6\u8fd4\u56de dict
\u3002
\u8fd9\u4e9b typing.Dict
\u3001 typing.List
\u3001 typing.Set
\u7b49\u90fd\u662f\u5bf9\u5e94\u57fa\u672c\u6570\u636e\u7ed3\u6784\u7684\u6cdb\u578b\u7248\u672c\u3002
\u6ce8\u610f\uff1a \u6839\u636e\u6587\u6863 \u6a21\u5757\u5185\u5bb9 \u4e00\u8282\u63cf\u8ff0\uff0c \u5728 Python 3.9 \u5df2\u7ecf\u5bf9\u4e00\u4e9b\u57fa\u672c\u6570\u636e \u63a5\u53e3\u505a\u4e86\u6cdb\u578b\u9002\u914d\uff0c\u8fd9\u548c\u73b0\u6709 typing \u4e0b\u7684\u6cdb\u578b\u7c7b\u578b\u91cd\u590d\uff0c \u6240\u4ee5\u4f1a\u5f03\u7528\u8fd9\u4e9b\u6cdb\u578b\u5bb9\u5668\u7c7b\u578b\uff0c\u5177\u4f53\u8bf7\u53c2\u8003\u4f53\u5305\u542b\u54ea\u4e9b\u8bf7\u53c2\u8003 PEP 585 \u3002 \u5982\u679c\u9700\u8981\u63d0\u524d\u4f7f\u7528\u65b0\u7279\u6027\uff0c\u5728 Python 3.7 \u5f00\u59cb\uff0c\u53ef\u4ee5\u5bfc\u5165 from __future__ import annotations
\u6765\u4f7f\u7528\u65b0\u7684\u6cdb\u578b\u7c7b\u578b\u3002 \u5b98\u65b9\u4f1a\u5728 Python 3.9 \u53d1\u5e03\u4e94\u5e74\u540e\u7684\u6536\u4e2a Python \u53d1\u884c\u7248\uff0c\u53732025\u5e7410\u67085\u65e5\u4e4b\u540e\u7684\u6536\u4e2a\u53d1\u884c\u7248\u4f1a\u79fb\u9664 PEP 585 \u4e2d\u5f03\u7528\u7684\u6cdb\u578b\u5bb9\u5668\u7c7b\u578b\u3002
"},{"location":"guidelines/advanced/type_hint/#23","title":"2.3 \u7279\u6b8a\u7c7b\u578b","text":"\"\"\"Example\"\"\"\nimport asyncio\nfrom typing import Callable, Any, Type, Tuple, Dict, Optional\nfrom functools import partial\nclass BaseTask:\n\"\"\"base Task\"\"\"\ndef run(self) -> bool:\n\"\"\"Run task\"\"\"\nraise NotImplementedError\ndef stop(self) -> None:\n\"\"\"Stop task\"\"\"\nraise NotImplementedError\nclass FileTask(BaseTask):\n\"\"\"File task\"\"\"\ndef run(self) -> bool:\npass\ndef stop(self) -> None:\npass\nclass NetworkTask(BaseTask):\n\"\"\"Network task\"\"\"\ndef run(self) -> bool:\npass\ndef stop(self) -> None:\npass\nKwargsType = Dict[str, Any]\nArgsType = Tuple[Any]\nasync def run_in_executor(\nfunc: Callable[..., Any],\nargs: Optional[ArgsType] = (),\nkwargs: Optional[KwargsType] = None\n) -> Any:\n\"\"\"Wrap a func in a threading executor \"\"\"\nif kwargs:\nfunc = partial(func, **kwargs)\nloop = asyncio.get_running_loop()\nreturn await loop.run_in_executor(None, func, *args)\ndef task_runner(task_kls: Type[BaseTask]) -> None:\n\"\"\"Task runner\"\"\"\ntask = task_kls()\nasyncio.run(run_in_executor(task.run))\n
\u4ece\u4e0a\u9762\u7684\u4f8b\u5b50\u53ef\u4ee5\u770b\u5230\uff0c\u4f7f\u7528\u4e86\u4e00\u4e9b\u65b0\u7684\u7c7b\u578b\u6807\u6ce8\u65b9\u5f0f\u3002
\u5728 run_in_executor
\u65b9\u6cd5\u4e4b\u524d\uff0c\u5b9a\u4e49\u4e86\u4e24\u4e2a\u7c7b\u578b\uff0c\u5e76\u8d4b\u4e88\u5176\u522b\u540d\uff0c\u65b9\u4fbf\u540e\u9762\u4f7f\u7528\u3002
\u5728 run_in_executor
\u65b9\u6cd5\u4e2d\u4f7f\u7528\u4e86 typing.Callable
\u3001 typing.Optional
\u3001 typing.Any
\u7279\u6b8a\u7c7b\u578b\u3002
\u5728 task_runner
\u4e2d\u4f7f\u7528 typing.Type
\u7c7b\u578b\uff0c\u8868\u660e task_kls
\u53c2\u6570\u662f\u4e00\u4e2a BaseTask
\u7c7b\u81ea\u8eab\uff0c \u800c\u4e0d\u662f\u5b83\u7684\u5bf9\u8c61\uff0c\u51c6\u786e\u8bf4\u662f\u5b83\u7684\u7c7b\u5bf9\u8c61\u3002
\u5982 a = int
\u548c b = type(a)
\u4e2d\uff0c a
\u548c b
\u6240\u6807\u6ce8\u7684\u7c7b\u578b\u662f\u4e00\u6837\u7684\uff0c\u90fd\u662f int
\u7c7b\u578b\u3002
"},{"location":"guidelines/advanced/type_hint/#3","title":"3. \u9ad8\u9636\u4f7f\u7528","text":""},{"location":"guidelines/advanced/type_hint/#31-callable","title":"3.1 \u53ef\u8c03\u5bf9\u8c61(Callable)","text":"\u4e0a\u4e00\u7ae0\u5df2\u7ecf\u63d0\u5230\u4e86\u53ef\u8c03\u5bf9\u8c61( Callable
) \u7684\u4f7f\u7528\uff0c\u8fd9\u91cc\u9700\u8981\u5728\u8be6\u7ec6\u8bf4\u660e\u4e00\u8d77\u5b83\u7684\u7528\u6cd5\u3002
from typing import Callable, Tuple\ndef task_a(name: str) -> str:\nreturn name\ndef task_sum(a: int, b: int) -> int:\nreturn a + b\ndef task_a_executor(func: Callable[[str], str], args: Tuple[str]) -> str:\nreturn func(*args)\ndef task_sum_executor(func: Callable[[int, int], int], args: Tuple[int]) -> int:\nreturn func(*args)\n
\u9488\u5bf9\u53ef\u8c03\u5bf9\u8c61\u4e2d\u9700\u8981\u4f20\u9012\u53c2\u6570\u7684\u7c7b\u578b\uff0c\u53ef\u4ee5\u5728 Callable
\u4e2d\u6807\u6ce8\u3002
\u4ece\u4e0a\u9762\u793a\u4f8b\uff0c\u5305\u62ec Callable
\u7684\u4f7f\u7528\u65b9\u6cd5\u4e2d\u53ef\u4ee5\u770b\u5230\uff0c\u5b83\u90fd\u662f\u5728\u6807\u6ce8\u5217\u8868\u53c2\u6570( args
) \uff0c\u4f46\u5982\u679c\u9700\u8981\u6807\u6ce8\u5b57\u5178\u53c2\u6570 \u5374\u65e0\u6cd5\u6807\u6ce8\u3002 \u4f8b\u5982\u4e00\u4e2a\u65b9\u6cd5 def foo(a: Optional[int] = None, *, b: Optional[int] = None) -> None: ...
\uff0c \u5b83\u5728\u65b9\u6cd5\u5b9a\u4e49\u9636\u6bb5\u5df2\u7ecf\u58f0\u660e\u4e86\u63a5\u6536 b
\u53c2\u6570\u65f6\uff0c \u5fc5\u987b\u4e3a\u5b57\u5178\u7c7b\u578b\uff0c\u4e5f\u5c31\u662f\u8bf4\u5f53\u4f60\u4e0d\u4f20\u9012 a
\u53c2\u6570\uff0c\u4f46\u53c8\u9700\u8981\u4f20\u9012 b
\u53c2\u6570\u7684\u65f6\u5019\uff0c\u5fc5\u987b\u8fd9\u4e48\u8c03\u7528 foo(b=3)
\uff0c\u5426\u5219\u4f20\u9012\u7684\u503c\uff0c\u53ea\u4f1a\u8d4b\u503c\u5230 a
\u4e0a\u9762\u3002 \u800c\u8fd9\u79cd\u7c7b\u578b\u7684\u8c03\u7528\u5bf9\u8c61\u5374\u65e0\u6cd5\u4f7f\u7528\u6b63\u5e38\u64cd\u4f5c\u7684 \u6807\u6ce8\u4e3a Callable[[int, \"b\": int], int]
\u3002
\u5bf9\u4e8e\u8fd9\u79cd\u60c5\u51b5\uff0c\u867d\u7136\u5b98\u65b9\u6587\u6863\u4e2d\u6ca1\u6709\u5bf9\u6b64\u8bf4\u660e\uff0c\u4f46\u53ef\u4ee5\u901a\u8fc7\u7ed3\u6784\u5b50\u7c7b\u578b\u5b9a\u4e49\u8c03\u7528\u5bf9\u8c61\u7684\u7c7b\u578b\u3002
\u4e86\u89e3 \u540d\u4e49\u5b50\u7c7b\u578b vs \u7ed3\u6784\u5b50\u7c7b\u578b
\u6240\u4ee5\u53ef\u4ee5\u8fd9\u4e48\u5b9a\u4e49\uff1a
from typing import Optional, Protocol\ndef foo(\na: Optional[int],\n*,\nb: Optional[int]\n) -> None: ...\nclass FooCallableType(Protocol):\ndef __call__(\nself,\na: Optional[int] = None,\n*,\nb: Optional[int] = None\n) -> None: ...\ndef foo_executor(func: FooCallableType) -> None: ...\n
\u53c2\u8003\uff1a python typing signature (typing.Callable) for function with kwargs
"},{"location":"guidelines/advanced/type_hint/#32","title":"3.2 \u5f02\u6b65\u7f16\u7a0b","text":"import asyncio\nfrom typing import Tuple, Any, Awaitable, Union, Callable, AsyncGenerator\nfrom asyncio import iscoroutinefunction\nasync def func(length: int) -> AsyncGenerator:\nfor i in range(length):\nyield i\nasync def run_in_executor(\nfunc: Union[Callable[..., Any], Awaitable[..., Any]],\nargs: Tuple[...]\n) -> Any:\nif iscoroutinefunction(func):\nreturn await func(*args)\nelse:\nloop = asyncio.get_running_loop()\nreturn await loop.run_in_executor(None, func, *args, )\n
\u9488\u5bf9\u5f02\u6b65\u7f16\u7a0b\u7684\u7684\u6240\u6709\u7c7b\u578b\uff0c\u90fd\u5df2\u7ecf\u5728 typing
\u4e0b\u5b9a\u4e49\u4e86\uff0c\u53ef\u4ee5\u5f88\u65b9\u4fbf\u7684\u53bb\u4f7f\u7528\u3002
"},{"location":"guidelines/project_management/code_lint/","title":"\u4ee3\u7801\u68c0\u6d4b","text":"\u4ee3\u7801\u68c0\u6d4b\u662f\u4f7f\u7528\u4e00\u4e9b\u5de5\u5177\u68c0\u67e5\u4ee3\u7801\u77e5\u5426\u7b26\u5408 Python \u76f8\u5173\u89c4\u8303\u3002
\u5f53\u524d\u4e3b\u6d41\u7684\u4ee3\u7801\u68c0\u6d4b\u89c4\u8303\u5305\u62ec
- black
- flake8
- pylint
- yapf
"},{"location":"guidelines/project_management/code_lint/#_2","title":"\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177","text":""},{"location":"guidelines/project_management/code_lint/#black","title":"black","text":"black \u662f PSF \u7ec4\u7ec7\u4e0b\u7684\u4e00\u4e2a\u4ee3\u7801\u683c\u5f0f\u5316\u5de5\u5177\u3002 \u5176\u7279\u70b9\u662f\u5f3a\u5236\u683c\u5f0f\u5316\u4ee3\u7801\uff0c\u4f7f\u4ee3\u7801\u4fdd\u6301\u4e00\u81f4\u6027\u3002\u4f46\u7f3a\u70b9\u662f\u4f1a\u81ea\u52a8\u8c03\u6574\u4ee3\u7801\u683c\u5f0f\u3002
\u7279\u70b9\uff1a
- \u7b26\u5408 PEP 8 \u6807\u51c6
- \u652f\u6301\u81ea\u5b9a\u4e49\u89c4\u5219
- \u81ea\u52a8\u683c\u5f0f\u5316\u4ee3\u7801
- IDE \u63d2\u4ef6
- psf \u793e\u533a\u7ef4\u62a4
"},{"location":"guidelines/project_management/code_lint/#flake8","title":"flake8","text":"flake8 \u662f pycqa \u7ec4\u7ec7\u4e0b\u7684\u4e00\u4e2a\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177\u3002\u5b83\u9075\u5faa PEP 8 \u89c4\u8303\uff0c \u6307\u793a\u51fa\u4e0d\u7b26\u5408\u89c4\u8303\u7684\u4ee3\u7801\u3002
\u7279\u70b9\uff1a
- \u7b26\u5408 PEP 8 \u89c4\u8303
- \u96c6\u5408\u4f7f\u7528 pycodestyle \uff0c pyflakes \uff0c mccabe \u7b49\u7b2c\u4e09\u65b9\u63d2\u4ef6\u3002
- \u652f\u6301\u81ea\u5b9a\u4e49\u89c4\u5219
- \u63d0\u793a\u4e0d\u7b26\u5408\u89c4\u8303\u7684\u5185\u5bb9
- IDE \u63d2\u4ef6
- git \u6216 Mercurial \u6269\u5c55
- pycoa \u793e\u533a\u7ef4\u62a4
"},{"location":"guidelines/project_management/code_lint/#pylint","title":"pylint","text":"pylint \u662f pycqa \u7ec4\u7ec7\u4e0b\u7ef4\u62a4\u7684\u5de5\u5177\u3002\u5b83\u4e0d\u4ec5\u4ec5\u662f\u4e00\u6b3e\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177\uff0c\u8fd8\u53ef\u4ee5\u53d1\u73b0\u53d8\u6210\u9519\u8bef\uff0c\u4ee3\u7801\u5f02\u5e38\uff0c\u5e76\u63d0\u4f9b\u7b80\u5355\u7684\u91cd\u6784\u5efa\u8bae\u3002
\u7279\u70b9\uff1a
- \u7b26\u5408 PEP 8 \u89c4\u8303
- \u652f\u6301\u81ea\u5b9a\u4e49\u89c4\u5219
- \u9519\u8bef\u68c0\u6d4b
- \u91cd\u6784\u5efa\u8bae
- IDE \u63d2\u4ef6
- pycoa \u793e\u533a\u7ef4\u62a4
"},{"location":"guidelines/project_management/code_lint/#yapf","title":"yapf","text":"yapf \u662f Google \u7ef4\u62a4\u7684\u4e00\u4e2a\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177\u3002\u5b83\u548c\u4e0a\u8ff0\u5de5\u5177\u4e0d\u540c\uff0c \u4f7f\u7528\u57fa\u4e8e clang-format \u7684\u7b97\u6cd5\u5c06\u4ee3\u7801\u91cd\u65b0\u683c\u5f0f\u5316\u4e3a\u590d\u5408\u98ce\u683c\u6307\u5357\u7684\u6700\u4f73\u683c\u5f0f\u3002\u7c7b\u4f3c\u4e8e Golang \u7684 gofmt
\u5de5\u5177\u3002 \u6240\u4ee5\u5b83\u548c black \u5de5\u5177\u6709\u70b9\u7c7b\u4f3c\u3002
\u7279\u70b9\uff1a
- \u7b26\u5408 PEP 8 \u89c4\u8303
- \u652f\u6301\u81ea\u5b9a\u4e49\u89c4\u5219
- \u81ea\u52a8\u683c\u5f0f\u5316\u4ee3\u7801
- IDE \u63d2\u4ef6
- google \u793e\u533a\u7ef4\u62a4
"},{"location":"guidelines/project_management/code_lint/#_3","title":"\u4f7f\u7528\u5b9e\u8df5","text":"\u867d\u7136\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177\u6709\u5f88\u591a\uff0c\u4f46\u662f\u5b83\u4eec\u7684\u521d\u8877\u90fd\u662f\u4e3a\u4e86\u8ba9 Python \u4ee3\u7801\u7b26\u5408\u4e00\u81f4\u7684\u98ce\u683c\u548c\u89c4\u8303\u3002\u53ea\u4e0d\u8fc7\u662f\u6709\u7684\u5de5\u5177\u66f4\u6fc0\u8fdb\u800c\u5df2\u3002\u5177\u6709\u826f\u597d\u7f16\u7801\u4e60\u60ef\u7684\u5f00\u53d1\u4eba\u5458\uff0c\u5199\u51fa\u7684\u4ee3\u7801\uff0c \u65e0\u8bba\u4f7f\u7528\u54ea\u79cd\u5de5\u5177\uff0c\u90fd\u80fd\u8f7b\u677e\u901a\u8fc7\u3002\u6240\u4ee5\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177\u7684\u6700\u7ec8\u76ee\u7684\u662f\u544a\u77e5\u5f00\u53d1\u4eba\u5458\u5c3d\u53ef\u80fd\u9075\u5b88\u4e00\u81f4\u7684\u98ce\u683c\u6765\u7f16\u5199\u4ee3\u7801\u3002
\u8003\u8651\u5230\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177\u7684\u6307\u5bfc\u6027\uff0c\u548c\u529f\u80fd\u6027\uff0c\u63a8\u8350\u4f7f\u7528 pylint \u4f5c\u4e3a\u9996\u9009\u68c0\u6d4b\u5de5\u5177\u3002\u5728\u5b9e\u8df5\u4e2d\u53d1\u73b0\u7531\u4e8e\u67d0\u4e9b\u5e93\u548c pylint \u7684\u517c\u5bb9\u6027\u95ee\u9898\uff0c\u5f53 \u4f7f\u7528 pylint \u6709\u95ee\u9898\u65f6\uff0c\u53ef\u4ee5\u4f7f\u7528 flake8 \u4f5c\u4e3a\u66ff\u4ee3\u7684\u68c0\u6d4b\u5de5\u5177\u3002
"},{"location":"guidelines/project_management/code_lint/#_4","title":"\u4f7f\u7528","text":"\u5728\u5b9e\u9645\u4f7f\u7528\u8fc7\u7a0b\u4e2d\uff0c\u53ef\u4ee5\u5c06\u4ee3\u7801\u68c0\u6d4b\u903b\u8f91\u653e\u5728\u81ea\u52a8\u5316\u5de5\u5177\u4e2d\u8fd0\u884c\u3002\u5c06\u903b\u8f91\u653e\u5728 tox
\u4e2d\uff0c\u53ef\u4ee5\u5728\u672c\u5730\u5f00\u53d1\u65f6\u65b9\u4fbf\u4f7f\u7528\u3002\u5728 CI \u9636\u6bb5\u53ea\u9700\u8981\u8c03\u7528 tox \u5c31\u53ef\u4ee5\u4e86\u3002
"},{"location":"guidelines/project_management/code_lint/#tox","title":"tox","text":"# tox (https://tox.readthedocs.io/) is a tool for running tests\n# in multiple virtualenvs. This configuration file will run the\n# test suite on all supported python versions. To use it, \"pip install tox\"\n# and then run \"tox\" from this directory.\n[tox]\nisolated_build = True\nenvlist =\npy{37,38,39,310}\nisort\nlint\n[testenv]\ndeps =\npipenv\nusedevelop = true\ncommands =\npipenv sync -d\npytest --cov=src\n[testenv:isort]\ndeps =\nisort\ncommands =\nisort . --check-only --diff\n[testenv:lint]\ndeps =\npipenv\nchangedir = {toxinidir}\ncommands =\npipenv sync -d\npylint src tests\n
"},{"location":"guidelines/project_management/code_lint/#github-action","title":"github action","text":"# This workflow will install Python dependencies, run tests and lint with a single version of Python\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions\nname: main\non: [push, pull_request]\njobs:\ntest:\nruns-on: ${{ matrix.os }}\nstrategy:\nfail-fast: false\nmatrix:\nos: [ubuntu-20.04]\npython: [\"3.7\", \"3.8\", \"3.9\", \"3.10\"]\nsteps:\n- uses: actions/checkout@v2\n- name: Set up Python ${{ matrix.python }} on ${{ matrix.os }}\nuses: actions/setup-python@v2\nwith:\npython-version: ${{ matrix.python }}\n- name: Install dependencies\nrun: |\npython -m pip install --upgrade pip\npip install tox\n- name: Test with tox\nrun: |\ntox -e py\nlinting:\nruns-on: ubuntu-latest\nsteps:\n- uses: actions/checkout@v2\n- uses: actions/setup-python@v2\n- run: pip install tox\n- run: |\ntox -e isort\ntox -e lint\n
"},{"location":"guidelines/project_management/code_lint/#gitlab-ci","title":"gitlab-ci","text":"default:\nimage: python:3.9\nbefore_script:\n- pip install -U pip\n.base_test:\nstage: test\nscript:\n- pip install -U tox\n- tox -e py\nstages:\n- test\n- build\n- upload\n# Due to gitlab ci not support matrix build. So use YAML anchors:\n# https://forum.gitlab.com/t/matrix-builds-in-ci/9629\ntest:py37:\nimage: python:3.7\nextends:\n- .base_test\ntest:py38:\nimage: python:3.8\nextends:\n- .base_test\ntest:py39:\nimage: python:3.9\nextends:\n- .base_test\ntest:py310:\nimage: python:3.10\nextends:\n- .base_test\ntest:lint:\nstage: test\nscript:\n- pip install -U tox\n- tox -e isort\n- tox -e lint\n
"},{"location":"guidelines/project_management/distribution/","title":"\u6784\u5efa\u4e0e\u53d1\u5e03","text":"\u4f5c\u4e3a\u9879\u76ee\u7684\u6700\u540e\u4e00\u73af\uff0c\u5206\u53d1\u81f3\u5173\u91cd\u8981\u3002\u6709\u826f\u597d\u7684\u5206\u53d1\u6d41\u7a0b\uff0c\u4fbf\u4e8e\u4f7f\u7528\u3002\u57fa\u4e8e Python \u81ea\u5e26\u7684\u5206\u53d1\u673a\u5236\u663e\u7136\u662f \u66f4\u597d\u7684\u9009\u62e9\u3002
\u672c\u6587\u5c06\u4ee5\u4e00\u4e2a\u6570\u636e\u5bfc\u51fa\u7684\u9879\u76ee\u8bb2\u8ff0\u3002
"},{"location":"guidelines/project_management/distribution/#1","title":"1. \u9879\u76ee\u51c6\u5907","text":"\u56e0\u4e3a\u672c\u6587\u7684\u91cd\u70b9\u662f\u5bf9\u6253\u5305\u5206\u53d1\uff0c\u6240\u4ee5\u9879\u76ee\u7684\u529f\u80fd\u5f00\u53d1\u5c31\u4e0d\u4f5c\u4e3a\u91cd\u70b9\u3002
\u9879\u76ee\u6e90\u4ee3\u7801\u53ef\u4ee5\u5728 pythonic-project-samples \u4e2d\u83b7\u53d6\u3002
\u9879\u76ee\u91c7\u7528 src
\u76ee\u5f55\u7ed3\u6784\uff0c\u9879\u76ee\u63cf\u8ff0\u4fe1\u606f\u90fd\u5728 pyproject.toml
\u4e2d\u5b9a\u4e49\u3002
"},{"location":"guidelines/project_management/distribution/#2","title":"2. \u9879\u76ee\u6253\u5305","text":""},{"location":"guidelines/project_management/distribution/#21","title":"2.1 \u6253\u5305\u5de5\u5177","text":"\u7531\u4e8e\u5386\u53f2\u539f\u56e0\uff0c Python \u7684\u6253\u5305\u8d70\u4e86\u5f88\u957f\u4e00\u6bb5\u8def\u4e86\uff0c\u4f46\u548c\u5176\u4ed6\u8bed\u8a00\u7684\u6253\u5305\u5de5\u5177\u76f8\u6bd4\uff0c\u4e3a\u4e86\u8fd8\u662f\u6709\u5f88\u957f\u4e00\u6bb5\u8def\u8981\u8d70\u3002
\u5728 PEP-517 \u4e2d\uff0c\u63d0\u5230\u4e86\u5f53\u524d Python \u6784\u5efa\u7cfb\u7edf\u7684\u4e0d\u8db3\u548c\u54cd\u5e94\u7684\u89e3\u51b3\u65b9\u6848\u3002 \u5176\u4e3b\u8981\u5c31\u662f\u89e3\u51b3\u8ba9 Python \u652f\u6301\u66f4\u52a0\u7075\u6d3b\u7684\u6784\u5efa\u7cfb\u7edf\u3002 PEP-518 \u5219\u63d0\u51fa\u4e3a\u9879\u76ee\u6307\u5b9a\u4e00\u4e2a\u6700\u5c0f\u7684\u6784\u5efa\u7cfb\u7edf\u3002
"},{"location":"guidelines/project_management/distribution/#211-setuptools","title":"2.1.1 setuptools","text":"Setuptools \u662f\u5f53\u524d\u4f7f\u7528\u6700\u4e3a\u5e7f\u6cdb\u7684\u6784\u5efa\u5de5\u5177\uff0c\u73b0\u5728\u7edd\u5927\u591a\u6570\u5de5\u5177 \u90fd\u5728\u4f7f\u7528 setuptools
\u6784\u5efa\u9879\u76ee\u3002\u5b83\u662f disutils
\u7684\u589e\u5f3a\u7248\u3002
Setuptools \u53ef\u4ee5\u8bf4\u662f\u73b0\u5728\u6700\u6210\u719f\u7684\u6784\u5efa\u5de5\u5177\u4e86\uff0c\u652f\u6301\u5e38\u7528\u7279\u6027\u5982\u4e0b\uff1a
- \u652f\u6301\u6253\u5305\u8d44\u6e90\u6587\u4ef6
- \u652f\u6301\u6253\u5305\u6570\u636e\u6587\u4ef6
- \u652f\u6301 CPython \u7f16\u8bd1\u5668
- \u652f\u6301 zip \u538b\u7f29\u9009\u9879
- \u652f\u6301\u5305\u547d\u540d\u7a7a\u95f4
- \u652f\u6301\u5b89\u88c5\u4f9d\u8d56
- \u652f\u6301\u53ef\u9009\u5b89\u88c5\u4f9d\u8d56
- \u652f\u6301\u6307\u5b9a Python \u7248\u672c
- \u652f\u6301\u6ce8\u518c Setuptools \u5b50\u547d\u4ee4
- \u652f\u6301\u5165\u53e3\u70b9\uff08Entry Points\uff09
\u7531\u4e8e Setuptools \u662f\u6700\u6210\u719f\u7684\u6784\u5efa\u5de5\u5177\uff0c\u6240\u4ee5\u4e0e\u5176\u5b83\u6784\u5efa\u5de5\u5177\u5bf9\u6bd4\u6765\u770b\uff0c\u5b83\u7684\u7f3a\u70b9\u53ef\u80fd\u5c31\u662f\u73b0\u5728\u7684\u914d\u7f6e\u4ecd\u9700\u8981\u5b9a\u4e49\u5728 setup.cfg
\u6216\u8005 setup.py
\u6587\u4ef6\u4e2d\uff0c\u800c\u4e0d\u662f\u5b9a\u4e49\u5728 pyproject.toml
\u6587\u4ef6\u4e2d\u3002
\u7f3a\u70b9\uff1a
- \u4e0d\u652f\u6301\u5728
pyproject.toml
\u4e2d\u5b9a\u4e49\u914d\u7f6e - \u4e0d\u652f\u6301\u53d1\u5e03\uff0c\u9700\u8981\u914d\u5408
twine
\u3002
"},{"location":"guidelines/project_management/distribution/#2111","title":"2.1.1.1 \u793a\u4f8b\u914d\u7f6e","text":"pyproject.toml :
[build-system]\nrequires = [\"setuptools\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n
\u589e\u52a0 setup.py
\u6216\u8005 setup.cfg
\u4e24\u79cd\u6709\u5176\u4e00\u5373\u53ef\u3002\u7136\u540e\u5728\u6587\u4ef6\u4e2d\u5b9a\u4e49\u914d\u7f6e\u3002
\u63a8\u8350\u4f7f\u7528 setup.cfg
\u3002
setup.cfgsetup.py [metadata]\nname = mypackage\nversion = 0.0.1\n[options]\npackages = mypackage\ninstall_requires =\nrequests\nimportlib; python_version == \"3.7\"\n
from setuptools import setup\nsetup(\nname='mypackage',\nversion='0.0.1',\npackages=['mypackage'],\ninstall_requires=[\n'requests',\n'importlib; python_version == \"2.6\"',\n],\n)\n
\u6b64\u65f6\u4f60\u7684\u9879\u76ee\u7ed3\u6784\u5e94\u5728\u662f\u8fd9\u6837\u7684\uff1a
~/mypackage/\n pyproject.toml\n setup.cfg # or setup.py\n mypackage/__init__.py\n
"},{"location":"guidelines/project_management/distribution/#2112","title":"2.1.1.2 \u901a\u7528\u65b9\u5f0f\u6784\u5efa","text":"\u5b89\u88c5 PEP-517 \u89c4\u8303\u7684\u5305\u751f\u6210\u5668 build \u548c setuptools\uff0cpip install build setuptools
\u3002
\u7136\u540e\u5f00\u59cb\u6784\u5efa python -m build wheel
\u3002
"},{"location":"guidelines/project_management/distribution/#2113-setuptools","title":"2.1.1.3 Setuptools \u6784\u5efa","text":"\u6b64\u65b9\u6cd5\u662f\u5728\u4e0d\u5b89\u88c5 build \u7684\u60c5\u51b5\u4e0b\u4f7f\u7528\u7684\u3002
\u5982\u679c\u4f60\u53ea\u662f\u7528\u4e86 setup.cfg
\u914d\u7f6e\u7684\u60c5\u51b5\u4e0b\uff0c\u4f60\u8fd8\u9700\u8981\u589e\u52a0\u4e00\u4e2a setup.py
\u6587\u4ef6\uff0c\u5185\u5bb9\u5982\u4e0b\uff1a
setup.py \uff1a
from setuptools import setup\nsetup()\n
\u5982\u679c\u4f60\u4ec5\u4f7f\u7528\u4e86 setup.py
\u914d\u7f6e Setuptools \u7684\u8bdd\uff0c\u53ef\u4ee5\u4e0d\u9700\u8981 setup.cfg
\u6587\u4ef6\u3002
\u5b89\u88c5\u4f9d\u8d56 pip install setuptools
\uff0c\u7136\u540e\u8fdb\u884c\u6784\u5efa python setup.py bdist_wheel
\u3002
"},{"location":"guidelines/project_management/distribution/#2114-twine","title":"2.1.1.4 twine \u53d1\u5e03","text":"\u7531\u4e8e setuptools \u4e0d\u652f\u6301\u53d1\u5e03\u529f\u80fd\uff0c\u6240\u4ee5\u9700\u8981\u501f\u52a9\u5176\u4ed6\u5de5\u5177\u5c06\u5305\u53d1\u5e03\u4e2d\u592e\u4ed3\u5e93\u3002
Twine \u662f Pypa \u56e2\u961f\u7ef4\u62a4\u7684\u4e00\u4e2a\u5c06 Python \u5305\u53d1\u5e03\u5230 Pypi \u7684\u5de5\u5177\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a pip install twine
\u3002
\u4f7f\u7528 Setuptools \u6784\u5efa\u9879\u76ee\uff0c\u6784\u5efa\u7ed3\u679c\u9ed8\u8ba4\u662f\u653e\u5728\u9879\u76ee\u6839\u76ee\u5f55\u7684 ./dist
\u4e0b\u9762 \u3002
\u53d1\u5e03\u9879\u76ee\u5230 Pypi \uff1a
twine upload dist/*\n
"},{"location":"guidelines/project_management/distribution/#212-flit","title":"2.1.2 flit","text":"Flit \u662f\u4e00\u4e2a\u8f7b\u91cf\u7b80\u5355\u7684 Python \u6784\u5efa\u5de5\u5177\uff0c\u5b83\u7684\u51fa\u73b0\u4e5f\u53ef\u4ee5\u8bf4\u662f\u5212\u65f6\u4ee3\u7684\uff0c\u56e0\u4e3a\u5b83\u7684\u51fa\u73b0\u4fc3\u8fdb\u4e86\u65b0\u6807\u51c6\u7684\u53d1\u73b0\uff0c \u5982 PEP-517 \u548c PEP-518 \u3002
Flit
\u5177\u6709\u5982\u4e0b\u7279\u70b9\uff1a
- \u7b80\u5355\u8f7b\u91cf
- \u652f\u6301\u53d1\u5e03
- \u652f\u6301\u6253\u5305\u6570\u636e\u6587\u4ef6
- \u652f\u6301\u5b50\u5305
- \u652f\u6301\u590d\u5236\u6784\u5efa
- \u652f\u6301\u5b89\u88c5\u4f9d\u8d56
- \u652f\u6301\u53ef\u9009\u5b89\u88c5\u4f9d\u8d56
- \u652f\u6301\u6307\u5b9a Python \u7248\u672c
- \u652f\u6301\u6ce8\u518c Flit \u5b50\u547d\u4ee4
- \u652f\u6301\u5165\u53e3\u70b9\uff08Entry Points\uff09
- \u652f\u6301
pyproject.toml
\u6587\u4ef6\u5b9a\u4e49\u914d\u7f6e
\u7f3a\u70b9\uff1a
- \u4e0d\u652f\u6301 CPython \u7f16\u8bd1
- \u4e0d\u652f\u6301 zip \u538b\u7f29\u9009\u9879
"},{"location":"guidelines/project_management/distribution/#2121","title":"2.1.2.1 \u793a\u4f8b\u914d\u7f6e","text":"pyproject.toml \uff1a
[build-system]\nrequires = [\"flit_core >=2,<4\"]\nbuild-backend = \"flit_core.buildapi\"\n[tool.flit.metadata]\nmodule = \"foobar\"\nauthor = \"Sir Robin\"\nauthor-email = \"robin@camelot.uk\"\nhome-page = \"https://github.com/sirrobin/foobar\"\n
"},{"location":"guidelines/project_management/distribution/#2122","title":"2.1.2.2 \u901a\u7528\u65b9\u5f0f\u6784\u5efa","text":"\u5b89\u88c5 PEP-517 \u89c4\u8303\u7684\u5305\u751f\u6210\u5668 build \u548c flit \uff0cpip install build flit
\u3002
\u7136\u540e\u5f00\u59cb\u6784\u5efa python -m build wheel
\u3002
"},{"location":"guidelines/project_management/distribution/#2123-flit","title":"2.1.2.3 flit \u6784\u5efa","text":"\u6b64\u65b9\u6cd5\u662f\u5728\u4e0d\u5b89\u88c5 build \u7684\u60c5\u51b5\u4e0b\u4f7f\u7528\u7684\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a pip install flit
\uff0c\u7136\u540e\u8fdb\u884c\u6784\u5efa flit build --format wheel
\u3002
"},{"location":"guidelines/project_management/distribution/#2124-flit","title":"2.1.2.4 flit \u53d1\u5e03","text":"flit
\u7684\u53d1\u5e03\u547d\u4ee4\u4f1a\u81ea\u884c\u5148\u6784\u5efa wheel
\u548c sdist
\u5305\uff0c\u7136\u540e\u4e0a\u4f20\u5230 Pypi \u6216\u8005\u5176\u4ed6\u4ed3\u5e93\u3002
flit build --format wheel\n
"},{"location":"guidelines/project_management/distribution/#213-poetry","title":"2.1.3 poetry","text":"python-poetry \u662f\u4e00\u4e2a\u66f4\u9ad8\u7ea7\u7684\u5de5\u5177\u3002\u5b83\u662f\u4e00\u4e2a\u6784\u5efa\u5de5\u5177\u7684\u540c\u65f6\u4e5f\u662f\u4e00\u4e2a\u4f9d\u8d56\u7ba1\u7406\u5de5\u5177\u3002 \u5728\u6784\u5efa\u4e0a\uff0c\u540c\u6837\u9075\u5faa\u4e86 PEP-517 \u89c4\u8303\uff0c\u5728\u4f9d\u8d56\u7ba1\u7406\u4e0a\uff0c\u6709\u70b9\u7c7b\u4f3c\u4e8e Pipenv \u3002
python-poetry \u5177\u6709\u5982\u4e0b\u7279\u70b9\uff1a
- \u652f\u6301\u73af\u5883\u7ba1\u7406
- \u652f\u6301\u53d1\u5e03
- \u652f\u6301 shell \u63d2\u4ef6\uff0c\u5982
bash
\u3001 Fish
\u3001 Zsh
- \u652f\u6301\u6253\u5305\u6570\u636e\u6587\u4ef6
- \u652f\u6301\u6ce8\u518c poetry \u5b50\u547d\u4ee4
- \u652f\u6301 Setuptools \u7684\u5165\u53e3\u70b9\uff08Entry Points\uff09
- \u652f\u6301\u5305\u547d\u540d\u7a7a\u95f4
- \u652f\u6301\u5b89\u88c5\u4f9d\u8d56
- \u652f\u6301\u53ef\u9009\u5b89\u88c5\u4f9d\u8d56
- \u652f\u6301\u5728
pyproject.toml
\u4e2d\u5b9a\u4e49\u914d\u7f6e
\u7f3a\u70b9\uff1a
- \u4e0d\u652f\u6301 CPython \u7f16\u8bd1
- \u4e0d\u652f\u6301 zip \u538b\u7f29\u9009\u9879
"},{"location":"guidelines/project_management/distribution/#2131","title":"2.1.3.1 \u793a\u4f8b\u914d\u7f6e","text":"[build-system]\nrequires = [\"poetry_core>=1.0.0\"]\nbuild-backend = \"poetry.core.masonry.api\"\n[tool.poetry]\nname = \"poetry-demo\"\nversion = \"0.1.0\"\ndescription = \"\"\nauthors = [\"S\u00e9bastien Eustace <sebastien@eustace.io>\"]\n[tool.poetry.dependencies]\npython = \"^3.10\"\n[tool.poetry.dev-dependencies]\npytest = \"^3.4\"\n
"},{"location":"guidelines/project_management/distribution/#22-poetry","title":"2.2 \u6253\u5305\u6784\u5efa\uff08poetry\uff09","text":"\u73b0\u9636\u6bb5\u9009\u7528 poetry
\u4f5c\u4e3a\u6784\u5efa\u5de5\u5177\u3002
\u4e3a\u9879\u76ee\u6307\u5b9a\u6240\u9700\u8981\u4f7f\u7528\u7684\u6784\u5efa\u5de5\u5177\u3002\u521b\u5efa pyproject.toml
\u6587\u4ef6\uff0c\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
[tool.poetry]\nname = \"file2mongo\"\nversion = \"0.1.0\"\ndescription = \"File data to MongoDB\"\nreadme = \"README.md\"\nauthors = [\"demo <demo@example.com>\"]\nlicense = \"MIT\"\nclassifiers = [\n\"Operating System :: OS Independent\",\n\"Programming Language :: Python :: 3.10\",\n]\n[tool.poetry.dependencies]\npython = \"^3.10\"\ndynaconf = \"^3.1.9\"\nclick = \"^8.1.3\"\npymongo = \"^4.3.3\"\n[tool.poetry.dev-dependencies]\npylint = \"^2.14.5\"\nisort = \"^5.10.1\"\npytest = \"^7.1.2\"\nmkdocs = \"^1.3.1\"\nmkdocs-material = \"^8.4.1\"\n[tool.poetry.plugins.\"scripts\"]\nfile2mongo = \"file2mongo.cmdline:main\"\n[build-system]\nrequires = [\"poetry-core>=1.0.0\"]\nbuild-backend = \"poetry.core.masonry.api\"\n
"},{"location":"guidelines/project_management/distribution/#221","title":"2.2.1 \u9879\u76ee\u57fa\u672c\u4fe1\u606f","text":"tool.poetry \u662f\u9879\u76ee\u57fa\u672c\u63cf\u8ff0\u4fe1\u606f\uff0c\u6709\u9879\u76ee\u540d\u79f0\uff0c\u7248\u672c\u53f7\uff0c\u4f5c\u8005\u76f8\u5173\u4fe1\u606f\u7b49\u3002
\u4e3a\u4e86\u8ba9\u522b\u4eba\u66f4\u7cbe\u51c6\u7684\u83b7\u53d6\u4f60\u7684\u5305\u7684\u4fe1\u606f\uff0c\u5e94\u8be5\u5c3d\u91cf\u5305\u542b\u5982\u4e0b\uff1a
name
\uff1a \u9879\u76ee\u540d\u79f0\u3002\u5fc5\u9700\u5b57\u6bb5 version
\uff1a \u7248\u672c\u53f7\u3002\u5fc5\u9700\u5b57\u6bb5 description
: \u9879\u76ee\u7b80\u77ed\u63cf\u8ff0\u3002\u5fc5\u9700\u5b57\u6bb5 license
\uff1a \u8bb8\u53ef\u8bc1\u3002\u53ef\u9009\u5b57\u6bb5\uff0c\u5efa\u8bae\u6dfb\u52a0 author
\uff1a \u9879\u76ee\u4f5c\u8005\u3002\u5fc5\u9700\u5b57\u6bb5 maintainers
: \u7ef4\u62a4\u8005\u3002\u8fd9\u662f\u4e00\u4e2a\u7ef4\u62a4\u8005\u5217\u8868\uff0c\u5e94\u8be5\u4e0e\u4f5c\u8005\u533a\u5206\u5f00\u6765\u3002 \u53ef\u80fd\u5305\u542b\u4e00\u4e2a\u7535\u5b50\u90ae\u4ef6\uff0c\u683c\u5f0f\u4e3a name <email>
\u3002\u53ef\u9009\u5b57\u6bb5 readme
: \u9879\u76ee\u7684 README \u6587\u4ef6\u6216\u76f8\u5bf9\u5e94\u7684\u8def\u5f84\u6216\u8def\u5f84\u5217\u8868\u3002\u53ef\u9009\u5b57\u6bb5 homepage
: \u9879\u76ee\u7f51\u7ad9\u7684 URL\u3002\u53ef\u9009\u5b57\u6bb5 keywords
\uff1a \u9879\u76ee\u5173\u952e\u5b57\uff0c\u6709\u52a9\u4e8e\u6a21\u7cca\u641c\u7d22\u5339\u914d\u3002\u53ef\u9009\u5b57\u6bb5
"},{"location":"guidelines/project_management/distribution/#222-options","title":"2.2.2 options","text":"\u6b64\u8282\u70b9\u5185\u5bb9\u867d\u7136\u4e3a\u53ef\u9009\uff0c\u4f46\u4e3a\u4e86\u9879\u76ee\u7684\u5b8c\u6574\u6027\uff0c\u6709\u4e9b\u5185\u5bb9\u8fd8\u662f\u9700\u8981\u7684\u3002
tool.poetry.dependencies
: \u9879\u76ee\u4f7f\u7528\u8fc7\u7a0b\u4e2d\u4f9d\u8d56\u7684\u5305 tool.poetry.scripts
: \u5b89\u88c5\u5305\u65f6\u5c06\u5b89\u88c5\u7684\u811a\u672c\u6216\u53ef\u6267\u884c\u6587\u4ef6 tool.poetry.extras
:\u53ef\u9009\u4f9d\u8d56\u9879\uff0c\u589e\u5f3a\u5305\uff0c\u4f46\u4e0d\u662f\u5fc5\u9700\u7684\uff0c\u53ef\u9009\u4f9d\u8d56\u9879\u7684\u96c6\u7fa4\u3002 tool.poetry.plugins
: \u63d2\u4ef6\uff0c\u53ef\u901a\u8fc7 importlib.metadata
\u5bfc\u5165 tool.poetry.urls
: \u81ea\u5b9a\u4e49 url\uff0c\u53d1\u5e03pypi\u540e\u5c55\u793a build-system
: \u6784\u5efa\u7cfb\u7edf\u5f15\u7528\u90e8\u5206
"},{"location":"guidelines/project_management/distribution/#2221-entrypoints","title":"2.2.2.1 \u5165\u53e3\u70b9 EntryPoints","text":"Entry-points \u53ef\u4ee5\u5f88\u65b9\u4fbf\u7684\u6ce8\u518c\u547d\u4ee4\u884c\u811a\u672c\uff0c\u6216\u8005\u63d0\u4f9b\u4e00\u79cd\u63d2\u4ef6\u52a0\u8f7d\u673a\u5236\u3002
\u6ce8\u518c\u547d\u4ee4\u884c \uff1a
\u4f8b\u5982\uff0c\u8981\u521b\u5efa\u540d\u4e3a foo
\u7684\u63a7\u5236\u53f0\u811a\u672c\uff0cpyproject.toml
\u6587\u4ef6\u6dfb\u52a0\u5982\u4e0b\u793a\u4f8b\uff1a
[tool.poetry.scripts]\nfoo = \"my_package.some_module:main_func\"\n
"},{"location":"guidelines/project_management/distribution/#223","title":"2.2.3 \u6784\u5efa","text":"\u5f53\u914d\u7f6e\u5b8c\u6210\u540e\uff0c\u5c31\u53ef\u4ee5\u5f00\u59cb\u6784\u5efa\u4e86\u3002
\u8fd0\u884c\u547d\u4ee4\uff1a
poetry build\n
\u8fd0\u884c\u5b8c\u6210\u540e\uff0c\u4f1a\u5728\u9879\u76ee\u6839\u76ee\u5f55\u7684 ./dist
\u4e2d\u751f\u6210\u4e24\u4e2a\u5206\u53d1\u6587\u4ef6\u3002\u4e00\u4e2a\u662f .tar.gz
\u7ed3\u5c3e\u7684\u6e90\u7801\u538b\u7f29\u5305\uff0c\u4e00\u4e2a\u662f .whl
\u7ed3\u5c3e\u7684\u4e8c\u8fdb\u5236\u5305\u3002
"},{"location":"guidelines/project_management/distribution/#3","title":"3 \u5206\u53d1","text":"\u6253\u5305\u540e\u7684\u6587\u4ef6\u53ef\u4ee5\u901a\u8fc7\u5206\u53d1\u624b\u6bb5\u7ed9\u5176\u4ed6\u4eba\u4f7f\u7528\u3002
"},{"location":"guidelines/project_management/distribution/#31","title":"3.1 \u624b\u52a8\u5206\u53d1","text":"\u624b\u52a8\u5206\u53d1\uff0c\u5373\u81ea\u5df1\u7ba1\u7406\u8fd9\u4e9b\u8f6f\u4ef6\u5305\uff0c\u5982\u901a\u8fc7\u590d\u5236\u3001 ftp
\u6216\u8005\u7f51\u7edc\u53d1\u9001\u7b49\u65b9\u5f0f\u3002 \u4f7f\u7528\u65f6\uff0c\u4e0b\u8f7d\u6240\u9700\u8981\u7684\u7248\u672c\u5206\u53d1\u5305\uff0c\u7136\u540e\u4f7f\u7528 Pip \u5b89\u88c5 pip install foo.whl
\u5373\u53ef\u3002
"},{"location":"guidelines/project_management/distribution/#32","title":"3.2 \u4f7f\u7528\u4ed3\u5e93\u5206\u53d1","text":"Python \u6240\u7528\u516c\u5f00\u5305\u90fd\u5b58\u653e\u5728 Pypi \uff0c\u5f53\u6211\u4eec\u4f7f\u7528 pip install requests
\u7684\u65f6\u5019\uff0c\u9ed8\u8ba4\u4f1a\u4ece Pypi \u4e2d\u67e5\u627e\u6700\u65b0\u7248\u672c\u7684\u5206\u53d1\u5305\uff0c\u627e\u5230\u4e86\u5c31\u5148\u4e0b\u8f7d\u5230\u672c\u5730\uff0c\u7136\u540e\u5b89\u88c5\u5230\u73af\u5883\u4e2d\u3002\u9664\u4e86\u5b98\u65b9\u4ed3\u5e93\uff0c\u8fd8\u652f\u6301\u79c1\u6709\u4ed3\u5e93\u3002
\u8981\u53d1\u5e03\u5230 Pypi \uff0c\u9996\u5148\u9700\u8981\u6ce8\u518c\u8d26\u53f7\uff0c\u5982\u679c\u662f\u8981\u6d4b\u8bd5\uff0c\u5219\u53ef\u4ee5\u4f7f\u7528\u6d4b\u8bd5\u4ed3\u5e93Test-Pypi \u3002\u5bf9\u4e8e\u79c1\u6709\u4ed3\u5e93\uff0c\u53ef\u4ee5\u53c2\u8003\u5177\u4f53\u6587\u6863poetry-publish\uff0c\u4f46\u4f7f\u7528\u65b9\u6cd5\u57fa\u672c\u4e00\u81f4\uff0c \u53ea\u9700\u8981\u66ff\u6362\u4e00\u4e0b\u4ed3\u5e93\u5730\u5740\u3002
\u4e0a\u4f20\u5230 Test-Pypi \uff1a
poetry publish -r https://test.pypi.org/ -u username -p password\n
\u586b\u5199\u7528\u6237\u540d\u548c\u5bc6\u7801\u5373\u53ef\u4e0a\u4f20\u3002
\u4e0a\u4f20\u5230 Pypi \uff1a
poetry publish -u username -p password\n
"},{"location":"guidelines/project_management/document/","title":"\u6587\u6863\u7ba1\u7406","text":"\u9879\u76ee\u6587\u6863\u7528\u6765\u8bf4\u660e\u548c\u8bb0\u5f55\u9879\u76ee\u7684\u4fe1\u606f\uff0c\u6709\u52a9\u4e8e\u5f00\u53d1\u4eba\u5458\u3001\u7ba1\u7406\u4eba\u5458\u3001\u4f7f\u7528\u8005\u7684\u4ea4\u6d41\u548c\u6c9f\u901a\u3002\u5728 Python \u9879\u76ee\u4e2d \u4e00\u822c\u901a\u8fc7 Mkdocs \u548c sphinx \u6765 \u6784\u5efa\u9879\u76ee\u6587\u6863\u3002\u4e24\u8005\u90fd\u652f\u6301 markdown \u6807\u8bb0\u7684\u6587\u4ef6\uff0c\u4f46\u540e\u8005\u4e5f\u652f\u6301 reStructuredText \u6807\u8bb0\u6587\u4ef6\u3002
"},{"location":"guidelines/project_management/document/#mkdocs","title":"mkdocs","text":"Mkdocs \u662f\u4e00\u4e2a\u5feb\u901f\u3001\u7b80\u5355\u7684\u9759\u6001\u7ad9\u70b9\u751f\u6210\u5de5\u5177\u3002\u53ef\u4ee5\u901a\u8fc7\u6307\u5b9a\u76ee\u5f55\u4e2d\u7684 markdown \u6807\u8bb0\u6587\u4ef6\uff0c\u6765\u751f\u6210\u9759\u6001\u7f51\u9875\u3002 \u4f7f\u7528 YAML \u683c\u5f0f\u914d\u7f6e\u6587\u4ef6\u3002
\u7279\u70b9\uff1a
- YAML \u5355\u6587\u4ef6\u914d\u7f6e
- \u751f\u6210\u9759\u6001\u7ad9\u70b9
- \u652f\u6301 markdown
- \u652f\u6301\u81ea\u5b9a\u4e49\u4e3b\u9898
- \u652f\u6301 markdown \u6269\u5c55\u6807\u8bb0
- \u652f\u6301\u63d2\u4ef6
"},{"location":"guidelines/project_management/document/#sphinx","title":"sphinx","text":"sphinx \u662f\u4f7f\u7528 reStructuredText \u6807\u8bb0\u7f16\u5199\u6587\u6863\uff0c\u5e76 \u751f\u6210\u9759\u6001\u7ad9\u70b9\u7684\u5de5\u5177\u3002
\u7279\u70b9\uff1a
- \u5355\u4e2a Python \u6587\u4ef6\u914d\u7f6e
- \u751f\u6210 HTML \u3001 ePub \u7b49\u591a\u79cd\u683c\u5f0f
- \u652f\u6301 markdown \u548c reStructuredText
- \u652f\u6301\u81ea\u5b9a\u4e49\u4e3b\u9898
- \u652f\u6301\u6269\u5c55
"},{"location":"guidelines/project_management/document/#_2","title":"\u5b9e\u8df5","text":"\u5728\u5f00\u53d1\u5b9e\u8df5\u4e2d\uff0c\u63a8\u8350\u4f7f\u7528 Mkdocs \uff0c\u56e0\u4e3a\u5b83\u7b80\u5355\u4e0a\u624b\uff0c\u5e76\u4e14\u6709\u8bb8\u591a\u4f18\u79c0\u7684\u7b2c\u4e09\u65b9\u4e3b\u9898\u3002
"},{"location":"guidelines/project_management/document/#_3","title":"\u5b9e\u8df5\u6848\u4f8b","text":"\u9996\u5148\u521b\u5efa\u4e00\u4e2a example-doc
\u7684\u76ee\u5f55\uff0c\u7136\u540e\u521d\u59cb\u5316\u9879\u76ee\u865a\u62df\u73af\u5883\uff0c\u5b89\u88c5\u73af\u5883\u4f9d\u8d56\uff1a
\u276f mkdir example-doc\n\u276f cd example-doc\n\u276f poetry init\nPackage name [example-doc]: \nVersion [0.1.0]: \nDescription []: \nAuthor [doc <doc@example.com>, n to skip]: \nLicense []: \nCompatible Python versions [^3.10]: \n\nWould you like to define your main dependencies interactively? (yes/no) [yes]\nYou can specify a package in the following forms:\n - A single name (requests): this will search for matches on PyPI\n - A name and a constraint (requests@^2.23.0)\n - A git url (git+https://github.com/python-poetry/poetry.git)\n - A git url with a revision (git+https://github.com/python-poetry/poetry.git#develop)\n - A file path (../my-package/my-package.whl)\n - A directory (../my-package/)\n - A url (https://example.com/packages/my-package-0.1.0.tar.gz)\n\nPackage to add or search for (leave blank to skip):\n\nWould you like to define your development dependencies interactively? (yes/no) [yes]\nPackage to add or search for (leave blank to skip):\n\nGenerated file\n\n[tool.poetry]\nname = \"example-doc\"\nversion = \"0.1.0\"\ndescription = \"\"\nauthors = [\"doc <doc@example.com>\"]\nreadme = \"README.md\"\npackages = [{include = \"example_doc\"}]\n\n[tool.poetry.dependencies]\npython = \"^3.10\"\n\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n\nDo you confirm generation? (yes/no) [yes]\n\n\u276f poetry shell\nCreating virtualenv example-doc-DN2_2NFH-py3.10 in C:\\Users\\qiang.xie\\AppData\\Local\\pypoetry\\Cache\\virtualenvs\nSpawning shell within C:\\Users\\qiang.xie\\AppData\\Local\\pypoetry\\Cache\\virtualenvs\\example-doc-DN2_2NFH-py3.10\n\u276f poetry add mkdocs\nUsing version ^1.4.2 for mkdocs\n\nUpdating dependencies\nResolving dependencies...\n\nWriting lock file\n\nPackage operations: 14 installs, 0 updates, 0 removals\n\n \u2022 Installing six (1.16.0)\n \u2022 Installing colorama (0.4.6)\n \u2022 Installing markupsafe (2.1.1)\n \u2022 Installing python-dateutil (2.8.2)\n \u2022 Installing pyyaml (6.0)\n \u2022 Installing click (8.1.3)\n \u2022 Installing ghp-import (2.1.0)\n \u2022 Installing jinja2 (3.1.2)\n \u2022 Installing packaging (22.0)\n \u2022 Installing pyyaml-env-tag (0.1)\n \u2022 Installing watchdog (2.2.0)\n \u2022 Installing mergedeep (1.3.4)\n \u2022 Installing markdown (3.3.7)\n \u2022 Installing mkdocs (1.4.2)\n
\u521d\u59cb\u5316\u6587\u6863\u914d\u7f6e\uff1a
\u276f mkdocs new .\nINFO - Writing config file: ./mkdocs.yml\nINFO - Writing initial docs: ./docs/index.md\n\u276f ls\ndocs mkdocs.yml pyproject.toml poetry.lock\n
\u7136\u540e\u542f\u52a8 mkdocs \u7684\u672c\u5730\u670d\u52a1\u5668\uff1a
\u276f mkdocs serve\nINFO - Building documentation...\nINFO - Cleaning site directory\nINFO - Documentation built in 0.05 seconds\nINFO - [11:00:22] Serving on http://127.0.0.1:8000/\n
\u7136\u540e\u6d4f\u89c8\u5668\u6253\u5f00 [http://127.0.0.1:8000] \u8bbf\u95ee\u751f\u6210\u7684\u6587\u6863\u7ad9\u70b9\u3002
\u7ad9\u70b9\u4f7f\u7528\u9ed8\u8ba4\u4e3b\u9898\uff0c\u98ce\u683c\u6709\u70b9\u590d\u53e4\u3002\u53ef\u4ee5\u4f7f\u7528 mkdocs-material \u8ba9\u7ad9\u70b9\u66f4\u597d\u770b\uff1a
\u5b89\u88c5 mkdocs-material
\uff1a
poetry add mkdocs-material\n
\u4fee\u6539\u914d\u7f6e\u6587\u4ef6 mkdocs.yml
\uff0c\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
theme:\nname: material\n
\u91cd\u65b0\u542f\u52a8 mkdocs serve
\uff0c\u5373\u53ef\u770b\u5230\u6ce8\u610f\u5df2\u7ecf\u6539\u53d8\u3002
\u5bf9\u4e8e Mkdocs \u7684\u66f4\u591a\u4f7f\u7528\u7ec6\u8282\u53ef\u4ee5\u53c2\u8003\u6587\u6863\uff1a
- Mkdocs \u5feb\u901f\u5f00\u59cb
- Mkdocs \u914d\u7f6e
- mkdocs-material \u4e3b\u9898
"},{"location":"guidelines/project_management/project_structure/","title":"\u9879\u76ee\u7ed3\u6784","text":"\u4ece\u54ea\u4e9b\u5730\u65b9\u63cf\u8ff0\uff1a
- \u5206\u522b\u63cf\u8ff0\u4e24\u79cd\u76ee\u5f55\u7ed3\u6784
- \u4e24\u79cd\u76ee\u5f55\u7ed3\u6784\u7684\u6bd4\u8f83\u4e0e\u533a\u522b
- \u5f53\u524d\u91c7\u7528\u7684\u7ed3\u6784
\u7531\u4e8e Python \u7b80\u5355\u6613\u7528\uff0c\u5f88\u591a\u5f00\u59cb\u4f7f\u7528 Python \u7684\u4eba\u90fd\u662f\u4ece\u4e00\u4e2a\u811a\u672c\u6587\u4ef6\u5f00\u59cb\uff0c\u9010\u6b65\u5f62\u6210\u591a\u4e2a Python \u6587\u4ef6\u7ec4\u6210\u7684\u7a0b\u5e8f\u3002\u4e5f\u6b63\u56e0\u4e3a\u5982\u6b64\u5927\u90e8\u5206\u4eba\u5e76\u6ca1\u4ee5\u4e00\u4e2a\u9879\u76ee\u6216\u5de5\u7a0b\u7684\u6982\u5ff5\u53bb\u770b\u5f85\u81ea\u5df1\u7684\u7a0b\u5e8f\u3002\u800c\u73b0\u5728\u793e\u533a\u4e2d\u7684\u6d41\u884c\u9879\u76ee\u4e5f\u5b58\u5728\u4e24\u79cd\u4e0d\u540c\u7684\u76ee\u5f55\u7ed3\u6784\u3002
"},{"location":"guidelines/project_management/project_structure/#1","title":"1 \u7b80\u5355\u7ed3\u6784","text":"Python \u9879\u76ee\u6253\u5305 \u6587\u7ae0\u4e2d\u4ee5\u4e00\u4e2a\u7b80\u5355\u9879\u76ee\u7ed3\u6784\u6f14\u793a\u4e86\u5982\u4f55\u6253\u5305\u4e00\u4e2a Python \u9879\u76ee
packaging_tutorial\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 example_pkg\n\u2502 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 pyproject.toml\n\u2514\u2500\u2500 tests\n
\u9879\u76ee\u7ed3\u6784\u4ee5\u6839\u76ee\u5f55\u5f00\u59cb\uff0c\u4f5c\u4e3a\u9879\u76ee\u7684\u73af\u5883\u3002\u56e0\u4e3a\uff0c\u4e3a\u4e86\u5728\u5f00\u53d1\u4e2d\u6b63\u5e38\u5bfc\u5165 example_pkg
\u4e2d\u6240\u6709\u7684\u4e1c\u897f\uff0c\u5c31\u9700\u8981\u5c06\u9879\u76ee\u6839\u76ee\u5f55\u6dfb\u52a0\u5230 sys.path
\u4e2d\u3002\u8fd9\u4e5f\u5c31\u8ba9\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\u7684\u6240\u6709\u5305\u90fd\u53d8\u6210\u4e86\u53ef\u5bfc\u5165\u3002\u5f53\u6709\u591a\u4e2a\u540c\u7ea7\u5305\u65f6\uff0c\u5b83\u4eec\u90fd\u662f\u6241\u5e73\u7684\u6563\u843d\u5728\u9879\u76ee\u6839\u76ee\u5f55\u3002\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\u53ef\u80fd\u8fd8\u5b58\u5728\u5176\u4ed6\u975e\u5305\u76ee\u5f55\uff0c\u5982 data
\u3001 docs
\u7b49\u3002\u5982\u679c\u9700\u8981\u672c\u5730\u5f15\u7528\u7b2c\u4e09\u65b9\u5e93\uff0c\u4e5f\u9700\u8981\u653e\u5230\u6839\u76ee\u5f55\uff0c\u4f46\u7b2c\u4e09\u65b9\u5305\u5e76\u4e0d\u662f\u9879\u76ee\u7684\u5b50\u5305\uff0c\u800c\u662f\u5b83\u7684\u4e00\u4e2a\u5f15\u7528\u3002\u8fd9\u6837\u505a\u4f1a\u9020\u6210\u804c\u8d23\u6df7\u4e71\u3002
\u6bd4\u5982\u8fd9\u6837\u7684\u4e00\u4e2a\u9879\u76ee\uff1a
tutorial\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 data\n| \u2514\u2500\u2500 user.json\n\u251c\u2500\u2500 docs\n\u2502 \u2514\u2500\u2500 history.md\n\u251c\u2500\u2500 user\n\u2502 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 views\n\u2502 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 requests # \u8fd9\u662f\u9700\u8981\u672c\u5730\u6253\u5305\u7684\u7b2c\u4e09\u65b9\u5305\n\u2502 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 pyproject.toml\n\u2514\u2500\u2500 tests\n
\u5f53\u591a\u4e2a\u76ee\u5f55\u6241\u5e73\u7684\u5206\u5e03\u5728\u9879\u76ee\u6839\u76ee\u5f55\u65f6\uff0c\u5b83\u4eec\u626e\u6f14\u8005\u4e0d\u540c\u7684\u529f\u80fd\uff0c\u5728\u5f00\u53d1\u4e0a\uff0c\u4f1a\u5e26\u4e86\u4e00\u5b9a\u7684\u6df7\u4e71\u3002\u800c\u4e14\u5728\u6253\u5305\u548c\u6d4b\u8bd5\u4e0a\u4e5f\u4f1a\u5e26\u6765\u4e00\u4e9b\u4e0d\u4fbf\u3002
\u5728\u6253\u5305\u4e0a\uff0c\u9700\u8981\u63d0\u4f9b\u66f4\u591a\u7684\u914d\u7f6e\u6392\u9664\u4e0d\u5fc5\u8981\u7684\u76ee\u5f55\uff0c\u5982 docs
\u6216\u8005\u5176\u4ed6\u4e0d\u9700\u8981\u6253\u5305\u4ec5\u9879\u76ee\u4e2d\u7684\u4e1c\u897f\u3002
\u5f53\u4f7f\u7528\u53ef\u7f16\u8f91\u5b89\u88c5\uff08 pip install -e .
\uff09 \u65f6\uff0c\u4f1a\u5c06\u9879\u76ee\u6839\u76ee\u5f55\u4e2d\u7684\u6240\u6709\u4e1c\u897f\u5b89\u88c5\u5230\u73af\u5883\u4e2d\uff0c\u5305\u62ec\u4e00\u4e9b\u4e0d\u9700\u8981\u7684\u3002
\u4f7f\u7528\u81ea\u52a8\u5316\u6d4b\u8bd5 tox
\u5de5\u5177\u65e0\u6cd5\u68c0\u6d4b\u5b89\u88c5\u4e4b\u540e\u7684\u95ee\u9898\uff0c\u56e0\u4e3a\u8fd9\u79cd\u76ee\u5f55\u73af\u5883\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\u73af\u5883\u4e2d\u7684\u5305\uff08\u9879\u76ee\u6839\u76ee\u5f55\u88ab\u6dfb\u52a0\u5230 sys.path
\u4e2d\u4e86\uff09\u3002
"},{"location":"guidelines/project_management/project_structure/#2-src","title":"2 src \u7ed3\u6784","text":"Pypa \u7ef4\u62a4\u7684\u793a\u4f8b\u9879\u76ee \u4e2d\u91c7\u7528\u4e86\u4e00\u79cd\u66f4\u63a8\u8350\u7684\u7ed3\u6784 src
\u7ed3\u6784\u3002
sampleproject\n\u251c\u2500\u2500 data\n\u251c\u2500\u2500 src\n| \u2514\u2500\u2500 sample\n| \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 pyproject.toml\n\u2514\u2500\u2500 tests\n
\u516d\u5e74\u524d\u7684\u8fd9\u7bc7\u6587\u7ae0 Packaging a python library \u5c31\u8be6\u7ec6\u9610\u8ff0\u4e86\u4f7f\u7528 src
\u7ed3\u6784\u6bd4\u7b80\u5355\u7ed3\u6784\u7684\u8bf8\u591a\u6709\u70b9\u3002\u800c\u73b0\u5728\u4e5f\u9010\u6e10\u88ab\u793e\u533a\u4f5c\u4e3a\u4e00\u4e2a\u6807\u51c6\u9075\u5faa\u3002\u867d\u7136\u793e\u533a\u4e2d\u6709\u5927\u91cf\u8001\u7684\u9879\u76ee\u4f9d\u7136\u91c7\u7528\u7b80\u5355\u5e03\u5c40\uff0c\u4f46\u65b0\u9879\u76ee\u63a8\u8350\u4f7f\u7528 src
\u7ed3\u6784\u3002
\u5982\u4e0b\u9762\u8fd9\u4e2a\u793a\u4f8b\u9879\u76ee\u7ed3\u6784\uff1a
sampleproject\n\u251c\u2500\u2500 data\n\u2502 \u2514\u2500\u2500 user.json\n\u251c\u2500\u2500 docs\n\u2502 \u2514\u2500\u2500 history.md\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 src\n\u2502 \u251c\u2500\u2500 requests\n\u2502 \u2502 \u2514\u2500\u2500 __init__.py\n\u2502 \u2514\u2500\u2500 sample\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 user\n\u2502 \u2502 \u2514\u2500\u2500 __init__.py\n\u2502 \u2514\u2500\u2500 views\n\u2502 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 tests\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 user\n\u2502 \u2502 \u2514\u2500\u2500 __init__.py\n\u2502 \u2514\u2500\u2500 views\n\u2502 \u2514\u2500\u2500 __init__.py\n\u2514\u2500\u2500 tox.ini\n
\u9879\u76ee\u7684\u5305\u7ed3\u6784\u5f88\u6e05\u6670\uff0c\u5728\u73af\u5883\u4e2d\u53ea\u9700\u8981\u5f15\u5165 src
\u76ee\u5f55\uff0c\u5c31\u53ef\u4ee5\u8f7b\u677e\u5bfc\u5165\u9879\u76ee\u6e90\u4ee3\u7801\u3002\u901a\u8fc7 pip install -e .
\u53ef\u7f16\u8f91\u5b89\u88c5\uff0c\u4e5f\u53ea\u4f1a\u5b89\u88c5 src
\u4e2d\u7684\u5305\u3002\u7ba1\u7406\u8d77\u6765\u66f4\u52a0\u6e05\u6670\u3002
"},{"location":"guidelines/project_management/project_structure/#3","title":"3 \u5b9e\u8df5","text":"\u4e0b\u9762\u4ee5\u4e00\u4e2a\u7b80\u5355\u771f\u5b9e\u7684\u9879\u76ee\u6765\u6f14\u793a\u4f7f\u7528 src
\u7ec4\u7ec7\u9879\u76ee
"},{"location":"guidelines/project_management/project_structure/#31","title":"3.1 \u521b\u5efa\u9879\u76ee","text":"\u521b\u5efa\u9879\u76ee:
mkdir sampleproject\ncd sampleproject\n
\u521d\u59cb\u5316\u7248\u672c\u7ba1\u7406\uff1a
git init\n# \u5982\u679c\u6ca1\u6709\u5168\u5c40\u7528\u6237\u540d\u548c\u90ae\u7bb1\uff0c\u9700\u8981\u5148\u914d\u7f6e\ngit config user.email example@example.com\ngit config user.name example\n
\u521b\u5efa\u9879\u76ee\u81ea\u8ff0\u6587\u4ef6\uff1a
touch README.md\n
"},{"location":"guidelines/project_management/project_structure/#32","title":"3.2 \u7f16\u5199\u9879\u76ee\u6e90\u4ee3\u7801","text":"\u521b\u5efa\u9879\u76ee\u5305\uff1a
mkdir src/sample_project\ntouch src/sample_project/__init__.py\n
\u521d\u59cb\u5316\u7248\u672c\u53f7\uff1a
src/sample_project/__init__.py
__version__ = '0.1.0'\n
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add click\n
\u521b\u5efa\u547d\u4ee4\u5165\u53e3\u6587\u4ef6\uff1a
src/sample_project/cmdline.py
import click\n@click.command()\ndef main():\nclick.echo('Hello world!')\nif __name__ == \"__main__\":\nmain()\n
"},{"location":"guidelines/project_management/project_structure/#33","title":"3.3 \u7f16\u5199\u6d4b\u8bd5","text":"\u521b\u5efa\u6d4b\u8bd5\u76ee\u5f55\uff1a
mkdir -p tests/sample_project\ntouch tests/sample_project/__init__.py\n
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add -D pytest\n
\u521b\u5efa\u6d4b\u8bd5\u6587\u4ef6\uff1a
tests/sample_project/test_cmdline.py
from click.testing import CliRunner\nfrom sample_project import cmdline\ndef test_main():\nrunner = CliRunner()\nresult = runner.invoke(cmdline.main)\nassert 'Hello world!' in result.output\n
\u8fd0\u884c\u6d4b\u8bd5\uff1a
pip install -e . # \u4ee5\u53ef\u7f16\u8f91\u5b89\u88c5\u65b9\u5f0f\u5230\u73af\u5883\u4e2d\npytest\n
\u6d4b\u8bd5\u8fd0\u884c\u6210\u529f\uff0c\u8bf4\u660e\u529f\u80fd\u6b63\u786e
"},{"location":"guidelines/project_management/project_structure/#34","title":"3.4 \u521d\u59cb\u5316\u6253\u5305\u914d\u7f6e","text":"\u7f16\u5199\u6253\u5305\u914d\u7f6e\uff1a
pyproject.toml
[tool.poetry]\nname = \"sample_project\"\nversion = \"0.1.0\"\ndescription = \"Sample Project\"\nreadme = \"README.md\"\nauthors = [\"example <example@example.com>\"]\nlicense = \"MIT\"\nclassifiers = [\n\"Operating System :: OS Independent\",\n\"Programming Language :: Python :: 3.10\",\n]\n[tool.poetry.dependencies]\npython = \"^3.10\"\nclick = \"^8.1.3\"\n[tool.poetry.dev-dependencies]\npytest = \"^7.1.2\"\n[tool.poetry.plugins.\"scripts\"]\nsample_project = \"sample_project.cmdline:main\"\n[build-system]\nrequires = [\"poetry-core>=1.0.0\"]\nbuild-backend = \"poetry.core.masonry.api\"\n
\u6253\u5305\uff1a
poetry build\n
"},{"location":"guidelines/project_management/project_structure/#35","title":"3.5 \u603b\u7ed3","text":"\u81f3\u6b64\uff0c\u4e00\u4e2a\u9879\u76ee\u5f00\u53d1\u5b8c\u6210\uff0c\u5b8c\u6574\u9879\u76ee\u7ed3\u6784\u5982\u4e0b\uff1a
\u251c\u2500\u2500 dist\n\u2502 \u251c\u2500\u2500 sample_project-0.1.0.tar.gz \n| \u2514\u2500\u2500 sample_project-0.1.0-py3-none-any.whl\n\u251c\u2500\u2500 poetry.lock\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 src\n\u2502 \u2514\u2500\u2500 sample_project\n\u2502 \u251c\u2500\u2500 cmdline.py\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2514\u2500\u2500 tests\n \u251c\u2500\u2500 __init__.py\n \u2514\u2500\u2500 sample_project\n \u251c\u2500\u2500 __init__.py\n \u2514\u2500\u2500 test_cmdline.py\n
"},{"location":"guidelines/tutorial/develop/","title":"\u529f\u80fd\u5f00\u53d1","text":"\u5728\u672c\u7ae0\u8282\u5185\u5bb9\uff0c\u4f60\u5c06\u5b66\u4e60\u5230\u5982\u4e0b\u5185\u5bb9\uff1a
- \u8bbe\u8ba1 ETL \u4e09\u4e2a\u9636\u6bb5\u7684\u63a5\u53e3\u5e76\u505a\u9ed8\u8ba4\u5b9e\u73b0
- \u4f7f\u7528\u63d2\u4ef6\u5316\u673a\u5236\u6ce8\u518c\u548c\u53d1\u73b0\u63a5\u53e3\u5b9e\u73b0
- \u901a\u8fc7\u914d\u7f6e\u9009\u62e9\u4f7f\u7528\u7684\u5b9e\u73b0\u5185\u5bb9
- \u66f4\u65b0\u547d\u4ee4\u884c
\u6839\u636e\u524d\u9762\u7684\u7cfb\u7edf\u8bbe\u8ba1\uff0c ETL \u9879\u76ee\u603b\u5171\u6709\u4e09\u4e2a\u6838\u5fc3\u6a21\u5757\uff0c\u5206\u522b\u662f extractor
\u3001 transformer
\u548c loader
\u3002\u4e3a\u4e86 \u80fd\u8fd0\u884c\u903b\u8f91\uff0c\u8fd8\u9700\u8981\u4e00\u4e2a manage
\u6a21\u5757\u7528\u6765\u7f16\u6392\u4e09\u4e2a\u6a21\u5757\u7684\u903b\u8f91\u3002\u7136\u540e\u4f1a\u5728\u547d\u4ee4\u884c\u4e2d\u6ce8\u518c\u4e00\u4e2a\u5165\u53e3\u65b9\u6cd5\uff0c\u8c03\u7528 mange
\u7684\u903b\u8f91\u3002
"},{"location":"guidelines/tutorial/develop/#extractor","title":"extractor","text":"extractor
\u7684\u4f5c\u7528\u662f\u4ece\u6e90\u76ee\u6807\u63d0\u53d6\u6570\u636e\uff0c\u76ee\u6807\u53ef\u4ee5\u662f\u6587\u4ef6\u3001\u6570\u636e\u5e93\u3001\u6d88\u606f\u961f\u5217\u7b49\u3002\u8fd9\u5178\u578b\u662f\u4e00\u4e2a\u591a\u5b9e\u73b0\u7684\u60c5\u51b5\uff0c\u540c\u65f6\u4e5f \u4e3a\u4e86\u7edf\u4e00\u5176\u4ed6\u5f00\u53d1\u4eba\u5458\u7f16\u5199\u81ea\u5df1\u7684 extractor
\uff0c\u5c31\u9700\u8981\u5bf9 extractor
\u505a\u51fa\u4e00\u4e2a\u62bd\u8c61\u8bbe\u8ba1\u3002\u6211\u4eec\u4f7f\u7528 BaseExtractor
\u7c7b \u505a\u4e00\u4e2a\u62bd\u8c61\u57fa\u7c7b\u3002
"},{"location":"guidelines/tutorial/develop/#extractor_1","title":"extractor \u57fa\u7c7b","text":"\u521b\u5efa extractor
\u5305\uff0c\u5e76\u5728\u91cc\u9762\u65b0\u5efa\u4e00\u4e2a base.py
\u6587\u4ef6\uff0c\u6587\u4ef6\u5185\u5bb9\u5982\u4e0b\uff1a
\u6ce8\u610f\uff1aPython \u7684\u5305\u662f\u4e00\u4e2a\u6587\u4ef6\u5939\uff0c\u91cc\u9762\u5fc5\u987b\u5305\u542b\u4e00\u4e2a __init__.py
\u6587\u4ef6\u3002\u53ea\u6709\u4e00\u4e2a\u7a7a\u6587\u4ef6\u5939\uff0c\u4e0d\u662f\u5408\u6cd5\u7684 Python \u5305\u3002
src/example_etl/extractor/base.py
\"\"\"Base extractor.\"\"\"\nfrom typing import Iterable\nclass BaseExtractor:\n\"\"\"Base extractor\"\"\"\ndef __init__(self, settings):\nself.settings = settings\nself.setup()\ndef setup(self):\n\"\"\"Setup something when init extractor\"\"\"\ndef extract(self) -> Iterable[str]:\n\"\"\"Extract data.\"\"\"\nraise NotImplementedError()\ndef close(self):\n\"\"\"Close something.\"\"\"\ndef __enter__(self):\nreturn self\ndef __exit__(self, exc_type, exc_val, exc_tb):\nself.close()\n
BaseExtractor
\u6709\u4e00\u4e2a\u62bd\u8c61\u65b9\u6cd5 extract
\uff0c\u9700\u8981\u5b9e\u73b0\u65f6\uff0c\u7ee7\u627f\u8be5\u7c7b\uff0c\u5e76\u5b9e\u73b0\u8fd9\u4e2a\u65b9\u6cd5\u5373\u53ef\u3002 BaseExtractor
\u540c\u65f6\u9ed8\u8ba4\u5b9e\u73b0\u4e86 __enter__
\u548c __exit__
\u4e24\u4e2a\u65b9\u6cd5\uff0c\u76ee\u7684\u662f\u8ba9\u5b9e\u73b0\u7c7b\u53ef\u4ee5\u901a\u8fc7 with
\u5173\u952e\u5b57\u8c03\u7528\uff0c\u5e76\u81ea\u52a8\u7ba1\u7406 close
\u65b9\u6cd5\u3002\u8fd9\u5bf9\u4e8e\u6570\u636e\u5e93 \u8fde\u63a5\u7684\u5b9e\u73b0\u5f88\u6709\u5e2e\u52a9\u3002
BaseExtractor
\u63a5\u6536\u4e00\u4e2a settings
\u5bf9\u8c61\uff0c\u8fd9\u4e2a\u5bf9\u8c61\u5176\u5b9e\u5c31\u662f example_etl.config.settings
\u5bf9\u8c61\uff0c\u8fd9\u91cc\u901a\u8fc7\u8c03\u7528\u8005\u4f20\u9012\u3002
extract
\u7684\u8fd4\u56de\u503c\u662f\u4e00\u4e2a\u53ef\u8fed\u4ee3\u7684\u5bf9\u8c61\uff0c\u8fed\u4ee3\u5185\u5bb9\u4e3a str
\u3002
"},{"location":"guidelines/tutorial/develop/#extractor-file","title":"extractor \u7684 file \u5b9e\u73b0","text":"\u57fa\u4e8e BaseExtractor
\u505a\u4e00\u4e2a\u6587\u4ef6\u63d0\u53d6\u5176\u5b9e\u73b0\u3002
\u5728 extractor
\u5305\u4e2d\u521b\u5efa\u6587\u4ef6 file.py
\uff0c\u5e76\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
src/example_etl/extractor/file.py
\"\"\"\nFile extractor\nextract data from file.\n\"\"\"\nimport logging\nfrom typing import Iterable\nfrom example_etl.constants import DEFAULT_ENCODING\nfrom example_etl.extractor.base import BaseExtractor\nlogger = logging.getLogger(__name__)\nclass FileExtractor(BaseExtractor):\n\"\"\"File extractor\"\"\"\ndef extract(self) -> Iterable[str]:\n\"\"\"Open and read file\"\"\"\nextractor_path = self.settings.FILE_EXTRACTOR_PATH\nlogger.info('Extract data from %s', extractor_path)\nwith open(extractor_path, 'r', encoding=DEFAULT_ENCODING) as file:\nfor i in file:\nyield i\n
\u5728\u5b9e\u73b0\u7684 extract
\u65b9\u6cd5\u4e2d\uff0c\u4ece FileExtractor.settings
\u5bf9\u8c61\u4e2d\u83b7\u53d6\u4e86\u4e00\u4e2a FILE_EXTRACTOR_PATH
\u53d8\u91cf\uff0c\u8fd9\u4e2a\u53d8\u91cf\u662f\u4ece \u914d\u7f6e\u6587\u4ef6\u4e2d\u83b7\u53d6\u7684\u3002\u56e0\u6b64\u9700\u8981\u5728\u914d\u7f6e\u6587\u4ef6 src/example_etl/config/settings.yml
\u4e2d\u589e\u52a0 file_extractor_path: /tmp/foo.txt
\u7684\u503c:
verbose: false\ndebug: false\nloglevel: warning\nlogpath: /tmp/example_etl\nfile_extractor_path: /tmp/foo.txt\n
extract
\u65b9\u6cd5\u4e2d\u76f4\u63a5\u53ef\u4ee5\u901a\u8fc7\u8fd4\u56de\u8fed\u4ee3\u5bf9\u8c61\u7684\u65b9\u5f0f\u81ea\u52a8\u7ba1\u7406\u6587\u4ef6\u8bfb\u5bf9\u8c61\u3002
\u6ce8\u610f\u4e00\u70b9\u7684\u662f\uff0c\u6253\u5f00\u6587\u4ef6\u65f6\u4f7f\u7528\u4e86\u9ed8\u8ba4\u5b57\u7b26\u96c6\u7684\u5e38\u91cf\u503c DEFAULT_ENCODING
\u3002\u6240\u4ee5\u8fd8\u8981\u521b\u5efa src/example_etl/constants.py
\uff0c \u5e76\u52a0\u5165\u5982\u4e0b\u5185\u5bb9\uff1a
\"\"\"Constants\"\"\"\nDEFAULT_ENCODING = 'utf-8'\n
file.py
\u6587\u4ef6\u4e2d\u8fd8\u521b\u5efa\u4e86\u4e00\u4e2a\u5168\u5c40 logger
\u5bf9\u8c61\uff0c\u5bf9\u8c61\u540d\u79f0\u4f7f\u7528\u4e86 __name__
\u83b7\u53d6\u8be5\u5305\u7684\u540d\u79f0\u3002\u5728\u6253\u5370\u65e5\u5fd7\u65f6\uff0c\u663e\u793a\u7684\u5305\u540d \u4e3a example_etl.extractor.file
\u3002\u5728 extract
\u65b9\u6cd5\u4e2d\u6253\u5370\u4e00\u6761\u6267\u884c\u8bb0\u5f55\u3002
"},{"location":"guidelines/tutorial/develop/#transformer","title":"transformer","text":"transformer
\u6a21\u5757\u7684\u529f\u80fd\u662f\u8f6c\u6362\u8bfb\u53d6\u5230\u7684\u903b\u8f91\u3002\u5728\u8fd9\u4e2a\u8fc7\u7a0b\u4e2d\uff0c\u901a\u8fc7\u63a5\u6536 extractor
\u8bfb\u53d6\u5230\u7684\u6587\u672c\uff0c\u5904\u7406\u540e\u4f20\u9012\u7ed9 loader
\u3002
\u6b64\u8fc7\u7a0b\u53ef\u4ee5\u6267\u884c\u53bb\u9664\u7a7a\u683c\u3001\u5220\u51cf\u5b57\u7b26\u7b49\u64cd\u4f5c\u3002
\u4e3a\u4e86\u65b9\u4fbf\u5b9e\u73b0\uff0c\u521b\u5efa\u4e00\u4e2a\u57fa\u7c7b BaseTransformer
\u3002
"},{"location":"guidelines/tutorial/develop/#transformer_1","title":"transformer \u57fa\u7c7b","text":"\u9996\u5148\u521b\u5efa transformer
\u5305\uff0c\u7136\u540e\u65b0\u5efa BaseTransformer.py
\u6587\u4ef6\uff1a
src/example_etl/transformer/base.py
\"\"\"Base transformer\"\"\"\nclass BaseTransformer:\n\"\"\"Base transformer\"\"\"\ndef __init__(self, settings):\nself.settings = settings\ndef transform(self, data: str) -> str:\n\"\"\"Transform data\"\"\"\nraise NotImplementedError()\n
BaseTransformer
\u540c\u6837\u63a5\u6536\u4e00\u4e2a settings
\u5bf9\u8c61\u3002\u5176\u62bd\u8c61\u65b9\u6cd5 transform
\u63a5\u6536\u4e00\u4e2a\u5b57\u7b26\u4e32\u7c7b\u578b\u7684 data
\u5e76\u8fd4\u56de str
\u7c7b\u578b \u7684\u6570\u636e\u3002
"},{"location":"guidelines/tutorial/develop/#tansformer","title":"tansformer \u53bb\u7a7a\u683c\u5b9e\u73b0","text":"BaseTransformer
\u5b9e\u73b0\u4e00\u4e2a\u53ef\u4ee5\u5220\u9664\u6587\u672c\u524d\u540e\u7a7a\u683c\u7684\u5b9e\u73b0 StripTransformer
\uff1a
\u521b\u5efa strip.py
src/example_etl/transformer/strip.py
\"\"\"Transform data and remove blank of data star and end.\"\"\"\nimport logging\nfrom example_etl.transformer.base import BaseTransformer\nlogger = logging.getLogger(__name__)\nclass StripTransformer(BaseTransformer):\n\"\"\"\n Transform data and remove blank of data star and end.\n \"\"\"\ndef transform(self, data: str) -> str:\n\"\"\"Remove blank of data star and end.\"\"\"\nlogger.debug('Strip data: \"%s\"', data)\nreturn data.strip()\n
StripTransformer
\u5b9e\u73b0\u662f\u901a\u8fc7\u5b57\u7b26\u4e32\u65b9\u6cd5 strip
\u5220\u9664\u63a5\u6536\u5230\u5b57\u7b26\u4e32\u6570\u636e\u524d\u540e\u7a7a\u683c\uff0c\u5e76\u8fd4\u56de\u7ed3\u679c\u3002
strip.py
\u6587\u4ef6\u4e2d\u540c\u6837\u521d\u59cb\u5316\u4e00\u4e2a logger
\u5bf9\u8c61\uff0c\u5728 transform
\u4e2d\u6253\u5370\u4e00\u6761\u8bb0\u5f55\u3002\u9700\u8981\u6ce8\u610f\u7684\u662f\uff0c\u8fd9\u91cc\u4f7f\u7528\u4e86 debug
\u65b9\u6cd5\uff0c\u6253\u5370\u7684\u65e5\u5fd7\u4e3a DEBUG
\u7ea7\u522b\u3002\u5f53\u65e5\u5fd7\u7ea7\u522b\u8bbe\u7f6e\u5728 INFO
\u65f6\uff0c\u8fd9\u91cc\u7684\u6267\u884c\u8bb0\u5f55\u662f\u4e0d\u4f1a\u6253\u5370\u7684\u3002\u5bf9\u4e8e \u5173\u6ce8\u4f4e\u7684\u8bb0\u5f55\uff0c\u53ef\u4ee5\u4f7f\u7528 DEBUG
\u3002
"},{"location":"guidelines/tutorial/develop/#loader","title":"loader","text":"loader
\u6a21\u5757\u7528\u6765\u5c06 transformer
\u8f6c\u6362\u7684\u6570\u636e\u52a0\u8f7d\u5230\u76ee\u6807\u4f4d\u7f6e\u3002\u76ee\u6807\u53ef\u4ee5\u662f\u6587\u4ef6\u3001\u6570\u636e\u5e93\u3001\u6d88\u606f\u961f\u5217\u7b49\u3002
\u540c\u6837\u7684\uff0c\u5bf9 loader
\u505a\u51fa\u62bd\u8c61\u7c7b BaseLoader
\u3002
"},{"location":"guidelines/tutorial/develop/#loader_1","title":"loader \u57fa\u7c7b","text":"\u5728 loader
\u5305\u4e2d\u521b\u5efa base.py
\u6587\u4ef6\uff0c\u6587\u4ef6\u5185\u5bb9\u5982\u4e0b\uff1a
src/example_etl/loader/base.py
\"\"\"Base loader\"\"\"\nclass BaseLoader:\n\"\"\"Base loader\"\"\"\ndef __init__(self, settings):\nself.settings = settings\nself.setup()\ndef setup(self):\n\"\"\"Setup something when init loader.\"\"\"\ndef load(self, data: str):\n\"\"\"Write data to loader\"\"\"\nraise NotImplementedError()\ndef close(self):\n\"\"\"Close something\"\"\"\ndef __exit__(self, exc_type, exc_val, exc_tb):\nself.close()\ndef __enter__(self):\nreturn self\n
\u5728 BaseLoader
\u4e2d\u6709\u4e00\u4e2a load
\u7684\u62bd\u8c61\u65b9\u6cd5\uff0c\u7528\u6765\u7ed9\u7ee7\u627f\u7c7b\u5b9e\u73b0\u3002\u9ed8\u8ba4\u7684 setup
\u65b9\u6cd5\u53ef\u4ee5\u5728\u521d\u59cb\u5316 \u65f6\u505a\u4e00\u4e9b\u903b\u8f91\uff0c\u6bd4\u5982\u6253\u5f00\u6587\u4ef6\u3001\u521b\u5efa\u6570\u636e\u5e93\u8fde\u63a5\u7b49\u3002 close
\u7528\u6765\u5173\u95ed\u8fd9\u4e9b\u903b\u8f91\u3002 __exit__
\u548c __enter__
\u53ef\u4ee5 \u8ba9 BaseLoader
\u901a\u8fc7 with
\u5173\u952e\u5b57\u4f7f\u7528\u3002
\u9700\u8981\u6ce8\u610f\u7684\u662f\uff0c BaseLoader
\u7684 load
\u65b9\u6cd5\u4e2d\u4e0d\u80fd\u6709\u521b\u5efa\u8fde\u63a5\u5bf9\u8c61\u7684\u903b\u8f91\uff0c\u56e0\u4e3a load
\u4f1a\u51fa\u73b0\u5728\u5faa\u73af\u4e2d\u7684\u3002
"},{"location":"guidelines/tutorial/develop/#loader-file","title":"loader \u7684 file \u5b9e\u73b0","text":"\u9ed8\u8ba4\u5b9e\u73b0\u4e00\u4e2a\u5c06\u6570\u636e\u5199\u5165\u6587\u4ef6\u7684\u5b9e\u73b0\u7c7b FileLoader
\u3002
\u5728 loader
\u5305\u4e2d\u521b\u5efa file.py
\u6587\u4ef6\uff0c\u6587\u4ef6\u5185\u5bb9\u5982\u4e0b\uff1a
src/example_etl/loader/file.py
\"\"\"\nFile loader\nWrite data to loader file.\n\"\"\"\nimport logging\nfrom example_etl.constants import DEFAULT_ENCODING\nfrom example_etl.loader.base import BaseLoader\nlogger = logging.getLogger(__name__)\nclass FileLoader(BaseLoader):\n\"\"\"\n File loader\n \"\"\"\nfile = None\ndef setup(self):\n\"\"\"Open a file when init loader.\"\"\"\nloader_path = self.settings.FILE_LOADER_PATH\nlogger.info('Write data to %s', loader_path)\nself.file = open(loader_path, 'w', encoding=DEFAULT_ENCODING) # pylint: disable=consider-using-with\ndef load(self, data: str):\n\"\"\"Write data to a file.\"\"\"\nself.file.write(data)\nself.file.flush()\ndef close(self):\n\"\"\"Close file object when task done.\"\"\"\nself.file.close()\n
\u8be5\u7c7b\u5728 setup
\u65b9\u6cd5\u4e2d\u6253\u5f00\u6587\u4ef6\u5bf9\u8c61\uff0c\u5e76\u5728 close
\u65b9\u6cd5\u4e2d\u5173\u95ed\u6587\u4ef6\u3002 load
\u65b9\u6cd5\u4f1a\u5199\u5165\u6570\u636e\uff0c\u5e76\u7acb\u5373 \u5c06\u5185\u5bb9\u5237\u65b0\u5230\u6587\u4ef6\u4e2d\u3002
\u521d\u59cb\u5316 FileLoader
\u65f6\u9700\u8981\u901a\u8fc7\u914d\u7f6e\u8bfb\u53d6\u6587\u4ef6\uff0c\u5e76\u5199\u5165\u3002\u6240\u4ee5\u9700\u8981\u5728\u914d\u7f6e\u6587\u4ef6 src/example_etl/config/settings.yml
\u4e2d\u589e\u52a0\u914d\u7f6e file_loader_path: /tmp/bar.txt
:
verbose: false\ndebug: false\nloglevel: warning\nlogpath: /tmp/example_etl\nfile_extractor_path: /tmp/foo.txt\nfile_loader_path: /tmp/bar.txt\n
"},{"location":"guidelines/tutorial/develop/#_2","title":"\u63d2\u4ef6\u6ce8\u518c","text":"\u4e09\u4e2a\u57fa\u7840\u6a21\u5757\u4f7f\u7528\u63d2\u4ef6\u673a\u5236\u81ea\u52a8\u53d1\u73b0\uff0c\u5e76\u901a\u8fc7\u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u9700\u8981\u4f7f\u7528\u7684\u5177\u4f53\u5b9e\u73b0\u3002\u5728\u540e\u7eed\u4f7f\u7528\u4e2d\uff0c\u57fa\u4e8e\u62bd\u8c61\u57fa\u7c7b \u5f00\u53d1\u7684\u5176\u4ed6\u5b9e\u73b0\u4e5f\u662f\u901a\u8fc7\u8fd9\u79cd\u6765\u505a\u3002
\u5b89\u88c5\u63d2\u4ef6\u6846\u67b6 stevedore \uff1a
poetry add stevedore\n
"},{"location":"guidelines/tutorial/develop/#_3","title":"\u6ce8\u518c\u63d2\u4ef6","text":"\u5c06\u4e0a\u8ff0\u5b9e\u73b0\u7684\u4e09\u4e2a\u7c7b\u6ce8\u518c\u5230\u547d\u540d\u7a7a\u95f4\u4e2d\u3002
\u7f16\u8f91 pyproject.toml
\u6587\u4ef6\uff0c\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
[tool.poetry]\nname = \"example_etl\"\nversion = \"0.1.0\"\ndescription = \"This is my first etl project.\"\nreadme = \"README.md\"\nauthors = [\"test <test@example.com>\"]\nlicense = \"MIT\"\nclassifiers = [\n\"Operating System :: OS Independent\",\n\"Programming Language :: Python :: 3.10\",\n]\n[tool.poetry.dependencies]\npython = \"^3.10\"\ndynaconf = \"^3.1.12\"\nclick = \"^8.1.3\"\n[tool.poetry.group.dev.dependencies]\npylint = \"^2.17.4\"\nisort = \"^5.12.0\"\npytest = \"^7.3.1\"\ntox = \"^4.5.2\"\nmkdocs = \"^1.4.3\"\nmkdocs-material = \"^8.5.11\"\npytest-pylint = \"^0.19.0\"\npre-commit = \"^3.3.2\"\n[tool.poetry.plugins.\"example_etl.extractor\"]\nfile = \"example_etl.extractor.file:FileExtractor\"\n[tool.poetry.plugins.\"example_etl.loader\"]\nfile = \"example_etl.loader.file:FileLoader\"\n[tool.poetry.plugins.\"example_etl.transformer\"]\nstrip = \"example_etl.transformer.strip:StripTransformer\"\n[tool.poetry.scripts]\nexample_etl = \"example_etl.cmdline:main\"\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n[tool.pytest.ini_options]\ntestpaths = \"tests\"\npython_files = \"tests.py test_*.py *_tests.py\"\n[tool.pylint.design]\nmax-line-length = 120\n
\u8fd9\u4e48\u505a\u7684\u76ee\u7684\u662f\u5c06 FileExtractor
\u3001 FileLoader
\u3001 StripTransformer
\u5206\u522b\u6ce8\u518c\u5230 entry_points
\u4e2d\uff0c \u7136\u540e\u5728\u7a0b\u5e8f\u4e2d\u4f7f\u7528 import.metadata
\u6839\u636e\u540d\u79f0\u7a7a\u95f4\u67e5\u627e\u3002\u800c stevedore
\u5219\u662f\u5c01\u88c5\u4e86\u67e5\u627e\u7684\u590d\u6742\u903b\u8f91\uff0c\u8ba9\u4f7f\u7528 \u66f4\u7b80\u5355\u3002
\u5c06\u9879\u76ee\u4ee5\u53ef\u7f16\u8f91\u6a21\u5f0f\u5b89\u88c5\u5230\u5f53\u524d\u73af\u5883\uff1a
poetry install\n
"},{"location":"guidelines/tutorial/develop/#_4","title":"\u7ba1\u7406\u6a21\u5757","text":"manage
\u6a21\u5757\u662f\u7528\u6765\u7f16\u6392\u524d\u9762\u4e09\u4e2a\u6a21\u5757\u7684\u903b\u8f91\u3002
\u521b\u5efa src/example_etl/manage.py
\uff0c\u6587\u4ef6\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Manage\"\"\"\nimport logging\nfrom typing import Type\nfrom stevedore import ExtensionManager\nfrom example_etl.config import settings\nfrom example_etl.exceptions import PluginNotFoundError\nfrom example_etl.extractor.base import BaseExtractor\nfrom example_etl.loader.base import BaseLoader\nfrom example_etl.transformer.base import BaseTransformer\nlogger = logging.getLogger(__name__)\nclass Manage:\n\"\"\"Manager\"\"\"\ndef __init__(self):\nself.extractor_kls: Type[BaseExtractor] = get_extension(\n'example_etl.extractor',\nsettings.EXTRACTOR_NAME,\n)\nself.loader_kls: Type[BaseLoader] = get_extension(\n'example_etl.loader',\nsettings.LOADER_NAME,\n)\nself.transformer_kls: Type[BaseTransformer] = get_extension(\n'example_etl.transformer',\nsettings.TRANSFORMER_NAME,\n)\nself.transformer: BaseTransformer = self.transformer_kls(settings)\ndef run(self):\n\"\"\"Run manage\"\"\"\nwith self.extractor_kls(settings) as extractor:\nwith self.loader_kls(settings) as loader:\nself.transform(extractor, loader)\nlogger.info('Exit example_etl.')\ndef transform(self, extractor: BaseExtractor, loader: BaseLoader):\n\"\"\"Transform data from extractor to loader.\"\"\"\nlogger.info('Start transformer data ......')\nfor i in extractor.extract():\ndata = self.transformer.transform(i)\nloader.load(data)\nlogger.info('Data processed.')\ndef get_extension(namespace: str, name: str):\n\"\"\"Get extension by name from namespace.\"\"\"\nextension_manager = ExtensionManager(namespace=namespace, invoke_on_load=False)\nfor ext in extension_manager.extensions:\nif ext.name == name:\nlogger.info('Load plugin: %s in namespace \"%s\"', ext.plugin, namespace)\nreturn ext.plugin\nraise PluginNotFoundError(namespace=namespace, name=name)\n
\u5728 manage
\u4e2d\u5c01\u88c5\u4e86\u4e00\u4e2a\u901a\u8fc7\u540d\u79f0\u7a7a\u95f4\u548c\u540d\u79f0\u4e24\u4e2a\u53c2\u6570\u67e5\u627e\u63d2\u4ef6\u7684\u65b9\u6cd5 get_extension
\u3002\u5f53\u627e\u4e0d\u5230\u5bf9\u5e94 \u7684\u63d2\u4ef6\u65f6\uff0c\u4f1a\u629b\u51fa PluginNotFoundError
\u5f02\u5e38\u3002
\u5728 Manage
\u7c7b\u7684 __init__
\u65b9\u6cd5\u4e2d\uff0c\u5206\u522b\u4ece\u4e09\u4e2a\u540d\u79f0\u7a7a\u95f4\u67e5\u627e\u5b9e\u73b0\u7c7b\uff0c\u67e5\u627e\u7684\u540d\u5b57\u5219\u662f\u901a\u8fc7\u914d\u7f6e\u6587\u4ef6\u7684 \u53d8\u91cf\u83b7\u53d6\u7684\uff0c\u8fd9\u6837\u5c31\u53ef\u4ee5\u901a\u8fc7\u914d\u7f6e\u7075\u6d3b\u5730\u8c03\u6574\u9700\u8981\u4f7f\u7528\u7684\u5177\u4f53\u5b9e\u73b0\u4e86\u3002
run
\u65b9\u6cd5\u4e2d\u4f7f\u7528 with
\u5173\u952e\u5b57\u5206\u522b\u521d\u59cb\u5316 extractor
\u548c loader
\uff0c\u5728\u903b\u8f91\u7ed3\u675f\u65f6\uff0c\u53ef\u4ee5\u81ea\u52a8\u7ba1\u7406 \u5728 close
\u4e2d\u5173\u95ed\u7684\u5bf9\u8c61\u3002
transform
\u65b9\u6cd5\u4e2d\u8c03\u7528 extractor.extract
\u65b9\u6cd5\u904d\u5386\u8bfb\u53d6\u7684\u6570\u636e\uff0c\u5e76\u5728\u8f6c\u6362\u540e\u5c06\u6570\u636e\u901a\u8fc7 loader.load
\u5199\u5165 \u76ee\u6807\u4f4d\u7f6e\u3002
\u5728\u4f7f\u7528 Manage
\u7684\u65f6\u5019\uff0c\u9700\u8981\u4ece\u914d\u7f6e\u4e2d\u8bfb\u53d6\u4e09\u4e2a\u5177\u4f53\u5b9e\u73b0\uff0c\u6240\u4ee5\u9700\u8981\u5728\u914d\u7f6e\u6587\u4ef6 src/example_etl/config/settings.yml
\u4e2d\u589e\u52a0\u5982\u4e0b\u53d8\u91cf\uff1a
verbose: false\ndebug: false\nloglevel: warning\nlogpath: /tmp/example_etl\nfile_extractor_path: /tmp/foo.txt\nfile_loader_path: /tmp/bar.txt\nextractor_name: file\nloader_name: file\ntransformer_name: strip\n
"},{"location":"guidelines/tutorial/develop/#_5","title":"\u5f02\u5e38\u5904\u7406","text":"\u5728\u4f7f\u7528\u5f02\u5e38\u7684\u65f6\u5019\uff0c\u5efa\u8bae\u521b\u5efa\u4e00\u4e2a\u9879\u76ee\u7ea7\u522b\u7684\u5f02\u5e38\u7d2f\uff0c\u7528\u6765\u5b9a\u4e49\u5f53\u524d\u9879\u76ee\u7684\u9876\u7ea7\u5f02\u5e38\u3002\u9879\u76ee\u5185\u90e8\u7684\u5176\u4ed6\u5f02\u5e38\u90fd \u9700\u8981\u57fa\u4e8e\u9879\u76ee\u9876\u7ea7\u5f02\u5e38\u5b9e\u73b0\u3002\u8fd9\u4e48\u505a\u7684\u4e00\u4e2a\u597d\u5904\u662f\u5f53\u4f60\u7684\u9879\u76ee\u88ab\u522b\u4eba\u5f15\u7528\u65f6\uff0c\u8c03\u7528\u65b9\u53ef\u4ee5\u901a\u8fc7\u6355\u83b7\u9879\u76ee\u9876\u7ea7 \u5f02\u5e38\uff0c\u6765\u7edf\u4e00\u5904\u7406\u9879\u76ee\u7684\u6240\u6709\u5f02\u5e38\u3002
\u521b\u5efa\u4e00\u4e2a src/example_etl/exceptions.py
\u6587\u4ef6\uff0c\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Exception\"\"\"\nclass EtlError(Exception):\n\"\"\"Etl error\"\"\"\nclass PluginNotFoundError(EtlError):\n\"\"\"PluginNotFoundError\"\"\"\ndef __init__(self, namespace: str, name: str):\nsuper().__init__()\nself._namespace = namespace\nself._name = name\ndef __repr__(self):\nreturn f'Can not found \"{self._name}\" plugin in {self._namespace}'\ndef __str__(self):\nreturn self.__repr__()\n
\u5728 exceptions.py
\u6587\u4ef6\u4e2d\u9996\u5148\u521b\u5efa\u4e86\u4e00\u4e2a\u5168\u5c40\u5f02\u5e38\u7c7b EtlError
\uff0c PluginNotFoundError
\u5f02\u5e38\u7ee7\u627f\u5b83\u3002 \u5f53\u9700\u8981\u6355\u83b7\u6240\u4ee5\u9879\u76ee\u5f02\u5e38\u65f6\uff0c\u53ef\u4ee5\u901a\u8fc7 EtlError
\u6355\u83b7\u3002
"},{"location":"guidelines/tutorial/develop/#_6","title":"\u589e\u52a0\u547d\u4ee4\u884c\u8c03\u7528","text":"\u7f16\u8f91 src/example_etl/cmdline.py
\u6587\u4ef6\uff0c\u4fee\u6539 rum
\u65b9\u6cd5\uff0c\u4fee\u6539\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Command line\"\"\"\nimport click\nfrom click import Context\nfrom example_etl import __version__\nfrom example_etl.config import settings\nfrom example_etl.log import init_log\nfrom example_etl.manage import Manage\n@click.group(invoke_without_command=True)\n@click.pass_context\n@click.option(\n'-V',\n'--version',\nis_flag=True,\nhelp='Show version and exit.'\n) # If it's true, it will override `settings.VERBOSE`\n@click.option('-v', '--verbose', is_flag=True, help='Show more info.')\n@click.option(\n'--debug',\nis_flag=True,\nhelp='Enable debug.'\n) # If it's true, it will override `settings.DEBUG`\ndef main(ctx: Context, version: str, verbose: bool, debug: bool):\n\"\"\"Main commands\"\"\"\nif version:\nclick.echo(__version__)\nelif ctx.invoked_subcommand is None:\nclick.echo(ctx.get_help())\nelse:\nif verbose:\nsettings.set('VERBOSE', True)\nif debug:\nsettings.set('DEBUG', True)\n@main.command()\ndef run():\n\"\"\"Run command\"\"\"\ninit_log()\nmanage = Manage()\nmanage.run()\n
\u5728\u4f7f\u7528\u547d\u4ee4 example_etl
\u8c03\u7528\u65f6\uff0c\u53ef\u4ee5\u901a\u8fc7\u4f20\u9012 run
\u6307\u4ee4\u8fd0\u884c\u3002
"},{"location":"guidelines/tutorial/develop/#_7","title":"\u68c0\u67e5\u4ee3\u7801","text":"\u7f16\u7801\u5b8c\u6210\u540e\uff0c\u5efa\u8bae\u901a\u8fc7 isort
\u68c0\u67e5\u5bfc\u5305\u98ce\u683c\uff0c\u4f7f\u7528 pylint
\u68c0\u67e5\u4ee3\u7801\u7684\u8bed\u6cd5\u548c\u7f16\u7801\u98ce\u683c\u3002
\u8fd0\u884c isort
\uff1a
$ isort .\nSkipped 1 files\n
\u8fd0\u884c pylint
\uff1a
$ pylint src tests\n************* Module example_etl.transformer.strip\nsrc/example_etl/transformer/strip.py:9:0: R0903: Too few public methods (1/2) (too-few-public-methods)\n************* Module example_etl.transformer.base\nsrc/example_etl/transformer/base.py:4:0: R0903: Too few public methods (1/2) (too-few-public-methods)\n-------------------------------------------------------------------\nYour code has been rated at 9.89/10 (previous run: 10.00/10, -0.11)\n
\u53ef\u4ee5\u770b\u5230\u6839\u636e pylint
\u7684\u9ed8\u8ba4\u8bed\u6cd5\u89c4\u8303\uff0c\u6211\u4eec\u6709\u4e24\u4e2a\u65b9\u6cd5\u4e0d\u7b26\u5408\u3002\u4f46\u6839\u636e\u5b9e\u9645\u60c5\u51b5\u6211\u4eec\u7684\u5b9e\u73b0\u662f\u6ca1\u6709\u95ee\u9898\u7684\uff0c\u6240\u4ee5\u6211\u4eec\u9700\u8981\u8c03\u6574 pylint
\u7684\u89c4\u5219\u3002
\u7f16\u8f91 src/example_etl/transformer/base.py
\uff0c\u8c03\u6574\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Base transformer\"\"\"\n# pylint: disable=too-few-public-methods\nclass BaseTransformer:\n\"\"\"Base transformer\"\"\"\ndef __init__(self, settings):\nself.settings = settings\ndef transform(self, data: str) -> str:\n\"\"\"Transform data\"\"\"\nraise NotImplementedError()\n
\u7f16\u8f91 src/example_etl/transformer/strip.py
\uff0c\u8c03\u6574\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Transform data and remove blank of data star and end.\"\"\"\nimport logging\nfrom example_etl.transformer.base import BaseTransformer\nlogger = logging.getLogger(__name__)\n# pylint: disable=too-few-public-methods\nclass StripTransformer(BaseTransformer):\n\"\"\"\n Transform data and remove blank of data star and end.\n \"\"\"\ndef transform(self, data: str) -> str:\n\"\"\"Remove blank of data star and end.\"\"\"\nlogger.debug('Strip data: \"%s\"', data)\nreturn data.strip()\n
\u4e0a\u9762\u4e24\u5904\u8c03\u6574\uff0c\u662f\u4f7f\u7528\u4e86 pylint \u7684\u89c4\u5219\u7981\u7528\u529f\u80fd\uff0c\u5728\u8fd9\u4e24\u4e2a\u6a21\u5757\u4e0a\uff0c\u6291\u5236 pylint \u7684 too-few-public-methods
\u89c4\u5219\u3002
\u6b64\u65f6\u518d\u6b21\u8fd0\u884c pylint
\u68c0\u67e5\u4ee3\u7801\uff1a
$ pylint src tests\n\n-------------------------------------------------------------------\nYour code has been rated at 10.00/10 (previous run: 9.89/10, +0.11)\n
\u53ef\u4ee5\u770b\u5230\u4ee3\u7801\u90fd\u6b63\u5e38\u4e86\u3002\u8fd9\u662f\u7b26\u5408\u6211\u4eec\u7684\u9884\u671f\u7684\u3002
"},{"location":"guidelines/tutorial/develop/#_8","title":"\u63d0\u4ea4\u4ee3\u7801","text":"\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u901a\u8fc7\u62bd\u8c61 ETL \u903b\u8f91\u4ee3\u7801\uff0c\u5e76\u6839\u636e\u5177\u4f53\u4e1a\u52a1\u505a\u4e00\u4e2a\u5b9e\u73b0\uff0c\u7136\u540e\u5c06\u5b9e\u73b0\u6ce8\u518c\u5230\u73af\u5883\u4e2d\uff0c\u5e76\u6839\u636e\u914d\u7f6e\u8c03\u7528\u5177\u4f53\u5b9e\u73b0\u3002
\u6b64\u65f6\u9879\u76ee\u7ed3\u6784\u5982\u4e0b\uff1a
example_etl\n\u251c\u2500\u2500 .editorconfig\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 .pre-commit-config.yaml\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 all.log\n\u251c\u2500\u2500 docs\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 development.md\n\u251c\u2500\u2500 poetry.lock\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 src\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 example_etl\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 cmdline.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 config\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 settings.yml\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 constants.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 exceptions.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 extractor\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 base.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 file.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 loader\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 base.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 file.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 log.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 manage.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 transformer\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 base.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 strip.py\n\u251c\u2500\u2500 tests\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 conftest.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 settings.yml\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 test_cmdline.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 test_log.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 test_version.py\n\u2514\u2500\u2500 tox.ini\n
\u63d0\u53ca\u672c\u6b21\u529f\u80fd\uff1a
git add .\ngit commit -m \"feat: add etl logic.\"\n
"},{"location":"guidelines/tutorial/init_project/","title":"\u521d\u59cb\u5316\u9879\u76ee","text":"\u5728\u672c\u7ae0\u8282\uff0c\u4f60\u8bb2\u5b66\u4e60\u5230\u4e00\u4e0b\u5185\u5bb9\uff1a
- \u4f7f\u7528 cookiecutter \u521d\u59cb\u5316\u4e00\u4e2a\u9879\u76ee\u7ed3\u6784
- \u67e5\u770b\u521d\u59cb\u5316\u9879\u76ee\u91cc\u9762\u7684\u4e3b\u8981\u6587\u4ef6\u5185\u5bb9
- \u5bf9\u9879\u76ee\u8fdb\u884c git \u521d\u59cb\u5316\u548c\u5185\u5bb9\u63d0\u4ea4
- \u4f7f\u7528 poetry \u521d\u59cb\u5316\u9879\u76ee\u7684 python \u73af\u5883
- \u4f7f\u7528 tox \u81ea\u52a8\u5316\u6d4b\u8bd5\u9879\u76ee\uff0c\u68c0\u67e5\u521d\u59cb\u5316\u7684\u9879\u76ee\u6709\u6ca1\u6709\u95ee\u9898
\u521d\u59cb\u5316\u9879\u76ee\u65f6\uff0c\u4f7f\u7528 cookiecutter \u52a0\u8f7d \u9879\u76ee\u6a21\u677f \u521b\u5efa\u3002 \u901a\u8fc7\u4ea4\u4e92\u64cd\u4f5c\uff0c\u53ef\u4ee5\u9009\u62e9\u4f7f\u7528\u7684\u529f\u80fd\u3002
"},{"location":"guidelines/tutorial/init_project/#_2","title":"\u521b\u5efa\u9879\u76ee\u9aa8\u67b6","text":"\u5728\u7ec8\u7aef\u8fd0\u884c\u547d\u4ee4\uff1a
cookiecutter https://github.com/pyloong/cookiecutter-pythonic-project\n
\u7136\u540e\u6839\u636e\u4ea4\u4e92\u63d0\u793a\uff0c\u9009\u62e9\u9700\u8981\u7684\u5185\u5bb9\u3002\u6700\u7ec8\u8f93\u5165\u5982\u4e0b\uff1a
\u276f cookiecutter https://github.com/pyloong/cookiecutter-pythonic-project\nproject_name [My Project]: example-etl\nproject_slug [example_etl]: \nproject_description [My Awesome Project!]: This is my first etl project.\nauthor_name [Author]: test\nauthor_email [test@example.com]: test@example.com\nversion [0.1.0]: \nSelect python_version:\n1 - 3.10\n2 - 3.11\nChoose from 1, 2 [1]: \nuse_src_layout [y]: \nuse_poetry [y]: \nuse_docker [n]: \nSelect ci_tools:\n1 - none\n2 - Gitlab\n3 - Github\nChoose from 1, 2, 3 [1]: \ninit_skeleton [n]: y\n
\u7136\u540e\u4f7f\u7528 vscode \u6253\u5f00\u9879\u76ee\uff1a
code example_etl\n
\u5efa\u8bae\u5728\u9879\u76ee\u5f00\u59cb\u7684\u65f6\u5019\u5c31\u521d\u59cb\u5316 git \u4ed3\u5e93\uff0c\u5e76\u5728\u540e\u7eed\u53ca\u65f6\u63d0\u4ea4\u529f\u80fd\u4fee\u6539\u3002
git init\ngit config user.name test\ngit config user.email test@example.com\ngit commit -m \"feat: init project.\"\n
"},{"location":"guidelines/tutorial/init_project/#_3","title":"\u9879\u76ee\u5185\u5bb9","text":"\u5728 IDE \u4e2d\u67e5\u770b\u9879\u76ee\uff0c\u53ef\u4ee5\u770b\u5230\u76ee\u5f55\u7ed3\u6784\u5982\u4e0b\uff1a
\u276f tree example_etl\nexample_etl\n\u251c\u2500\u2500 .editorconfig\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 .pre-commit-config.yaml\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 docs\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 development.md\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 src\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 example_etl\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 cmdline.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 config\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 settings.yml\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 log.py\n\u251c\u2500\u2500 tests\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 conftest.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 settings.yml\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 test_cmdline.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 test_log.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 test_version.py\n\u2514\u2500\u2500 tox.ini\n\n6 directories, 19 files\n
"},{"location":"guidelines/tutorial/init_project/#pyprojecttoml","title":"pyproject.toml","text":"pyproject.toml
\u662f\u9879\u76ee\u7684\u6253\u5305\u914d\u7f6e\u6587\u4ef6\u3002\u6587\u4ef6\u524d\u9762 tool.poetry
\u4e2d\u8bbe\u7f6e\u4e86\u9879\u76ee\u7684\u57fa\u672c\u4fe1\u606f\u3002 tool.poetry.dependencies
\u4e2d\u914d\u7f6e\u4e86\u6b64\u9879\u76ee\u7684\u4f9d\u8d56\u5e93\u3002
\u6587\u4ef6\u5185\u5bb9\u5982\u4e0b\uff1a
[tool.poetry]\nname = \"example_etl\"\nversion = \"0.1.0\"\ndescription = \"This is my first etl project.\"\nreadme = \"README.md\"\nauthors = [\"test <test@example.com>\"]\nlicense = \"MIT\"\nclassifiers = [\n\"Operating System :: OS Independent\",\n\"Programming Language :: Python :: 3.10\",\n]\n[tool.poetry.dependencies]\npython = \"^3.10\"\ndynaconf = \"^3.1.12\"\nclick = \"^8.1.3\"\n[tool.poetry.group.dev.dependencies]\npylint = \"^2.17.4\"\nisort = \"^5.12.0\"\npytest = \"^7.3.1\"\ntox = \"^4.5.2\"\nmkdocs = \"^1.4.3\"\nmkdocs-material = \"^8.5.11\"\npytest-pylint = \"^0.19.0\"\npre-commit = \"^3.3.2\"\n[tool.poetry.scripts]\nexample_etl = \"example_etl.cmdline:main\"\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n[tool.pytest.ini_options]\ntestpaths = \"tests\"\npython_files = \"tests.py test_*.py *_tests.py\"\n[tool.pylint.design]\nmax-line-length = 120\n
"},{"location":"guidelines/tutorial/init_project/#srcexample_etlcmdlinepy","title":"src/example_etl/cmdline.py","text":"src/example_etl/cmdline.py
\u662f\u4f7f\u7528 click
\u7f16\u5199\u7684\u4e00\u4e2a\u547d\u4ee4\u884c\u5165\u53e3\u6587\u4ef6\uff0c\u901a\u8fc7\u4e00\u4e9b\u81ea\u5b9a\u4e49\u547d\u4ee4\u548c\u53c2\u6570\u6765\u63a7\u5236\u7a0b\u5e8f\u7684\u903b\u8f91\u3002
\"\"\"Command line\"\"\"\nimport click\nfrom click import Context\nfrom example_etl import __version__\nfrom example_etl.config import settings\nfrom example_etl.log import init_log\n@click.group(invoke_without_command=True)\n@click.pass_context\n@click.option(\n'-V',\n'--version',\nis_flag=True,\nhelp='Show version and exit.'\n) # If it's true, it will override `settings.VERBOSE`\n@click.option('-v', '--verbose', is_flag=True, help='Show more info.')\n@click.option(\n'--debug',\nis_flag=True,\nhelp='Enable debug.'\n) # If it's true, it will override `settings.DEBUG`\ndef main(ctx: Context, version: str, verbose: bool, debug: bool):\n\"\"\"Main commands\"\"\"\nif version:\nclick.echo(__version__)\nelif ctx.invoked_subcommand is None:\nclick.echo(ctx.get_help())\nelse:\nif verbose:\nsettings.set('VERBOSE', True)\nif debug:\nsettings.set('DEBUG', True)\n@main.command()\ndef run():\n\"\"\"Run command\"\"\"\ninit_log()\nclick.echo('run......')\n
"},{"location":"guidelines/tutorial/init_project/#srcexample_etllogpy","title":"src/example_etl/log.py","text":"src/example_etl/log.py
\u662f\u9884\u5b9a\u4e49\u65e5\u5fd7\u914d\u7f6e\u6587\u4ef6\uff0c\u5f53\u9879\u76ee\u542f\u52a8\u65f6\uff0c\u4f1a\u81ea\u52a8\u521d\u59cb\u5316\u9ed8\u8ba4\u7684\u65e5\u5fd7\u914d\u7f6e\u3002
\"\"\"Log\"\"\"\nimport logging\nimport os\nfrom logging.config import dictConfig\nfrom example_etl.config import settings\nos.makedirs(settings.LOGPATH, exist_ok=True)\ndef verbose_formatter(verbose: int) -> str:\n\"\"\"formatter factory\"\"\"\nif verbose is True:\nreturn 'verbose'\nreturn 'simple'\ndef update_log_level(debug: bool, level: str) -> str:\n\"\"\"update log level\"\"\"\nif debug is True:\nlevel_num = logging.DEBUG\nelse:\nlevel_num = logging.getLevelName(level)\nsettings.set('LOGLEVEL', logging.getLevelName(level_num))\nreturn settings.LOGLEVEL\ndef init_log() -> None:\n\"\"\"Init log config.\"\"\"\nlog_level = update_log_level(settings.DEBUG, str(settings.LOGLEVEL).upper())\nlog_config = {\n\"version\": 1,\n\"disable_existing_loggers\": False,\n\"formatters\": {\n'verbose': {\n'format': '%(asctime)s %(levelname)s %(name)s %(process)d %(thread)d %(message)s',\n},\n'simple': {\n'format': '%(asctime)s %(levelname)s %(name)s %(message)s',\n},\n},\n\"handlers\": {\n\"console\": {\n\"formatter\": verbose_formatter(settings.VERBOSE),\n'level': 'DEBUG',\n\"class\": \"logging.StreamHandler\",\n},\n'file': {\n'class': 'logging.handlers.RotatingFileHandler',\n'level': 'DEBUG',\n'formatter': verbose_formatter(settings.VERBOSE),\n'filename': os.path.join(settings.LOGPATH, 'all.log'),\n'maxBytes': 1024 * 1024 * 1024 * 200, # 200M\n'backupCount': '5',\n'encoding': 'utf-8'\n},\n},\n\"loggers\": {\n'': {'level': log_level, 'handlers': ['console']},\n}\n}\ndictConfig(log_config)\n
"},{"location":"guidelines/tutorial/init_project/#srcexample_etlconfig__init__py","title":"src/example_etl/config/__init__.py","text":"src/example_etl/config/__init__.py
\u662f\u4f7f\u7528 dynaconf
\u521d\u59cb\u5316\u7684\u914d\u7f6e\u4e2d\u5fc3\uff0c\u9879\u76ee\u6240\u6709\u7684\u914d\u7f6e\u90fd\u662f \u4ece settings
\u5bf9\u8c61\u4e2d\u83b7\u53d6\uff0c\u5b83\u4f1a\u8bfb\u53d6\u9879\u76ee\u7ea7\u522b\u7684\u9ed8\u8ba4\u914d\u7f6e\u6587\u4ef6\uff0c\u4e5f\u4f1a\u8bfb\u53d6\u81ea\u5b9a\u4e49\u914d\u7f6e\u6587\u4ef6\u3002
\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u52a0\u8f7d\u7684\u914d\u7f6e\u6587\u4ef6\u5982\u4e0b\uff1a
src/example_etl/config/settings.yml
\uff1a\u9879\u76ee\u9ed8\u8ba4\u914d\u7f6e\u6587\u4ef6 src/example_etl/config/settings.local.yml
\uff1a\u8fd9\u4e2a\u5728\u9879\u76ee\u4e2d\u662f\u4e0d\u4f1a git \u8ffd\u8e2a\u7684\uff0c\u5c5e\u4e8e\u672c\u5730\u81ea\u5b9a\u4e49\u914d\u7f6e <sys.prefix>/etc/example_etl/settings.yml
\uff1a\u64cd\u4f5c\u7cfb\u7edf\u5916\u90e8\u914d\u7f6e\u6587\u4ef6\u3002\u9ed8\u8ba4\u8fd9\u4e2a\u914d\u7f6e\u6587\u4ef6\u548c\u9879\u76ee\u9ed8\u8ba4\u914d\u7f6e\u6587\u4ef6\u7684\u5185\u5bb9\u4e00\u81f4\u3002 - \u4f7f\u7528
EXAMPLE_ETL_<name>=<value>
\u73af\u5883\u53d8\u91cf\u4f20\u9012
\u4f18\u5148\u7ea7\u4ece\u4ece\u4e0a\u5012\u4e0b\u4f9d\u6b21\u589e\u5927\uff0c\u4f18\u5148\u7ea7\u9ad8\u7684\u4f1a\u8986\u76d6\u4f18\u5148\u7ea7\u4f4e\u7684\u914d\u7f6e\u3002
\"\"\"\nConfiguration center.\nUse https://www.dynaconf.com/\n\"\"\"\"\"\nimport os\nimport sys\nfrom pathlib import Path\nfrom dynaconf import Dynaconf\n_base_dir = Path(__file__).parent.parent\n_settings_files = [\n# All config file will merge.\nPath(__file__).parent / 'settings.yml', # Load default config.\n]\n# User configuration. It will be created automatically by the pip installer .\n_external_files = [\nPath(sys.prefix, 'etc', 'example_etl', 'settings.yml')\n]\nsettings = Dynaconf(\n# Set env `EXAMPLE_ETL_FOO='bar'`\uff0cuse `settings.FOO` .\nenvvar_prefix='EXAMPLE_ETL',\nsettings_files=_settings_files, # load user configuration.\n# environments=True, # Enable multi-level configuration\uff0ceg: default, development, production\nload_dotenv=True, # Enable load .env\n# env_switcher='EXAMPLE_ETL_ENV',\nlowercase_read=False, # If true, can't use `settings.foo`, but can only use `settings.FOO`\nincludes=_external_files, # Customs settings.\nbase_dir=_base_dir, # `settings.BASE_DIR`\n)\n
"},{"location":"guidelines/tutorial/init_project/#_4","title":"\u521d\u59cb\u5316\u73af\u5883","text":"\u9879\u76ee\u4f7f\u7528 poetry
\u7ba1\u7406\u865a\u62df\u73af\u5883\uff0c\u8fd0\u884c\u547d\u4ee4\u81ea\u52a8\u521b\u5efa\u865a\u62df\u73af\u5883\uff0c\u540c\u65f6\u5b89\u88c5\u5f00\u53d1\u73af\u5883\u4f9d\u8d56
"},{"location":"guidelines/tutorial/init_project/#_5","title":"\u5b89\u88c5\u9879\u76ee\u9ed8\u8ba4\u4f9d\u8d56","text":"\u5728\u9879\u76ee\u76ee\u5f55\u6267\u884c poetry install -v
\u4ee5\u53ef\u89c6\u5316\u8fc7\u7a0b\u5b89\u88c5\u4f9d\u8d56\uff1a
$ poetry install -v\nCreating virtualenv example-etl-B-7RVLBy-py3.10 in /Users/kevin/Library/Caches/pypoetry/virtualenvs\nUsing virtualenv: /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10\nUpdating dependencies\nResolving dependencies... (7.5s)\nFinding the necessary packages for the current system\n\nPackage operations: 52 installs, 1 update, 0 removals\n\n\u2022 Installing six (1.16.0)\n\u2022 Installing lazy-object-proxy (1.9.0)\n\u2022 Installing markupsafe (2.1.2)\n\u2022 Installing python-dateutil (2.8.2)\n\u2022 Installing pyyaml (6.0)\n\u2022 Installing typing-extensions (4.6.2)\n\u2022 Installing wrapt (1.15.0)\n\u2022 Installing astroid (2.15.5)\n\u2022 Installing certifi (2023.5.7)\n\u2022 Installing charset-normalizer (3.1.0)\n\u2022 Installing click (8.1.3)\n\u2022 Installing dill (0.3.6)\n\u2022 Installing filelock (3.12.0)\n\u2022 Installing exceptiongroup (1.1.1)\n\u2022 Installing ghp-import (2.1.0)\n\u2022 Installing idna (3.4)\n\u2022 Installing distlib (0.3.6)\n\u2022 Installing iniconfig (2.0.0)\n\u2022 Installing isort (5.12.0)\n\u2022 Installing jinja2 (3.1.2)\n\u2022 Installing markdown (3.3.7)\n\u2022 Installing mccabe (0.7.0)\n\u2022 Installing mergedeep (1.3.4)\n\u2022 Installing packaging (23.1)\n\u2022 Installing platformdirs (3.5.1)\n\u2022 Installing pyyaml-env-tag (0.1)\n\u2022 Installing pluggy (1.0.0)\n\u2022 Updating setuptools (67.7.2 -> 67.8.0)\n\u2022 Installing tomli (2.0.1)\n\u2022 Installing urllib3 (2.0.2)\n\u2022 Installing tomlkit (0.11.8)\n\u2022 Installing watchdog (3.0.0)\n\u2022 Installing cachetools (5.3.1)\n\u2022 Installing cfgv (3.3.1)\n\u2022 Installing chardet (5.1.0)\n\u2022 Installing colorama (0.4.6)\n\u2022 Installing identify (2.5.24)\n\u2022 Installing mkdocs (1.4.3)\n\u2022 Installing mkdocs-material-extensions (1.1.1)\n\u2022 Installing nodeenv (1.8.0)\n\u2022 Installing pygments (2.15.1)\n\u2022 Installing pylint (2.17.4)\n\u2022 Installing pymdown-extensions (10.0.1)\n\u2022 Installing pyproject-api (1.5.1)\n\u2022 Installing pytest (7.3.1)\n\u2022 Installing requests (2.31.0)\n\u2022 Installing toml (0.10.2)\n\u2022 Installing virtualenv (20.23.0)\n\u2022 Installing dynaconf (3.1.12)\n\u2022 Installing mkdocs-material (8.5.11)\n\u2022 Installing pre-commit (3.3.2)\n\u2022 Installing pytest-pylint (0.19.0)\n\u2022 Installing tox (4.5.2)\nWriting lock file\n\nInstalling the current project: example_etl (0.1.0)\n
\u7136\u540e\u6267\u884c poetry shell
\u8fdb\u5165\u5230\u865a\u62df\u73af\u5883\u3002
\u5728\u4f7f\u7528 vscode \u7684\u65f6\u5019\uff0c\u53ef\u4ee5\u8fd0\u884c Ctrl + Shift + p
\u6253\u5f00\u6307\u4ee4\uff0c\u8f93\u5165 > Python: Select Interpreter
\u9009\u62e9\u521a\u521a\u521b\u5efa\u7684\u865a\u62df\u73af\u5883\u3002 \u5982\u679c\u770b\u4e0d\u5230\uff0c\u53ea\u9700\u8981\u70b9\u51fb\u65c1\u8fb9\u7684\u5237\u65b0\u6309\u94ae\u5373\u53ef\u3002\u7136\u540e\u91cd\u65b0\u6253\u5f00\u4e00\u4e2a\u65b0\u7684\u7ec8\u7aef\uff0c\u4f1a\u81ea\u52a8\u8fdb\u5165\u865a\u62df\u73af\u5883\u3002
"},{"location":"guidelines/tutorial/init_project/#_6","title":"\u8fd0\u884c\u6d4b\u8bd5","text":"\u4e3a\u4e86\u4fdd\u8bc1\u521d\u59cb\u5316\u9879\u76ee\u662f\u6b63\u5e38\u7684\uff0c\u5728\u8fdb\u884c\u540e\u7eed\u6b65\u9aa4\u4e4b\u524d\uff0c\u8fd0\u884c\u81ea\u52a8\u5316\u6d4b\u8bd5\u903b\u8f91\uff1a
tox\n
\u53ef\u4ee5\u770b\u5230\u6700\u540e\u8f93\u51fa\u5982\u4e0b\uff1a
$ tox\npy310: install_deps> python -I -m pip install poetry\n.pkg: install_requires> python -I -m pip install poetry-core\n.pkg: _optional_hooks> python /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pyproject_api/_backend.py True poetry.core.masonry.api\n.pkg: get_requires_for_build_sdist> python /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pyproject_api/_backend.py True poetry.core.masonry.api\n.pkg: prepare_metadata_for_build_wheel> python /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pyproject_api/_backend.py True poetry.core.masonry.api\n.pkg: build_sdist> python /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pyproject_api/_backend.py True poetry.core.masonry.api\npy310: install_package_deps> python -I -m pip install 'click<9.0.0,>=8.1.3' 'dynaconf<4.0.0,>=3.1.12'\npy310: install_package> python -I -m pip install --force-reinstall --no-deps /private/tmp/example_etl/.tox/.tmp/package/1/example_etl-0.1.0.tar.gz\npy310: commands[0]> poetry install -v\nUsing virtualenv: /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10\nInstalling dependencies from lock file\n\nFinding the necessary packages for the current system\n\nPackage operations: 0 installs, 0 updates, 0 removals, 53 skipped\n\n\u2022 Installing astroid (2.15.5): Skipped for the following reason: Already installed\n \u2022 Installing identify (2.5.24): Skipped for the following reason: Already installed\n \u2022 Installing chardet (5.1.0): Skipped for the following reason: Already installed\n \u2022 Installing charset-normalizer (3.1.0): Skipped for the following reason: Already installed\n \u2022 Installing click (8.1.3): Skipped for the following reason: Already installed\n \u2022 Installing colorama (0.4.6): Skipped for the following reason: Already installed\n \u2022 Installing distlib (0.3.6): Skipped for the following reason: Already installed\n \u2022 Installing isort (5.12.0): Skipped for the following reason: Already installed\n \u2022 Installing jinja2 (3.1.2): Skipped for the following reason: Already installed\n \u2022 Installing idna (3.4): Skipped for the following reason: Already installed\n \u2022 Installing certifi (2023.5.7): Skipped for the following reason: Already installed\n \u2022 Installing iniconfig (2.0.0): Skipped for the following reason: Already installed\n \u2022 Installing lazy-object-proxy (1.9.0): Skipped for the following reason: Already installed\n \u2022 Installing mkdocs-material (8.5.11): Skipped for the following reason: Already installed\n \u2022 Installing dynaconf (3.1.12): Skipped for the following reason: Already installed\n \u2022 Installing mkdocs-material-extensions (1.1.1): Skipped for the following reason: Already installed\n \u2022 Installing markdown (3.3.7): Skipped for the following reason: Already installed\n \u2022 Installing packaging (23.1): Skipped for the following reason: Already installed\n \u2022 Installing mergedeep (1.3.4): Skipped for the following reason: Already installed\n \u2022 Installing mkdocs (1.4.3): Skipped for the following reason: Already installed\n \u2022 Installing pre-commit (3.3.2): Skipped for the following reason: Already installed\n \u2022 Installing nodeenv (1.8.0): Skipped for the following reason: Already installed\n \u2022 Installing filelock (3.12.0): Skipped for the following reason: Already installed\n \u2022 Installing mccabe (0.7.0): Skipped for the following reason: Already installed\n \u2022 Installing markupsafe (2.1.2): Skipped for the following reason: Already installed\n \u2022 Installing pytest (7.3.1): Skipped for the following reason: Already installed\n \u2022 Installing pytest-pylint (0.19.0): Skipped for the following reason: Already installed\n \u2022 Installing ghp-import (2.1.0): Skipped for the following reason: Already installed\n \u2022 Installing pylint (2.17.4): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml-env-tag (0.1): Skipped for the following reason: Already installed\n \u2022 Installing requests (2.31.0): Skipped for the following reason: Already installed\n \u2022 Installing dill (0.3.6): Skipped for the following reason: Already installed\n \u2022 Installing platformdirs (3.5.1): Skipped for the following reason: Already installed\n \u2022 Installing exceptiongroup (1.1.1): Skipped for the following reason: Already installed\n \u2022 Installing python-dateutil (2.8.2): Skipped for the following reason: Already installed\n \u2022 Installing pygments (2.15.1): Skipped for the following reason: Already installed\n \u2022 Installing pyproject-api (1.5.1): Skipped for the following reason: Already installed\n \u2022 Installing cfgv (3.3.1): Skipped for the following reason: Already installed\n \u2022 Installing six (1.16.0): Skipped for the following reason: Already installed\n \u2022 Installing virtualenv (20.23.0): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml (6.0): Skipped for the following reason: Already installed\n \u2022 Installing cachetools (5.3.1): Skipped for the following reason: Already installed\n \u2022 Installing tomlkit (0.11.8): Skipped for the following reason: Already installed\n \u2022 Installing tox (4.5.2): Skipped for the following reason: Already installed\n \u2022 Installing urllib3 (2.0.2): Skipped for the following reason: Already installed\n \u2022 Installing setuptools (67.8.0): Skipped for the following reason: Already installed\n \u2022 Installing toml (0.10.2): Skipped for the following reason: Already installed\n \u2022 Installing wrapt (1.15.0): Skipped for the following reason: Already installed\n \u2022 Installing typing-extensions (4.6.2): Skipped for the following reason: Already installed\n \u2022 Installing pymdown-extensions (10.0.1): Skipped for the following reason: Already installed\n \u2022 Installing watchdog (3.0.0): Skipped for the following reason: Already installed\n \u2022 Installing tomli (2.0.1): Skipped for the following reason: Already installed\n \u2022 Installing pluggy (1.0.0): Skipped for the following reason: Already installed\n\nInstalling the current project: example_etl (0.1.0)\npy310: commands[1]> poetry run pytest tests\n================================================================================================================================= test session starts =================================================================================================================================\nplatform darwin -- Python 3.10.11, pytest-7.3.1, pluggy-1.0.0\ncachedir: .tox/py310/.pytest_cache\nrootdir: /private/tmp/example_etl\nconfigfile: pyproject.toml\nplugins: pylint-0.19.0\ncollected 10 items tests/test_cmdline.py ..... [ 50%]\ntests/test_exceptions.py . [ 50%]\ntests/test_log.py ..... [ 91%]\ntests/test_version.py . [100%]\n================================================================================================================================== warnings summary ===================================================================================================================================\n../../../Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pkg_resources/__init__.py:121\n /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pkg_resources/__init__.py:121: DeprecationWarning: pkg_resources is deprecated as an API\n warnings.warn(\"pkg_resources is deprecated as an API\", DeprecationWarning)\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n============================================================================================================================ 10 passed, 1 warning in 0.01s ============================================================================================================================\npy310: OK \u2714 in 14.05 seconds\nisort: install_deps> python -I -m pip install isort\nisort: install_package_deps> python -I -m pip install 'click<9.0.0,>=8.1.3' 'dynaconf<4.0.0,>=3.1.12'\nisort: install_package> python -I -m pip install --force-reinstall --no-deps /private/tmp/example_etl/.tox/.tmp/package/2/example_etl-0.1.0.tar.gz\nisort: commands[0]> isort . --check-only --diff\nSkipped 1 files\nisort: OK \u2714 in 3.05 seconds\npylint: install_deps> python -I -m pip install poetry\npylint: install_package_deps> python -I -m pip install 'click<9.0.0,>=8.1.3' 'dynaconf<4.0.0,>=3.1.12'\npylint: install_package> python -I -m pip install --force-reinstall --no-deps /private/tmp/example_etl/.tox/.tmp/package/3/example_etl-0.1.0.tar.gz\npylint: commands[0]> poetry install -v\nUsing virtualenv: /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10\nInstalling dependencies from lock file\n\nFinding the necessary packages for the current system\n\nPackage operations: 0 installs, 0 updates, 0 removals, 53 skipped\n\n\u2022 Installing astroid (2.15.5): Skipped for the following reason: Already installed\n \u2022 Installing certifi (2023.5.7): Skipped for the following reason: Already installed\n \u2022 Installing cfgv (3.3.1): Skipped for the following reason: Already installed\n \u2022 Installing cachetools (5.3.1): Skipped for the following reason: Already installed\n \u2022 Installing dill (0.3.6): Skipped for the following reason: Already installed\n \u2022 Installing click (8.1.3): Skipped for the following reason: Already installed\n \u2022 Installing colorama (0.4.6): Skipped for the following reason: Already installed\n \u2022 Installing chardet (5.1.0): Skipped for the following reason: Already installed\n \u2022 Installing distlib (0.3.6): Skipped for the following reason: Already installed\n \u2022 Installing markdown (3.3.7): Skipped for the following reason: Already installed\n \u2022 Installing filelock (3.12.0): Skipped for the following reason: Already installed\n \u2022 Installing identify (2.5.24): Skipped for the following reason: Already installed\n \u2022 Installing idna (3.4): Skipped for the following reason: Already installed\n \u2022 Installing isort (5.12.0): Skipped for the following reason: Already installed\n \u2022 Installing charset-normalizer (3.1.0): Skipped for the following reason: Already installed\n \u2022 Installing dynaconf (3.1.12): Skipped for the following reason: Already installed\n \u2022 Installing markupsafe (2.1.2): Skipped for the following reason: Already installed\n \u2022 Installing ghp-import (2.1.0): Skipped for the following reason: Already installed\n \u2022 Installing mergedeep (1.3.4): Skipped for the following reason: Already installed\n \u2022 Installing iniconfig (2.0.0): Skipped for the following reason: Already installed\n \u2022 Installing mkdocs-material (8.5.11): Skipped for the following reason: Already installed\n \u2022 Installing nodeenv (1.8.0): Skipped for the following reason: Already installed\n \u2022 Installing exceptiongroup (1.1.1): Skipped for the following reason: Already installed\n \u2022 Installing pyproject-api (1.5.1): Skipped for the following reason: Already installed\n \u2022 Installing pluggy (1.0.0): Skipped for the following reason: Already installed\n \u2022 Installing python-dateutil (2.8.2): Skipped for the following reason: Already installed\n \u2022 Installing jinja2 (3.1.2): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml-env-tag (0.1): Pending...\n \u2022 Installing pymdown-extensions (10.0.1): Skipped for the following reason: Already installed\n \u2022 Installing packaging (23.1): Skipped for the following reason: Already installed\n \u2022 Installing mccabe (0.7.0): Skipped for the following reason: Already installed\n \u2022 Installing pytest-pylint (0.19.0): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml (6.0): Skipped for the following reason: Already installed\n \u2022 Installing pygments (2.15.1): Skipped for the following reason: Already installed\n \u2022 Installing pymdown-extensions (10.0.1): Skipped for the following reason: Already installed\n \u2022 Installing packaging (23.1): Skipped for the following reason: Already installed\n \u2022 Installing mccabe (0.7.0): Skipped for the following reason: Already installed\n \u2022 Installing pytest-pylint (0.19.0): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml (6.0): Skipped for the following reason: Already installed\n \u2022 Installing pygments (2.15.1): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml-env-tag (0.1): Skipped for the following reason: Already installed\n \u2022 Installing pymdown-extensions (10.0.1): Skipped for the following reason: Already installed\n \u2022 Installing packaging (23.1): Skipped for the following reason: Already installed\n \u2022 Installing mccabe (0.7.0): Skipped for the following reason: Already installed\n \u2022 Installing pytest-pylint (0.19.0): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml (6.0): Skipped for the following reason: Already installed\n \u2022 Installing pygments (2.15.1): Skipped for the following reason: Already installed\n \u2022 Installing pylint (2.17.4): Skipped for the following reason: Already installed\n \u2022 Installing tox (4.5.2): Skipped for the following reason: Already installed\n \u2022 Installing setuptools (67.8.0): Skipped for the following reason: Already installed\n \u2022 Installing platformdirs (3.5.1): Skipped for the following reason: Already installed\n \u2022 Installing toml (0.10.2): Skipped for the following reason: Already installed\n \u2022 Installing watchdog (3.0.0): Skipped for the following reason: Already installed\n \u2022 Installing tomlkit (0.11.8): Skipped for the following reason: Already installed\n \u2022 Installing mkdocs-material-extensions (1.1.1): Skipped for the following reason: Already installed\n \u2022 Installing typing-extensions (4.6.2): Skipped for the following reason: Already installed\n \u2022 Installing urllib3 (2.0.2): Skipped for the following reason: Already installed\n \u2022 Installing lazy-object-proxy (1.9.0): Skipped for the following reason: Already installed\n \u2022 Installing six (1.16.0): Skipped for the following reason: Already installed\n \u2022 Installing tomli (2.0.1): Skipped for the following reason: Already installed\n \u2022 Installing mkdocs (1.4.3): Skipped for the following reason: Already installed\n \u2022 Installing requests (2.31.0): Skipped for the following reason: Already installed\n \u2022 Installing pytest (7.3.1): Skipped for the following reason: Already installed\n \u2022 Installing virtualenv (20.23.0): Skipped for the following reason: Already installed\n \u2022 Installing wrapt (1.15.0): Skipped for the following reason: Already installed\n \u2022 Installing pre-commit (3.3.2): Skipped for the following reason: Already installed\n\nInstalling the current project: example_etl (0.1.0)\npylint: commands[1]> poetry run pylint tests src\n\n--------------------------------------------------------------------\nYour code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)\n.pkg: _exit> python /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pyproject_api/_backend.py True poetry.core.masonry.api\n py310: OK (14.05=setup[9.47]+cmd[3.84,0.75] seconds)\nisort: OK (3.05=setup[2.67]+cmd[0.38] seconds)\npylint: OK (12.92=setup[7.86]+cmd[2.91,2.15] seconds)\ncongratulations :) (30.10 seconds)\n
\u81f3\u6b64\uff0c\u9879\u76ee\u73af\u5883\u521d\u59cb\u5316\u5b8c\u6210\u3002\u4e00\u5207\u6b63\u5e38\u3002
"},{"location":"guidelines/tutorial/init_project/#_7","title":"\u63d0\u4ea4\u4ee3\u7801","text":"\u5728\u5f00\u53d1\u65f6\uff0c\u9700\u8981\u517b\u6210\u53ca\u65f6\u63d0\u4ea4\u4ee3\u7801\u7684\u597d\u4e60\u60ef\u3002
git add .\ngit commit -m \"feat: init project env.\"\n
"},{"location":"guidelines/tutorial/publish/","title":"\u6253\u5305\u53d1\u5e03","text":"\u672c\u7ae0\u8282\uff0c\u4f60\u53ef\u4ee5\u5b66\u5230\uff1a
- \u4f7f\u7528 poetry \u6784\u5efa\u9879\u76ee
- \u4f7f\u7528 poetry \u5c06\u9879\u76ee\u53d1\u5e03\u5230 pypi
- \u5b89\u88c5\u5e76\u4f7f\u7528\u4f60\u53d1\u5e03\u5230 pypi \u7684\u9879\u76ee
\u9879\u76ee\u5f00\u53d1\u6d4b\u8bd5\u5b8c\u6210\u540e\uff0c\u53ef\u4ee5\u5c06\u9879\u76ee\u53d1\u5e03\u5230 Pypi \u4ed3\u5e93\u4e2d\uff0c\u7136\u540e\u5728\u5176\u4ed6\u5730\u65b9\u901a\u8fc7 Pip \u547d\u4ee4\u5373\u53ef \u5b89\u88c5\u4f7f\u7528\u3002\u5bf9\u4e8e\u4e00\u4e9b\u5de5\u5177\u5305\u6bd4\u8f83\u65b9\u4fbf\u3002
"},{"location":"guidelines/tutorial/publish/#_2","title":"\u6253\u5305","text":"\u6839\u636e PEP 517 \u89c4\u8303\uff0c\u65b0\u7684\u6253\u5305\u673a\u5236\u53ef\u901a\u8fc7 poetry
\u5de5\u5177\u6765\u64cd\u4f5c\u3002
\u6267\u884c\u6784\u5efa\u547d\u4ee4\uff1a
$ poetry build\nBuilding example_etl (0.1.0)\n- Building sdist\n - Built example_etl-0.1.0.tar.gz\n - Building wheel\n - Built example_etl-0.1.0-py3-none-any.whl\n
"},{"location":"guidelines/tutorial/publish/#_3","title":"\u53d1\u5e03","text":"\u672c\u9879\u76ee\u4e3a\u6d4b\u8bd5\u9879\u76ee\uff0c\u4ee5\u4e0b\u64cd\u4f5c\u53ef\u4ee5\u5c06\u9879\u76ee\u53d1\u5e03\u5230 https://test.pypi.org/ \u3002
\u9996\u5148\u5728 https://test.pypi.org/ \u6ce8\u518c\u8d26\u53f7\u3002
\u7136\u540e\u914d\u7f6e poetry \u53d1\u5e03\u7684\u4ed3\u5e93\uff1a
poetry config repositories.testpypi https://test.pypi.org/legacy/\n
\u7136\u540e\u586b\u5199\u6839\u636e\u4f60\u7684\u7528\u6237\u540d\u5bc6\u7801\u4fee\u6539\u5982\u4e0b\u547d\u4ee4\u540e\uff0c\u5c06\u9879\u76ee\u53d1\u5e03\u5230 testpypi \u4e0a\u3002
poetry publish --repository=testpypi --username=USERNAME --password=PASSWORD\n
\u6ce8\u610f\uff1a\u4e0d\u5efa\u8bae\u5c06\u6d4b\u8bd5\u9879\u76ee\u53d1\u5e03\u90fd https://pypi.org/ \u4e0a\u3002\u5728 https://pypi.org/ \u4e0a\u7684\u9879\u76ee\u540d\u79f0\u662f\u5168\u5c40\u552f\u4e00\u7684\uff0c \u6240\u4ee5\u5982\u679c\u4f60\u8003\u8651\u5230\u5c06\u9879\u76ee\u53d1\u5e03\u5230 https://pypi.org/ \u4e0a\u524d\uff0c\u5e94\u5b9a\u4e00\u4e2a\u4e0d\u5b58\u5728\u540d\u79f0\u3002
"},{"location":"guidelines/tutorial/publish/#_4","title":"\u5b89\u88c5\u6d4b\u8bd5","text":"\u9879\u76ee\u6b63\u5e38\u53d1\u5e03\u540e\uff0c\u53ef\u4ee5\u901a\u8fc7 pip \u5b89\u88c5\u5230\u672c\u5730\u4f7f\u7528\uff1a
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple example-etl\n
\u7531\u4e8e\u6211\u4eec\u4f7f\u7528\u7684\u662f\u6d4b\u8bd5\u4ed3\u5e93\uff0c\u6240\u4ee5\u9700\u8981\u6307\u5b9a --index-url https://test.pypi.org/simple/
\u53c2\u6570\uff0c\u7528\u6765\u5b89\u88c5\u53d1\u5e03\u5230 https://test.pypi.org/ \u7684\u5305 \u540c\u65f6\u4f7f\u7528 --extra-index-url https://pypi.org/simple
\u53c2\u6570\uff0c\u6765\u5b89\u88c5 example-etl
\u4f9d\u8d56\u7684\u5305\u3002
\u8f93\u51fa\u7ed3\u679c\u5982\u4e0b\uff1a
\u276f pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple example-etl\nLooking in indexes: https://test.pypi.org/simple/, https://pypi.org/simple\nCollecting example-etl\n Downloading https://test-files.pythonhosted.org/packages/f3/51/8cea9e34ae2f0e48abb6a0aa58cdf29d4d2900bdd97e45b8d4ee24b357f0/example_etl-0.1.0-py3-none-any.whl (9.5 kB)\nCollecting stevedore<6.0.0,>=5.1.0\n Downloading stevedore-5.1.0-py3-none-any.whl (49 kB)\n \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 49.6/49.6 kB 195.3 kB/s eta 0:00:00\nCollecting click<9.0.0,>=8.1.3\n Downloading click-8.1.3-py3-none-any.whl (96 kB)\n \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 96.6/96.6 kB 389.6 kB/s eta 0:00:00\nCollecting dynaconf<4.0.0,>=3.1.12\n Downloading dynaconf-3.1.12-py2.py3-none-any.whl (211 kB)\n \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 211.8/211.8 kB 878.8 kB/s eta 0:00:00\nCollecting pbr!=2.1.0,>=2.0.0\n Downloading pbr-5.11.1-py2.py3-none-any.whl (112 kB)\n \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 112.7/112.7 kB 1.7 MB/s eta 0:00:00\nInstalling collected packages: pbr, dynaconf, click, stevedore, example-etl\nSuccessfully installed click-8.1.3 dynaconf-3.1.12 example-etl-0.1.0 pbr-5.11.1 stevedore-5.1.0\n Downloading example_etl-0.0.1.dev0-py3-none-any.whl (14 kB)\nCollecting dynaconf==3.1.7\n Downloading dynaconf-3.1.7-py2.py3-none-any.whl (200 kB)\n |\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 200 kB 850 kB/s \nCollecting stevedore==3.5.0\n Downloading stevedore-3.5.0-py3-none-any.whl (49 kB)\n |\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 49 kB 747 kB/s \nCollecting click==8.0.3\n Downloading click-8.0.3-py3-none-any.whl (97 kB)\n |\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 97 kB 554 kB/s \nCollecting pbr!=2.1.0,>=2.0.0\n Downloading pbr-5.8.0-py2.py3-none-any.whl (112 kB)\n |\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 112 kB 1.9 MB/s \nInstalling collected packages: pbr, stevedore, dynaconf, click, example-etl\nSuccessfully installed click-8.0.3 dynaconf-3.1.7 example-etl-0.0.1.dev0 pbr-5.8.0 stevedore-3.5.0\n
"},{"location":"guidelines/tutorial/test/","title":"\u6d4b\u8bd5","text":"\u5728\u672c\u7ae0\u8282\uff0c\u4f60\u5c06\u5b66\u5230\u5982\u4e0b\u5185\u5bb9\uff1a
- \u4f7f\u7528 pytest \u7f16\u5199\u5355\u5143\u6d4b\u8bd5
- \u4f7f\u7528 pytest-mock \u6a21\u62df\u5355\u5143\u6d4b\u8bd5\u4e2d\u7684\u4f9d\u8d56\u903b\u8f91
- \u4f7f\u7528 tox \u81ea\u52a8\u5316\u6d4b\u8bd5\u6d41\u7a0b
\u6d4b\u8bd5\u662f\u4fdd\u969c\u5b89\u5168\u4e0a\u7ebf\u4e00\u4e2a\u91cd\u8981\u7684\u6b65\u9aa4\uff0c\u7f16\u5199\u826f\u597d\u7684\u6d4b\u8bd5\uff0c\u53ef\u4ee5\u5728\u53d1\u5e03\u4e4b\u524d\u5c3d\u53ef\u80fd\u907f\u514d BUG \u51fa\u73b0\u3002 \u5728\u4fee\u6539\u529f\u80fd\u540e\uff0c\u4e5f\u53ef\u4ee5\u901a\u8fc7\u56de\u5f52\u6d4b\u8bd5\uff0c\u68c0\u67e5\u73b0\u6709\u529f\u80fd\u7684\u7a33\u5b9a\u6027\u3002
\u7f16\u5199\u5355\u5143\u6d4b\u8bd5\u8fc7\u7a0b\uff0c\u548c\u5f00\u53d1\u987a\u5e8f\u4e00\u76f4\uff0c\u73b0\u6d4b\u8bd5\u4e09\u4e2a\u6a21\u5757\uff0c\u518d\u6d4b\u8bd5 manage
\u6a21\u5757\uff0c\u6700\u540e\u6d4b\u8bd5\u8c03\u7528\u903b\u8f91\u3002
\u6d4b\u8bd5\u65f6\uff0c\u4f7f\u7528\u7684\u662f pytest
\u5de5\u5177\uff0c\u800c\u4e0d\u662f\u4f7f\u7528 unittest
\u3002
"},{"location":"guidelines/tutorial/test/#_2","title":"\u8c03\u6574\u6d4b\u8bd5\u4ee3\u7801","text":"\"\"\"Test cmdline\"\"\"\nfrom __future__ import annotations # PEP 585\nimport pytest\nfrom click.testing import CliRunner\nfrom example_etl import __version__\nfrom example_etl.cmdline import main\n@pytest.mark.parametrize(\n['invoke_args', 'exit_code', 'output_keyword'],\n[\n([], 0, 'help'),\n(['--help'], 0, 'help'),\n(['--version'], 0, __version__),\n(['-V'], 0, __version__),\n]\n)\ndef test_main(\nclicker: CliRunner,\ninvoke_args: list[str],\nexit_code: int,\noutput_keyword: str,\n):\n\"\"\"Test main cmdline\"\"\"\nresult = clicker.invoke(main, invoke_args)\nassert result.exit_code == exit_code\nassert output_keyword in result.output\n
"},{"location":"guidelines/tutorial/test/#extractor","title":"\u6d4b\u8bd5 extractor","text":"\u5728 tests
\u5305\u4e2d\u65b0\u5efa test_extractor.py
\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Test extractor\"\"\"\nimport pytest\nfrom example_etl.extractor.base import BaseExtractor\nfrom example_etl.extractor.file import FileExtractor\ndef test_base_source(mocker):\n\"\"\"Test base extractor\"\"\"\nclose_mock = mocker.patch.object(BaseExtractor, 'close')\nwith pytest.raises(NotImplementedError):\nwith BaseExtractor(mocker.MagicMock()) as base:\nbase.extract()\nassert close_mock.called_once()\ndef test_file_source(mocker, foo_file):\n\"\"\"Test file extractor\"\"\"\nextractor = FileExtractor(mocker.MagicMock())\nextractor.settings.FILE_EXTRACTOR_PATH = foo_file\ndata = list(extractor.extract())\nassert data == ['foo']\n
\u6d4b\u8bd5\u4ee3\u7801\u4e2d\uff0c\u5206\u522b\u6d4b\u8bd5\u4e86 BaseExtractor
\u548c FileExtractor
\u7684\u903b\u8f91\u3002
\u5728\u6d4b\u8bd5\u903b\u8f91\u4e2d\u4f7f\u7528\u4e86 mocker
\u529f\u80fd\uff0c\u53ef\u4ee5\u5728\u6d4b\u8bd5\u5355\u5143\u903b\u8f91\u65f6\uff0c\u5c06\u5176\u4f9d\u8d56\u7684\u4e1c\u897f mock \u6389\u3002\u5728 pytest
\u6d4b\u8bd5\u6846\u67b6\u4e2d\uff0c \u4f7f\u7528 pytest-mock
\u6269\u5c55\u53ef\u4ee5\u5f88\u65b9\u4fbf\u7684\u4f7f\u7528 mocker
\u5bf9\u8c61\u3002
\u5b89\u88c5 pytest-mock
\uff1a
poetry add --group dev pytest-mock\n
\u8fd9\u91cc\u4f7f\u7528\u4e86 poetry add -D
\uff0c\u610f\u601d\u662f\u5c06 pytest-mock
\u5b89\u88c5\u5230\u5f00\u53d1\u73af\u5883\u4f9d\u8d56\u4e2d\u3002 \u5f53\u5728\u4e00\u4e2a\u65b0\u73af\u5883 poetry install
\u5b89\u88c5\u65f6\uff0c\u5b89\u88c5\u6240\u6709\u975e\u53ef\u9009\u7ec4\u7684\u4f9d\u8d56\u9879\u3002
\u6d4b\u8bd5\u4ee3\u7801\u4e2d\u540c\u65f6\u4f7f\u7528\u4e86 foo_file
\u7684 fixture \uff0c\u5b83\u5b9a\u4e49\u5728 conftest.py
\u4e2d\uff0c\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Test config\"\"\"\nimport tempfile\nimport pytest\nfrom click.testing import CliRunner\n@pytest.fixture()\ndef clicker():\n\"\"\"clicker fixture\"\"\"\nyield CliRunner()\n@pytest.fixture()\ndef foo_file():\n\"\"\"foo file\"\"\"\nwith tempfile.NamedTemporaryFile(mode='w') as file:\nfile.write('foo')\nfile.flush()\nyield file.name\n
\u7136\u540e\u5728\u547d\u4ee4\u884c\u4e2d\u8fd0\u884c pytest
\uff0c\u6d4b\u8bd5\u521a\u521a\u7f16\u5199\u7684\u6d4b\u8bd5\u4ee3\u7801\u3002\u53ef\u4ee5\u770b\u5230\u5982\u4e0b\u8f93\u51fa\uff1a
\u276f pytest\n================================================================================================================================= test session starts =================================================================================================================================\nplatform darwin -- Python 3.10.11, pytest-7.3.1, pluggy-1.0.0\nrootdir: /private/tmp/example_etl\nconfigfile: pyproject.toml\ntestpaths: tests\nplugins: pylint-0.19.0, mock-3.10.0\ncollected 12 items \n\ntests/test_cmdline.py ..... [ 35%]\ntests/test_exceptions.py . [ 42%]\ntests/test_extractor.py .. [ 57%]\ntests/test_log.py ..... [ 92%]\ntests/test_version.py . [100%]\n\n================================================================================================================================== warnings summary ===================================================================================================================================\n../../../Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pkg_resources/__init__.py:121\n /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pkg_resources/__init__.py:121: DeprecationWarning: pkg_resources is deprecated as an API\n warnings.warn(\"pkg_resources is deprecated as an API\", DeprecationWarning)\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n============================================================================================================================ 12 passed, 1 warning in 0.04s ============================================================================================================================\n
\u6d4b\u8bd5\u6210\u529f\u3002
\u8bf4\u660e\uff1a \u4e0a\u9762\u6d4b\u8bd5\u7ed3\u679c\u4e2d\u6709 pkg_resources
\u7684\u8b66\u544a\uff0c\u8fd9\u662f\u7531\u4e8e\u5f53\u524d\u7248\u672c\u7684 dynaocnf
\u4e2d\u7684\u4e00\u4e2a\u903b\u8f91\u5728 python3.10 \u4e0b\u88ab\u63d0\u793a\u51fa API \u5f03\u7528\u7684\u8b66\u544a\u9020\u6210\u7684\u3002\u8fd9\u4e2a\u95ee\u9898\u5728 dynaconf
\u7684\u4e0b\u4e00\u4e2a\u7248\u672c\u4e2d\u5df2\u7ecf\u4fee\u590d\u4e86\u3002\u5f53 dynaconf
\u7684\u4e0b\u4e00\u4e2a\u7248\u672c\u53d1\u5e03\u540e\uff0c\u53ef\u4ee5\u5c06 dynaconf
\u7684\u7248\u672c\u5347\u7ea7\u5230\u4e0b\u4e00\u4e2a\u7248\u672c\uff0c\u8fd9\u4e2a\u8b66\u544a\u5c31\u4f1a\u6d88\u5931\u3002\u5f53\u524d dynaconf
\u7248\u672c\u4e3a 3.1.12
\u3002
"},{"location":"guidelines/tutorial/test/#transformer","title":"\u6d4b\u8bd5 transformer","text":"\u5728 tests
\u5305\u4e2d\u521b\u5efa test_transformer.py
\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Test transformer\"\"\"\nimport pytest\nfrom example_etl.transformer.base import BaseTransformer\nfrom example_etl.transformer.strip import StripTransformer\ndef test_base_process(mocker):\n\"\"\"Test base transformer\"\"\"\nprocess = BaseTransformer(mocker.MagicMock())\nwith pytest.raises(NotImplementedError):\nprocess.transform('foo')\n@pytest.mark.parametrize(\n'data, expect_value',\n[\n('xx ', 'xx'),\n(' xx ', 'xx'),\n('xx', 'xx'),\n]\n)\ndef test_strip_process(mocker, data, expect_value):\n\"\"\"Test strip transformer\"\"\"\nprocessor = StripTransformer(mocker.MagicMock())\nres = processor.transform(data)\nassert res == expect_value\n
\u5728\u6d4b\u8bd5 test_strip_process
\u65f6\uff0c\u4f7f\u7528\u4e86 pytest \u7684\u53c2\u6570\u5316\u6d4b\u8bd5\u3002\u53ef\u4ee5\u5728\u4e00\u4e2a\u6d4b\u8bd5\u903b\u8f91\u4e2d\uff0c\u6d4b\u8bd5\u4e0d\u540c\u7684\u8f93\u5165\u8f93\u51fa\u503c\u3002
\u518d\u6b21\u8fd0\u884c pytest
\u547d\u4ee4\uff0c\u68c0\u6d4b\u6d4b\u8bd5\u662f\u5426\u6b63\u786e\u3002
"},{"location":"guidelines/tutorial/test/#loader","title":"\u6d4b\u8bd5 loader","text":"\u5728 tests
\u5305\u4e2d\u521b\u5efa test_loader.py
\uff0c\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Test loader\"\"\"\nimport tempfile\nfrom pathlib import Path\nimport pytest\nfrom example_etl.loader.base import BaseLoader\nfrom example_etl.loader.file import FileLoader\ndef test_base_dest(mocker):\n\"\"\"Test base loader\"\"\"\nclose_mock = mocker.patch.object(BaseLoader, 'close')\nwith BaseLoader(mocker.MagicMock()) as base:\nwith pytest.raises(NotImplementedError):\nbase.load('foo')\nassert close_mock.called_once()\ndef test_file_dest(mocker):\n\"\"\"Test file loader\"\"\"\nwith tempfile.NamedTemporaryFile() as file:\nsettings_mock = mocker.MagicMock()\nsettings_mock.FILE_LOADER_PATH = file.name\nwith FileLoader(settings_mock) as loader:\nloader.load('foo')\nfile = Path(file.name)\nstat = file.stat()\nassert stat.st_size == 3\n
\u5728\u6d4b\u8bd5 test_file_dest
\u65f6\uff0c\u4f7f\u7528\u4e86\u4e00\u4e2a\u4e34\u65f6\u6587\u4ef6\u4f5c\u4e3a\u76ee\u6807\u5199\u5165\uff0c\u4f7f\u7528\u4e86 with
\u5173\u952e\u5b57\u6253\u5f00\u6587\u4ef6\uff0c \u5728\u6d4b\u8bd5\u5b8c\u6210\u540e\uff0c\u4f1a\u81ea\u52a8\u5220\u9664\u4e34\u65f6\u6587\u4ef6\u3002
\u518d\u6b21\u8fd0\u884c pytest
\u68c0\u67e5\u6d4b\u8bd5\u7ed3\u679c\u3002
"},{"location":"guidelines/tutorial/test/#manage","title":"\u6d4b\u8bd5 manage","text":"manage
\u7684\u903b\u8f91\u540c\u6837\u9700\u8981\u6d4b\u8bd5\uff0c\u5728 tests
\u5305\u4e2d\u521b\u5efa test_manage.py
\u6587\u4ef6\uff0c\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Test manage\"\"\"\nimport pytest\nfrom example_etl.exceptions import PluginNotFoundError\nfrom example_etl.extractor.file import FileExtractor\nfrom example_etl.manage import Manage, get_extension\ndef test_get_extension():\n\"\"\"Test get extension\"\"\"\nplugin = get_extension('example_etl.extractor', 'file')\nassert plugin is FileExtractor\ndef test_get_extension_error():\n\"\"\"Test get extension error\"\"\"\nwith pytest.raises(PluginNotFoundError):\nget_extension('example_etl.extractor', 'xxx')\ndef test_manage_run(mocker):\n\"\"\"Test manage run\"\"\"\nmocker.patch('example_etl.manage.get_extension')\nprocess_mock = mocker.patch.object(Manage, 'transform')\nmanage = Manage()\nmanage.run()\nassert process_mock.called_once()\ndef test_manage_transform(mocker):\n\"\"\"Test manage transform\"\"\"\nmagic_mock = mocker.MagicMock()\nmanage = Manage()\nmanage.transformer = magic_mock\nmagic_mock.extract.return_value = [1, 2]\nmanage.transform(magic_mock, magic_mock)\nassert magic_mock.extract.called_once()\nassert magic_mock.load.call_count == 2\nassert magic_mock.transform.call_count == 2\n
\u5728\u6d4b\u8bd5\u65f6\uff0c\u9700\u8981\u4fdd\u8bc1\u914d\u7f6e\u6587\u4ef6\u4e2d\u5b58\u5728\u4e4b\u524d\u5728\u4ee3\u7801\u4e2d\u4f7f\u7528\u7684\u53d8\u91cf\u3002
\u518d\u6b21\u8fd0\u884c pytest
\u68c0\u67e5\u6d4b\u8bd5\u7ed3\u679c\u3002
"},{"location":"guidelines/tutorial/test/#_3","title":"\u68c0\u67e5\u6d4b\u8bd5\u8986\u76d6\u7387","text":"\u6d4b\u8bd5\u8986\u76d6\u7387\u6307\u793a\u7f16\u5199\u7684\u5355\u5143\u6d4b\u8bd5\uff0c\u8986\u76d6\u4e86\u591a\u5c11\u6e90\u4ee3\u7801\u3002\u80fd\u591f\u901a\u8fc7\u6d4b\u8bd5\u8986\u76d6\u7387\u67e5\u770b\u8fd8\u6709\u54ea\u4e9b\u5185\u5bb9\u6ca1\u6709\u88ab\u6d4b\u8bd5\u5230\u3002
\u589e\u52a0 pytest-cov
\u4f9d\u8d56\uff1a
poetry add --group dev pytest-cov\n
\u8fd0\u884c pytest --cov
\u67e5\u770b\u6d4b\u8bd5\u8986\u76d6\u7387\u3002
\u276f pytest --cov\n================================================================================================================================= test session starts =================================================================================================================================\nplatform darwin -- Python 3.10.11, pytest-7.3.1, pluggy-1.0.0\nrootdir: /private/tmp/example_etl\nconfigfile: pyproject.toml\ntestpaths: tests\nplugins: pylint-0.19.0, mock-3.10.0, cov-4.1.0\ncollected 23 items \n\ntests/test_cmdline.py ..... [ 21%]\ntests/test_extractor.py .. [ 30%]\ntests/test_loader.py .. [ 39%]\ntests/test_log.py ..... [ 60%]\ntests/test_manage.py .... [ 78%]\ntests/test_transformer.py .... [ 95%]\ntests/test_version.py . [100%]\n\n================================================================================================================================== warnings summary ===================================================================================================================================\n../../../Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pkg_resources/__init__.py:121\n /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pkg_resources/__init__.py:121: DeprecationWarning: pkg_resources is deprecated as an API\n warnings.warn(\"pkg_resources is deprecated as an API\", DeprecationWarning)\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n\n--------- coverage: platform darwin, python 3.10.11-final-0 ----------\nName Stmts Miss Cover\n-------------------------------------------------------------\nsrc/example_etl/__init__.py 1 0 100%\nsrc/example_etl/cmdline.py 26 0 100%\nsrc/example_etl/config/__init__.py 8 0 100%\nsrc/example_etl/constants.py 1 0 100%\nsrc/example_etl/exceptions.py 10 2 80%\nsrc/example_etl/extractor/__init__.py 0 0 100%\nsrc/example_etl/extractor/base.py 13 0 100%\nsrc/example_etl/extractor/file.py 12 0 100%\nsrc/example_etl/loader/__init__.py 0 0 100%\nsrc/example_etl/loader/base.py 12 0 100%\nsrc/example_etl/loader/file.py 15 0 100%\nsrc/example_etl/log.py 19 0 100%\nsrc/example_etl/manage.py 33 0 100%\nsrc/example_etl/transformer/__init__.py 0 0 100%\nsrc/example_etl/transformer/base.py 5 0 100%\nsrc/example_etl/transformer/strip.py 7 0 100%\ntests/__init__.py 7 0 100%\ntests/conftest.py 12 0 100%\ntests/test_cmdline.py 10 0 100%\ntests/test_extractor.py 14 0 100%\ntests/test_loader.py 20 0 100%\ntests/test_log.py 9 0 100%\ntests/test_manage.py 25 0 100%\ntests/test_transformer.py 12 0 100%\ntests/test_version.py 3 0 100%\n-------------------------------------------------------------\nTOTAL 274 2 99%\n\n============================================================================================================================ 23 passed, 1 warning in 0.08s ============================================================================================================================\n
\u901a\u8fc7\u8986\u76d6\u7387\u53ef\u4ee5\u770b\u5230 src/example_etl/exceptions.py
\u7684\u903b\u8f91\u8fd8\u6709\u6ca1\u6d4b\u8bd5\u7684\u3002
"},{"location":"guidelines/tutorial/test/#_4","title":"\u5b8c\u5584\u5176\u4ed6\u6d4b\u8bd5","text":"\u5728 tests
\u6a21\u5757\u4e2d\u521b\u5efa test_exceptions.py
\u6587\u4ef6\uff0c\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Test exception\"\"\"\nfrom example_etl.exceptions import PluginNotFoundError\ndef test_plugin_not_found_error():\n\"\"\"test plugin not found error\"\"\"\nerror = PluginNotFoundError('foo', 'bar')\nassert str(error) == 'Can not found \"bar\" plugin in foo'\n
\u518d\u6b21\u8fd0\u884c pytest --cov
\u53ef\u4ee5\u770b\u5230\u8986\u76d6\u7387 100% \u3002
"},{"location":"guidelines/tutorial/test/#_5","title":"\u4ee3\u7801\u98ce\u683c\u68c0\u6d4b","text":"\u4e3a\u4e86\u8ba9\u5f00\u53d1\u98ce\u683c\u8fbe\u5230\u7edf\u4e00\uff0c\u4f7f\u7528\u4ee3\u7801\u683c\u5f0f\u5316\u5de5\u5177\u68c0\u6d4b\u3002
\u4f7f\u7528 isort
\u5c06\u5bfc\u5305\u90e8\u5206\u683c\u5f0f\u5316\u4e3a\u7edf\u4e00\u683c\u5f0f\uff0c\u4f7f\u7528 pylint
\u68c0\u6d4b\u4ee3\u7801\u662f\u5426\u7b26\u5408 PEP8 \u89c4\u8303\uff0c\u540c\u65f6\u8fd8\u80fd\u68c0\u6d4b \u4e00\u4e9b\u4e0d\u6807\u51c6\u7684\u7684\u8bed\u6cd5\uff0c\u5e76\u7ed9\u51fa\u4fee\u6539\u5efa\u8bae\u3002
\u6267\u884c isort . --check-only --diff
\u68c0\u6d4b\u4ee3\u7801\u98ce\u683c\uff0c\u5e76\u4ec5\u8f93\u51fa\u4e0d\u7b26\u5408\u89c4\u8303\u7684\u5bfc\u5305\uff0c\u6267\u884c isort
\u4f1a\u81ea\u52a8\u683c\u5f0f \u5316\u4ee3\u7801\u3002
\u8fd0\u884c pylint src tests
\u68c0\u67e5 src
\u76ee\u5f55\u548c tests
\u76ee\u5f55\u4e0b\u7684 Python \u4ee3\u7801\u3002\u4f1a\u8f93\u51fa\u4e0d\u7b26\u5408\u89c4\u8303\u7684\u5185\u5bb9\uff0c\u7136\u540e \u6839\u636e\u5efa\u8bae\u4fee\u6539\u5373\u53ef\u3002
"},{"location":"guidelines/tutorial/test/#_6","title":"\u81ea\u52a8\u5316\u6d4b\u8bd5","text":"\u9879\u76ee\u9ed8\u8ba4\u5e26\u6709 tox
\u81ea\u52a8\u5316\u914d\u7f6e\u3002\u5f53\u5f00\u53d1\u5b8c\u6210\u540e\uff0c\u76f4\u63a5\u8fd0\u884c tox
\uff0c\u4f1a\u81ea\u52a8\u5728\u6a21\u62df\u73af\u5883\u4e2d\u6d4b\u8bd5\u4ee3\u7801\u3002\u6d4b\u8bd5\u65f6\uff0c\u4f1a \u521b\u5efa\u72ec\u7acb\u7684\u865a\u62df\u73af\u5883\uff0c\u7136\u540e\u5c06\u9879\u76ee\u6253\u5305\u5b89\u88c5\u5230\u73af\u5883\u4e2d\uff0c\u518d\u8fdb\u884c\u6d4b\u8bd5\u3002
tox
\u4f1a\u81ea\u52a8\u6267\u884c pytest
\u6d4b\u8bd5\uff0c \u5bfc\u5305\u68c0\u6d4b\uff0c\u4ee3\u7801\u98ce\u683c\u68c0\u6d4b\u3002
"},{"location":"guidelines/tutorial/tutorial/","title":"\u521d\u7ea7\u6559\u7a0b","text":"\u521d\u7ea7\u6559\u7a0b\u662f\u4e00\u4e2a ETL \u793a\u4f8b\u9879\u76ee\u3002\u5b83\u548c\u4e4b\u524d\u7684\u5feb\u901f\u4e0a\u624b\u4e0d\u540c\u7684\u662f\u5305\u542b\u4e86\u66f4\u591a Python \u5de5\u7a0b\u5316\u7684\u5185\u5bb9\u3002 \u4e3b\u8981\u4e00\u4e0b\u51e0\u4e2a\u70b9\uff1a
- \u4f7f\u7528\u9879\u76ee\u6a21\u677f\u521d\u59cb\u5316\u9879\u76ee
- \u4f7f\u7528\u914d\u7f6e\u7cfb\u7edf\u52a0\u8f7d\u9879\u76ee\u914d\u7f6e\uff0c\u5e76\u4e14\u53ef\u4ee5\u8bfb\u53d6\u5916\u90e8\u914d\u7f6e\u6587\u4ef6\uff0c\u4f7f\u7528 YAML \u683c\u5f0f\u6587\u4ef6
- \u4f7f\u7528\u63d2\u4ef6\u5316\u673a\u5236\u5f00\u53d1\u81ea\u5b9a\u4e49\u903b\u8f91\uff0c\u5e76\u80fd\u81ea\u52a8\u53d1\u73b0
- \u5b8c\u6574\u7684\u6253\u5305\u53d1\u5e03\u6d41\u7a0b\uff0c\u5e76\u5c06\u9879\u76ee\u53d1\u5e03\u5230 pypi
- \u7f16\u5199\u9879\u76ee\u8bf4\u660e\u6587\u6863\uff0c\u5e76\u81ea\u52a8\u6784\u5efa\u9759\u6001\u7ad9\u70b9
- \u6784\u5efa Docker \u955c\u50cf\uff0c\u9879\u76ee\u53ef\u4ee5\u5bb9\u5668\u5316\u8fd0\u884c
- \u5b8c\u6574\u7684\u5355\u5143\u6d4b\u8bd5\uff0c\u8986\u76d6\u7387 100%
"},{"location":"guidelines/tutorial/tutorial/#_2","title":"\u9879\u76ee\u8bbe\u8ba1","text":"ETL \u793a\u4f8b\u9879\u76ee\u7684\u8bbe\u8ba1\u6709\u4e09\u90e8\u5206\u7ec4\u6210\uff0c\u5206\u522b\u4e3a extractor
\u3001loader
\u548c transformer
\u3002 extractor
\u8d1f\u8d23\u4ece\u6e90\u76ee\u6807\u63d0\u53d6 \u6570\u636e\uff0c transform
\u8d1f\u8d23\u5904\u7406\u4e2d\u95f4\u7684\u8f6c\u6362\u903b\u8f91\uff0c loader
\u8d1f\u8d23\u5c06\u5904\u7406\u540e\u7684\u7ed3\u679c\u52a0\u8f7d\u5230\u76ee\u6807\u4f4d\u7f6e\u3002
\u5728\u4e09\u4e2a\u6a21\u5757\u4e2d\uff0c\u90fd\u6709\u5bf9\u5e94\u7684\u62bd\u8c61\u57fa\u7c7b\u3002\u5982\u679c\u9700\u8981\u81ea\u5b9a\u4e49\uff0c\u53ea\u9700\u5b9e\u73b0\u5bf9\u5e94\u7684\u903b\u8f91\uff0c\u5e76\u5c06\u5b9e\u73b0\u7684\u7c7b\u6ce8\u518c\u5230\u547d\u540d\u7a7a\u95f4\uff0c\u7136\u540e\u901a\u8fc7\u914d\u7f6e \u6587\u4ef6\u52a0\u8f7d\u5b9e\u73b0\u7684\u540d\u79f0\uff0c\u5373\u53ef\u6b63\u5e38\u4f7f\u7528\u3002
"},{"location":"guidelines/tutorial/tutorial/#_3","title":"\u4f7f\u7528\u8bf4\u660e","text":"\u56e0\u4e3a\u8be5\u9879\u76ee\u5df2\u7ecf\u53d1\u5e03\u5230 Pypi \u4e2d\uff0c\u6240\u4ee5\u53ef\u4ee5\u76f4\u63a5\u901a\u8fc7 pip \u547d\u4ee4\u5b89\u88c5\u4f7f\u7528\u3002
\u5efa\u8bae\u5728\u865a\u62df\u73af\u5883\u4e2d\u5b89\u88c5\uff01
pip install example-etl\n
\u7136\u540e\u4f7f\u7528 example_etl
\u547d\u4ee4
example_etl --help\n
\u9ed8\u8ba4\u53ea\u5b9e\u73b0\u4ece\u6587\u672c\u6309\u884c\u63d0\u53d6\uff0c\u7136\u540e\u5220\u9664\u6587\u672c\u524d\u540e\u7a7a\u683c\uff0c\u518d\u5c06\u6587\u672c\u5199\u5165\u76ee\u6807\u6587\u4ef6\u7684\u4e09\u4e2a\u5b9e\u73b0\u3002
"},{"location":"introduction/ides/","title":"Python \u5f00\u53d1\u5de5\u5177","text":"\u80fd\u591f\u505a Python \u5f00\u53d1\u7684\u5de5\u5177\u6709\u5f88\u591a\uff0c\u751a\u81f3\u6765\u8bf4\uff0c\u5982\u679c\u4f60\u4e60\u60ef\u6bd4\u8f83\u597d\uff0c\u90fd\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528 VIM \u6216\u8005 Windows \u4e0b\u7684\u8bb0\u4e8b\u672c \u6765\u7f16\u5199 Python \u4ee3\u7801\u3002\u4f46\u662f\u4e3a\u4e86\u9762\u5411\u4f01\u4e1a\u548c\u5de5\u7a0b\u5316\u5f00\u53d1\uff0c\u63a8\u8350\u4f7f\u7528\u96c6\u6210\u4e86\u8bf8\u591a\u7279\u6027\u7684\u5f00\u53d1\u5de5\u5177\uff0c\u6765\u6539\u5584\u5f00\u53d1\u4f53\u9a8c\u3002
\u5f53\u524d\u4e3b\u6d41\u7684\u5f00\u53d1\u5de5\u5177\u6709\uff1a
- Visual Studio Code
- Pycharm
- Eclipse
\u4e0a\u8ff0\u8f6f\u4ef6\u9664\u4e86 Pycharm \u4e13\u4e1a\u7248\u662f\u6536\u8d39\u7684\uff0c\u5176\u4f59\u90fd\u662f\u514d\u8d39\u3002\u5982\u679c\u9700\u8981\u4f7f\u7528 Pycharm\uff0c \u5efa\u8bae\u4f7f\u7528\u793e\u533a\u7248\uff0c\u6216\u8005\u8d2d\u4e70\u6b63\u7248\u3002
"},{"location":"introduction/ides/#visual-studio-code","title":"Visual Studio Code","text":"VScode \u662f\u5fae\u8f6f\u5f00\u53d1\u7684\u4e00\u6b3e\u6587\u672c\u7f16\u8f91\u5668\uff0c\u901a\u8fc7\u81ea\u5e26\u7684\u63d2\u4ef6\u7cfb\u7edf\uff0c\u53ef\u4ee5\u5c06\u6587\u672c\u7f16\u8f91\u5668\u6253\u9020\u6210\u4e00\u4e2a\u96c6\u6210\u5f00\u53d1\u5de5\u5177\u3002
\u4ece\u5b98\u7f51\u4e0b\u8f7d\u5e76\u5b89\u88c5\u3002
"},{"location":"introduction/ides/#_1","title":"\u914d\u7f6e","text":"\u4ece\u63d2\u4ef6\u4e2d\u5fc3\u5b89\u88c5\u4e2d\u6587\u63d2\u4ef6\uff1a
\u4ece\u63d2\u4ef6\u4e2d\u5fc3\u5b89\u88c5 Python Extension Pack
\u63d2\u4ef6\uff1a
"},{"location":"introduction/ides/#_2","title":"\u4f7f\u7528","text":"\u521b\u5efa\u4e00\u4e2a\u4e34\u65f6\u76ee\u5f55\uff0c\u7136\u540e\u9009\u62e9\u4f7f\u7528 vscode \u6253\u5f00\u3002Windows \u53ef\u4ee5\u901a\u8fc7\u53f3\u51fb\uff0c\u9009\u62e9 \u901a\u8fc7 Code \u6253\u5f00
\uff0c Linux \u53ef\u4ee5\u5728\u7ec8\u7aef\u4f7f\u7528 code demo
\u547d\u4ee4\u6253\u5f00\u3002
\u7136\u540e\u4f7f\u7528\u5feb\u6377\u952e Ctrl + `
\u7ec4\u5408\u952e\u6253\u5f00\u7ec8\u7aef\uff0c\u6267\u884c poetry init
\u6839\u636e\u63d0\u793a\u64cd\u4f5c\uff0c\u521d\u59cb\u5316 pyproject.toml
\u7684\u914d\u7f6e\u6587\u4ef6\uff0c\u6267\u884c poetry shell
\u8fdb\u884c\u865a\u62df\u73af\u5883\u521b\u5efa\uff0c\u6267\u884c poetry install
\u8fdb\u884c\u4f9d\u8d56\u5b89\u88c5\uff1a
\u7136\u540e\u4f7f\u7528 Ctrl + Shift + p
\u6253\u5f00 vscode \u7684\u6307\u4ee4\u7a97\u53e3\uff0c\u5728\u7a97\u53e3\u4e2d\u8f93\u5165 >python: select Interpreter
\u6765\u9009\u62e9\u9879\u76ee\u9700\u8981\u4f7f\u7528\u7684 Python \u89e3\u91ca\u5668\uff0c \u7136\u540e\u9009\u62e9\u4e0a\u9762\u4e00\u6b65\u521b\u5efa\u7684 Python \u89e3\u91ca\u5668\uff1a
\u7136\u540e\u53ef\u4ee5\u770b\u5230\u7a97\u53e3\u7684\u5de6\u4e0b\u89d2\u5df2\u7ecf\u51fa\u73b0\u4e86\u4e0a\u4e00\u6b65\u9009\u62e9 Python \u89e3\u91ca\u5668\u3002
\u521b\u5efa\u65b0\u6587\u4ef6 demo.py
\uff0c\u5e76\u8f93\u5165\u5982\u4e0b\u4ee3\u7801\uff1a
import sys\nprint(sys.version)\n
\u7136\u540e\u53f3\u51fb\u8be5\u6587\u4ef6\uff0c\u9009\u62e9 \u5728\u7ec8\u7aef\u4e2d\u8fd0\u884c Python \u6587\u4ef6
\uff1a
\u53ef\u4ee5\u770b\u5230\u8f93\u5165\uff1a
\u66f4\u591a\u5173\u4e8e\u5728 vscode \u4e2d\u4f7f\u7528 Python \u7684\u5185\u5bb9\uff0c\u8bf7\u53c2\u8003 Getting Started with Python in VS Code \u3002
"},{"location":"introduction/ides/#_3","title":"\u95ee\u9898\u6392\u67e5","text":""},{"location":"introduction/ides/#vscode","title":"vscode \u7ec8\u7aef\u65e0\u6cd5\u81ea\u52a8\u542f\u7528 \u865a\u62df\u73af\u5883","text":""},{"location":"introduction/ides/#_4","title":"\u95ee\u9898\u539f\u56e0","text":"\u7531\u4e8e poetry \u4e3a\u7ec8\u7aef(\u9ed8\u8ba4\u662f Powershell) \u542f\u52a8\u865a\u62df\u73af\u5883\u65f6\uff0c\u4f7f\u7528\u7684 ps1 \u811a\u672c\u6587\u4ef6\uff0c\u800c Powershell \u9ed8\u8ba4\u7684\u6267\u884c\u7b56\u7565 \u662f\u7981\u7528\u811a\u672c\u6587\u4ef6\u6267\u884c\u7684\u3002\u6240\u4ee5\u5f53 vscode \u914d\u7f6e\u4e86\u865a\u62df\u73af\u5883\u540e\uff0c\u542f\u52a8\u7ec8\u7aef\uff0c\u4f1a\u65e0\u6cd5\u6267\u884c\u811a\u672c\u6587\u4ef6\u3002
"},{"location":"introduction/ides/#_5","title":"\u89e3\u51b3\u65b9\u6cd5","text":"\u53c2\u8003 about_Execution_Policies \u7684\u8bf4\u660e\uff0c\u5e76\u66f4\u6539\u5f53\u524d\u7528\u6237\u7684 Powershell \u6267\u884c\u7b56\u7565\uff1a
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser\n
\u7136\u540e\u5173\u95ed\u7ec8\u7aef\u7a97\u53e3\u540e\u518d\u6b21\u6253\u5f00\u5373\u53ef\u3002
"},{"location":"introduction/install/","title":"Python \u73af\u5883\u5b89\u88c5","text":"\u672c\u6587\u4ee5\u622a\u56fe\u8bb0\u5f55\u7684\u5f62\u5f0f\u5c55\u793a\u5982\u4f55\u5728\u4e3b\u6d41\u64cd\u4f5c\u7cfb\u7edf\u4e0a\u5b89\u88c5 Python \u73af\u5883\u3002
\u5728 Python \u73af\u5883\u9009\u62e9\u4e0a\uff0c\u63a8\u8350\u4f7f\u7528\u8f83\u65b0\u7684 Python \u7248\u672c\u3002\u6839\u636e\u5b98\u65b9\u53d1\u5e03\u6d88\u606f \uff0c \u81ea 2020\u5e74 1 \u6708 1 \u65e5\u8d77\uff0c Python 2 \u5c06\u505c\u6b62\u7ef4\u62a4\uff0c\u5305\u62ec\u4efb\u4f55\u65b0\u7684\u9519\u8bef\u62a5\u544a\u3001\u4fee\u590d\u548c\u66f4\u6539\u3002 \u6240\u4ee5\u5f3a\u70c8\u5efa\u8bae\u4f60\u5728\u7248\u672c\u9009\u62e9\u4e0a\u4f7f\u7528 Python 3.7 \u4e4b\u540e\u7684\u7248\u672c\u3002\u8003\u8651\u5230 Python 3 \u5404\u4e2a\u7248\u672c\u7684\u65b0\u7279\u6027\u548c\u517c\u5bb9\u6027\uff0c \u5efa\u8bae\u9009\u62e9 Python 3.9 \u6216 Python 3.10 \u3002
\u622a\u6b62\u5230\u5f53\u524d\u65f6\u95f4\uff082021-12-03\uff09\uff0c Python \u5404\u4e2a\u7248\u672c\u7684\u72b6\u6001\u5982\u4e0b\uff1a
Branch Schedule Status First release End-of-life main PEP 664 features 2022-10-03 2027-10 3.10 PEP 619 bugfix 2021-10-04 2026-10 3.9 PEP 596 bugfix 2020-10-05 2025-10 3.8 PEP 569 security 2019-10-14 2024-10 3.7 PEP 537 security 2018-06-27 2023-06-27 3.6 PEP 494 security 2016-12-23 2021-12-23 \u672c\u6587\u5b89\u88c5\u7684\u7248\u672c\u4f7f\u7528\u6700\u65b0\u7684\u7a33\u5b9a\u7248 python 3.10
\uff0c\u4f1a\u5728\u5982\u4e0b\u64cd\u4f5c\u7cfb\u7edf\u4e0a\u5b89\u88c5\uff1a
- Windows 11 \uff1a \u5b89\u88c5\u5305\u5b89\u88c5
- Ubuntu Desktop 21 \uff1a \u6e90\u4ee3\u7801\u7f16\u8bd1\u5b89\u88c5
\u4ece Python \u4e0b\u8f7d\u9875\u9762 \u627e\u5230 Python 3.10 \u7684\u4e0b\u8f7d\u9875\u9762 \u7136\u540e\u4e0b\u8f7d\u5bf9\u5e94\u7684\u6587\u4ef6\u5373\u53ef\u3002
"},{"location":"introduction/install/#1","title":"1 \u5b89\u88c5","text":"\u5173\u4e8e Python \u7684\u5b89\u88c5\u4f7f\u7528\u7684\u66f4\u591a\u7ec6\u8282\uff0c\u53ef\u4ee5\u53c2\u8003 Python\u5b89\u88c5\u548c\u4f7f\u7528 \u3002
"},{"location":"introduction/install/#11-windows-11","title":"1.1 Windows 11","text":""},{"location":"introduction/install/#111-python","title":"1.1.1 \u5b89\u88c5 Python \u73af\u5883","text":"\u4e0b\u8f7d Windows installer(64-bit) \u5230\u672c\u5730\uff0c\u7136\u540e\u53cc\u51fb\u8fd0\u884c\u5b89\u88c5\u6587\u4ef6\u3002
\u6ce8\u610f\uff1a\u5b89\u88c5\u65f6\uff0c\u9700\u8981\u8d26\u6237\u63a7\u5236\u6743\u9650\u3002
\u5982\u679c\u60f3\u8981\u5c06 Python \u5b89\u88c5\u5230\u9ed8\u8ba4\u76ee\u5f55\uff0c\u76f4\u63a5\u70b9\u51fb Install Now
\u5373\u53ef\u3002
\u70b9\u51fb Customize Installation
:
\u6b64\u65f6\u53ef\u4ee5\u9009\u62e9\u53ef\u9009\u7684\u7279\u6027\uff0c\u4e0d\u8fc7\u8fd8\u4e0d\u662f\u4f60\u4e0d\u77e5\u9053\u5b83\u4eec\u662f\u505a\u4ec0\u4e48\u7684\uff0c\u6216\u8005\u4e0d\u6e05\u695a\u4f60\u662f\u5426\u9700\u8981\u5b83\u4eec\uff0c\u90a3\u4e48\u4fdd\u6301\u9ed8\u8ba4\u5373\u53ef\u3002 \u7136\u540e\u70b9\u51fb Next
\uff1a
\u7136\u540e\u8fdb\u884c\u5982\u4e0b\u64cd\u4f5c\uff1a
- \u52fe\u9009
Install for all users
\u5c06 Python \u5b89\u88c5\u4e3a\u6240\u6709\u7528\u6237\u53ef\u7528 - \u52fe\u9009
Add Python to environment variables
\u5c06\u4f1a\u81ea\u52a8\u521b\u5efa Python \u7684\u73af\u5883\u53d8\u91cf\u3002\u6b64\u9009\u9879\u4f1a\u5728 Windows \u73af\u5883 PATH
\u4e2d\u65b0\u589e\u4e24\u4e2a\u53d8\u91cf C:\\devtools\\Python310\\Scripts\\
\u548c C:\\devtools\\Python310\\
\u3002\u76ee\u5f55\u4e3a Python \u7684\u5b89\u88c5\u76ee\u5f55\u3002 - \u5982\u679c\u6709\u9700\u8981\uff0c\u4fee\u6539
Customize install location
\u4e0b\u7684\u5b89\u88c5\u8def\u5f84\u3002
\u7136\u540e\u70b9\u51fb Install
\uff0c\u5c06 Python \u5b89\u88c5\u5230\u6307\u5b9a\u7684\u76ee\u5f55\u3002\u6b64\u8fc7\u7a0b\u9700\u8981\u8d26\u6237\u6388\u6743\u3002
\u7b49\u5f85\u5b89\u88c5\u5b8c\u6210\u540e\uff0c\u70b9\u51fb Close
\u3002\u5f53\u7136\u5efa\u8bae\u70b9\u51fb Disable path length limit
\uff0c\u6765\u7981\u7528 Windows \u4e0b\u7684 260 \u5b57\u8282\u6587\u4ef6 \u8def\u5f84\u7684\u9650\u5236\u3002
\u81f3\u6b64\u5b89\u88c5\u5b8c\u6210\u3002
\u66f4\u591a\u5173\u4e8e Windows \u7cfb\u7edf\u7684\u5176\u4ed6\u7ec6\u8282\uff0c\u8bf7\u53c2\u8003 \u5728Windows\u4e0a\u4f7f\u7528 Python \u3002
"},{"location":"introduction/install/#112-python","title":"1.1.2 \u6d4b\u8bd5 Python \u73af\u5883","text":"\u6253\u5f00 Windows \u7684 CMD \uff0c\u7136\u540e\u8f93\u5165 python --version
\u5373\u53ef\u83b7\u5f97 Python \u7248\u672c\uff1a
"},{"location":"introduction/install/#12-ubuntu-desktop-21","title":"1.2 Ubuntu Desktop 21","text":"\u5bf9\u4e8e\u7f16\u8bd1\u5b89\u88c5\uff0c\u9002\u7528\u4e8e\u5927\u90e8\u5206 Linux \u7cfb\u7edf\uff0c\u9664\u4e86 Python \u5b89\u88c5\u8fc7\u7a0b\u4e2d\u7684\u4f9d\u8d56\u5305\u5728\u7279\u5b9a\u64cd\u4f5c\u7cfb\u7edf\u4e2d\u6709\u533a\u522b\u5916\uff0c\u5176\u4ed6\u64cd\u4f5c\u90fd\u662f\u4e00\u81f4\u7684\u3002
"},{"location":"introduction/install/#121","title":"1.2.1 \u5b89\u88c5\u4f9d\u8d56","text":"sudo apt-get install build-essential gdb lcov pkg-config \\\nlibbz2-dev libffi-dev libgdbm-dev libgdbm-compat-dev liblzma-dev \\\nlibncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev \\\nlzma lzma-dev tk-dev uuid-dev zlib1g-dev\n
"},{"location":"introduction/install/#122-python","title":"1.2.2 \u5b89\u88c5 Python \u73af\u5883","text":"\u4e0b\u8f7d XZ compressed source tarball \u6e90\u7801\u5305\uff0c\u7136\u540e\u89e3\u538b\u5230 /tmp
\uff0c \u7136\u540e\u89e3\u538b\uff1a
cd /tmp/\nwget https://www.python.org/ftp/python/3.10.0/Python-3.10.0.tar.xz\ntar -Jxf Python-3.10.0.tar.xz\ncd Python-3.10.0/\n
\u4f7f\u7528 ./configure \u8fdb\u884c\u9884\u7f16\u8bd1\u3002\u5728\u9884\u7f16\u8bd1\u8fc7\u7a0b\u4e2d\uff0c\u53ef\u4ee5\u6307\u5b9a\u8981\u7f16\u8bd1\u5230\u6e90\u4ee3\u7801\u4e2d\u7684\u5185\u5bb9\u3002\u4f7f\u7528 ./configure --help \u53ef\u4ee5\u67e5\u770b\u652f\u6301\u54ea\u4e9b\u9009\u9879\u3002
\u4e00\u822c\u4f1a\u8fdb\u884c\u5982\u4e0b\u64cd\u4f5c\uff1a
./configure --enable-optimizations\n
\u5982\u679c\u9700\u8981\u5b89\u88c5\u5230\u5176\u4ed6\u4f4d\u7f6e\uff0c\u53ef\u4ee5\u4f7f\u7528 --prefix=/usr/bin \u6307\u5b9a\u3002\u9ed8\u8ba4\u662f\u5b89\u88c5\u5230 /usr/local/bin \u3002
\u5f53\u51fa\u73b0\u5982\u4e0b\u8f93\u51fa\uff0c\u8bf4\u660e\u9884\u7f16\u8bd1\u5b8c\u6210\uff1a
creating Modules/Setup.local\ncreating Makefile\n
\u7f16\u8bd1 \u4f7f\u7528 make \u547d\u4ee4\u7f16\u8bd1\u6784\u5efa
make -s -j2\n
-j \u53ef\u4ee5\u6307\u5b9a\u5e76\u53d1\u6784\u5efa\u4efb\u52a1\u6570\u3002\u5982\u679c\u591a\u6838 CPU \u53ef\u4ee5\u6307\u5b9a\u6838\u5fc3\u6570\u3002
\u5b89\u88c5
sudo make altinstall\n
\u4f7f\u7528 altinstall \u53ef\u4ee5\u907f\u514d\u8986\u76d6\u7cfb\u7edf\u73b0\u6709\u9ed8\u8ba4\u547d\u4ee4\u3002\u5373\u4e0d\u4f1a\u8986\u76d6 python \u547d\u4ee4\u3002
"},{"location":"introduction/install/#123-python","title":"1.2.3 \u6d4b\u8bd5 Python \u73af\u5883","text":"\u6253\u5f00\u7ec8\u7aef\uff0c\u8fd0\u884c python3.10 --version
\u4f1a\u8f93\u51fa Python \u7684\u7248\u672c\u3002
\u81f3\u6b64 Python \u73af\u5883\u5b89\u88c5\u5b8c\u6210\u3002
\u66f4\u591a\u5173\u4e8e Unix \u7cfb\u7edf\u7684\u5176\u4ed6\u7ec6\u8282\uff0c\u8bf7\u53c2\u8003 \u5728\u7c7bUnix\u73af\u5883\u4e0b\u4f7f\u7528Python \u3002
"},{"location":"introduction/install/#2","title":"2 \u4ed3\u5e93\u52a0\u901f","text":"\u9274\u4e8e\u56fd\u5185\u7f51\u7edc\u7684\u95ee\u9898\uff0c\u4e3a\u4e86\u5feb\u901f\u5b89\u88c5 Python \u4f9d\u8d56\u5305\uff0c\u6700\u597d\u4f7f\u7528\u56fd\u5185\u955c\u50cf\u4ed3\u5e93\u52a0\u901f Pypi \u7684\u5305\u3002
Pypi \u56fd\u5185\u955c\u50cf\u6709\u5f88\u591a\uff0c\u73b0\u5728\u63a8\u8350\u5982\u4e0b\u51e0\u4e2a\uff1a
- \u6e05\u534e mirror
- \u963f\u91cc\u4e91 mirror
- 163 mirror
\u4e0b\u9762\u4f7f\u7528\u963f\u91cc\u4e91\u955c\u50cf\u914d\u7f6e\uff0c\u5982\u679c\u9700\u8981\u4f7f\u7528\u5176\u4ed6\u955c\u50cf\u4ed3\u5e93\uff0c\u6539\u52a8 index-url
\u540e\u9762\u7684\u5730\u5740\u5373\u53ef\uff1a
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/\n
"},{"location":"introduction/install/#3","title":"3 \u591a\u73af\u5883\u5171\u5b58","text":"\u591a\u73af\u5883\u5171\u5b58\u662f\u4e3a\u4e86\u5728\u540c\u4e00\u4e2a\u64cd\u4f5c\u7cfb\u7edf\u4e2d\uff0c\u540c\u65f6\u4f7f\u7528\u4e0d\u540c\u7248\u672c\u7684 Python \u73af\u5883\uff0c\u6216\u8005\u7f16\u5199\u7684\u7a0b\u5e8f\u9700\u8981\u5728 \u4e0d\u540c\u7248\u672c\u4e0b\u8fd0\u884c\u6d4b\u8bd5\u3002
"},{"location":"introduction/install/#31-windows","title":"3.1 Windows","text":"\u7ecf\u6d4b\u8bd5\uff0c\u7531\u4e8e DLL \u7684\u95ee\u9898\uff0c\u65e0\u6cd5\u901a\u8fc7 Windows \u7684 mklink
\u547d\u4ee4\u8f6f\u8fde\u63a5\u4e00\u4e2a\u65b0\u7684 python.exe
\u53ef\u6267\u884c\u7a0b\u5e8f\u7684\u522b\u540d\u3002
"},{"location":"introduction/install/#32-linux","title":"3.2 Linux","text":"Linux \u672c\u8eab\u7684\u4f18\u52bf\uff0c\u53ef\u4ee5\u4f7f\u7528\u8f6f\u8fde\u63a5\u751f\u6210\u4e0d\u540c\u7684\u53ef\u6267\u884c\u6587\u4ef6\u540d\u3002\u5728\u5b89\u88c5\u597d Python 3.10 \u7248\u672c\u540e\uff0c\u9ed8\u8ba4\u4f1a\u5728\u751f\u6210 /usr/local/bin/python3.10
\u53ef\u6267\u884c\u6587\u4ef6\u3002\u5982\u679c\u9700\u8981\u5c06\u9ed8\u8ba4\u7684 Python \u547d\u4ee4\u66ff\u6362\u4e3a python3.10
\u5219\u53ef\u4ee5\u5220\u9664\u539f\u6709\u7684 python
\u547d\u4ee4\uff0c\u7136\u540e\u91cd\u65b0\u8f6f\u8fde\u63a5\u3002
# \u5907\u4efd\u5f53\u524d\u9ed8\u8ba4\u7684 python3 \u547d\u4ee4\u5230 /tmp\nmv /usr/bin/python3 /tmp\n# \u91cd\u65b0\u8fde\u63a5 python3 \u547d\u4ee4\nln -s /usr/local/bin/python3.10 /usr/bin/python3\n\n# \u5907\u4efd\u5f53\u524d\u9ed8\u8ba4 pip3 \u547d\u4ee4\nmv /usr/bin/pip3 /tmp\n# \u91cd\u65b0\u8fde\u63a5 pip3 \u547d\u4ee4\nln -s /usr/local/bin/pip3.10 /usr/bin/pip3\n
"},{"location":"introduction/install/#4","title":"4 \u95ee\u9898\u6392\u67e5","text":""},{"location":"introduction/install/#41-linux","title":"4.1 Linux \u5b89\u88c5\u51fa\u73b0\u95ee\u9898","text":"\u5982\u679c\u7f16\u8bd1\u8fc7\u7a0b\u4e2d\u51fa\u73b0\u95ee\u9898\uff0c\u8bf7\u68c0\u67e5\u4f9d\u8d56\u662f\u5426\u5b89\u88c5\u5b8c\u6210\u3002
Debian / Ubuntu \u7cfb\u5217\u64cd\u4f5c\u7cfb\u7edf\u4f9d\u8d56\u5982\u4e0b\uff1a
sudo apt-get install build-essential gdb lcov pkg-config \\\nlibbz2-dev libffi-dev libgdbm-dev libgdbm-compat-dev liblzma-dev \\\nlibncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev \\\nlzma lzma-dev tk-dev uuid-dev zlib1g-dev\n
\u5bf9\u4e8e RHEL \u7cfb\u5217\u64cd\u4f5c\u7cfb\u7edf\uff0c\u4f9d\u8d56\u5b89\u88c5\u5982\u4e0b\uff1a
sudo dnf install dnf-plugins-core # install this to use 'dnf builddep'\nsudo dnf builddep python3\n
"},{"location":"introduction/install/#42-python","title":"4.2 \u5378\u8f7d Python","text":"\u6ce8\u610f\uff1a\u5982\u679c\u662f Linux \u64cd\u4f5c\u7cfb\u7edf\uff0c\u4f60\u5e94\u8be5\u81f3\u5c11\u4fdd\u7559\u7cfb\u7edf\u7684\u9ed8\u8ba4 Python \u73af\u5883\uff0c\u6216\u8005\u4e00\u4e2a\u5176\u4ed6\u7248\u672c\u7684 PYthon \u73af\u5883\uff0c\u5426\u5219 \u64cd\u4f5c\u7cfb\u7edf\u53ef\u80fd\u65e0\u6cd5\u6b63\u5e38\u4f7f\u7528\u3002
\u8981\u5378\u8f7d\u5bf9\u5e94\u7248\u672c\u7684 Python \u73af\u5883\uff0c\u53ea\u9700\u8981\u5c06\u7cfb\u7edf\u6839\u76ee\u5f55\u76f8\u5173\u76ee\u5f55\u67e5\u627e\u5230\uff0c\u7136\u540e\u5220\u9664\u5373\u53ef\u3002
\u5bf9\u4e8e\u7f16\u8bd1\u5b89\u88c5\u7684 Python \u73af\u5883\uff0c\u4f1a\u5c06 Python \u5b89\u88c5\u5230\u5982\u4e0b\u51e0\u4e2a\u76ee\u5f55\uff1a
/usr/lib/python3.10
/usr/local/lib/libpython3.10.a
/usr/local/lib/python3.10
/usr/local/include/python3.10
/usr/local/bin/python3.10-config
/usr/local/bin/python3.10
/usr/local/share/man/man1/python3.10.1
\u8fd0\u884c\u547d\u4ee4\u5220\u9664\uff1a
# \u521b\u5efa\u5907\u4efd\u76ee\u5f55\uff0c\u4ee5\u4fbf\u51fa\u73b0\u95ee\u9898\uff0c\u53ef\u4ee5\u6267\u884c\u6062\u590d\n# \u6ce8\u610f\u4e0d\u8981\u5728 /tmp \u4e0b\u521b\u5efa\uff0c\u5982\u679c\u91cd\u542f\u7cfb\u7edf /tmp \u4e0b\u7684\u6587\u4ef6\u4f1a\u5220\u9664\u3002\n# \u653e\u5728\u5bb6\u76ee\u5f55\uff0c\u53ef\u4ee5\u901a\u8fc7\u5e94\u6025\u6a21\u5f0f\u627e\u5230\u76f8\u5e94\u6587\u4ef6\u3002\n# \u7b49\u786e\u4fdd\u64cd\u4f5c\u7cfb\u7edf\u6ca1\u6709\u4efb\u4f55\u5f02\u5e38\u95ee\u9898\u7684\u65f6\u5019\uff0c\u518d\u5220\u9664\nmkdir ~/removed_python310\nmv -f \\\n/usr/lib/python3.10 \\\n/usr/local/lib/libpython3.10.a \\\n/usr/local/lib/python3.10 \\\n/usr/local/include/python3.10 \\\n/usr/local/bin/python3.10-config \\\n/usr/local/bin/python3.10 \\\n/usr/local/share/man/man1/python3.10.1 \\\n~/removed_python310\n
\u5982\u679c\u4f60\u66fe\u4f7f\u7528\u8fc7 pip3.10
\u5b89\u88c5\u4f9d\u8d56\uff0c\u8bf7\u68c0\u67e5\u7528\u6237\u76ee\u5f55\u4e0b\u662f\u5426\u5b58\u5728\u76f8\u5173\u4f9d\u8d56\u76ee\u5f55\uff1a
/home/god/.local/lib/python3.10
"},{"location":"introduction/virtualenv/","title":"\u865a\u62df\u73af\u5883","text":"\u6587\u7ae0\u8981\u70b9\uff1a
- \u4ecb\u7ecd Python \u7684\u865a\u62df\u73af\u5883
- \u4ecb\u7ecd\u5e76\u4f7f\u7528 Python \u4e2d\u5e38\u89c1\u7684\u865a\u62df\u73af\u5883
- \u603b\u7ed3\u5f00\u53d1\u4e2d\u7684\u865a\u62df\u73af\u5883\u7684\u4f7f\u7528\u5b9e\u8df5
"},{"location":"introduction/virtualenv/#1","title":"1. \u6982\u8ff0","text":"Python \u5e94\u7528\u7a0b\u5e8f\u901a\u5e38\u4f1a\u4f7f\u7528\u4e0d\u5728\u6807\u51c6\u5e93\u5185\u7684\u8f6f\u4ef6\u5305\u548c\u6a21\u5757\u3002\u5e94\u7528\u7a0b\u5e8f\u6709\u65f6\u9700\u8981\u7279\u5b9a\u7248\u672c\u7684\u5e93\uff0c\u56e0\u4e3a\u5e94\u7528\u7a0b\u5e8f\u53ef\u80fd\u9700\u8981\u4fee\u590d\u7279\u5b9a\u7684\u9519\u8bef\uff0c\u6216\u8005\u53ef\u4ee5\u4f7f\u7528\u5e93\u7684\u8fc7\u65f6\u7248\u672c\u7684\u63a5\u53e3\u7f16\u5199\u5e94\u7528\u7a0b\u5e8f\u3002
\u8fd9\u610f\u5473\u7740\u4e00\u4e2a Python \u73af\u5883\u53ef\u80fd\u65e0\u6cd5\u6ee1\u8db3\u6bcf\u4e2a\u5e94\u7528\u7a0b\u5e8f\u7684\u8981\u6c42\u3002\u5982\u679c\u5e94\u7528\u7a0b\u5e8f A \u9700\u8981\u7279\u5b9a\u6a21\u5757\u7684 1.0 \u7248\u672c\uff0c\u4f46\u5e94\u7528\u7a0b\u5e8f B \u9700\u8981 2.0 \u7248\u672c\uff0c\u5219\u9700\u6c42\u5b58\u5728\u51b2\u7a81\uff0c\u5b89\u88c5\u7248\u672c 1.0 \u6216 2.0 \u5c06\u5bfc\u81f4\u67d0\u4e00\u4e2a\u5e94\u7528\u7a0b\u5e8f\u65e0\u6cd5\u8fd0\u884c\u3002
\u8fd9\u4e2a\u95ee\u9898\u7684\u89e3\u51b3\u65b9\u6848\u662f\u521b\u5efa\u4e00\u4e2a virtual environment \uff0c\u4e00\u4e2a\u76ee\u5f55\u6811\uff0c\u5176\u4e2d\u5b89\u88c5\u6709\u7279\u5b9aPython\u7248\u672c\uff0c\u4ee5\u53ca\u8bb8\u591a\u5176\u4ed6\u5305\u3002
\u7136\u540e\uff0c\u4e0d\u540c\u7684\u5e94\u7528\u5c06\u53ef\u4ee5\u4f7f\u7528\u4e0d\u540c\u7684\u865a\u62df\u73af\u5883\u3002 \u8981\u89e3\u51b3\u5148\u524d\u9762\u4f8b\u5b50\u4e2d\u7684\u51b2\u7a81\uff0c\u5e94\u7528\u7a0b\u5e8f A \u53ef\u4ee5\u62e5\u6709\u81ea\u5df1\u7684\u5b89\u88c5\u4e86 1.0 \u7248\u672c\u7684\u865a\u62df\u73af\u5883\uff0c\u800c\u5e94\u7528\u7a0b\u5e8f B \u5219\u62e5\u6709\u5b89\u88c5\u4e86 2.0 \u7248\u672c\u7684\u53e6\u4e00\u4e2a\u865a\u62df\u73af\u5883\u3002 \u5982\u679c\u5e94\u7528\u7a0b\u5e8f B \u8981\u6c42\u5c06\u67d0\u4e2a\u5e93\u5347\u7ea7\u5230 3.0 \u7248\u672c\uff0c\u4e5f\u4e0d\u4f1a\u5f71\u54cd\u5e94\u7528\u7a0b\u5e8f A \u7684\u73af\u5883\u3002
"},{"location":"introduction/virtualenv/#2","title":"2. \u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177","text":"\u73b0\u5728 Python \u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\u8d8a\u6765\u8d8a\u5f3a\u5927\u3002\u5e38\u89c1\u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\u5982\u4e0b\uff1a
venv
\uff1a Python \u6807\u51c6\u5e93\u4e2d\u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177 conda
\uff1a Anaconda \u4e0b\u7684\u7ba1\u7406\u5de5\u5177 Virtualenv
\uff1a \u7b2c\u4e09\u65b9\u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\uff0c\u73b0\u5728\u5728 Pypa \u4e2d\u7ef4\u62a4\u3002 Pipenv
\uff1a \u7b2c\u4e09\u65b9\u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\uff0c\u73b0\u5728\u5728 Pypa \u4e2d\u7ef4\u62a4\u3002 poetry
\uff1a \u7b2c\u4e09\u65b9\u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\u3002
"},{"location":"introduction/virtualenv/#21-venv","title":"2.1 venv","text":"venv
\u662f Python \u6807\u51c6\u5e93\u4e2d\u7684\u4e00\u4e2a\u6a21\u5757\u3002\u5982\u679c\u7cfb\u7edf\u4e2d\u6709\u591a\u4e2a\u7248\u672c\u7684 Python \u73af\u5883\uff0c\u53ef\u4ee5\u521b\u5efa\u6307\u5b9a\u7248\u672c\u7684\u865a\u62df\u73af\u5883\u3002
\u521b\u5efa\u865a\u62df\u73af\u5883\uff1a
\u5728\u5f53\u524d\u76ee\u5f55\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a demo
\u7684\u865a\u62df\u73af\u5883\u76ee\u5f55\uff1a
python3 -m venv demo\n
\u5982\u679c demo
\u4e0d\u5b58\u5728\uff0c\u5c31\u4f1a\u521b\u5efa\u8be5\u76ee\u5f55\uff0c\u540c\u65f6\u5728\u91cc\u9762\u521b\u5efa Python \u89e3\u91ca\u5668\uff0c\u6807\u51c6\u5e93\u548c\u5404\u79cd\u652f\u6301\u6587\u4ef6\u7684\u526f\u672c\u76ee\u5f55\u3002
\u901a\u5e38\u521b\u5efa\u4ee5\u70b9\u5f00\u5934\u7684 .venv
\u76ee\u5f55\u3002\u65e2\u53ef\u4ee5\u505a\u5230\u9690\u85cf\u76ee\u5f55\u7684\u6548\u679c\uff0c\u4e5f\u53ef\u4ee5\u548c\u5e38\u89c1\u7684 .env
\u73af\u5883\u53d8\u91cf\u5b9a\u4e49\u6587\u4ef6\u533a\u5206\u3002
\u4f7f\u7528\u865a\u62df\u73af\u5883\uff1a
\u4e0b\u9762\u6fc0\u6d3b\u73af\u5883\u53d8\u91cf
Windows:
demo\\Scripts\\activate.bat\n
Unix \u6216 MacOs \u4e0a\uff1a
source demo/bin/active\n
\u6fc0\u6d3b\u540e\u5c31\u53ef\u4ee5\u5728\u7ec8\u7aef\u4e2d\u4f7f\u7528\u521b\u5efa\u7684\u865a\u62df\u73af\u5883\u4e86\u3002
$ source demo/bin/activate\n(demo) $ python\nPython 3.7.3 (default, Oct 28 2020, 14:33:53)\n[GCC 8.3.0] on linux\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n>>> import sys\n>>> sys.version\n'3.7.3 (default, Oct 28 2020, 14:33:53) \\n[GCC 8.3.0]'\n>>> sys.path\n['', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/tmp/test/demo/lib/python3.7/site-packages']\n
\u9000\u51fa\u865a\u62df\u73af\u5883\uff1a
deactive\n
"},{"location":"introduction/virtualenv/#22-conda","title":"2.2 Conda","text":"Conda \u662f\u5728 Windows\uff0c macOS \u548c Linux \u4e0a\u8fd0\u884c\u7684\u5f00\u6e90\u8f6f\u4ef6\u5305\u7ba1\u7406\u7cfb\u7edf\u548c\u73af\u5883\u7ba1\u7406\u7cfb\u7edf\u3002 Conda \u5feb\u901f\u5b89\u88c5\uff0c\u8fd0\u884c\u548c\u66f4\u65b0\u8f6f\u4ef6\u5305\u53ca\u5176\u4f9d\u8d56\u9879\u3002Conda \u53ef\u4ee5\u8f7b\u677e\u5730\u5728\u672c\u5730\u8ba1\u7b97\u673a\u4e0a\u7684\u73af\u5883\u4e2d\u521b\u5efa\uff0c\u4fdd\u5b58\uff0c\u52a0\u8f7d\u548c\u5207\u6362\u3002\u5b83\u662f\u4e3a Python \u7a0b\u5e8f\u521b\u5efa\u7684\uff0c\u4f46\u53ef\u4ee5\u6253\u5305\u548c\u5206\u53d1\u9002\u7528\u4e8e\u4efb\u4f55\u8bed\u8a00\u7684\u8f6f\u4ef6\u3002
Conda \u4f5c\u4e3a\u8f6f\u4ef6\u5305\u7ba1\u7406\u5668\u53ef\u4ee5\u5e2e\u52a9\u60a8\u67e5\u627e\u548c\u5b89\u88c5\u8f6f\u4ef6\u5305\u3002\u5982\u679c\u60a8\u9700\u8981\u4e00\u4e2a\u9700\u8981\u4f7f\u7528\u5176\u4ed6\u7248\u672c\u7684 Python \u7684\u8f6f\u4ef6\u5305\uff0c\u5219\u65e0\u9700\u5207\u6362\u5230\u5176\u4ed6\u73af\u5883\u7ba1\u7406\u5668\uff0c\u56e0\u4e3a Conda \u4e5f\u662f\u73af\u5883\u7ba1\u7406\u5668\u3002\u4ec5\u9700\u51e0\u4e2a\u547d\u4ee4\uff0c\u60a8\u5c31\u53ef\u4ee5\u8bbe\u7f6e\u4e00\u4e2a\u5b8c\u5168\u72ec\u7acb\u7684\u73af\u5883\u6765\u8fd0\u884c\u8be5\u4e0d\u540c\u7248\u672c\u7684Python\uff0c\u540c\u65f6\u7ee7\u7eed\u5728\u6b63\u5e38\u73af\u5883\u4e2d\u8fd0\u884c\u60a8\u901a\u5e38\u7684 Python \u7248\u672c\u3002
\u5728\u9ed8\u8ba4\u914d\u7f6e\u4e0b\uff0cConda \u53ef\u4ee5\u5b89\u88c5\u548c\u7ba1\u7406\u5728 repo.anaconda.com \u4e0a\uff0c\u7531 Anaconda\u00ae \u5ba1\u67e5\u548c\u7ef4\u62a4\u7684\u4e0a\u5343\u4e2a\u8f6f\u4ef6\u5305\u3002
Conda\u53ef\u4ee5\u4e0e Travis CI \u548c AppVeyor \u7b49\u6301\u7eed\u96c6\u6210\u7cfb\u7edf\u7ed3\u5408\u4f7f\u7528\uff0c\u4ee5\u63d0\u4f9b\u9891\u7e41\uff0c\u81ea\u52a8\u7684\u4ee3\u7801\u6d4b\u8bd5\u3002
\u6240\u6709\u7248\u672c\u7684 Anaconda \u548c Miniconda \u4e2d\u90fd\u5305\u542b conda \u8f6f\u4ef6\u5305\u548c\u73af\u5883\u7ba1\u7406\u5668\u3002
\u64cd\u4f5c\u524d\u63d0\uff1a
\u8bf7\u786e\u4fdd Python \u73af\u5883\u662f\u7531 Anaconda \u6216 Miniconda \u63d0\u4f9b\u7684\u3002
\u521b\u5efa\u865a\u62df\u73af\u5883\uff1a
\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a demo
\u76ee\u5f55\u7684\u865a\u62df\u73af\u5883
conda create --name demo\n
\u4f7f\u7528\u865a\u62df\u73af\u5883\uff1a
C:\\Users\\test>conda activate demo\n\n(demo) C:\\Users\\test>python\nPython 3.8.3 (default, Jul 2 2020, 17:30:36) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n>>> import sys\n>>> sys.path\n['', 'C:\\\\ProgramData\\\\Anaconda3\\\\python38.zip', 'C:\\\\ProgramData\\\\Anaconda3\\\\DLLs', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib', 'C:\\\\ProgramData\\\\Anaconda3', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\win32', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\win32\\\\lib', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\Pythonwin']\n>>> sys.version\n'3.8.3 (default, Jul 2 2020, 17:30:36) [MSC v.1916 64 bit (AMD64)]'\n
\u9000\u51fa\u865a\u62df\u73af\u5883\uff1a
deactivate\n
"},{"location":"introduction/virtualenv/#23-virtualenv","title":"2.3 Virtualenv","text":"Virtualenv \u662f\u4e00\u4e2a\u7b2c\u4e09\u65b9\u5e93\uff0c\u73b0\u5728\u7531 Pypa \u7ba1\u7406\u3002\u5176\u5177\u6709\u6bd4 venv
\u66f4\u5f3a\u5927\u7684\u529f\u80fd\uff0c\u4f46\u73b0\u5728 Virtualenv \u7684\u4e00\u4e9b\u529f\u80fd\u4e5f\u5728\u6162\u6162\u662f\u914d\u5230 venv
\u4e0a\u3002
\u5b89\u88c5\uff1a
pip install -U virtualenv\n
Virtualenv \u5728 Conda \u73af\u5883\u4e0b\u4f1a\u6709 Bug \u30021
\u521b\u5efa\u865a\u62df\u73af\u5883\uff1a
\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a venv
\u76ee\u5f55\u7684\u865a\u62df\u73af\u5883
virtualenv venv\n
\u4f7f\u7528\u865a\u62df\u73af\u5883\uff1a
source venv/bin/activate\n
\u597d\u7528\u7684\u5de5\u5177\uff1a
\u642d\u914d VirtualenvWrapper \u53ef\u4ee5\u66f4\u65b9\u4fbf\u7684\u4f7f\u7528\u548c\u7ba1\u7406\u865a\u62df\u73af\u5883\u3002
Linux \u5b89\u88c5\uff1a
pip install virtualenvwrapper\n# \u6267\u884c virtualvnewrapper \u521d\u59cb\u5316\u811a\u672c\u3002\u53ef\u4ee5\u5c06\u4e0b\u9762\u8fd9\u4e00\u884c\u52a0\u5165\u5230 `~/.bashrc` \u4e2d\uff0c\u65b9\u4fbf\u5f53\u524d\u7528\u6237\u4f7f\u7528\uff0c\u6216\u8005\u52a0\u5165\u5230 `/etc/profile` \u4e2d\u65b9\u4fbf\u6240\u6709\u7528\u6237\u4f7f\u7528\nsource /usr/local/bin/virtualenvwrapper.sh\n
\u521b\u5efa\u865a\u62df\u73af\u5883\uff1a
# \u6267\u884c\u547d\u4ee4\uff0c\u9ed8\u8ba4\u4f1a\u5728 `~/.virtualenvs` \u4e0b\u521b\u5efa\u5bf9\u5e94\u540d\u79f0\u7684\u865a\u62df\u73af\u5883\u76ee\u5f55\uff0c\u540c\u65f6\u521d\u59cb\u5316\u865a\u62df\u73af\u5883\u3002\n# \u6240\u6709\u865a\u62df\u73af\u5883\u90fd\u4f1a\u96c6\u4e2d\u5b58\u653e\u5728\u8fd9\u91cc\u3002\u907f\u514d\u4e86\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\u6709\u865a\u62df\u73af\u5883\u76ee\u5f55\u3002\nmkvirtualenv venv\n
\u4f7f\u7528\u865a\u62df\u73af\u5883\uff1a
workon venv\n
\u5220\u9664\u865a\u62df\u73af\u5883\uff1a
rmvirtualenv venv\n
Windows \u5b89\u88c5\uff1a
pip install virtualenvwrapper-win\n
\u8bbe\u7f6e\u73af\u5883\u53d8\u91cf WORKON_HOME=D:/virtualenvs
\u4f7f\u7528\u7684\u65b9\u6cd5\u548c\u4e0a\u9762\u4e00\u81f4\u3002
"},{"location":"introduction/virtualenv/#24-pipenv","title":"2.4 Pipenv","text":"Pipenv \u662f\u4e00\u4e2a\u66f4\u9ad8\u7ea7\u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\uff0c\u5176\u4f9d\u8d56 Virtualenv
\uff0c\u5e76\u5728\u4e4b\u4e0a\u505a\u4e86\u8bb8\u591a\u5176\u4ed6\u529f\u80fd\u3002\u6b63\u5982\u5176\u5b98\u7f51\u4e2d\u6240\u8bf4\uff0c\u5b83\u7684\u76ee\u7684\u662f\u8981\u628a\u6240\u6709\u6700\u597d\u7684\u5305\u7ba1\u7406\uff08 bundler
, composer
, npm
\uff0c yarn
\u7b49\uff09\u5f15\u5165\u5230 Python \u4e2d\u3002
Pipenv \u5177\u6709\u5982\u4e0b\u7279\u70b9
- \u96c6\u4e2d\u5b58\u50a8\u865a\u62df\u73af\u5883\uff0c\u5982\u679c\u4e0d\u5b58\u5728\u5219\u76f4\u63a5\u521b\u5efa\u3002\u53ef\u4ee5\u901a\u8fc7
WORKON_HOME
\u73af\u5883\u53d8\u91cf\u914d\u7f6e\u3002 - \u751f\u6210
Pipfile
\u548c Pipfile.lock
\u3002\u524d\u8005\u8bb0\u5f55\u4f9d\u8d56\u9879\u3001\u5b89\u88c5\u6e90\u3001\u8981\u4f7f\u7528\u7684 Python \u7248\u672c\uff0c\u540e\u8005\u8bb0\u5f55\u6240\u5b89\u88c5\u7684\u7684\u7248\u672c\u7684 Hash \u503c\u7b49\u4fe1\u606f\u3002 - \u81ea\u52a8\u5b89\u88c5\u5378\u8f7d\u4f9d\u8d56\uff0c\u81ea\u52a8\u6e05\u9664\u65e0\u7528\u7684\u4f9d\u8d56\u3002
- \u81ea\u52a8\u52a0\u8f7d
.env
\u6587\u4ef6\u3002 - \u80fd\u6839\u636e\u4f9d\u8d56\u6811\u7684\u5173\u7cfb\u68c0\u6d4b\u4f9d\u8d56\u51b2\u7a81\u3002
\u5b89\u88c5\uff1a
pip install pipenv\n
\u521b\u5efa\u865a\u62df\u73af\u5883\uff1a
\u5728\u9879\u76ee\u6839\u76ee\u5f55\u6267\u884c pipenv install
\uff1a
root@b2e8a92bace7:~/demo# pipenv install\nCreating a virtualenv for this project...\nPipfile: /root/demo/Pipfile\nUsing /usr/local/bin/python3 (3.7.7) to create virtualenv...\n\u2838 Creating virtual environment...created virtual environment CPython3.7.7.final.0-64 in 175ms\n creator CPython3Posix(dest=/root/.virtualenvs/demo-xfYnOzmm, clear=False, no_vcs_ignore=False, global=False) \n seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/root/.local/share/virtualenv) \n added seed packages: pip==20.2.4, setuptools==50.3.2, wheel==0.35.1 \n activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator \n\n\u2714 Successfully created virtual environment! \nVirtualenv location: /root/.virtualenvs/demo-xfYnOzmm\nCreating a Pipfile for this project...\nPipfile.lock not found, creating...\nLocking [dev-packages] dependencies...\nLocking [packages] dependencies...\nUpdated Pipfile.lock (a65489)!\nInstalling dependencies from Pipfile.lock (a65489)...\n \ud83d\udc0d \u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589 0/0 \u2014 00:00:00\nTo activate this project's virtualenv, run pipenv shell.\nAlternatively, run a command inside the virtualenv with pipenv run.\n
\u4f7f\u7528\u865a\u62df\u73af\u5883\uff1a
\u5355\u6b21\u4f7f\u7528
# \u67e5\u770b\u865a\u62df\u73af\u5883\u7684 Python \u7248\u672c\npipenv run python --version\n
\u8fdb\u5165\u865a\u62df\u73af\u5883
pipenv shell\n
\u5b89\u88c5\u4f9d\u8d56\uff1a
pipenv install tox\n
\u4f9d\u8d56\u5b89\u88c5\u5b8c\u6210\u540e\uff0c\u4f1a\u66f4\u65b0 Pipfile
\u6587\u4ef6\uff0c\u540c\u65f6\u66f4\u65b0 Pipfile.lock
\u6587\u4ef6\uff0c\u8bb0\u5f55\u5b89\u88c5\u7684\u7248\u672c\u548c\u5bf9\u5e94 HASH \u503c\u3002
"},{"location":"introduction/virtualenv/#25-poetry","title":"2.5 Poetry (\u63a8\u8350\u4f7f\u7528)","text":"Poetry \u662f\u540e\u671f\u4e4b\u79c0\uff0c\u5b83\u7684\u96c4\u5fc3\u4e0d\u4ec5\u4ec5\u662f\u505a Pipenv \u7684\u4e8b\uff0c\u5b83\u8fd8\u60f3\u628a Python \u7684\u6253\u5305\u7ba1\u7406\u4e00\u5e76\u505a\u4e86\uff0c\u5e76\u6d88\u9664 setup.py
\u6587\u4ef6\u3002\u5b83\u4f7f\u7528\u57fa\u4e8e PEP517 \u89c4\u8303\u7684 pyproject.toml
\u6587\u4ef6\u8bb0\u5f55\u4fe1\u606f\uff0c\u5e76\u6253\u5305\u3002 \u5177\u4f53\u5185\u5bb9\u53ef\u4ee5\u53c2\u8003 PEP 517 -- A build-system independent format for source trees \u3002 \u5f53\u524d\u57fa\u4e8e PEP517 \u7684\u6784\u5efa\u6a21\u5f0f\u5df2\u7ecf\u5b8c\u5168\u53ef\u7528\u3002\u5728 pip \u7684\u53d1\u884c\u8bb0\u5f55\u4e2d\uff0c\u6700\u65e9\u662f\u5728 18.1 (2018-10-05) \u5c31\u5f15\u5165\u4e86 PEP517 \u7684 0.2
\u7248\u672c\u3002
\u5728\u4f7f\u7528\u4e0a\uff0cPoetry \u7ed9\u4eba\u7684\u611f\u89c9\u66f4\u73b0\u4ee3\u5316\u3002
\u5b89\u88c5\uff1a
pip install poetry\n
\u4f7f\u7528\uff1a
# \u4f7f\u7528\u524d\u9700\u8981\u5148\u521d\u59cb\u5316\u9879\u76ee\u7684\u57fa\u672c\u4fe1\u606f\uff0c\u751f\u6210 `pyproject.toml` \u6587\u4ef6\npoetry init\n\n# \u5b89\u88c5\u4f9d\u8d56\npoetry add tox\n\n# \u8fdb\u5165\u865a\u62df\u73af\u5883\npoetry shell\n\n# \u6784\u5efa\u9879\u76ee\npoetry build\n\n# \u53d1\u5e03\u9879\u76ee\npoetry publish\n
"},{"location":"introduction/virtualenv/#3-poetry","title":"3. \u865a\u62df\u73af\u5883\u5b9e\u8df5(Poetry)","text":"\u4f17\u591a\u7684\u865a\u62df\u73af\u5883\uff0c\u548c\u5bf9\u5e94\u7684\u5de5\u5177\uff0c\u5728\u9009\u62e9\u65f6\u96be\u514d\u6709\u70b9\u56f0\u60d1\uff0c\u8981\u9009\u62e9\u4e00\u4e2a\u597d\u7528\u7684\u5de5\u5177\uff0c\u6700\u4f73\u9014\u5f84\u5c31\u662f\u81ea\u5df1\u90fd\u5c1d\u8bd5\u4e00\u904d\u3002
\u4e0a\u8ff0\u51e0\u4e2a\u4e3b\u6d41\u865a\u62df\u73af\u5883\u5de5\u5177\u9664\u4e86 venv
\u662f\u5185\u7f6e\u5e93\uff0c\u5176\u4ed6\u51e0\u4e2a\u90fd\u662f\u57fa\u4e8e Virtualenv
\u518d\u6b21\u5f00\u53d1\uff0c\u5e76\u63d0\u4f9b\u4e86\u5176\u4ed6\u529f\u80fd\u3002\u4f46\u662f Virtualenv
\u6ca1\u6709\u63d0\u4f9b\u4f9d\u8d56\u68c0\u6d4b\u7684\u529f\u80fd\uff0c\u800c\u4e14\u4f9d\u8d56\u5305\u7684\u7ba1\u7406\u8fd8\u662f\u9700\u8981\u4f7f\u7528 pip
\u547d\u4ee4\uff0c\u4f9d\u8d56\u9879\u9700\u8981\u901a\u8fc7 requirements.txt
\u3002
\u5f53\u4f60\u9700\u8981\u7ba1\u7406\u4e0d\u540c\u5f00\u53d1\u73af\u5883\u4e0b\u7684\u4f9d\u8d56\u65f6\uff0c\u5c31\u9700\u8981\u4e24\u4e2a\u6216\u66f4\u591a\u4e2a requirements.txt
\u3002\u4f8b\u5982 requirements-devlopment.txt
\uff0c requirements-production.txt
\u6216\u8fd9 requirements-test.txt
\u3002
Poetry
\u662f\u4e00\u4e2a\u66f4\u9177\u7684\u5de5\u5177\uff0c\u65e0\u8bba\u662f\u4ea4\u4e92\u5730\u8f93\u51fa\uff0c\u8fd8\u662f\u5b83\u57fa\u4e8e PEP517 \u7684\u7279\u6027\u3002\u5b83\u5141\u8bb8\u60a8\u58f0\u660e\u60a8\u7684\u9879\u76ee\u6240\u4f9d\u8d56\u7684\u5e93\uff0c\u5b83\u5c06\u4e3a\u60a8\u7ba1\u7406\uff08\u5b89\u88c5/\u66f4\u65b0\uff09\u5b83\u4eec\u3002Poetry \u63d0\u4f9b\u4e86\u4e00\u4e2a\u9501\u5b9a\u6587\u4ef6\u4ee5\u786e\u4fdd\u53ef\u91cd\u590d\u5b89\u88c5\uff0c\u5e76\u53ef\u4ee5\u6784\u5efa\u60a8\u7684\u9879\u76ee\u4ee5\u4f9b\u5206\u53d1\u3002 poetry \u901a\u8fc7\u914d\u7f6e\u6587\u4ef6 pyproject.toml \u6765\u5b8c\u6210\u4f9d\u8d56\u7ba1\u7406\u3001\u73af\u5883\u914d\u7f6e\u3001\u57fa\u672c\u4fe1\u606f\u914d\u7f6e\u7b49\u529f\u80fd\uff0c\u76f8\u5f53\u4e8e\u628a Python \u9879\u76ee\u4e2d\u7684 Pipfile\u3001setup.py\u3001setup.cfg\u3001requirements.txt\u3001MANIFEST.in \u878d\u5408\u5230\u4e00\u8d77\u3002
\u7efc\u5408\u6765\u770b Poetry
\u5c31\u663e\u5f97\u66f4\u52a0\u5408\u9002\uff0c\u652f\u6301\u591a\u79cd\u73af\u5883\u7ba1\u7406\uff0c\u63d0\u4f9b\u4f9d\u8d56\u5173\u7cfb\u6821\u9a8c\uff0c\u548c\u4f9d\u8d56\u7684 poetry.lock \u6587\u4ef6\uff0c\u4e5f\u6709\u81ea\u52a8\u7ba1\u7406\u4f9d\u8d56\u7684\u64cd\u4f5c\u3002\u540c\u65f6\u53ef\u4ee5\u7528\u4e8e Python \u5de5\u7a0b\u6253\u5305\u548c\u53d1\u5e03\u3002
\u4e0b\u9762\u4ee5\u4e00\u4e2a\u9879\u76ee\u7684\u751f\u547d\u5468\u671f\u63cf\u8ff0\u5982\u4f55\u66f4\u597d\u7684\u4f7f\u7528 Poetry
\u3002
"},{"location":"introduction/virtualenv/#31","title":"3.1 \u521d\u59cb\u5316\u9879\u76ee","text":"\u521d\u59cb\u5316\u9879\u76ee\uff0c\u4f7f\u7528 Poetry
\u5728\u9879\u76ee\u6839\u76ee\u5f55\u521b\u5efa\u5f53\u524d\u9879\u76ee\u7684\u865a\u62df\u73af\u5883\u3002
poetry init\n
\u8fdb\u5165\u5f53\u524d\u9879\u76ee\u7684\u865a\u62df\u73af\u5883\u3002
poetry shell\n
"},{"location":"introduction/virtualenv/#32","title":"3.2 \u5b89\u88c5\u9879\u76ee\u4f9d\u8d56","text":"\u5f53\u9700\u8981\u533a\u5206\u5f00\u53d1\u73af\u5883\u548c\u666e\u901a\u73af\u5883\u65f6\uff0c\u5c31\u53ef\u4ee5\u901a\u8fc7 add -D
\u9009\u9879\u5b89\u88c5\u5f00\u53d1\u73af\u5883\u4f9d\u8d56
poetry add -D pytest tox\n
\u4e00\u822c\u7684\u4f9d\u8d56\u76f4\u63a5\u5b89\u88c5\u5373\u53ef\u3002
poetry add django requests scrapy sqlalchemy\n
"},{"location":"introduction/virtualenv/#33","title":"3.3 \u6e05\u7406\u4f9d\u8d56","text":"\u5f53\u9700\u8981\u4ece\u73af\u5883\u4e2d\u6e05\u9664\u4e0d\u5728\u9700\u8981\u7684\u4f9d\u8d56\u65f6\uff0c\u53ef\u4ee5\u4f7f\u7528\u547d\u4ee4\u5378\u8f7d
poetry remove scrapy\n
\u6216\u8005\u76f4\u63a5\u4fee\u6539 pyproject.toml
\u6587\u4ef6\uff0c\u5220\u9664\u4e0d\u518d\u9700\u8981\u7684\u5185\u5bb9\uff0c\u7136\u540e\u901a\u8fc7 poetry lock
\u66f4\u65b0 poetry.lock
\u6587\u4ef6\u3002
"},{"location":"introduction/virtualenv/#34","title":"3.4 \u90e8\u7f72","text":"\u5728\u90e8\u7f72\u65f6\uff0c\u5f3a\u70c8\u63a8\u8350\u4f7f\u7528 poetry install
\u5b89\u88c5\u5728 pyproject.toml
\u6587\u4ef6\u4e2d\u4f9d\u8d56\u5305\u3002
"},{"location":"introduction/virtualenv/#35-requirementstxt","title":"3.5 \u751f\u6210 requirements.txt
","text":"\u4f7f\u7528 poetry show
\u53ef\u4ee5\u770b\u5230\u6240\u6709\u4f9d\u8d56\u5217\u8868\u3002
# \u67e5\u770b\u6240\u6709\u4f9d\u8d56\npoetry show\n# \u4ec5\u6240\u6709\u5f00\u53d1\u4f9d\u8d56\npoetry show --only dev
poetry export -f requirements.txt --output --without-hashes\n
-
\u5982\u679c\u4f60\u7684\u662f Conda \u73af\u5883\uff0c\u8bf7\u4f7f\u7528 20.0.34
\u4e4b\u524d\u7684 Virtualenv \u3002\u5177\u4f53\u8bf7\u53c2\u8003 virtualenv==20.0.34 not compatible with python on windows #12094 \u548c conda support - Windows 3.7+ #1986 \u3002\u5982\u679c\u8fd9\u4e2a\u95ee\u9898\u5df2\u7ecf\u4fee\u590d\uff0c\u8bf7\u5ffd\u7565\u3002\u00a0\u21a9
"},{"location":"practices/web/","title":"\u5feb\u901f\u4e0a\u624b","text":"\u8fd9\u662f\u4e00\u4e2a\u5feb\u901f\u4e0a\u624b\u7684\u793a\u4f8b\u9879\u76ee\uff0c\u65e8\u5728\u901a\u8fc7\u4e00\u4e2a\u5c3d\u53ef\u80fd\u5305\u542b\u4e3b\u8981\u77e5\u8bc6\u70b9\u7684\u7b80\u5355\u9879\u76ee\uff0c\u6765\u5411\u4f7f\u7528\u8005\u5c55\u793a\u4e00\u4e2a\u66f4 Python \u5316\u7684\u9879\u76ee\u5f00\u53d1\u6d41\u7a0b\u3002
\u793a\u4f8b\u9879\u76ee\u662f\u4e00\u4e2a\u4f7f\u7528\u5f02\u6b65\u5fae Web \u6846\u67b6 Fastapi \u5f00\u53d1\u7684\u535a\u5ba2\u7cfb\u7edf\u3002\u9879\u76ee\u4e1a\u52a1\u529f\u80fd\u6bd4\u8f83\u7b80\u5355\uff0c\u4f46\u5b8c\u6574\u4f53\u73b0\u4e86\u4e00\u4e2a\u9879\u76ee\u4ece\u73af\u5883\u642d\u5efa\uff0c\u5230\u5f00\u53d1\uff0c\u6700\u540e\u6d4b\u8bd5\u53d1\u5e03\u7684\u5b8c\u6574\u6d41\u7a0b\u3002
"},{"location":"practices/web/#1","title":"1. \u5f00\u53d1\u73af\u5883\u642d\u5efa","text":""},{"location":"practices/web/#11-python","title":"1.1 Python \u73af\u5883","text":"\u9274\u4e8e\u5b98\u65b9\u5df2\u7ecf\u505c\u6b62\u5bf9 Python 2 \u7684\u652f\u6301 1 \uff0c\u6211\u4eec\u4e0d\u63a8\u8350\u518d\u4f7f\u7528 Python 2 \u8fdb\u884c\u5f00\u53d1\u3002\u6839\u636e\u5f53\u524d Python \u7248\u672c\u4f7f\u7528\u60c5\u51b5\uff0c\u63a8\u8350\u4f7f\u7528 Python 3.7+ \u3002
\u5177\u4f53\u7684\u7248\u672c\u7684 Python \u73af\u5883\u53ef\u4ee5\u5728 \u5b98\u7f51 \u4e0b\u8f7d\u3002\u4e3a\u4e86\u4f7f\u7528\u4fbf\u5229\u6027\uff0c\u53ef\u4ee5\u9009\u62e9 Anaconda 2 \u3002
"},{"location":"practices/web/#12","title":"1.2 \u5f00\u53d1\u5de5\u5177","text":"\u63a8\u8350\u4f7f\u7528 Pycharm \u4f5c\u4e3a\u4e3b\u8981\u5f00\u53d1\u5de5\u5177\uff0c\u53ef\u4ee5\u9009\u62e9\u793e\u533a\u7248\u672c\u514d\u8d39\u4f7f\u7528\u3002
Visual Studio Code \u662f\u5fae\u8f6f\u5f00\u53d1\u7684\u4e00\u6b3e\u514d\u8d39\u8f7b\u91cf\u6587\u672c\u7f16\u8f91\u5668\uff0c\u901a\u8fc7\u5b89\u88c5\u63d2\u4ef6\u53ef\u4ee5\u81ea\u5b9a\u4e49\u6210\u4e00\u6b3e\u529f\u80fd\u5f3a\u5927\u7684 IDE \u3002\u5728\u5bf9 Python \u7684\u652f\u6301\u4e0a\uff0c\u5df2\u7ecf\u6709\u4e86\u8f83\u4e3a\u5b8c\u5584\u7684\u63d2\u4ef6\u4f53\u7cfb\uff0c\u6b64\u65b9\u6848\u4e5f\u53ef\u4ee5\u4f5c\u4e3a\u5907\u7528\u3002
"},{"location":"practices/web/#13","title":"1.3 \u865a\u62df\u73af\u5883\u5de5\u5177","text":"\u63a8\u8350\u4f7f\u7528 poetry\u3002poetry \u76f8\u6bd4\u4f7f\u7528 requirements.txt
\u7ba1\u7406\u4f9d\u8d56\u5217\u8868\uff0c\u66f4\u52a0\u5f3a\u5927\u3002\u5b83\u652f\u6301\u540c\u65f6\u7ba1\u7406\u5f00\u53d1\u751f\u4ea7\u73af\u5883\u4f9d\u8d56\uff0c\u81ea\u52a8\u67e5\u627e\u865a\u62df\u73af\u5883\uff0c\u751f\u6210\u4f9d\u8d56\u9501\u5b9a\u6587\u4ef6\u7b49\u5176\u4ed6\u7279\u6027\u3002
\u5728\u5b89\u88c5\u597d Python \u73af\u5883\u540e\uff0c\u5e94\u8be5\u5728\u5168\u5c40\u73af\u5883\u4e2d\u5b89\u88c5 poetry \u3002
"},{"location":"practices/web/#14-git","title":"1.4 Git \u4f7f\u7528","text":"\u63a8\u8350\u4f7f\u7528 Git \u5bf9\u9879\u76ee\u8fdb\u884c\u7248\u672c\u7ba1\u7406\u3002\u6240\u4ee5\u9700\u8981\u63d0\u524d\u5b89\u88c5 Git \uff0c\u5e76\u719f\u6089\u5e38\u7528 Git \u7684\u6982\u5ff5\u548c\u5e38\u7528 Git \u547d\u4ee4\u3002
"},{"location":"practices/web/#2","title":"2. \u9879\u76ee\u521d\u59cb\u5316","text":""},{"location":"practices/web/#21","title":"2.1 \u521d\u59cb\u5316\u9879\u76ee\u7ed3\u6784","text":"\u9879\u76ee\u7ed3\u6784\u91c7\u7528 src
\u76ee\u5f55\u7ed3\u6784\uff0c\u8be6\u89c1 pypa/sampleproject \u3002
\u521b\u5efa\u9879\u76ee\u76ee\u5f55\u7ed3\u6784\uff1a
.\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 src\n\u2502 \u2514\u2500\u2500 example_blog\n\u2502 \u2514\u2500\u2500 __init__.py\n\u2514\u2500\u2500 tests\n \u2514\u2500\u2500 __init__.py\n
\u521d\u59cb\u5316\u9879\u76ee\u865a\u62df\u73af\u5883\uff1a
poetry init\n
\u6839\u636e\u4ea4\u4e92\u5f0f\u63d0\u793a\uff0c\u8fdb\u884c\u76f8\u5e94\u5185\u5bb9\u9009\u53d6\u586b\u5199\uff0c\u5b89\u88c5\u5b8c\u6210\u540e\uff0c\u9879\u76ee\u76ee\u5f55\u4f1a\u81ea\u52a8\u751f\u6210 pyproject.toml
\u6587\u4ef6\u3002
"},{"location":"practices/web/#22","title":"2.2 \u521d\u59cb\u5316\u9879\u76ee\u57fa\u672c\u4fe1\u606f","text":"\u7f16\u8f91 pyproject.toml
\u6587\u4ef6\uff0c \u914d\u7f6e\u9879\u76ee\u63cf\u8ff0\u4fe1\u606f\uff1a
[tool.poetry]\nname = \"example_blog\"\nversion = \"0.1.0\"\ndescription = \"This is example blog system.\"\nauthors = [\"huagang517 <huagang517@126.com>\"]\nreadme = \"README.md\"\n[tool.poetry.dependencies]\npython = \"^3.10\"\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n
"},{"location":"practices/web/#23","title":"2.3 \u589e\u52a0\u9879\u76ee\u81ea\u8ff0\u6587\u4ef6","text":"\u7f16\u5199 README.md
\u6587\u4ef6
# \u4e00\u4e2a\u7b80\u5355\u535a\u5ba2\u7cfb\u7edf\u793a\u4f8b.\n\u6b64\u9879\u76ee\u662f\u4e00\u4e2a\u7b80\u5355\u7684\u535a\u5ba2\u7cfb\u7edf\uff0c\u63d0\u4f9b\u4e00\u4e9b\u7528\u6237\u7ba1\u7406\u548c\u535a\u5ba2\u6587\u7ae0\u7ba1\u7406\u3002\u76ee\u7684\u662f\u6f14\u793a\u5982\u4f55\u505a\u4e00\u4e2a\u66f4\u52a0 Pythonic \u7684\u9879\u76ee\u3002\n\n\u5982\u679c\u60a8\u6709\u4efb\u4f55\u610f\u89c1\u548c\u5efa\u8bae\uff0c\u6b22\u8fce\u5f00\u542f ISSUE \u53d1\u8d77\u8ba8\u8bba\u3002\u671f\u5f85\u4e0e\u60a8\u6253\u9020\u66f4\u52a0\u5b8c\u7f8e\u7684 Python \u793a\u4f8b\u3002\n\n## \u534f\u4f5c\u5f00\u53d1\n- Fork \u4ed3\u5e93\n- \u7f16\u5199\u4ee3\u7801\uff0c\u6d4b\u8bd5\uff0c\u63d0\u4ea4\n- \u53d1\u8d77 PR\n- \u5ba1\u6838\u901a\u8fc7\u540e\u5408\u5e76\uff0c\u534f\u4f5c\u5b8c\u6210\n
"},{"location":"practices/web/#24-gitignore","title":"2.4 \u589e\u52a0 .gitignore
","text":"# Created by .ignore support plugin (hsz.mobi)\n### Python template\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n# Usually these files are written by a python script from a template\n# before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n# For a library or package, you might want to ignore these files since the code is\n# intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n# However, in case of collaboration, if having platform-specific dependencies or dependencies\n# having no cross-platform support, pipenv may install dependencies that don't work, or not\n# install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n### Windows template\n# Windows thumbnail cache files\nThumbs.db\nThumbs.db:encryptable\nehthumbs.db\nehthumbs_vista.db\n\n# Dump file\n*.stackdump\n\n# Folder config file\n[Dd]esktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n### Linux template\n*~\n\n# temporary files which can be created if a process still has a handle open of a deleted file\n.fuse_hidden*\n\n# KDE directory preferences\n.directory\n\n# Linux trash folder which might appear on any partition or disk\n.Trash-*\n\n# .nfs files are created when an open file is removed but is still being accessed\n.nfs*\n\n### macOS template\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n.vscode\n.idea\n
"},{"location":"practices/web/#25","title":"2.5 \u5b89\u88c5\u5f00\u53d1\u5305","text":"poetry install
"},{"location":"practices/web/#26-git","title":"2.6 \u521d\u59cb Git \u63d0\u4ea4","text":"git init\ngit config user.name example\ngit config user.email example@example.com\ngit add .\ngit commit -m \"feat: First commit!\"\n
"},{"location":"practices/web/#3","title":"3. \u9879\u76ee\u529f\u80fd\u5f00\u53d1","text":""},{"location":"practices/web/#31","title":"3.1 \u521b\u5efa\u547d\u4ee4\u884c\u5165\u53e3","text":"\u547d\u4ee4\u884c\u5165\u53e3\u662f\u542f\u52a8\u9879\u76ee\u7684\u4e3b\u5165\u53e3\uff0c\u5e38\u89c1\u7684\u505a\u6cd5\u662f\u4f7f\u7528\u4e00\u4e2a __main__
\u51fd\u6570\uff0c\u8c03\u7528\u542f\u52a8\u4ee3\u7801\uff0c\u7136\u540e\u4f7f\u7528 python
\u547d\u4ee4\u542f\u52a8\u8be5\u6587\u4ef6\u3002\u4f46\u5bf9\u4e8e\u591a\u7ea7\u547d\u4ee4\u53c2\u6570\u7684\u60c5\u51b5\u5c31\u6bd4\u8f83\u9ebb\u70e6\uff0c\u63a8\u8350\u4f7f\u7528 click \u5de5\u5177\u7f16\u5199\u5165\u53e3\u903b\u8f91\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add click\n
\u67e5\u770b pyproject.toml
\uff0c\u5c06\u589e\u52a0\u5b89\u88c5\u4f9d\u8d56\uff1a
[tool.poetry.dependencies]\nclick = \"^8.1.3\"\n
\u521b\u5efa src/example_blog/cmdline.py
\u6587\u4ef6\uff1a
@click.group(invoke_without_command=True)\n@click.pass_context\n@click.option('-V', '--version', is_flag=True, help='Show version and exit.')\ndef main(ctx, version):\nif version:\nclick.echo(__version__)\nelif ctx.invoked_subcommand is None:\nclick.echo(ctx.get_help())\n
\u7f16\u8f91 pyproject.toml
\uff0c\u5c06\u547d\u4ee4\u884c\u5165\u53e3\u6ce8\u518c\u5230\u9879\u76ee\u63cf\u8ff0\u6587\u4ef6\u4e2d\uff1a
[tool.poetry.scripts]\nexample_blog = \"example_blog.cmdline:main\"\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Add cmdline.\"\n
"},{"location":"practices/web/#32","title":"3.2 \u5f15\u5165\u9879\u76ee\u914d\u7f6e\u7cfb\u7edf","text":"\u9879\u76ee\u7684\u914d\u7f6e\u7cfb\u7edf\u662f\u4e00\u4e2a\u9879\u76ee\u7684\u6838\u5fc3\u9a71\u52a8\uff0c\u4f7f\u7528\u914d\u7f6e\u7cfb\u7edf\u4fbf\u4e8e\u7ba1\u7406\u6563\u843d\u5728\u5404\u5904\u7684\u914d\u7f6e\u53c2\u6570\uff0c\u4e5f\u65b9\u4fbf\u5728\u542f\u52a8\u524d\u901a\u8fc7\u8c03\u6574\u914d\u7f6e\uff0c\u6539\u53d8\u7cfb\u7edf\u884c\u4e3a\u3002
Dynaconf \u662f\u4e00\u4e2a\u9ad8\u5ea6\u7075\u6d3b\u7684\u914d\u7f6e\u7ba1\u7406\u5de5\u5177\uff0c\u652f\u6301\u591a\u73af\u5883\u5206\u5c42\uff0c\u591a\u79cd\u914d\u7f6e\u5bfc\u5165\u7b49\u6709\u70b9\u3002\u5728\u9879\u76ee\u5f00\u53d1\u4e2d\uff0c\u63a8\u8350\u4f7f\u7528\u5982\u4e0b\u5b9e\u8df5\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add dynaconf\n
\u67e5\u770b pyproject.toml
\uff0c\u5c06\u589e\u52a0\u5b89\u88c5\u4f9d\u8d56\uff1a
[tool.poetry.dependencies]\nclick = \"^8.1.3\"\ndynaconf = \"^3.1.11\"\n
\u5efa\u7acb\u914d\u7f6e\u5305\uff0c\u548c\u914d\u7f6e\u6587\u4ef6\uff1a
mkdir src/example_blog/config\ntouch src/example_blog/config/__init__.py\ntouch src/example_blog/config/settings.yml\n
\u7f16\u8f91 src/example_blog/config/__init__.py
\uff0c \u521d\u59cb\u5316\u5168\u5c40\u914d\u7f6e\u5bf9\u8c61\uff1a
import os\nimport sys\nfrom pathlib import Path\nfrom dynaconf import Dynaconf\n_BASE_DIR = Path(__file__).parent.parent\nsettings_files = [\nPath(__file__).parent / 'settings.yml',\n] # \u6307\u5b9a\u7edd\u5bf9\u8def\u5f84\u52a0\u8f7d\u9ed8\u8ba4\u914d\u7f6e\nsettings = Dynaconf(\nenvvar_prefix=\"EXAMPLE_BLOG\", # \u73af\u5883\u53d8\u91cf\u524d\u7f00\u3002\u8bbe\u7f6e`EXAMPLE_BLOG_FOO='bar'`\uff0c\u4f7f\u7528`settings.FOO`\nsettings_files=settings_files,\nenvironments=False, # \u542f\u7528\u591a\u5c42\u6b21\u65e5\u5fd7\uff0c\u652f\u6301 dev, pro\nload_dotenv=True, # \u52a0\u8f7d .env\nenv_switcher=\"EXAMPLE_BLOG_ENV\", # \u7528\u4e8e\u5207\u6362\u6a21\u5f0f\u7684\u73af\u5883\u53d8\u91cf\u540d\u79f0 EXAMPLE_BLOG_ENV=production\nlowercase_read=False, # \u7981\u7528\u5c0f\u5199\u8bbf\u95ee\uff0c settings.name \u662f\u4e0d\u5141\u8bb8\u7684\nincludes=[os.path.join(sys.prefix, 'etc', 'example_blog', 'settings.yml')], # \u81ea\u5b9a\u4e49\u914d\u7f6e\u8986\u76d6\u9ed8\u8ba4\u914d\u7f6e\nbase_dir=_BASE_DIR, # \u7f16\u7801\u4f20\u5165\u914d\u7f6e\n)\n
\u7f16\u8f91 src/example_blog/config/settings.yml
\uff0c\u521d\u59cb\u5316\u914d\u7f6e\uff1a
LOG_LEVEL: INFO\n
\u7f16\u8f91 src/example_blog/config/settings.local.yml
\uff0c\u589e\u52a0\u672c\u5730\u5f00\u53d1\u914d\u7f6e\uff1a
LOG_LEVEL: DEBUG\n
\u6839\u636e Dynaconf \u89c4\u5219\uff0c settings.local.yml
\u7684\u914d\u7f6e\u4e3a\u672c\u5730\u914d\u7f6e\uff0c\u4e14\u4f18\u5148\u7ea7\u6bd4 settings.yml
\u4f4e\uff0c\u6240\u4ee5\u672c\u5730\u914d\u7f6e\u4f1a\u5728\u540e\u9762\u52a0\u8f7d\uff0c\u8986\u76d6\u4e4b\u524d\u7684\u914d\u7f6e\u3002
\u7f16\u8f91 .gitignore
\uff0c\u5c06\u6240\u6709\u672c\u5730\u914d\u7f6e\u6392\u9664\u7248\u672c\u63a7\u5236\u4e4b\u5916\u3002
**/settings.local.yml\n
\u63d0\u4ea4\u4ee3\u7801:
git add .\ngit commit -m \"feat: Add config.\"\n
"},{"location":"practices/web/#33","title":"3.3 \u5f15\u5165\u65e5\u5fd7","text":"\u521b\u5efa src/example_blog/log.py
\uff0c\u521d\u59cb\u5316 log \uff1a
from logging.config import dictConfig\nfrom example_blog.config import settings\ndef init_log():\nlog_config = {\n'version': 1,\n'disable_existing_loggers': False,\n'formatters': {\n'sample': {'format': '%(asctime)s %(levelname)s %(message)s'},\n'verbose': {'format': '%(asctime)s %(levelname)s %(name)s %(process)d %(thread)d %(message)s'},\n\"access\": {\n\"()\": \"uvicorn.logging.AccessFormatter\",\n\"fmt\": '%(asctime)s %(levelprefix)s %(client_addr)s - \"%(request_line)s\" %(status_code)s',\n},\n},\n'handlers': {\n\"console\": {\n\"formatter\": 'verbose',\n'level': 'DEBUG',\n\"class\": \"logging.StreamHandler\",\n},\n},\n'loggers': {\n'': {'level': settings.LOG_LEVEL, 'handlers': ['console']},\n},\n}\ndictConfig(log_config)\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Add log\"\n
"},{"location":"practices/web/#34","title":"3.4 \u6570\u636e\u8bbf\u95ee","text":"\u6570\u636e\u5c42\u662f\u5e94\u7528\u7684\u6700\u5e95\u5c42\uff0c\u548c\u6570\u636e\u5b58\u50a8\u6253\u4ea4\u9053\u3002\u4f7f\u7528 sqlalchemy \u4f5c\u5e95\u5c42\u6570\u636e\u6a21\u578b\u5efa\u6a21\u548c\u6570\u636e\u8bbf\u95ee\u64cd\u4f5c\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add sqlalchemy mysqlclient\n
\u67e5\u770b pyproject.toml
\uff0c\u5c06\u589e\u52a0\u5b89\u88c5\u4f9d\u8d56\uff1a
[tool.poetry.dependencies]\nclick = \"^8.1.3\"\ndynaconf = \"^3.1.11\"\nsqlalchemy = \"^1.4.44\"\nmysqlclient = \"^2.1.1\"\n
\u7f16\u5199 src/example_blog/config/settings.yml
\uff0c\u589e\u52a0\u6570\u636e\u5e93\u914d\u7f6e\u4fe1\u606f\uff1a
# ######################################################################################################\n# # https://docs.sqlalchemy.org/en/13/core/engines.html\nDATABASE:\nDRIVER: mysql\nNAME: example_blog\nHOST: 127.0.0.1\nPORT: 3306\nUSERNAME: root\nPASSWORD: root\nQUERY:\ncharset: utf8mb4\n
\u8b66\u544a
settings.yml
\u4e3a\u7cfb\u7edf\u9ed8\u8ba4\u914d\u7f6e\uff0c\u4f1a\u88ab git \u8ffd\u8e2a\u7ba1\u7406\uff0c\u4e0d\u8981\u586b\u5199\u771f\u6b63\u7684\u6570\u636e\u5e93\u8fde\u63a5\u4fe1\u606f\u3002\u771f\u5b9e\u914d\u7f6e\u4fe1\u606f\u53ef\u4ee5\u5199\u5728 settings.local.yml
\u6587\u4ef6\u4e2d\uff0c\u4f1a\u8986\u76d6\u9ed8\u8ba4\u914d\u7f6e\u3002
\u65b0\u5efa src/example_blog/db.py
\uff0c\u521b\u5efa sqlalchemy \u8bbf\u95ee\u5bf9\u8c61\uff1a
\"\"\"Database connections\"\"\"\nfrom sqlalchemy.engine import create_engine\nfrom sqlalchemy.engine.base import Engine\nfrom sqlalchemy.engine.url import URL\nfrom sqlalchemy.orm import scoped_session, sessionmaker\nfrom example_blog.config import settings\nurl = URL(\ndrivername=settings.DATABASE.DRIVER,\nusername=settings.DATABASE.get('USERNAME', None),\npassword=settings.DATABASE.get('PASSWORD', None),\nhost=settings.DATABASE.get('HOST', None),\nport=settings.DATABASE.get('PORT', None),\ndatabase=settings.DATABASE.get('NAME', None),\nquery=settings.DATABASE.get('QUERY', None),\n)\nengine: Engine = create_engine(url, echo=True)\nSessionFactory = sessionmaker(bind=engine, autocommit=False, autoflush=True)\nScopedSession = scoped_session(SessionFactory)\n
\u521b\u5efa src/example_blog/models.py
\uff0c\u521b\u5efa\u6570\u636e\u6a21\u578b\uff1a
\"\"\"Models\"\"\"\nfrom datetime import datetime\nfrom sqlalchemy import Column, DateTime, Integer, String, Text\nfrom sqlalchemy.ext.declarative import declarative_base, declared_attr\nclass CustomBase:\n\"\"\"https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/mixins.html\"\"\"\n@declared_attr\ndef __tablename__(cls):\nreturn cls.__name__.lower()\n__table_args__ = {\n'mysql_engine': 'InnoDB',\n'mysql_collate': 'utf8mb4_general_ci'\n}\nid = Column(Integer, primary_key=True, autoincrement=True)\nBaseModel = declarative_base(cls=CustomBase)\nclass Article(BaseModel):\n\"\"\"Article table\"\"\"\ntitle = Column(String(500))\nbody = Column(Text(), nullable=True)\ncreate_time = Column(DateTime, default=datetime.now, nullable=False)\nupdate_time = Column(DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)\n
\u4e3a\u4e86\u5728\u5e94\u7528\u4e2d\u66f4\u65b9\u4fbf\u7684\u4f7f\u7528\u6570\u636e\u6a21\u578b\u5bf9\u8c61\uff0c\u5f15\u5165 pydantic \u6765\u5b9a\u4e49\u4e00\u4e9b\u5bf9\u8c61\u6a21\u578b\u7684\u57fa\u672c\u4fe1\u606f\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add pydantic\n
\u67e5\u770b pyproject.toml
\uff0c\u5c06\u589e\u52a0\u5b89\u88c5\u4f9d\u8d56\uff1a
[tool.poetry.dependencies]\nclick = \"^8.1.3\"\ndynaconf = \"^3.1.11\"\nsqlalchemy = \"^1.4.44\"\nmysqlclient = \"^2.1.1\"\npydantic = \"^1.10.2\"\n
\u521b\u5efa src/example_blog/schemas.py
\uff0c\u521b\u5efa\u5bf9\u8c61\u6a21\u578b\uff1a
from datetime import datetime\nfrom typing import Optional, TypeVar\nfrom pydantic import BaseModel, constr\nfrom example_blog.models import BaseModel as DBModel\nModelType = TypeVar('ModelType', bound=DBModel)\nCreateSchema = TypeVar('CreateSchema', bound=BaseModel)\nUpdateSchema = TypeVar('UpdateSchema', bound=BaseModel)\nclass InDBMixin(BaseModel):\nid: int\nclass Config:\norm_mode = True\nclass BaseArticle(BaseModel):\ntitle: constr(max_length=500)\nbody: Optional[str] = None\nclass ArticleSchema(BaseArticle, InDBMixin):\ncreate_time: datetime\nupdate_time: datetime\nclass CreateArticleSchema(BaseArticle):\npass\nclass UpdateArticleSchema(BaseArticle):\ntitle: Optional[constr(max_length=500)] = None\n
\u521b\u5efa src/example_blog/dao.py
\uff0c\u521b\u5efa\u6570\u636e\u8bbf\u95ee\u5c42\uff1a
from typing import Generic, List\nfrom fastapi.encoders import jsonable_encoder\nfrom sqlalchemy.orm import Session\nfrom example_blog.models import Article\nfrom example_blog.schemas import CreateSchema, ModelType, UpdateSchema, CreateArticleSchema, UpdateArticleSchema\nclass BaseDAO(Generic[ModelType, CreateSchema, UpdateSchema]):\nmodel: ModelType\ndef get(self, session: Session, offset=0, limit=10) -> List[ModelType]:\nresult = session.query(self.model).offset(offset).limit(limit).all()\nreturn result\ndef get_by_id(self, session: Session, pk: int, ) -> ModelType:\nreturn session.query(self.model).get(pk)\ndef create(self, session: Session, obj_in: CreateSchema) -> ModelType:\n\"\"\"Create\"\"\"\nobj = self.model(**jsonable_encoder(obj_in))\nsession.add(obj)\nsession.commit()\nreturn obj\ndef patch(self, session: Session, pk: int, obj_in: UpdateSchema) -> ModelType:\n\"\"\"Patch\"\"\"\nobj = self.get_by_id(session, pk)\nupdate_data = obj_in.dict(exclude_unset=True)\nfor key, val in update_data.items():\nsetattr(obj, key, val)\nsession.add(obj)\nsession.commit()\nsession.refresh(obj)\nreturn obj\ndef delete(self, session: Session, pk: int) -> None:\n\"\"\"Delete\"\"\"\nobj = self.get_by_id(session, pk)\nsession.delete(obj)\nsession.commit()\ndef count(self, session: Session):\nreturn session.query(self.model).count()\nclass ArticleDAO(BaseDAO[Article, CreateArticleSchema, UpdateArticleSchema]):\nmodel = Article\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Add models and DAO\"\n
"},{"location":"practices/web/#35","title":"3.5 \u670d\u52a1\u5c42","text":"\u521b\u5efa src/example_blog/services.py
\uff0c\u521b\u5efa\u670d\u52a1\uff1a
\"\"\"Service\"\"\"\nfrom typing import Generic, List\nfrom sqlalchemy.orm import Session\nfrom example_blog.dao import ArticleDAO, BaseDAO\nfrom example_blog.models import Article\nfrom example_blog.schemas import CreateSchema, ModelType, UpdateSchema\nclass BaseService(Generic[ModelType, CreateSchema, UpdateSchema]):\ndao: BaseDAO\ndef get(self, session: Session, offset=0, limit=10) -> List[ModelType]:\n\"\"\"\"\"\"\nreturn self.dao.get(session, offset=offset, limit=limit)\ndef total(self, session: Session) -> int:\nreturn self.dao.count(session)\ndef get_by_id(self, session: Session, pk: int) -> ModelType:\n\"\"\"Get by id\"\"\"\nreturn self.dao.get_by_id(session, pk)\ndef create(self, session: Session, obj_in: CreateSchema) -> ModelType:\n\"\"\"Create a object\"\"\"\nreturn self.dao.create(session, obj_in)\ndef patch(self, session: Session, pk: int, obj_in: UpdateSchema) -> ModelType:\n\"\"\"Update\"\"\"\nreturn self.dao.patch(session, pk, obj_in)\ndef delete(self, session: Session, pk: int) -> None:\n\"\"\"Delete a object\"\"\"\nreturn self.dao.delete(session, pk)\nclass ArticleService(BaseService[Article, CreateSchema, UpdateSchema]):\ndao = ArticleDAO()\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Add services.\"\n
"},{"location":"practices/web/#36-fastapi","title":"3.6 \u5f15\u5165 Fastapi","text":"Fastapi \u662f\u4e00\u4e2a\u8f7b\u91cf\u7684 Web \u6846\u67b6\uff0c\u73b0\u5728\u5f15\u5165\uff0c\u4f7f\u5176\u4f5c\u4e3a API \u5c42
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add fastapi uvicorn\n
\u67e5\u770b pyproject.toml
\uff0c\u589e\u52a0\u5b89\u88c5\u4f9d\u8d56\uff1a
[tool.poetry.dependencies]\nclick = \"^8.1.3\"\ndynaconf = \"^3.1.11\"\nsqlalchemy = \"^1.4.44\"\nmysqlclient = \"^2.1.1\"\npydantic = \"^1.10.2\"\nfastapi = \"^0.88.0\"\nuvicorn = \"^0.20.0\"\n
\u521b\u5efa src/examp.e_blog/views.py
\uff0c\u521b\u5efa\u89c6\u56fe\uff1a
from fastapi import APIRouter, Depends\nfrom sqlalchemy.orm import Session\nfrom example_blog.dependencies import CommonQueryParams, get_db\nfrom example_blog.schemas import (ArticleSchema, CreateArticleSchema,\nUpdateArticleSchema)\nfrom example_blog.services import ArticleService\nrouter = APIRouter()\n_service = ArticleService()\n@router.get('/articles')\ndef get(\nsession: Session = Depends(get_db),\ncommons: CommonQueryParams = Depends()\n):\nreturn _service.get(session, offset=commons.offset, limit=commons.limit)\n@router.get('/articles/{pk}')\ndef get_by_id(\npk: int,\nsession: Session = Depends(get_db)\n):\nreturn _service.get_by_id(session, pk)\n@router.post('/articles', response_model=ArticleSchema)\ndef create(\nobj_in: CreateArticleSchema,\nsession: Session = Depends(get_db),\n):\nreturn _service.create(session, obj_in)\n@router.patch('/articles/{pk}', response_model=ArticleSchema)\ndef patch(\npk: int,\nobj_in: UpdateArticleSchema,\nsession: Session = Depends(get_db)\n):\nreturn _service.patch(session, pk, obj_in)\n@router.delete('/articles/{pk}')\ndef delete(\npk: int,\nsession: Session = Depends(get_db)\n):\nreturn _service.delete(session, pk)\n
\u521b\u5efa src/example_blog/middlewares.py
\uff0c\u521b\u5efa\u6570\u636e\u5e93\u4f1a\u8bdd\u4e2d\u95f4\u4ef6\uff1a
from typing import Callable\nfrom fastapi import FastAPI, Request, Response\nfrom example_blog.db import SessionFactory\nasync def db_session_middleware(request: Request, call_next: Callable) -> Response:\nresponse = Response('Internal server error', status_code=500)\ntry:\nrequest.state.db = SessionFactory()\nresponse = await call_next(request)\nfinally:\nrequest.state.db.close()\nreturn response\ndef init_middleware(app: FastAPI) -> None:\napp.middleware('http')(db_session_middleware)\n
\u521b\u5efa src/example_blog/dependencies.py
\uff0c\u521b\u5efa Fastapi \u7684\u4f9d\u8d56\u9879\uff1a
from fastapi import Request\nfrom sqlalchemy.orm import Session\ndef get_db(request: Request) -> Session:\nreturn request.state.db\nclass CommonQueryParams:\ndef __init__(self, offset: int = 1, limit: int = 10):\nself.offset = offset - 1\nif self.offset < 0:\nself.offset = 0\nself.limit = limit\nif self.limit < 0:\nself.limit = 10\n
\u521b\u5efa src/example_blog/routes.py
\uff0c\u521b\u5efa\u8def\u7531\uff1a
from fastapi import APIRouter, FastAPI\nfrom example_blog import views\ndef router_v1():\nrouter = APIRouter()\nrouter.include_router(views.router, tags=['Article'])\nreturn router\ndef init_routers(app: FastAPI):\napp.include_router(router_v1(), prefix='/api/v1', tags=['v1'])\n
\u521b\u5efa src/example_blog/server.py
\uff0c\u521b\u5efa\u670d\u52a1\u542f\u52a8\u903b\u8f91\uff1a
\"\"\"server\"\"\"\nimport uvicorn\nfrom fastapi import FastAPI\nfrom example_blog import middlewares, routes\nfrom example_blog.config import settings\nfrom example_blog.log import init_log\nclass Server:\ndef __init__(self):\ninit_log()\nself.app = FastAPI()\ndef init_app(self):\nmiddlewares.init_middleware(self.app)\nroutes.init_routers(self.app)\ndef run(self):\nself.init_app()\nuvicorn.run(\napp=self.app,\nhost=settings.HOST,\nport=settings.PORT,\n)\n
\u4fee\u6539 src/example_blog/config/settings.yml
\uff0c\u589e\u52a0\u670d\u52a1\u914d\u7f6e\uff1a
HOST: 127.0.0.1\nPORT: 8000\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Add api service.\"\n
"},{"location":"practices/web/#37","title":"3.7 \u7f16\u5199\u542f\u52a8\u547d\u4ee4","text":"\u7f16\u8f91 src/example_blog/cmdline.py
\uff0c\u589e\u52a0\u542f\u52a8 Server \u903b\u8f91\uff1a
@main.command()\n@click.option('-h', '--host', show_default=True, help=f'Host IP. Default: {settings.HOST}')\n@click.option('-p', '--port', show_default=True, type=int, help=f'Port. Default: {settings.PORT}')\n@click.option('--level', help='Log level')\ndef server(host, port, level):\n\"\"\"Start server.\"\"\"\nkwargs = {\n'LOGLEVEL': level,\n'HOST': host,\n'PORT': port,\n}\nfor name, value in kwargs.items():\nif value:\nsettings.set(name, value)\nServer().run()\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Add server cmdline.\"\n
"},{"location":"practices/web/#38-server","title":"3.8 \u542f\u52a8 Server","text":"\u5c06\u672c\u9879\u76ee\u4ee5\u53ef\u7f16\u8f91\u65b9\u5f0f\u5b89\u88c5\u5230\u5f53\u524d Python \u73af\u5883\uff1a
pip install -e .\n
\u547d\u4ee4\u884c\u8fd0\u884c\uff1a
example_blog server\n
\u53ef\u4ee5\u770b\u5230\u5982\u4e0b\u8f93\u51fa\uff1a
INFO: Started server process [21687]\n2020-12-28 18:11:56,341 INFO uvicorn.error 21687 139772921304768 Started server process [21687]\nINFO: Waiting for application startup.\n2020-12-28 18:11:56,341 INFO uvicorn.error 21687 139772921304768 Waiting for application startup.\nINFO: Application startup complete.\n2020-12-28 18:11:56,341 INFO uvicorn.error 21687 139772921304768 Application startup complete.\nINFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\n2020-12-28 18:11:56,341 INFO uvicorn.error 21687 139772921304768 Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\n
\u6d4f\u89c8\u5668\u6253\u5f00 http://127.0.0.1:8000/docs \u5373\u53ef\u67e5\u770b\u63a5\u53e3\u6587\u6863\u3002
\u63d0\u4ea4\u4ee3\u7801
"},{"location":"practices/web/#39","title":"3.9 \u5f15\u5165\u8fc1\u79fb\u5de5\u5177","text":"\u4e3a\u4e86\u4fbf\u4e8e\u6570\u636e\u6a21\u578b\u53d8\u66f4\uff0c\u5f15\u5165 alembic \u505a\u6570\u636e\u5e93\u8fc1\u79fb\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add alembic\n
\u67e5\u770b pyproject.toml
\uff0c\u5c06\u589e\u52a0\u5b89\u88c5\u4f9d\u8d56\uff1a
[tool.poetry.dependencies]\nclick = \"^8.1.3\"\ndynaconf = \"^3.1.11\"\nsqlalchemy = \"^1.4.44\"\nmysqlclient = \"^2.1.1\"\npydantic = \"^1.10.2\"\nfastapi = \"^0.88.0\"\nuvicorn = \"^0.20.0\"\nalembic = \"^1.8.1\"\n
\u521d\u59cb\u5316 alembic \uff1a
alembic init migration\nmv alembic.ini src/example_blog/migration\n
\u5c06 alembic \u7684\u76f8\u5173\u6587\u4ef6\u5168\u90e8\u653e\u5230 src/example_blog/migration
\u76ee\u5f55\u4e2d
\u4fee\u6539 src/example_blog/migration/alembic.ini
\uff1a
# A generic, single database configuration.\n[alembic]\n# path to migration scripts\n;script_location = src/example_blog/migration\nscript_location = .\n# template used to generate migration files\n# file_template = %%(rev)s_%%(slug)s\n# timezone to use when rendering the date\n# within the migration file as well as the filename.\n# string value is passed to dateutil.tz.gettz()\n# leave blank for localtime\n# timezone =\n# max length of characters to apply to the\n# \"slug\" field\n# truncate_slug_length = 40\n# set to 'true' to run the environment during\n# the 'revision' command, regardless of autogenerate\n# revision_environment = false\n# set to 'true' to allow .pyc and .pyo files without\n# a source .py file to be detected as revisions in the\n# versions/ directory\n# sourceless = false\n# version location specification; this defaults\n# to src/example_blog/migration/versions. When using multiple version\n# directories, initial revisions must be specified with --version-path\n# version_locations = %(here)s/bar %(here)s/bat src/example_blog/migration/versions\n# the output encoding used when revision files\n# are written from script.py.mako\n# output_encoding = utf-8\n;sqlalchemy.url = driver://user:pass@localhost/dbname\n[post_write_hooks]\n# post_write_hooks defines scripts or Python functions that are run\n# on newly generated revision scripts. See the documentation for further\n# detail and examples\n# format using \"black\" - use the console_scripts runner, against the \"black\" entrypoint\n# hooks=black\n# black.type=console_scripts\n# black.entrypoint=black\n# black.options=-l 79\n# Logging configuration\n[loggers]\nkeys = root,sqlalchemy,alembic\n[handlers]\nkeys = console\n[formatters]\nkeys = generic\n[logger_root]\nlevel = WARN\nhandlers = console\nqualname =\n[logger_sqlalchemy]\nlevel = WARN\nhandlers =\nqualname = sqlalchemy.engine\n[logger_alembic]\nlevel = INFO\nhandlers =\nqualname = alembic\n[handler_console]\nclass = StreamHandler\nargs = (sys.stderr,)\nlevel = NOTSET\nformatter = generic\n[formatter_generic]\nformat = %(levelname)-5.5s [%(name)s] %(message)s\ndatefmt = %H:%M:%S\n
\u4fee\u6539 src/example_blog/migration/env.py
\uff1a
from logging.config import fileConfig\nfrom alembic import context\nfrom sqlalchemy import engine_from_config, pool\nfrom example_blog import db\nfrom example_blog.models import BaseModel\n# this is the Alembic Config object, which provides\n# access to the values within the .ini file in use.\nconfig = context.config\n# Interpret the config file for Python logging.\n# This line sets up loggers basically.\nfileConfig(config.config_file_name)\n# add your model's MetaData object here\n# for 'autogenerate' support\n# from myapp import mymodel\n# target_metadata = mymodel.Base.metadata\n# target_metadata = None\ntarget_metadata = BaseModel.metadata\n# other values from the config, defined by the needs of env.py,\n# can be acquired:\n# my_important_option = config.get_main_option(\"my_important_option\")\n# ... etc.\ndef run_migrations_offline():\n\"\"\"Run migrations in 'offline' mode.\n This configures the context with just a URL\n and not an Engine, though an Engine is acceptable\n here as well. By skipping the Engine creation\n we don't even need a DBAPI to be available.\n Calls to context.execute() here emit the given string to the\n script output.\n \"\"\"\ncontext.configure(\nurl=db.url,\ntarget_metadata=target_metadata,\nliteral_binds=True,\ndialect_opts={\"paramstyle\": \"named\"},\n)\nwith context.begin_transaction():\ncontext.run_migrations()\ndef run_migrations_online():\n\"\"\"Run migrations in 'online' mode.\n In this scenario we need to create an Engine\n and associate a connection with the context.\n \"\"\"\nconfiguration = config.get_section(config.config_ini_section)\nconfiguration['sqlalchemy.url'] = str(db.url)\nconnectable = engine_from_config(\nconfiguration,\nprefix=\"sqlalchemy.\",\npoolclass=pool.NullPool,\n)\nwith connectable.connect() as connection:\ncontext.configure(\nconnection=connection, target_metadata=target_metadata\n)\nwith context.begin_transaction():\ncontext.run_migrations()\nif context.is_offline_mode():\nrun_migrations_offline()\nelse:\nrun_migrations_online()\n
\u7f16\u5199 src/example_blog/cmdline.py
\uff0c\u521b\u5efa\u8fc1\u79fb\u547d\u4ee4\uff1a
from pathlib import Path\nfrom alembic import config\nfrom click import Context\n@main.command()\n@click.pass_context\n@click.option('-h', '--help', is_flag=True)\n@click.argument('args', nargs=-1)\ndef migrate(ctx: Context, help, args):\n\"\"\"usage migrate -- arguments \"\"\"\nwith utils.chdir(Path(__file__).parent / 'migration'):\nargv = list(args)\nif help:\nargv.append('--help')\nconfig.main(prog=ctx.command_path, argv=argv)\n
\u521b\u5efa utils.py
\uff1a
\"\"\"Utils\"\"\"\nimport contextlib\nimport os\nfrom os import PathLike\nfrom typing import Union\n@contextlib.contextmanager\ndef chdir(path: Union[str, PathLike]):\ncwd = os.getcwd()\nos.chdir(path)\nyield\nos.chdir(cwd)\n
\u63d0\u793a
\u7531\u4e8e\u4f7f\u7528\u4e86 click \u5305\u88c5\u4e86 alembic \u547d\u4ee4\uff0c\u5728\u4f7f\u7528\u4e0a\u4f1a\u6709\u70b9\u4e0d\u540c\uff0c\u9ed8\u8ba4\u5e94\u8be5\u4f7f\u7528 migrate --
\u540e\u52a0 alembic \u7684\u5176\u4ed6\u53c2\u6570\uff0c\u5426\u5219\u591a\u53c2\u6570\u7684\u60c5\u51b5\u4e0b\u4f1a\u65e0\u6cd5\u8bc6\u522b\u3002
\u4e3a\u4e86\u5c06 src/example_blog/migration
\u6253\u5305\u5230\u9879\u76ee\u4e2d\uff0c\u9700\u8981\u5c06\u5176\u53d8\u6210 Python \u5305\u3002
\u521b\u5efa src/example_blog/migration/__init__.py
\u548c src/example_blog/migration/versions/__init__.py
\u521b\u5efa\u7a7a\u767d\u6570\u636e\u5e93\u8fc1\u79fb\u7248\u672c\uff1a
example_blog migrate -- revision -m \"init\"\n
\u6267\u884c\u8fc1\u79fb\uff1a
example_blog migrate -- upgrade head\n
\u521b\u5efa\u7b2c\u4e00\u4e2a\u6570\u636e\u5e93\u8fc1\u79fb\u7248\u672c\uff1a
example_blog migrate -- revision --autogenerate -m \"init_table\"\n
\u6267\u884c\u8fc1\u79fb\uff1a
example_blog migrate -- upgrade head\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"Add alembic migrate.\"\n
"},{"location":"practices/web/#4","title":"4. \u6d4b\u8bd5\u548c\u4f18\u5316\u4ee3\u7801","text":"\u6d4b\u8bd5\u662f\u8f6f\u4ef6\u5f00\u53d1\u4e2d\u91cd\u8981\u7684\u4e00\u73af\uff0c\u80fd\u591f\u5728\u53d1\u5e03\u4e4b\u524d\u68c0\u67e5\u51fa\u66f4\u591a\u53ef\u80fd\u51fa\u73b0\u7684\u5f02\u5e38\u60c5\u51b5\u3002
\u6d4b\u8bd5\u6846\u67b6\u9009\u7528\u6bd4\u8f83\u5e38\u7528\u7684 pytest \uff0c\u5b83\u5177\u6709\u5f3a\u5927\u7684\u529f\u80fd\u548c\u5f88\u597d\u7684\u517c\u5bb9\u6027\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add -D pytest\n
\u521b\u5efa tests/settings.yml
\uff0c\u521d\u59cb\u5316\u6d4b\u8bd5\u914d\u7f6e\uff1a
DEBUG: false\nLOG_LEVEL: INFO\n\nHOST: 127.0.0.1\nPORT: 8000\n\nDATABASE:\n DRIVER: mysql\n NAME: example_blog\n HOST: 127.0.0.1\n PORT: 3306\n USERNAME: root\n PASSWORD: root\n QUERY:\n charset: utf8mb4\n
\u7f16\u8f91 tests/__init__.py
\uff0c\u52a0\u8f7d\u6d4b\u8bd5\u914d\u7f6e\uff1a
import os\nfrom example_blog.config import settings\nsettings.load_file(os.path.join(os.path.dirname(__file__), 'settings.yml'))\nsettings.load_file(os.path.join(os.path.dirname(__file__), 'settings.local.yml'))\n
\u867d\u7136\u672c\u5730\u5f00\u53d1\u914d\u7f6e\u53ef\u4ee5\u4e34\u65f6\u8c03\u6574\uff0c\u4f46\u5bf9\u4e8e\u5f00\u53d1\u73af\u5883\u548c\u6d4b\u8bd5\u73af\u5883\u4f9d\u7136\u6709\u4e9b\u4e0d\u4e00\u6837\u3002\u4ece\u4e0a\u9762\u4ee3\u7801\u4e2d\u53ef\u4ee5\u770b\u5230\u52a0\u8f7d\u4e86\u4e24\u4e2a\u6d4b\u8bd5\u914d\u7f6e\uff0c\u548c Dynaconf \u89c4\u5219\u4e00\u6837\uff0c settings.local.yml
\u914d\u7f6e\u4e3a\u672c\u5730\u914d\u7f6e\uff0c\u4e0d\u4f1a\u88ab\u4ee3\u7801\u8ffd\u8e2a\uff0c\u53ea\u4e0d\u8fc7\u8fd9\u91cc\u662f\u624b\u52a8\u5b9e\u73b0\u7684\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"test: Init test.\"\n
"},{"location":"practices/web/#41","title":"4.1 \u6d4b\u8bd5\u6570\u636e\u8bbf\u95ee\u5c42","text":"\u7f16\u5199\u6d4b\u8bd5\u914d\u7f6e\uff1a
\u65b0\u5efa tests/conftest.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u914d\u7f6e\uff1a
\"\"\"Test config\"\"\"\nimport os\nfrom pathlib import Path\nimport pytest\nfrom alembic import command, config\nfrom sqlalchemy.orm import Session\nfrom example_blog import migration\nfrom example_blog.config import settings\nfrom example_blog.db import SessionFactory\nfrom example_blog.models import Article\n@pytest.fixture()\ndef migrate():\n\"\"\"Re-init database when run a test.\"\"\"\nos.chdir(Path(migration.__file__).parent)\nalembic_config = config.Config('./alembic.ini')\nalembic_config.set_main_option('script_location', os.getcwd())\nprint('\\n----- RUN ALEMBIC MIGRATION: -----\\n')\ncommand.downgrade(alembic_config, 'base')\ncommand.upgrade(alembic_config, 'head')\ntry:\nyield\nfinally:\ncommand.downgrade(alembic_config, 'base')\ndb_name = settings.DATABASE.get('NAME')\nif settings.DATABASE.DRIVER == 'sqlite' and os.path.isfile(db_name):\ntry:\nos.remove(db_name)\nexcept FileNotFoundError:\npass\n@pytest.fixture()\ndef session(migrate) -> Session:\n\"\"\"session fixture\"\"\"\n_s = SessionFactory()\nyield _s\n_s.close()\n@pytest.fixture()\ndef init_article(session):\n\"\"\"Init article\"\"\"\na_1 = Article(title='Hello world', body='Hello world, can you see me?')\na_2 = Article(title='Love baby', body='I love you everyday, and i want with you.')\na_3 = Article(title='Tomorrow', body='When the sun rises, this day is fine day, cheer up.')\nsession.add_all([a_1, a_2, a_3])\nsession.commit()\n
\u7f16\u5199\u6570\u636e\u8bbf\u95ee\u5c42\u7528\u4f8b\uff1a
import pytest\nfrom example_blog.dao import ArticleDAO\nfrom example_blog.models import Article\nfrom example_blog.schemas import CreateArticleSchema, UpdateArticleSchema\nclass TestArticle:\n@pytest.fixture()\ndef dao(self, init_article):\nyield ArticleDAO()\ndef test_get(self, dao, session):\nusers = dao.get(session)\nassert len(users) == 3\nusers = dao.get(session, limit=2)\nassert len(users) == 2\nusers = dao.get(session, offset=4)\nassert not users\ndef test_get_by_id(self, dao, session):\nuser = dao.get_by_id(session, 1)\nassert user.id == 1\ndef test_create(self, dao, session):\norigin_count = session.query(dao.model).count()\nobj_in = CreateArticleSchema(title='test')\ndao.create(session, obj_in)\ncount = session.query(dao.model).count()\nassert origin_count + 1 == count\ndef test_patch(self, dao, session):\nobj: Article = session.query(dao.model).first()\nbody = obj.body\nobj_in = UpdateArticleSchema(body='test')\nupdated_obj: Article = dao.patch(session, obj.id, obj_in)\nassert body != updated_obj.body\ndef test_delete(self, dao, session):\norigin_count = session.query(dao.model).count()\ndao.delete(session, 1)\ncount = session.query(dao.model).count()\nassert origin_count - 1 == count\ndef test_count(self, dao, session):\ncount = dao.count(session)\nassert count == 3\n
\u8fd0\u884c\u6d4b\u8bd5\uff1a
pytest tests/test_dao.py\n
\u5982\u679c\u8fd0\u884c\u6210\u529f\uff0c\u5219\u6d4b\u8bd5\u6b63\u786e\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"test: Add dao test.\"\n
"},{"location":"practices/web/#42","title":"4.2 \u6d4b\u8bd5\u670d\u52a1\u5c42","text":"\u521b\u5efa tests/test_services.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u7528\u4f8b\uff1a
import pytest\nfrom example_blog.schemas import CreateArticleSchema, UpdateArticleSchema\nfrom example_blog.services import ArticleService\nclass TestArticleService:\n@pytest.fixture()\ndef service(self, init_article):\nyield ArticleService()\ndef test_get(self, service, session):\nobjs = service.get(session)\nassert len(objs) == 3\nobjs = service.get(session, limit=2)\nassert len(objs) == 2\nobjs = service.get(session, offset=5)\nassert not objs\ndef test_total(self, service, session):\ntotal = service.total(session)\nassert total == 3\ndef test_by_id(self, service, session):\n__obj = session.query(service.dao.model).first()\nobj = service.get_by_id(session, __obj.id)\nassert obj.id == __obj.id\ndef test_create(self, service, session):\norigin_count = service.total(session)\nobj_in = CreateArticleSchema(title='test')\nservice.create(session, obj_in)\ncount = service.total(session)\nassert origin_count + 1 == count\ndef test_patch(self, service, session):\norigin_obj = session.query(service.dao.model).first()\nbody = origin_obj.body\nobj_in = UpdateArticleSchema(body='test')\nobj = service.patch(session, origin_obj.id, obj_in)\nassert body != obj.body\ndef test_delete(self, service, session):\norigin_count = service.total(session)\nobj = session.query(service.dao.model).first()\nservice.delete(session, obj.id)\ncount = service.total(session)\nassert origin_count - 1 == count\n
\u8fd0\u884c\u6d4b\u8bd5\uff1a
pytest tests/test_services.py\n
\u5982\u679c\u8fd0\u884c\u6210\u529f\uff0c\u5219\u6d4b\u8bd5\u6b63\u786e\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"test: Add service test.\"\n
"},{"location":"practices/web/#43","title":"4.3 \u6d4b\u8bd5\u89c6\u56fe\u5c42","text":"\u7f16\u8f91 tests/conftest.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u914d\u7f6e\uff1a
from fastapi.testclient import TestClient\nfrom example_blog import migration, server\n@pytest.fixture\ndef client():\n\"\"\"Fast api test client factory\"\"\"\n_s = server.Server()\n_s.init_app()\n_c = TestClient(app=_s.app)\nyield _c\n
\u7531\u4e8e Fastapi \u7684 TestClient
\u4f9d\u8d56 requests
\uff0c\u6240\u4ee5\u9700\u8981\u5148\u5b89\u88c5\uff1a
poetry add -D requests\n
\u521b\u5efa tests/test_views.py
\uff0c\u6d4b\u8bd5\u8bd5\u56fe\uff1a
import pytest\nfrom fastapi.encoders import jsonable_encoder\nfrom fastapi.responses import Response\nfrom example_blog.models import Article\nfrom example_blog.schemas import ModelType\ndef test_docs(client):\n\"\"\"Test view\"\"\"\nresponse = client.get('/docs')\nassert response.status_code == 200\nclass BaseTest:\nversion = 'v1'\nbase_url: str\nmodel: ModelType\n@pytest.fixture()\ndef init_data(self):\npass\ndef url(self, pk: int = None) -> str:\nurl_split = ['api', self.version, self.base_url]\nif pk:\nurl_split.append(str(pk))\nreturn '/'.join(url_split)\ndef assert_response_ok(self, response: Response):\nassert response.status_code == 200\ndef test_get(self, client, session, init_data):\ncount = session.query(self.model).count()\nresponse = client.get(self.url())\nself.assert_response_ok(response)\nassert count == len(response.json())\ndef test_get_by_id(self, client, session, init_data):\nobj = session.query(self.model).first()\nresponse = client.get(self.url(obj.id))\nself.assert_response_ok(response)\nassert jsonable_encoder(obj) == response.json()\ndef test_delete(self, client, session, init_data):\ncount = session.query(self.model).count()\nsession.close()\nresponse = client.delete(self.url(1))\nself.assert_response_ok(response)\nafter_count = session.query(self.model).count()\nassert after_count == 2\nassert count - 1 == after_count\nclass TestArticle(BaseTest):\nmodel = Article\nbase_url = 'articles'\n@pytest.fixture()\ndef init_data(self, init_article):\npass\ndef test_create(self, client, session, init_data):\nresponse = client.post(\nself.url(),\njson={'title': 'xxx'}\n)\nself.assert_response_ok(response)\nassert response.json().get('title') == 'xxx'\ndef test_patch(self, client, session, init_data):\nobj = session.query(Article).first()\nresponse = client.patch(self.url(obj.id), json={'body': 'xxx'})\nself.assert_response_ok(response)\nassert response.json().get('body') != obj.body\n
\u8fd0\u884c\u6d4b\u8bd5\uff1a
pytest tests/test_views.py\n
\u5982\u679c\u8fd0\u884c\u6210\u529f\uff0c\u5219\u6d4b\u8bd5\u6b63\u786e\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"test: Add view test.\"\n
"},{"location":"practices/web/#44","title":"4.4 \u6d4b\u8bd5\u547d\u4ee4\u884c","text":"\u7f16\u8f91 tests/conftest.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u914d\u7f6e\uff1a
from click.testing import CliRunner\n@pytest.fixture\ndef cli():\nrunner = CliRunner(echo_stdin=True, mix_stderr=False)\nyield runner\n
\u521b\u5efa tests/test_cmdline.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u7528\u4f8b\uff1a
import uvicorn\nfrom alembic import config\nimport example_blog\nfrom example_blog import cmdline\ndef test_main(cli):\nresult = cli.invoke(cmdline.main)\nassert result.exit_code == 0\nresult = cli.invoke(cmdline.main, '-V')\nassert result.exit_code == 0\nassert str(result.output).strip() == example_blog.__version__\ndef test_run(cli, mocker):\nmock_run = mocker.patch.object(uvicorn, 'run')\nresult = cli.invoke(cmdline.main, ['server', '-h', '127.0.0.1', '-p', '8080'])\nassert result.exit_code == 0\nmock_run.assert_called_once_with(app=mocker.ANY, host='127.0.0.1', port=8080)\ndef test_migrate(cli, mocker):\nmock_main = mocker.patch.object(config, 'main')\ncli.invoke(cmdline.main, ['migrate', '--help'])\nmock_main.assert_called_once()\n
\u56e0\u4e3a\u5355\u5143\u6d4b\u8bd5\u4e2d\u4f7f\u7528\u4e86 mock \uff0c\u6240\u4ee5\u5b89\u88c5\u914d\u5408 pytest \u4f7f\u7528\u7684 pytest-mock
poetry add -D pytest-mock\n
\u8fd0\u884c\u6d4b\u8bd5\uff1a
pytest tests/test_views.py\n
\u5982\u679c\u8fd0\u884c\u6210\u529f\uff0c\u5219\u6d4b\u8bd5\u6b63\u786e\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"test: Add cmdline test.\"\n
"},{"location":"practices/web/#45","title":"4.5 \u5176\u4ed6\u6d4b\u8bd5","text":"\u521b\u5efa tests/test_dependencies.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u7528\u4f8b\uff1a
import pytest\nfrom example_blog.dependencies import CommonQueryParams\n@pytest.mark.parametrize(\n['args', 'expect_value'],\n[\n((), (0, 10)),\n((0,), (0, 10)),\n((-10, -10), (0, 10)),\n((5, 100), (4, 100)),\n]\n)\ndef test_common_query_params(args, expect_value):\nparams = CommonQueryParams(*args)\nassert params.offset == expect_value[0]\nassert params.limit == expect_value[1]\n
\u521b\u5efa tests/test_utils.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u7528\u4f8b\uff1a
import os\nfrom example_blog.utils import chdir\ndef test_chdir():\npath = '/tmp'\ncwd = os.getcwd()\nwith chdir(path):\nassert path == os.getcwd()\nassert cwd == os.getcwd()\n
\u8fd0\u884c\u6d4b\u8bd5\uff1a
pytest\n
\u5982\u679c\u8fd0\u884c\u6210\u529f\uff0c\u5219\u6d4b\u8bd5\u6b63\u786e\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"test: Add other test.\"\n
\u81f3\u6b64\uff0c\u6240\u6709\u6d4b\u8bd5\u8fd0\u884c\u5b8c\u6bd5\uff0c\u9664\u4e86 src/example_blog/migration
\u4e4b\u5916\u7684\u5305\u7684\u6d4b\u8bd5\u5df2\u7ecf\u53ef\u4ee5\u5168\u90e8\u8986\u76d6\u3002
"},{"location":"practices/web/#46","title":"4.6 \u4f18\u5316\u4ee3\u7801","text":"\u4ee3\u7801\u98ce\u683c\u548c\u4ee3\u7801\u89c4\u8303\u662f\u4e00\u4e2a\u5f00\u53d1\u4eba\u5458\u5f00\u53d1\u4fee\u517b\u7684\u4f53\u73b0\uff0c\u597d\u7684\u4ee3\u7801\u80fd\u591f\u8ba9\u4eba\u773c\u524d\u4e00\u4eae\u3002\u4e3a\u4e86\u89c4\u8303\uff0c\u793e\u533a\u5f00\u53d1\u8bb8\u591a\u5de5\u5177\u7528\u4e8e\u68c0\u6d4b\u4ee3\u7801\u3002
"},{"location":"practices/web/#461","title":"4.6.1 \u4f18\u5316\u5bfc\u5165","text":"isort \u662f\u4e00\u4e2a\u81ea\u52a8\u683c\u5f0f\u5316\u5bfc\u5165\u7684\u5de5\u5177\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add -D isort\n
\u683c\u5f0f\u5316\u4ee3\u7801\uff1a
isort .\n
\u6b64\u65f6\u53ef\u4ee5\u4e0d\u7528\u5148\u6025\u7740\u63d0\u4ea4\uff0c\u5728\u540e\u9762\u5bf9\u4ee3\u7801\u98ce\u683c\u68c0\u6d4b\u7684\u65f6\u5019\u53ef\u80fd\u8fd8\u4f1a\u518d\u6b21\u683c\u5f0f\u5316\u4ee3\u7801\u3002
"},{"location":"practices/web/#462","title":"4.6.2 \u4f18\u5316\u4ee3\u7801\u98ce\u683c","text":"flake8 \u662f\u4e00\u4e2a\u9075\u5faa PEP8 \u89c4\u8303\u68c0\u6d4b\u4ee3\u7801\u7684\u5de5\u5177\u3002\u4f7f\u7528\u8be5\u5de5\u5177\uff0c\u53ef\u4ee5\u68c0\u6d4b\u51fa\u54ea\u4e9b\u4ee3\u7801\u4e0d\u7b26\u5408 PEP8 \u89c4\u8303\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add -D flake8\n
\u68c0\u6d4b\u4ee3\u7801\uff1a
flake8\n
\u6839\u636e\u8f93\u51fa\u63d0\u793a\uff0c\u53c2\u7167 flake8 \u89c4\u5219 \u8fdb\u884c\u8c03\u6574\uff0c\u76f4\u81f3\u5b8c\u5168\u7b26\u5408\u4e3a\u6b62\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Lint code\"\n
"},{"location":"practices/web/#5","title":"5. \u6253\u5305\u53d1\u5e03","text":"\u5230\u8fd9\u4e00\u6b65\uff0c pyproject.toml
\u6587\u4ef6\u5e94\u8be5\u662f\u8fd9\u6837\u7684\uff1a
[tool.poetry]\nname = \"example_blog\"\nversion = \"0.1.0\"\ndescription = \"This is example blog system.\"\nauthors = [\"huagang <huagang517@126.com>\"]\nreadme = \"README.md\"\n[tool.poetry.dependencies]\npython = \"^3.10\"\nfastapi-sa = \"^0.0.1.dev0\"\nsqlalchemy = \"^1.4.44\"\nmysqlclient = \"^2.1.1\"\npydantic = \"^1.10.2\"\ndynaconf = \"^3.1.11\"\nfastapi = \"^0.88.0\"\nuvicorn = \"^0.20.0\"\nalembic = \"^1.8.1\"\n[tool.poetry.group.dev.dependencies]\npytest = \"^7.2.0\"\nisort = \"^5.10.1\"\nrequests = \"^2.28.1\"\npytest-mock = \"^3.10.0\"\nflake8 = \"^6.0.0\"\n[tool.poetry.scripts]\nexample_blog = \"example_blog.cmdline:main\"\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n
\u5728\u6574\u4e2a\u5f00\u53d1\u8fc7\u7a0b\u4e2d\uff0c\u662f\u9010\u6b65\u4e30\u5bcc\u6b64\u6587\u4ef6\u7684\u3002\u8fd9\u662f\u9879\u76ee\u7684\u63cf\u8ff0\u6587\u4ef6\uff0c\u63cf\u8ff0\u4e86\u6253\u5305\u7684\u914d\u7f6e\u4fe1\u606f\u3002
"},{"location":"practices/web/#51","title":"5.1 \u6253\u5305","text":"poetry build\n
\u5728 dist
\u76ee\u5f55\u4e2d\u53ef\u4ee5\u770b\u5230\u4e24\u4e2a\u6587\u4ef6\uff0c\u4e00\u4e2a\u662f .tar.gz
\u7684\u6e90\u7801\u6253\u5305\u6587\u4ef6\uff0c\u4e00\u4e2a\u662f .whl
\u7684\u4e8c\u8fdb\u5236\u6587\u4ef6\u3002
"},{"location":"practices/web/#52","title":"5.2 \u53d1\u5e03","text":"\u5c06\u5f00\u53d1\u597d\u7684\u9879\u76ee\u53d1\u5e03\u5230\u7d22\u5f15\u4ed3\u5e93\uff0c\u6216\u5185\u7f51\u7684\u79c1\u6709\u4ed3\u5e93\u3002
\u4f7f\u7528 poetry \u4e0a\u4f20\uff1a
poetry publish\n
-
https://www.python.org/doc/sunset-python-2/\u00a0\u21a9
-
\u73b0\u5728 Anaconda / Miniconda \u5728 Windows \u4e0a\u4f7f\u7528\u865a\u62df\u73af\u5883\u5de5\u5177 Virtualenv \u5b58\u5728\u4e00\u4e9b\u517c\u5bb9\u95ee\u9898\uff0c\u800c\u4e14 Pipenv \u662f\u4f9d\u8d56\u8fd9\u4e2a\u5de5\u5177\u7684\u3002\u8bf7\u53c2\u8003 conda support - Windows 3.7+ #1986 \u548c virtualenv==20.0.34 not compatible with python on windows #12094 \u21a9
"},{"location":"standard/language_rules/","title":"Python \u8bed\u8a00\u89c4\u8303","text":"\u672c\u6587\u6863\u4e3a Google Python Style Guide \u7b2c\u4e8c\u7ae0 Python Language Rules \u7684\u8bd1\u6587\u3002
\u6700\u540e\u66f4\u65b0\u65f6\u95f4\uff1a 2023-06-26
\u5982\u679c\u6709\u7ffb\u8bd1\u9519\u8bef\u6216\u8868\u8ff0\u4e0d\u51c6\u786e\u7684\u95ee\u9898\uff0c\u6b22\u8fce\u63d0\u4ea4 PR\uff0c\u611f\u8c22\u60a8\u7684\u53c2\u4e0e\u3002
"},{"location":"standard/language_rules/#11-lint","title":"1.1 Lint","text":"\u4f7f\u7528 pylintrc \u914d\u7f6e\uff0c\u5bf9\u4f60\u7684\u4ee3\u7801\u8fd0\u884c pylint
\u3002
"},{"location":"standard/language_rules/#111","title":"1.1.1 \u5b9a\u4e49","text":"Pylint
\u662f\u4e00\u4e2a\u5728 Python \u6e90\u4ee3\u7801\u4e2d\u67e5\u627e bug \u548c\u98ce\u683c\u95ee\u9898\u7684\u5de5\u5177\u3002\u5bf9\u4e8e C \u548c C++ \u8fd9\u6837\u7684\u4e0d\u90a3\u4e48\u52a8\u6001\u7684\u8bed\u8a00\uff0c\u8fd9\u4e9b\u95ee\u9898\u901a\u5e38\u7531\u7f16\u8bd1\u5668\u6765\u6355\u83b7\u3002\u7531\u4e8e Python \u7684\u52a8\u6001\u7279\u6027\uff0c\u6709\u4e9b\u8b66\u544a\u53ef\u80fd\u4e0d\u5bf9\u3002\u4e0d\u8fc7\u4f2a\u544a\u8b66\u5e94\u8be5\u5f88\u5c11\u3002
"},{"location":"standard/language_rules/#112","title":"1.1.2 \u4f18\u70b9","text":"\u53ef\u4ee5\u6355\u83b7\u5bb9\u6613\u5ffd\u89c6\u7684\u9519\u8bef\uff0c\u4f8b\u5982\u8f93\u5165\u9519\u8bef\uff0c\u4f7f\u7528\u672a\u8d4b\u503c\u7684\u53d8\u91cf\u7b49\u3002
"},{"location":"standard/language_rules/#113","title":"1.1.3 \u7f3a\u70b9","text":"pylint
\u4e0d\u5b8c\u7f8e\u3002\u8981\u5229\u7528\u5176\u4f18\u52bf\uff0c\u6211\u4eec\u6709\u65f6\u4faf\u9700\u8981\uff1a\u56f4\u7ed5\u7740\u5b83\u6765\u5199\u4ee3\u7801\u3001\u6291\u5236\u5176\u544a\u8b66\u3001\u6539\u8fdb\u5b83\u6216\u8005\u5ffd\u7565\u5b83\u3002
"},{"location":"standard/language_rules/#114","title":"1.1.4 \u7ed3\u8bba","text":"\u786e\u4fdd\u5bf9\u4f60\u7684\u4ee3\u7801\u8fd0\u884c pylint
\u3002
\u6291\u5236\u4e0d\u51c6\u786e\u7684\u8b66\u544a\uff0c\u4ee5\u4fbf\u80fd\u591f\u5c06\u5176\u4ed6\u8b66\u544a\u66b4\u9732\u51fa\u6765\u3002\u4f60\u53ef\u4ee5\u901a\u8fc7\u8bbe\u7f6e\u4e00\u4e2a\u884c\u6ce8\u91ca\u6765\u6291\u5236\u544a\u8b66\uff1a
def do_PUT(self): # WSGI name, so pylint: disable=invalid-name\n...\n
pylint
\u8b66\u544a\u662f\u4ee5\u4e00\u4e2a\u7b26\u53f7\u540d (\u5982 empty-docstring
) \u6765\u6807\u8bc6\u7684\uff0cGoogle \u7279\u5b9a\u7684\u8b66\u544a\u4ee5 g- \u5f00\u5934\u3002
\u5982\u679c\u4ece\u7b26\u53f7\u540d\u79f0\u4e2d\u770b\u4e0d\u51fa\u7981\u7528\u7684\u539f\u56e0\uff0c\u90a3\u4e48\u8bf7\u5bf9\u5176\u589e\u52a0\u4e00\u4e2a\u8be6\u7ec6\u89e3\u91ca\u3002
\u91c7\u7528\u8fd9\u79cd\u6291\u5236\u65b9\u5f0f\u7684\u597d\u5904\u662f\u6211\u4eec\u53ef\u4ee5\u8f7b\u677e\u67e5\u627e\u6291\u5236\u5e76\u56de\u987e\u5b83\u4eec\u3002
\u60a8\u53ef\u4ee5\u901a\u8fc7\u6267\u884c\u4ee5\u4e0b\u64cd\u4f5c\u6765\u83b7\u53d6 pylint
\u8b66\u544a\u5217\u8868\uff1a
pylint --list-msgs\n
\u83b7\u53d6\u5173\u4e8e\u7279\u5b9a\u6d88\u606f\u7684\u66f4\u591a\u4fe1\u606f\uff0c\u53ef\u4ee5\u6267\u884c\uff1a
pylint --help-msg=invalid-name\n
\u76f8\u6bd4\u8f83\u4e8e\u4e4b\u524d\u4f7f\u7528\u7684 pylint: disable-msg
\uff0c\u672c\u6587\u63a8\u8350\u4f7f\u7528 pylint: disable
\u3002
\u672a\u4f7f\u7528\u53c2\u6570\u7684\u8b66\u544a\u53ef\u4ee5\u901a\u8fc7\u5220\u9664\u51fd\u6570\u5f00\u5934\u7684\u53d8\u91cf\u6765\u6d88\u9664\u3002\u5e76\u5305\u542b\u4e00\u4e2a\u6ce8\u91ca\u89e3\u91ca\u4e3a\u4ec0\u4e48\u5220\u9664\u5b83\u3002\u4f7f\u7528 \u201cUnused.\u201d \u6ce8\u91ca\u5c31\u8db3\u591f\u4e86\u3002\u4f8b\u5982\uff1a
def viking_cafe_order(spam, beans, eggs=None):\ndel beans, eggs # Unused by vikings.\nreturn spam + spam + spam\n
\u8981\u6291\u5236\u8fd9\u79cd\u8b66\u544a\u7684\u5e38\u89c1\u5f62\u5f0f\u8fd8\u5305\u62ec\u4f7f\u7528 \u201c_\" \u4f5c\u4e3a\u672a\u4f7f\u7528\u53c2\u6570\u7684\u6807\u8bc6\u7b26\uff0c\u6216\u5728\u53c2\u6570\u540d\u524d\u52a0\u4e0a \u201cunused_\u201d\uff0c\u6216\u5c06\u5b83\u4eec\u8d4b\u503c\u7ed9 \u201c_\"\u3002
\u4e0a\u8ff0\u7684\u8fd9\u4e9b\u5f62\u5f0f\u90fd\u662f\u5141\u8bb8\u7684\uff0c\u4f46\u4e0d\u518d\u63a8\u8350\u3002\u8c03\u7528\u65b9\u6cd5\u65f6\u6309\u540d\u79f0\u4f20\u9012\u7684\u8fd9\u4e9b\u53c2\u6570\uff0c\u5b9e\u9645\u4e0a\u5e76\u4e0d\u4e00\u5b9a\u4f1a\u4f7f\u7528\u3002
"},{"location":"standard/language_rules/#12","title":"1.2 \u5bfc\u5165","text":"\u53ea\u5bf9\u5305\u548c\u6a21\u5757\u4f7f\u7528 import
\u8bed\u53e5\uff0c\u800c\u4e0d\u662f\u5355\u72ec\u7684\u7c7b\u6216\u51fd\u6570\u3002
"},{"location":"standard/language_rules/#121","title":"1.2.1 \u5b9a\u4e49","text":"\u6a21\u5757\u95f4\u5171\u4eab\u4ee3\u7801\u7684\u91cd\u7528\u673a\u5236\u3002
"},{"location":"standard/language_rules/#122","title":"1.2.2 \u4f18\u70b9","text":"\u547d\u540d\u7a7a\u95f4\u7ba1\u7406\u7ea6\u5b9a\u5341\u5206\u7b80\u5355\u3002\u6bcf\u4e2a\u6807\u8bc6\u7b26\u7684\u6765\u6e90\u90fd\u7528\u4e00\u79cd\u4e00\u81f4\u7684\u65b9\u5f0f\u6307\u793a\uff1ax.Obj
\u8868\u793a Obj
\u5bf9\u8c61\u5b9a\u4e49\u5728\u6a21\u5757 x
\u4e2d\u3002
"},{"location":"standard/language_rules/#123","title":"1.2.3 \u7f3a\u70b9","text":"\u6a21\u5757\u540d\u4ecd\u53ef\u80fd\u51b2\u7a81\u3002\u6709\u4e9b\u6a21\u5757\u540d\u592a\u957f, \u4e0d\u592a\u65b9\u4fbf\u3002
"},{"location":"standard/language_rules/#124","title":"1.2.4 \u7ed3\u8bba","text":" - \u4f7f\u7528
import x
\u6765\u5bfc\u5165\u5305\u548c\u6a21\u5757\u3002 - \u4f7f\u7528
from x import y
\uff0c\u5176\u4e2d x
\u662f\u5305\u524d\u7f00\uff0cy
\u662f\u4e0d\u5e26\u524d\u7f00\u7684\u6a21\u5757\u540d\u3002 -
\u5728\u4ee5\u4e0b\u4efb\u4e00\u60c5\u51b5\u4e0b\u4f7f\u7528 from x import y as z
\uff1a
- \u540d\u5b57\u90fd\u4e3a
y
\u7684\u6a21\u5757\u3002 y
\u4e0e\u5f53\u524d\u6a21\u5757\u4e2d\u9876\u7ea7\u540d\u79f0\u51b2\u7a81\u3002 y
\u4e0e\u4f5c\u4e3a\u516c\u5171 API \u4e00\u90e8\u5206\u7684\u516c\u5171\u53c2\u6570\u540d\u79f0\uff08\u4f8b\u5982\u529f\u80fd\uff09\u51b2\u7a81\u3002 y
\u662f\u4e00\u4e2a\u957f\u540d\u79f0\uff0c\u4f7f\u7528\u4e0d\u592a\u65b9\u4fbf\u3002 y
\u5728\u4ee3\u7801\u4e0a\u4e0b\u6587\u4e2d\u8fc7\u4e8e\u901a\u7528\uff08\u4f8b\u5982\uff1afrom storage.file_system import options as fs_options
\uff09
-
\u53ea\u6709\u5f53 z
\u662f\u6807\u51c6\u7f29\u5199\uff08\u4f8b\u5982\uff0cnumpy
\u4e3a np
\uff09\u65f6\uff0c\u624d\u4f7f\u7528 import y as z
\u3002
\u4f8b\u5982\uff0c\u6a21\u5757 sound.effects.echo
\u53ef\u4ee5\u7528\u5982\u4e0b\u65b9\u5f0f\u5bfc\u5165\uff1a
from sound.effects import echo\n...\necho.EchoFilter(input, output, delay=0.7, atten=4)\n
\u5bfc\u5165\u65f6\u4e0d\u8981\u4f7f\u7528\u76f8\u5bf9\u540d\u79f0\u3002\u5373\u4f7f\u6a21\u5757\u5728\u540c\u4e00\u4e2a\u5305\u4e2d\uff0c\u4e5f\u8981\u4f7f\u7528\u5b8c\u6574\u5305\u540d\u3002\u8fd9\u80fd\u5e2e\u52a9\u4f60\u907f\u514d\u65e0\u610f\u95f4\u5bfc\u5165\u4e00\u4e2a\u5305\u4e24\u6b21\u3002
\u4ee5\u4e0b\u89c4\u5219\u4e0d\u53d7\u7ea6\u675f\uff1a
-
\u7528\u4e8e\u652f\u6301\u9759\u6001\u5206\u6790\u548c\u7c7b\u578b\u68c0\u67e5\uff1a
- typing
- collections.abc
- typing_extensions
-
\u91cd\u5b9a\u5411\u6a21\u5757 Six.moves
"},{"location":"standard/language_rules/#13","title":"1.3 \u5305","text":"\u4f7f\u7528\u6a21\u5757\u7684\u5168\u8def\u5f84\u540d\u6765\u5bfc\u5165\u6bcf\u4e2a\u6a21\u5757\u3002
"},{"location":"standard/language_rules/#131","title":"1.3.1 \u4f18\u70b9","text":"\u907f\u514d\u6a21\u5757\u540d\u79f0\u51b2\u7a81\u6216\u56e0\u6a21\u5757\u641c\u7d22\u8def\u5f84\u4e0e\u4f5c\u8005\u9884\u671f\u4e0d\u7b26\u800c\u5bfc\u81f4\u7684\u9519\u8bef\u5bfc\u5165\u3002\u67e5\u627e\u5305\u66f4\u5bb9\u6613\u3002
"},{"location":"standard/language_rules/#132","title":"1.3.2 \u7f3a\u70b9","text":"\u56e0\u4e3a\u8981\u590d\u5236\u5305\u5c42\u6b21\u7ed3\u6784\uff0c\u6240\u4ee5\u5728\u90e8\u7f72\u4ee3\u7801\u65f6\u4f1a\u66f4\u52a0\u56f0\u96be\u3002\u4f46\u662f\u5bf9\u4e8e\u73b0\u4ee3\u7684\u90e8\u7f72\u673a\u5236\u6765\u8bf4\uff0c\u8fd9\u5e76\u4e0d\u662f\u771f\u6b63\u7684\u95ee\u9898\u3002
"},{"location":"standard/language_rules/#133","title":"1.3.3 \u7ed3\u8bba","text":"\u6240\u6709\u7684\u65b0\u4ee3\u7801\u90fd\u5e94\u8be5\u7528\u5b8c\u6574\u5305\u540d\u6765\u5bfc\u5165\u6bcf\u4e2a\u6a21\u5757\u3002
\u5e94\u8be5\u50cf\u4e0b\u9762\u8fd9\u6837\u5bfc\u5165\uff1a
\u63a8\u8350
# Reference absl.flags in code with the complete name (verbose).\nimport absl.flags\nfrom doctor.who import jodie\n_FOO = absl.flags.DEFINE_string(...)\n
\u63a8\u8350
# Reference flags in code with just the module name (common).\nfrom absl import flags\nfrom doctor.who import jodie\n_FOO = flags.DEFINE_string(...)\n
\u4e0d\u63a8\u8350 ( \u5047\u8bbe jodie.py
\u6587\u4ef6\u5728 doctor/who/
\u4e2d )
# Unclear what module the author wanted and what will be imported. The actual\n# import behavior depends on external factors controlling sys.path.\n# Which possible jodie module did the author intend to import?\nimport jodie\n
\u5c3d\u7ba1\u5728\u67d0\u4e9b\u73af\u5883\u4e2d\u4f1a\u53d1\u751f\u8fd9\u79cd\u60c5\u51b5\uff0c\u4f46\u4e0d\u5e94\u5047\u5b9a\u4e3b\u4e8c\u8fdb\u5236\u6587\u4ef6\u6240\u5728\u7684\u76ee\u5f55\u4f4d\u4e8e sys.path
\u4e2d\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u4ee3\u7801\u5e94\u5047\u5b9a import jodie
\u5f15\u7528\u4e86\u540d\u4e3a jodie
\u7684\u7b2c\u4e09\u65b9\u6216\u9876\u7ea7\u7a0b\u5e8f\u5305\uff0c\u800c\u4e0d\u662f\u672c\u5730\u7684 jodie.py
\u3002
"},{"location":"standard/language_rules/#14","title":"1.4 \u5f02\u5e38","text":"\u5141\u8bb8\u4f7f\u7528\u5f02\u5e38\uff0c\u4f46\u5fc5\u987b\u5c0f\u5fc3\u3002
"},{"location":"standard/language_rules/#141","title":"1.4.1 \u5b9a\u4e49","text":"\u5f02\u5e38\u662f\u4e00\u79cd\u8df3\u51fa\u4ee3\u7801\u5757\u7684\u6b63\u5e38\u63a7\u5236\u6d41\u6765\u5904\u7406\u9519\u8bef\u6216\u8005\u5176\u5b83\u5f02\u5e38\u6761\u4ef6\u7684\u65b9\u5f0f\u3002
"},{"location":"standard/language_rules/#141_1","title":"1.4.1 \u4f18\u70b9","text":"\u6b63\u5e38\u64cd\u4f5c\u4ee3\u7801\u7684\u63a7\u5236\u6d41\u4e0d\u4f1a\u548c\u9519\u8bef\u5904\u7406\u4ee3\u7801\u6df7\u5728\u4e00\u8d77\u3002\u5f53\u67d0\u79cd\u6761\u4ef6\u53d1\u751f\u65f6\uff0c\u5b83\u4e5f\u5141\u8bb8\u63a7\u5236\u6d41\u8df3\u8fc7\u591a\u4e2a\u6846\u67b6\u3002\u4f8b\u5982\uff0c\u4e00\u6b65\u8df3\u51fa N \u4e2a\u5d4c\u5957\u7684\u51fd\u6570\uff0c\u800c\u4e0d\u5fc5\u7ee7\u7eed\u6267\u884c\u9519\u8bef\u7684\u4ee3\u7801\u3002
"},{"location":"standard/language_rules/#142","title":"1.4.2 \u7f3a\u70b9","text":"\u53ef\u80fd\u4f1a\u5bfc\u81f4\u8ba9\u4eba\u56f0\u60d1\u7684\u63a7\u5236\u6d41\u3002\u8c03\u7528\u5e93\u65f6\u5bb9\u6613\u9519\u8fc7\u9519\u8bef\u60c5\u51b5\u3002
"},{"location":"standard/language_rules/#144","title":"1.4.4 \u7ed3\u8bba","text":"\u5f02\u5e38\u5fc5\u987b\u9075\u5b88\u7279\u5b9a\u6761\u4ef6\uff1a
-
\u5982\u679c\u6709\u5fc5\u8981\uff0c\u8bf7\u4f7f\u7528\u5185\u7f6e\u5f02\u5e38\u7c7b\u3002\u4f8b\u5982\uff0c\u629b\u51fa ValureError
\u6765\u6307\u793a\u7f16\u7a0b\u9519\u8bef\u3002\u6bd4\u5982\u8fdd\u53cd\u4e86\u524d\u7f6e\u6761\u4ef6\uff08\u9700\u8981\u4e00\u4e2a\u6b63\u6570\uff0c\u4f46\u4f20\u9012\u4e86\u4e00\u4e2a\u8d1f\u6570\uff09\u3002\u4e0d\u8981\u4f7f\u7528 assert
\u8bed\u53e5\u9a8c\u8bc1\u516c\u5171 API \u7684\u53c2\u6570\u503c\u3002assert
\u7528\u4e8e\u786e\u4fdd\u5185\u90e8\u6b63\u786e\u6027\uff0c\u4e0d\u5f97\u5f3a\u5236\u4f7f\u7528\uff0c\u4e5f\u4e0d\u8868\u793a\u53d1\u751f\u4e86\u67d0\u4e9b\u610f\u5916\u4e8b\u4ef6\u3002\u5982\u679c\u5728\u540e\u4e00\u79cd\u60c5\u51b5\u4e0b\u9700\u8981\u4f7f\u7528\u5f02\u5e38\uff0c\u8bf7\u4f7f\u7528 raise \u8bed\u53e5\u3002\u4f8b\u5982\uff1a
\u63a8\u8350
def connect_to_next_port(self, minimum):\n\"\"\"Connects to the next available port.\n Args:\n minimum: A port value greater or equal to 1024.\n Returns:\n The new minimum port.\n Raises:\n ConnectionError: If no available port is found.\n \"\"\"\nif minimum < 1024:\n# Note that this raising of ValueError is not mentioned in the doc\n# string's \"Raises:\" section because it is not appropriate to\n# guarantee this specific behavioral reaction to API misuse.\nraise ValueError(f'Min. port must be at least 1024, not {minimum}.')\nport = self._find_next_open_port(minimum)\nif not port:\nraise ConnectionError(\nf'Could not connect to service on port {minimum} or higher.')\nassert port >= minimum, (\nf'Unexpected port {port} when minimum was {minimum}.')\nreturn port\n
\u4e0d\u63a8\u8350
def connect_to_next_port(self, minimum):\n\"\"\"Connects to the next available port.\n Args:\n minimum: A port value greater or equal to 1024.\n Returns:\n The new minimum port.\n \"\"\"\nassert minimum >= 1024, 'Minimum port must be at least 1024.'\nport = self._find_next_open_port(minimum)\nassert port is not None\nreturn port\n
-
\u6a21\u5757\u6216\u5305\u5e94\u8be5\u5b9a\u4e49\u81ea\u5df1\u7684\u7279\u5b9a\u57df\u7684\u5f02\u5e38\u57fa\u7c7b\u3002\u8fd9\u4e2a\u57fa\u7c7b\u5e94\u8be5\u4ece\u5185\u5efa\u7684 Exception
\u7c7b\u7ee7\u627f\u3002\u5f02\u5e38\u540d\u79f0\u5e94\u8be5\u4ee5 Error
\u7ed3\u5c3e\uff0c\u800c\u4e14\u4e0d\u5e94\u8be5\u96be\u4ee5\u7406\u89e3\uff08foo.FooError
\uff09\u3002
-
\u6c38\u8fdc\u4e0d\u8981\u4f7f\u7528 expect:
\u8bed\u53e5\u6765\u6355\u83b7\u6240\u6709\u5f02\u5e38\uff0c\u4e5f\u4e0d\u8981\u6355\u83b7 Exception
\u6216\u8005 StandardError
\uff0c\u9664\u975e\uff1a
- \u91cd\u65b0\u89e6\u53d1\u8be5\u5f02\u5e38\uff0c\u6216
- \u5728\u7a0b\u5e8f\u4e2d\u521b\u5efa\u4e00\u4e2a\u9694\u79bb\u70b9\uff0c\u5176\u4e2d\u5f02\u5e38\u4e0d\u4f1a\u4f20\u64ad\uff0c\u800c\u662f\u88ab\u8bb0\u5f55\u548c\u6291\u5236\uff0c\u4f8b\u5982\u901a\u8fc7\u4fdd\u62a4\u7ebf\u7a0b\u7684\u6700\u5916\u5c42\u5757\u6765\u9632\u6b62\u7a0b\u5e8f\u5d29\u6e83\u3002
\u5728\u5f02\u5e38\u8fd9\u65b9\u9762, Python \u975e\u5e38\u5bbd\u5bb9\uff0c expect:
\u53ef\u4ee5\u6355\u83b7\u6240\u6709\u62fc\u5199\u9519\u8bef\u7684\u540d\u79f0\uff0c sys.exit()
\u8c03\u7528\uff0c Ctrl+C
\u4e2d\u65ad\uff0cunittest
\u5931\u8d25\u548c\u6240\u6709\u4f60\u4e0d\u60f3\u6355\u83b7\u7684\u5176\u4ed6\u5f02\u5e38\u3002
- \u5c3d\u91cf\u51cf\u5c11
try/except
\u5757\u4e2d\u7684\u4ee3\u7801\u91cf\u3002 try
\u5757\u7684\u4f53\u79ef\u8d8a\u5927\uff0c\u671f\u671b\u4e4b\u5916\u7684\u5f02\u5e38\u5c31\u8d8a\u5bb9\u6613\u88ab\u89e6\u53d1\u3002\u5728\u8fd9\u4e9b\u60c5\u51b5\u4e0b\uff0ctry/except
\u5757\u5c06\u9690\u85cf\u771f\u6b63\u7684\u9519\u8bef\u3002 - \u4f7f\u7528
finally
\u5b50\u53e5\u6765\u6267\u884c\u90a3\u4e9b\u65e0\u8bba try
\u5757\u4e2d\u6709\u6ca1\u6709\u5f02\u5e38\u90fd\u5e94\u8be5\u88ab\u6267\u884c\u7684\u4ee3\u7801\u3002\u8fd9\u5bf9\u4e8e\u6e05\u7406\u8d44\u6e90\u5e38\u5e38\u5f88\u6709\u7528\uff0c\u4f8b\u5982\u5173\u95ed\u6587\u4ef6\u3002
"},{"location":"standard/language_rules/#15","title":"1.5 \u5168\u5c40\u53d8\u91cf","text":"\u907f\u514d\u5168\u5c40\u53d8\u91cf\u3002
"},{"location":"standard/language_rules/#151","title":"1.5.1 \u5b9a\u4e49","text":"\u5b9a\u4e49\u5728\u6a21\u5757\u7ea7\u7684\u53d8\u91cf\u3002
"},{"location":"standard/language_rules/#152","title":"1.5.2 \u4f18\u70b9","text":"\u5076\u5c14\u6709\u7528\u3002
"},{"location":"standard/language_rules/#153","title":"1.5.3 \u7f3a\u70b9","text":" - \u7834\u574f\u5c01\u88c5\uff1a\u8fd9\u79cd\u8bbe\u8ba1\u53ef\u80fd\u4f1a\u8ba9\u6709\u6548\u76ee\u6807\u7684\u5b9e\u73b0\u53d8\u5f97\u56f0\u96be\u3002\u4f8b\u5982\u4f7f\u7528\u5168\u5c40\u72b6\u6001\u6765\u7ba1\u7406\u6570\u636e\u5e93\u8fde\u63a5\uff0c\u5219\u540c\u65f6\u8fde\u63a5\u4e24\u4e2a\u4e0d\u540c\u7684\u6570\u636e\u5e93\u53d8\u5f97\u56f0\u96be\uff08\u4f8b\u5982\u518d\u8fc1\u79fb\u671f\u95f4 \u8ba1\u7b97\u5dee\u5f02\uff09\u3002\u5168\u5c40\u6ce8\u518c\u8868\u4e5f\u5bb9\u6613\u51fa\u73b0\u7c7b\u4f3c\u7684\u95ee\u9898\u3002
- \u5bfc\u5165\u65f6\u53ef\u80fd\u6539\u53d8\u6a21\u5757\u884c\u4e3a\uff0c\u56e0\u4e3a\u5bfc\u5165\u6a21\u5757\u65f6\u4f1a\u5bf9\u6a21\u5757\u7ea7\u53d8\u91cf\u8d4b\u503c\u3002
"},{"location":"standard/language_rules/#154","title":"1.5.4 \u7ed3\u8bba","text":"\u907f\u514d\u4f7f\u7528\u5168\u5c40\u53d8\u91cf\u3002
\u5982\u679c\u9700\u8981\uff0c\u5168\u5c40\u53d8\u91cf\u5e94\u8be5\u4ec5\u5728\u6a21\u5757\u5185\u90e8\u53ef\u7528\uff0c\u5e76\u901a\u8fc7\u5728\u540d\u79f0\u524d\u52a0\u4e0a _
\u524d\u7f00\u4f7f\u5176\u6210\u4e3a\u6a21\u5757\u7684\u5185\u90e8\u53d8\u91cf\u3002\u5916\u90e8\u8bbf\u95ee\u5fc5\u987b\u901a\u8fc7\u6a21\u5757\u7ea7\u7684\u516c\u5171\u51fd\u6570\u6765\u8bbf\u95ee\u3002\u5177\u4f53\u8bf7\u53c2\u9605\u547d\u540d\u89c4\u8303\u3002\u8bf7\u5728\u6ce8\u91ca\u6216\u4e0e\u6ce8\u91ca\u76f8\u5173\u7684\u6587\u6863\u4e2d\u8bf4\u660e\u4f7f\u7528\u53ef\u53d8\u5168\u5c40\u72b6\u6001\u7684\u8bbe\u8ba1\u539f\u56e0\u3002
\u6a21\u5757\u7ea7\u5e38\u91cf\u662f\u5141\u8bb8\u548c\u9f13\u52b1\u4f7f\u7528\u7684\u3002\u4f8b\u5982\uff1a _MAX_HOLY_HANDGRENADE_COUNT = 3
(\u5bf9\u5185\u90e8\u4f7f\u7528\u5e38\u91cf)\u6216 SIR_LANCELOTS_FAVORITE_COLOR = \"blue\"
(\u5bf9\u516c\u5171 API \u5e38\u91cf)\u3002\u5e38\u91cf\u7684\u547d\u540d\u5fc5\u987b\u4f7f\u7528\u5168\u5927\u5199\u548c\u4e0b\u5212\u7ebf\u3002\u5177\u4f53\u8bf7\u53c2\u9605\u547d\u540d\u89c4\u8303\u3002
"},{"location":"standard/language_rules/#16","title":"1.6 \u5d4c\u5957/\u5c40\u90e8/\u5185\u90e8\u7c7b\u6216\u51fd\u6570","text":"\u5728\u9700\u8981\u5173\u95ed\u5c40\u90e8\u53d8\u91cf\u65f6\u9f13\u52b1\u4f7f\u7528\u5d4c\u5957\u672c\u5730\u5185\u90e8\u7c7b\u6216\u51fd\u6570\uff0c\u5d4c\u5957\u7c7b\u66f4\u597d\u3002
"},{"location":"standard/language_rules/#161","title":"1.6.1 \u5b9a\u4e49","text":"\u7c7b\u53ef\u4ee5\u5b9a\u4e49\u5728\u65b9\u6cd5\u3001\u51fd\u6570\u6216\u8005\u7c7b\u4e2d\u3002\u51fd\u6570\u53ef\u4ee5\u5b9a\u4e49\u5728\u65b9\u6cd5\u6216\u51fd\u6570\u4e2d\u3002\u5c01\u95ed\u533a\u95f4\u4e2d\u5b9a\u4e49\u7684\u53d8\u91cf\u5bf9\u5d4c\u5957\u51fd\u6570\u662f\u53ea\u8bfb\u7684\u3002
"},{"location":"standard/language_rules/#162","title":"1.6.2 \u4f18\u70b9","text":"\u5141\u8bb8\u5b9a\u4e49\u4ec5\u7528\u4e8e\u6709\u6548\u8303\u56f4\u7684\u5de5\u5177\u7c7b\u548c\u51fd\u6570\u3002\u975e\u5e38\u50cf ADT-y \u3002\u901a\u5e38\u7528\u4e8e\u5b9e\u73b0\u88c5\u9970\u5668\u3002
"},{"location":"standard/language_rules/#163","title":"1.6.3 \u7f3a\u70b9","text":" - \u4e0d\u80fd\u76f4\u63a5\u6d4b\u8bd5\u5d4c\u5957\u51fd\u6570\u548c\u7c7b\u3002
- \u5d4c\u5957\u4f1a\u4f7f\u5916\u90e8\u51fd\u6570\u66f4\u957f\u3002
- \u53ef\u8bfb\u6027\u66f4\u5dee\u3002
"},{"location":"standard/language_rules/#164","title":"1.6.4 \u7ed3\u8bba","text":"\u53ef\u4ee5\u4f7f\u7528\uff0c\u4f46\u6709\u4e00\u4e9b\u9650\u5236\u3002\u907f\u514d\u4f7f\u7528\u5d4c\u5957\u51fd\u6570\u6216\u7c7b\uff0c\u9664\u975e\u8981\u5173\u95ed\u5c40\u90e8\u503c\u3002\u4e0d\u8981\u4ec5\u4ec5\u4e3a\u4e86\u5bf9\u7528\u6237\u9690\u85cf\u6a21\u5757\u7684\u67d0\u4e2a\u51fd\u6570\u800c\u8fdb\u884c\u5d4c\u5957\u3002\u76f8\u53cd\uff0c\u5e94\u8be5\u5728\u6a21\u5757\u7ea7\u522b\u7684\u540d\u79f0\u4e0a\u52a0 _
\u524d\u7f00\uff0c\u8fd9\u6837\u65b9\u4fbf\u6d4b\u8bd5\u3002
"},{"location":"standard/language_rules/#17","title":"1.7 \u63a8\u5bfc\u5f0f\u548c\u751f\u6210\u8868\u8fbe\u5f0f","text":"\u53ef\u4ee5\u5728\u7b80\u5355\u60c5\u51b5\u4e0b\u4f7f\u7528\u3002
"},{"location":"standard/language_rules/#171","title":"1.7.1 \u5b9a\u4e49","text":"List
\u3001Dict
\u548c Set
\u63a8\u5bfc\u5f0f\u4e0e\u751f\u6210\u5668\u8868\u8fbe\u5f0f\u63d0\u4f9b\u4e86\u4e00\u79cd\u7b80\u6d01\u800c\u6709\u6548\u7684\u65b9\u6cd5\u6765\u521b\u5efa\u5217\u8868\u548c\u8fed\u4ee3\u5668\uff0c\u800c\u4e0d\u5fc5\u501f\u52a9\u4f20\u7edf\u7684\u5faa\u73af\u3001map()
\u3001filter()
\u6216\u8005 lambda
\u3002
"},{"location":"standard/language_rules/#172","title":"1.7.2 \u4f18\u70b9","text":"\u7b80\u5355\u7684\u63a8\u5bfc\u5f0f\u53ef\u4ee5\u6bd4\u5176\u4ed6\u7684\u5b57\u5178\u3001\u5217\u8868\u6216\u96c6\u5408\u521b\u5efa\u6280\u672f\u66f4\u52a0\u6e05\u6670\u7b80\u5355\u3002\u751f\u6210\u5668\u8868\u8fbe\u5f0f\u53ef\u4ee5\u5341\u5206\u9ad8\u6548\uff0c\u56e0\u4e3a\u5b83\u4eec\u907f\u514d\u4e86\u521b\u5efa\u6574\u4e2a\u5217\u8868\u3002
"},{"location":"standard/language_rules/#173","title":"1.7.3 \u7f3a\u70b9","text":"\u590d\u6742\u7684\u63a8\u5bfc\u5f0f\u6216\u8005\u751f\u6210\u5668\u8868\u8fbe\u5f0f\u53ef\u80fd\u96be\u4ee5\u9605\u8bfb\u3002
"},{"location":"standard/language_rules/#174","title":"1.7.4 \u7ed3\u8bba","text":"\u9002\u7528\u4e8e\u7b80\u5355\u60c5\u51b5\u3002\u6bcf\u4e2a\u90e8\u5206\u5e94\u8be5\u5355\u72ec\u7f6e\u4e8e\u4e00\u884c\uff1amapping
\u8868\u8fbe\u5f0f\uff0cfor
\u5b50\u53e5\uff0cfilter
\u8868\u8fbe\u5f0f\u3002\u7981\u6b62\u591a\u91cd for
\u8bed\u53e5\u6216\u8fc7\u6ee4\u5668\u8868\u8fbe\u5f0f\u3002\u590d\u6742\u60c5\u51b5\u4e0b\u8fd8\u662f\u4f7f\u7528\u5faa\u73af\u3002
\u63a8\u8350
result = [mapping_expr for value in iterable if filter_expr]\nresult = [{'key': value} for value in iterable\nif a_long_filter_expression(value)]\nresult = [complicated_transform(x)\nfor x in iterable if predicate(x)]\ndescriptive_name = [\ntransform({'key': key, 'value': value}, color='black')\nfor key, value in generate_iterable(some_input)\nif complicated_condition_is_met(key, value)\n]\nresult = []\nfor x in range(10):\nfor y in range(5):\nif x * y > 10:\nresult.append((x, y))\nreturn {x: complicated_transform(x)\nfor x in long_generator_function(parameter)\nif x is not None}\nsquares_generator = (x**2 for x in range(10))\nunique_names = {user.name for user in users if user is not None}\neat(jelly_bean for jelly_bean in jelly_beans\nif jelly_bean.color == 'black')\n
\u4e0d\u63a8\u8350
result = [complicated_transform(\nx, some_argument=x+1)\nfor x in iterable if predicate(x)]\nresult = [(x, y) for x in range(10) for y in range(5) if x * y > 10]\nreturn ((x, y, z)\nfor x in range(5)\nfor y in range(5)\nif x != y\nfor z in range(5)\nif y != z)\n
"},{"location":"standard/language_rules/#18","title":"1.8 \u9ed8\u8ba4\u8fed\u4ee3\u5668\u548c\u64cd\u4f5c\u7b26","text":"\u5982\u679c\u7c7b\u578b\u652f\u6301\uff0c\u5c31\u4f7f\u7528\u9ed8\u8ba4\u8fed\u4ee3\u5668\u548c\u64cd\u4f5c\u7b26\u3002\u6bd4\u5982\u5217\u8868\uff0c\u5b57\u5178\u53ca\u6587\u4ef6\u7b49\u3002
"},{"location":"standard/language_rules/#181","title":"1.8.1 \u5b9a\u4e49","text":"\u5bb9\u5668\u7c7b\u578b\uff0c\u50cf\u5b57\u5178\u548c\u5217\u8868\uff0c\u5b9a\u4e49\u4e86\u9ed8\u8ba4\u7684\u8fed\u4ee3\u5668\u548c\u5173\u7cfb\u6d4b\u8bd5\u64cd\u4f5c\u7b26\uff08 in
\u548c not in
\uff09
"},{"location":"standard/language_rules/#182","title":"1.8.2 \u4f18\u70b9","text":"\u9ed8\u8ba4\u64cd\u4f5c\u7b26\u548c\u8fed\u4ee3\u5668\u7b80\u5355\u9ad8\u6548\uff0c\u5b83\u4eec\u76f4\u63a5\u8868\u8fbe\u4e86\u64cd\u4f5c\uff0c\u6ca1\u6709\u989d\u5916\u7684\u65b9\u6cd5\u8c03\u7528\u3002\u4f7f\u7528\u9ed8\u8ba4\u64cd\u4f5c\u7b26\u7684\u51fd\u6570\u662f\u901a\u7528\u7684\u3002\u5b83\u53ef\u4ee5\u7528\u4e8e\u652f\u6301\u8be5\u64cd\u4f5c\u7684\u4efb\u4f55\u7c7b\u578b\u3002
"},{"location":"standard/language_rules/#183","title":"1.8.3 \u7f3a\u70b9","text":"\u60a8\u65e0\u6cd5\u901a\u8fc7\u8bfb\u53d6\u65b9\u6cd5\u540d\u79f0\u6765\u5224\u65ad\u5bf9\u8c61\u7684\u7c7b\u578b\uff08\u9664\u975e\u53d8\u91cf\u5177\u6709\u7c7b\u578b\u6ce8\u91ca\uff09\u3002\u8fd9\u4e5f\u662f\u4e00\u4e2a\u4f18\u70b9\u3002
"},{"location":"standard/language_rules/#184","title":"1.8.4 \u7ed3\u8bba","text":"\u5982\u679c\u7c7b\u578b\u652f\u6301\uff0c\u5c31\u4f7f\u7528\u9ed8\u8ba4\u8fed\u4ee3\u5668\u548c\u64cd\u4f5c\u7b26\uff0c\u4f8b\u5982\u5217\u8868\u3001\u5b57\u5178\u548c\u6587\u4ef6\u3002\u5185\u5efa\u7c7b\u578b\u4e5f\u5b9a\u4e49\u4e86\u8fed\u4ee3\u5668\u65b9\u6cd5\u3002\u4f18\u5148\u8003\u8651\u8fd9\u4e9b\u65b9\u6cd5\uff0c\u800c\u4e0d\u662f\u90a3\u4e9b\u8fd4\u56de\u5217\u8868\u7684\u65b9\u6cd5\u3002\u5f53\u7136\uff0c\u8fd9\u6837\u904d\u5386\u5bb9\u5668\u65f6\uff0c\u4f60\u5c06\u4e0d\u80fd\u4fee\u6539\u5bb9\u5668\u3002
\u63a8\u8350
for key in adict: ...\nif obj in alist: ...\nfor line in afile: ...\nfor k, v in adict.items(): ...\n
\u4e0d\u63a8\u8350
for key in adict.keys(): ...\nfor line in afile.readlines(): ...\n
"},{"location":"standard/language_rules/#19","title":"1.9 \u751f\u6210\u5668","text":"\u6309\u9700\u4f7f\u7528\u751f\u6210\u5668\u3002
"},{"location":"standard/language_rules/#191","title":"1.9.1 \u5b9a\u4e49","text":"\u6240\u8c13\u751f\u6210\u5668\u51fd\u6570\uff0c\u5c31\u662f\u6bcf\u5f53\u5b83\u6267\u884c\u4e00\u6b21\u751f\u6210 yield
\u8bed\u53e5\uff0c\u5b83\u5c31\u8fd4\u56de\u4e00\u4e2a\u8fed\u4ee3\u5668\uff0c\u8fd9\u4e2a\u8fed\u4ee3\u5668\u751f\u6210\u4e00\u4e2a\u503c\u3002 \u751f\u6210\u503c\u540e\uff0c\u751f\u6210\u5668\u51fd\u6570\u7684\u8fd0\u884c\u72b6\u6001\u5c06\u88ab\u6302\u8d77\uff0c\u76f4\u5230\u9700\u8981\u4e0b\u4e00\u6b21\u503c\u4e3a\u6b62\u3002
"},{"location":"standard/language_rules/#192","title":"1.9.2 \u4f18\u70b9","text":"\u7b80\u5316\u4ee3\u7801\uff0c\u56e0\u4e3a\u6bcf\u6b21\u8c03\u7528\u65f6\uff0c\u5c40\u90e8\u53d8\u91cf\u548c\u63a7\u5236\u6d41\u7684\u72b6\u6001\u90fd\u4f1a\u88ab\u4fdd\u5b58\u3002\u6bd4\u8d77\u4e00\u6b21\u521b\u5efa\u4e00\u7cfb\u5217\u503c\u7684\u51fd\u6570\uff0c\u751f\u6210\u5668\u4f7f\u7528\u7684\u5185\u5b58\u66f4\u5c11\u3002
"},{"location":"standard/language_rules/#193","title":"1.9.3 \u7f3a\u70b9","text":"\u751f\u6210\u5668\u4e2d\u7684\u5c40\u90e8\u53d8\u91cf\u4e0d\u4f1a\u88ab\u5783\u573e\u6536\u96c6\uff0c\u76f4\u5230\u751f\u6210\u5668\u8017\u5c3d\u6216\u672c\u8eab\u88ab\u5783\u573e\u6536\u96c6\u3002
"},{"location":"standard/language_rules/#194","title":"1.9.4 \u7ed3\u8bba","text":"\u9f13\u52b1\u4f7f\u7528\u3002\u6ce8\u610f\u5728\u751f\u6210\u5668\u51fd\u6570\u7684\u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u4f7f\u7528\u201cYields:
\u201d\u800c\u4e0d\u662f\u201cReturns:
\u201d\u3002
\u5982\u679c\u751f\u6210\u5668\u7ba1\u7406\u7740\u4e00\u4e2a\u6602\u8d35\u7684\u8d44\u6e90\uff0c\u8bf7\u786e\u4fdd\u5f3a\u5236\u8fdb\u884c\u6e05\u7406\u3002
\u4e00\u4e2a\u5f88\u597d\u7684\u6e05\u7406\u65b9\u5f0f\u662f\u4f7f\u7528\u4e0a\u4e0b\u6587\u7ba1\u7406\u5668 PEP-0533 \u6765\u5305\u88c5\u751f\u6210\u5668\u3002
"},{"location":"standard/language_rules/#110-lambda","title":"1.10 Lambda \u51fd\u6570","text":"\u9002\u7528\u4e8e\u5355\u884c\u51fd\u6570\u3002\u5e38\u7528\u4e8e\u4e3a map()
\u548c filter()
\u4e4b\u7c7b\u7684\u9ad8\u9636\u51fd\u6570\u5b9a\u4e49\u56de\u8c03\u51fd\u6570\u6216\u8005\u64cd\u4f5c\u7b26\u3002
"},{"location":"standard/language_rules/#1101","title":"1.10.1 \u5b9a\u4e49","text":"Lambdas
\u5728\u4e00\u4e2a\u8868\u8fbe\u5f0f\u4e2d\u5b9a\u4e49\u533f\u540d\u51fd\u6570\uff0c\u800c\u4e0d\u662f\u5728\u8bed\u53e5\u4e2d\u3002
"},{"location":"standard/language_rules/#1102","title":"1.10.2 \u4f18\u70b9","text":"\u65b9\u4fbf\u3002
"},{"location":"standard/language_rules/#1103","title":"1.10.3 \u7f3a\u70b9","text":"\u6bd4\u672c\u5730\u51fd\u6570\u66f4\u96be\u9605\u8bfb\u548c\u8c03\u8bd5\u3002\u6ca1\u6709\u51fd\u6570\u540d\u610f\u5473\u7740\u5806\u6808\u8ddf\u8e2a\u66f4\u96be\u7406\u89e3\u3002\u7531\u4e8e lambda
\u51fd\u6570\u901a\u5e38\u53ea\u5305\u542b\u4e00\u4e2a\u8868\u8fbe\u5f0f\uff0c\u56e0\u6b64\u5176\u8868\u8fbe\u80fd\u529b\u6709\u9650\u3002
"},{"location":"standard/language_rules/#1104","title":"1.10.4 \u7ed3\u8bba","text":"\u9002\u7528\u4e8e\u5355\u884c\u51fd\u6570\u3002\u5982\u679c\u4ee3\u7801\u8d85\u8fc760-80\u4e2a\u5b57\u7b26\uff0c\u6700\u597d\u8fd8\u662f\u5b9a\u4e49\u6210\u5e38\u89c4\uff08\u5d4c\u5957\uff09\u51fd\u6570\u3002
\u5bf9\u4e8e\u5e38\u89c1\u7684\u64cd\u4f5c\u7b26\uff0c\u4f8b\u5982\u4e58\u6cd5\u64cd\u4f5c\u7b26\uff0c\u4f7f\u7528 operator
\u6a21\u5757\u4e2d\u7684\u51fd\u6570\u4ee5\u4ee3\u66ff lambda
\u51fd\u6570\u3002\u4f8b\u5982\uff0c\u63a8\u8350\u4f7f\u7528 operator.mul
\u800c\u4e0d\u662f lambda x, y: x * y
\u3002
"},{"location":"standard/language_rules/#111_1","title":"1.11 \u6761\u4ef6\u8868\u8fbe\u5f0f","text":"\u9002\u7528\u4e8e\u5355\u884c\u51fd\u6570\u3002
"},{"location":"standard/language_rules/#1111","title":"1.11.1 \u5b9a\u4e49","text":"\u6761\u4ef6\u8868\u8fbe\u5f0f\u662f\u5bf9\u4e8e if
\u8bed\u53e5\u7684\u4e00\u79cd\u66f4\u4e3a\u7b80\u77ed\u7684\u53e5\u6cd5\u89c4\u5219\u3002\u4f8b\u5982 x = 1 if cond else 2
\u3002
"},{"location":"standard/language_rules/#1112","title":"1.11.2 \u4f18\u70b9","text":"\u6bd4 if
\u8bed\u53e5\u66f4\u52a0\u7b80\u77ed\u548c\u65b9\u4fbf\u3002
"},{"location":"standard/language_rules/#1112_1","title":"1.11.2 \u7f3a\u70b9","text":"\u6bd4 if
\u8bed\u53e5\u96be\u4e8e\u9605\u8bfb\u3002\u5982\u679c\u8868\u8fbe\u5f0f\u5f88\u957f\uff0c\u96be\u4e8e\u5b9a\u4f4d\u6761\u4ef6\u3002
"},{"location":"standard/language_rules/#1114","title":"1.11.4 \u7ed3\u8bba","text":"\u9002\u7528\u4e8e\u5355\u884c\u51fd\u6570\u3002\u6bcf\u4e2a\u90e8\u5206\u5fc5\u987b\u653e\u5728\u4e00\u884c\u4e0a\uff1a true-expression, if-expression, else-expression
\u3002\u5728\u5176\u4ed6\u60c5\u51b5\u4e0b\uff0c\u63a8\u8350\u4f7f\u7528\u5b8c\u6574\u7684 if
\u8bed\u53e5\u3002
\u63a8\u8350
one_line = 'yes' if predicate(value) else 'no'\nslightly_split = ('yes' if predicate(value) else 'no, nein, nyet')\nthe_longest_ternary_style_that_can_be_done = (\n'yes, true, affirmative, confirmed, correct'\nif predicate(value) else 'no, false, negative, nay')\n
\u4e0d\u63a8\u8350
bad_line_breaking = ('yes' if predicate(value) else 'no')\nportion_too_long = ('yes' if some_long_module.some_long_predicate_function(\nreally_long_variable_name) else 'no, false, negative, nay')\n
"},{"location":"standard/language_rules/#112_1","title":"1.12 \u9ed8\u8ba4\u53c2\u6570\u503c","text":"\u9002\u7528\u4e8e\u5927\u90e8\u5206\u60c5\u51b5\u3002
"},{"location":"standard/language_rules/#1121","title":"1.12.1 \u5b9a\u4e49","text":"\u4f60\u53ef\u4ee5\u5728\u51fd\u6570\u53c2\u6570\u5217\u8868\u7684\u6700\u540e\u6307\u5b9a\u53d8\u91cf\u7684\u503c\uff0c\u4f8b\u5982\uff0c def(a, b=0):
\u3002\u5982\u679c\u8c03\u7528 foo
\u65f6\u53ea\u5e26\u4e00\u4e2a\u53c2\u6570\uff0c\u5219 b
\u88ab\u8bbe\u4e3a 0
\uff0c\u5982\u679c\u5e26\u4e24\u4e2a\u53c2\u6570\uff0c\u5219 b
\u7684\u503c\u7b49\u4e8e\u7b2c\u4e8c\u4e2a\u53c2\u6570\u3002
"},{"location":"standard/language_rules/#1122","title":"1.12.2 \u4f18\u70b9","text":"\u4f60\u7ecf\u5e38\u4f1a\u78b0\u5230\u4e00\u4e9b\u4f7f\u7528\u5927\u91cf\u9ed8\u8ba4\u503c\u7684\u51fd\u6570\uff0c\u4f46\u5076\u5c14\uff08\u6bd4\u8f83\u5c11\u89c1\uff09\u4f60\u60f3\u8981\u8986\u76d6\u8fd9\u4e9b\u9ed8\u8ba4\u503c\u3002\u9ed8\u8ba4\u53c2\u6570\u503c\u63d0\u4f9b\u4e86\u4e00\u79cd\u7b80\u5355\u7684\u65b9\u6cd5\u6765\u5b8c\u6210\u8fd9\u4ef6\u4e8b\uff0c\u4f60\u4e0d\u9700\u8981\u4e3a\u8fd9\u4e9b\u7f55\u89c1\u7684\u4f8b\u5916\u5b9a\u4e49\u5927\u91cf\u51fd\u6570\u3002\u540c\u65f6\uff0c Python \u4e5f\u4e0d\u652f\u6301\u91cd\u8f7d\u65b9\u6cd5\u548c\u51fd\u6570\uff0c\u9ed8\u8ba4\u53c2\u6570\u662f\u4e00\u79cd\u201c\u6a21\u62df\u201d\u91cd\u8f7d\u884c\u4e3a\u7684\u7b80\u5355\u65b9\u5f0f\u3002
"},{"location":"standard/language_rules/#1123","title":"1.12.3 \u7f3a\u70b9","text":"\u9ed8\u8ba4\u53c2\u6570\u53ea\u5728\u6a21\u5757\u52a0\u8f7d\u65f6\u6c42\u503c\u4e00\u6b21\u3002\u5982\u679c\u53c2\u6570\u662f\u5217\u8868\u6216\u5b57\u5178\u4e4b\u7c7b\u7684\u53ef\u53d8\u7c7b\u578b\uff0c\u8fd9\u53ef\u80fd\u4f1a\u5bfc\u81f4\u95ee\u9898\u3002\u5982\u679c\u51fd\u6570\u4fee\u6539\u4e86\u5bf9\u8c61\uff08\u4f8b\u5982\uff0c\u5411\u5217\u8868\u8ffd\u52a0\u9879\uff09\uff0c\u9ed8\u8ba4\u503c\u5c31\u88ab\u4fee\u6539\u4e86\u3002
"},{"location":"standard/language_rules/#1124","title":"1.12.4 \u7ed3\u8bba","text":"\u9f13\u52b1\u4f7f\u7528\uff0c\u4e0d\u8981\u5728\u51fd\u6570\u6216\u65b9\u6cd5\u5b9a\u4e49\u4e2d\u4f7f\u7528\u53ef\u53d8\u5bf9\u8c61\u4f5c\u4e3a\u9ed8\u8ba4\u503c\u3002
\u63a8\u8350
def foo(a, b=None):\nif b is None:\nb = []\n
def foo(a, b: Optional[Sequence] = None):\nif b is None:\nb = []\n
def foo(a, b: Sequence = ()): # Empty tuple OK since tuples are immutable\n...\n
\u4e0d\u63a8\u8350
def foo(a, b=[]):\n...\n
def foo(a, b=time.time()): # The time the module was loaded???\n...\n
from absl import flags\n_FOO = flags.DEFINE_string(...)\ndef foo(a, b=_FOO.value): # sys.argv has not yet been parsed...\n...\n
def foo(a, b: Mapping = {}): # Could still get passed to unchecked code\n...\n
"},{"location":"standard/language_rules/#113-properties","title":"1.13 \u5c5e\u6027\uff08properties\uff09","text":"\u5c5e\u6027\uff08properties\uff09\u53ef\u4ee5\u7528\u4e8e\u63a7\u5236\u9700\u8981\u8fdb\u884c\u7b80\u5355\u8ba1\u7b97\u6216\u903b\u8f91\u7684\u5c5e\u6027\u7684\u83b7\u53d6\u6216\u8bbe\u7f6e\u3002\u5176\u5b9e\u73b0\u5fc5\u987b\u7b26\u5408\u5e38\u89c4\u5c5e\u6027\u8bbf\u95ee\u7684\u4e00\u822c\u671f\u671b\uff1a\u5b83\u4eec\u5e94\u8be5\u662f\u7b80\u5355\uff0c\u76f4\u63a5\uff0c\u4e14\u6613\u4e8e\u7406\u89e3\u3002
"},{"location":"standard/language_rules/#1131","title":"1.13.1 \u5b9a\u4e49","text":"\u4e00\u79cd\u7528\u4e8e\u5305\u88c5\u65b9\u6cd5\u8c03\u7528\u7684\u65b9\u5f0f\u3002\u5f53\u8fd0\u7b97\u91cf\u4e0d\u5927\uff0c\u5b83\u662f\u83b7\u53d6\u548c\u8bbe\u7f6e\u5c5e\u6027\u7684\u6807\u51c6\u65b9\u5f0f\u3002
"},{"location":"standard/language_rules/#1132","title":"1.13.2 \u4f18\u70b9","text":" - \u5141\u8bb8\u8fdb\u884c\u5c5e\u6027\u8bbf\u95ee\u548c\u8d4b\u503c\u7684 API\uff0c\u800c\u4e0d\u662f\u8c03\u7528
getter
\u548c setter
\u65b9\u6cd5\u3002 - \u53ef\u4f7f\u5f97\u5c5e\u6027\u4e3a\u53ea\u8bfb\u3002
- \u5141\u8bb8\u5ef6\u8fdf\u52a0\u8f7d\u3002
- \u63d0\u4f9b\u4e86\u4e00\u79cd\u65b9\u5f0f\uff0c\u5728\u7c7b\u7684\u5185\u90e8\u4e0e\u5916\u90e8\u72ec\u7acb\u6f14\u5316\u65f6\uff0c\u4ecd\u7136\u80fd\u591f\u4fdd\u6301\u7c7b\u7684\u516c\u5171\u63a5\u53e3\u4e0d\u53d8\u3002
"},{"location":"standard/language_rules/#1133","title":"1.13.3 \u7f3a\u70b9","text":" - \u4f1a\u9690\u85cf\u7c7b\u4f3c\u64cd\u4f5c\u7b26\u91cd\u8f7d\u7684\u526f\u4f5c\u7528\u3002
- \u5bf9\u4e8e\u5b50\u7c7b\u53ef\u80fd\u4f1a\u9020\u6210\u6df7\u6dc6\u3002
"},{"location":"standard/language_rules/#1134","title":"1.13.4 \u7ed3\u8bba","text":"\u4e0e\u8fd0\u7b97\u7b26\u91cd\u8f7d\u4e00\u6837\uff0c\u5728\u5fc5\u8981\u7684\u60c5\u51b5\u4e0b\u53ef\u4ee5\u4f7f\u7528\u5c5e\u6027\uff0c\u5e76\u4e14\u5e94\u7b26\u5408\u5178\u578b\u5c5e\u6027\u8bbf\u95ee\u7684\u9884\u671f\uff1b\u5426\u5219\uff0c\u8bf7\u9075\u5faa getters
\u548c setters
\u89c4\u5219\u8fdb\u884c\u64cd\u4f5c\u3002
\u4f8b\u5982\uff0c\u4f7f\u7528\u5c5e\u6027\u540c\u65f6\u83b7\u53d6\u548c\u8bbe\u7f6e\u5185\u90e8\u5c5e\u6027\u662f\u4e0d\u5141\u8bb8\u7684\uff1a\u56e0\u4e3a\u6ca1\u6709\u8fdb\u884c\u4efb\u4f55\u8ba1\u7b97\uff0c\u6240\u4ee5\u5c5e\u6027\u662f\u4e0d\u5fc5\u8981\u7684\uff08\u53ef\u4ee5\u5c06\u5c5e\u6027\u516c\u5f00\u800c\u4e0d\u7528\u4f7f\u7528\u5c5e\u6027\uff09\u3002\u76f8\u6bd4\u4e4b\u4e0b\uff0c\u4f7f\u7528\u5c5e\u6027\u6765\u63a7\u5236\u5c5e\u6027\u8bbf\u95ee\u6216\u8ba1\u7b97\u4e00\u4e2a\u5f88\u5bb9\u6613\u5f97\u51fa\u7684\u503c\u662f\u88ab\u5141\u8bb8\u7684\uff1a\u56e0\u4e3a\u903b\u8f91\u7b80\u5355\uff0c\u4e14\u6613\u4e8e\u7406\u89e3\u3002
\u5e94\u8be5\u4f7f\u7528 @property
\u88c5\u9970\u5668\u521b\u5efa\u5c5e\u6027\u3002\u624b\u52a8\u5b9e\u73b0\u5c5e\u6027\u63cf\u8ff0\u7b26\u88ab\u8ba4\u4e3a\u662f\u5a01\u529b\u8fc7\u5927\u7684\u7279\u6027\u3002
\u5c5e\u6027\u7684\u7ee7\u627f\u53ef\u80fd\u662f\u4e0d\u660e\u663e\u7684\u3002\u4e0d\u8981\u4f7f\u7528\u5c5e\u6027\u6765\u5b9e\u73b0\u5b50\u7c7b\u53ef\u80fd\u60f3\u8981\u91cd\u5199\u548c\u6269\u5c55\u7684\u8ba1\u7b97\u3002
"},{"location":"standard/language_rules/#114-true-false","title":"1.14 True
/ False
\u7684\u6c42\u503c","text":"\u5c3d\u53ef\u80fd\u4f7f\u7528\u9690\u5f0f False
\u3002
"},{"location":"standard/language_rules/#1141","title":"1.14.1 \u5b9a\u4e49","text":"Python \u5728\u5e03\u5c14\u4e0a\u4e0b\u6587\u4e2d\u4f1a\u5c06\u67d0\u4e9b\u503c\u6c42\u503c\u4e3a False
\u3002\u6309\u7b80\u5355\u7684\u76f4\u89c9\u6765\u8bb2\uff0c\u5c31\u662f\u6240\u6709\u7684\u7a7a\u503c\u90fd\u88ab\u8ba4\u4e3a\u662f False
\uff0c\u56e0\u6b64 0
, None
\uff0c[]
\uff0c{}
\uff0c''
\u90fd\u88ab\u8ba4\u4e3a\u662f False
\u3002
"},{"location":"standard/language_rules/#1142","title":"1.14.2 \u4f18\u70b9","text":"\u4f7f\u7528 Python \u5e03\u5c14\u503c\u7684\u6761\u4ef6\u8bed\u53e5\u66f4\u6613\u8bfb\u4e5f\u66f4\u4e0d\u6613\u72af\u9519\u3002\u5927\u90e8\u5206\u60c5\u51b5\u4e0b\uff0c\u4e5f\u66f4\u5feb\u3002
"},{"location":"standard/language_rules/#1143","title":"1.14.3 \u7f3a\u70b9","text":"\u5bf9\u4e8e C / C ++ \u5f00\u53d1\u4eba\u5458\u6765\u8bf4\uff0c\u53ef\u80fd\u770b\u8d77\u6765\u6709\u70b9\u602a\u3002
"},{"location":"standard/language_rules/#1144","title":"1.14.4 \u7ed3\u8bba","text":"\u5c3d\u53ef\u80fd\u4f7f\u7528\u9690\u5f0f\u7684 false
\uff0c\u4f8b\u5982\uff1a\u4f7f\u7528 if foo:
\u800c\u4e0d\u662f if foo !=[]:
\u3002\u4e0d\u8fc7\u8fd8\u662f\u6709\u4e00\u4e9b\u6ce8\u610f\u4e8b\u9879\u9700\u8981\u4f60\u94ed\u8bb0\u5728\u5fc3\uff1a
- \u603b\u662f\u4f7f\u7528
if foo is None:
\u6216 if foo is not None:
\u6765\u68c0\u67e5 None
\u503c\u3002\u4f8b\u5982\uff0c\u5f53\u4f60\u8981\u6d4b\u8bd5\u4e00\u4e2a\u9ed8\u8ba4\u503c\u662f None
\u7684\u53d8\u91cf\u6216\u53c2\u6570\u662f\u5426\u88ab\u8bbe\u4e3a\u5176\u5b83\u503c\u3002\u8fd9\u4e2a\u503c\u5728\u5e03\u5c14\u8bed\u4e49\u4e0b\u53ef\u80fd\u662f false
! - \u6c38\u8fdc\u4e0d\u8981\u7528
==
\u5c06\u4e00\u4e2a\u5e03\u5c14\u91cf\u4e0e False
\u76f8\u6bd4\u8f83\u3002\u4f7f\u7528 if not x:
\u4ee3\u66ff\u3002\u5982\u679c\u4f60\u9700\u8981\u533a\u5206 False
\u548c None
\uff0c\u4f60\u5e94\u8be5\u7528\u50cf if not x and x is not None:
\u8fd9\u6837\u7684\u8bed\u53e5\u3002 - \u5bf9\u4e8e\u5e8f\u5217\uff08\u5b57\u7b26\u4e32\u3001\u5217\u8868\u3001\u5143\u7ec4\uff09\uff0c \u8981\u6ce8\u610f\u7a7a\u5e8f\u5217\u662f
False
\u3002\u56e0\u6b64\uff1a if seq:
\u6216\u8005 if not seq:
\u6bd4 if len(seq):
\u6216 if not len(seq)
\u8981\u66f4\u597d\u3002 -
\u5904\u7406\u6574\u6570\u65f6\uff0c\u4f7f\u7528\u9690\u5f0f False
\u53ef\u80fd\u4f1a\u5f97\u4e0d\u507f\u5931\uff08\u5373\u4e0d\u5c0f\u5fc3\u5c06 None
\u5f53\u505a 0
\u6765\u5904\u7406\uff09\u3002\u4f60\u53ef\u4ee5\u5c06\u4e00\u4e2a\u5df2\u77e5\u662f\u6574\u578b\uff08\u4e14\u4e0d\u662f len()
\u7684\u8fd4\u56de\u7ed3\u679c\uff09\u7684\u503c\u4e0e 0
\u6bd4\u8f83\u3002
\u63a8\u8350
if not users:\nprint('no users')\nif i % 10 == 0:\nself.handle_multiple_of_ten()\ndef f(x=None):\nif x is None:\nx = []\n
\u4e0d\u63a8\u8350
if len(users) == 0:\nprint('no users')\nif not i % 10:\nself.handle_multiple_of_ten()\ndef f(x=None):\nx = x or []\n
-
\u6ce8\u610f\uff1a '0'
\uff08\u5373\uff1a 0
\u4f5c\u4e3a\u5b57\u7b26\u4e32\uff09\u7684\u8ba1\u7b97\u7ed3\u679c\u662f True
\u3002
- \u6ce8\u610f\uff1a Numpy \u6570\u7ec4\u53ef\u80fd\u4f1a\u5728\u9690\u5f0f\u5e03\u5c14\u4e0a\u4e0b\u6587\u4e2d\u5f15\u53d1\u5f02\u5e38\u3002\u6d4b\u8bd5\u4e00\u7ec4
np.array
\u4e3a\u7a7a\u9996\u9009 .size
\u5c5e\u6027 \uff08\u4f8b\u5982 if not users.size
\uff09\u3002
"},{"location":"standard/language_rules/#116-lexical-scoping","title":"1.16 \u8bcd\u6cd5\u4f5c\u7528\u57df\uff08Lexical Scoping\uff09","text":"\u63a8\u8350\u4f7f\u7528
"},{"location":"standard/language_rules/#1161","title":"1.16.1 \u5b9a\u4e49","text":"\u5d4c\u5957\u7684 Python \u51fd\u6570\u53ef\u4ee5\u5f15\u7528\u5916\u5c42\u51fd\u6570\u4e2d\u5b9a\u4e49\u7684\u53d8\u91cf\uff0c\u4f46\u662f\u4e0d\u80fd\u591f\u5bf9\u5b83\u4eec\u8d4b\u503c\u3002\u53d8\u91cf\u7ed1\u5b9a\u7684\u89e3\u6790\u662f\u4f7f\u7528\u8bcd\u6cd5\u4f5c\u7528\u57df\uff0c\u4e5f\u5c31\u662f\u57fa\u4e8e\u9759\u6001\u7684\u7a0b\u5e8f\u6587\u672c\u3002 \u5bf9\u4e00\u4e2a\u5757\u4e2d\u7684\u67d0\u4e2a\u540d\u79f0\u7684\u4efb\u4f55\u8d4b\u503c\u90fd\u4f1a\u5bfc\u81f4Python \u5c06\u5bf9\u8be5\u540d\u79f0\u7684\u5168\u90e8\u5f15\u7528\u5f53\u505a\u5c40\u90e8\u53d8\u91cf\uff0c\u751a\u81f3\u662f\u8d4b\u503c\u524d\u7684\u5904\u7406\u3002 \u5982\u679c\u78b0\u5230 global
\u58f0\u660e\uff0c\u8be5\u540d\u79f0\u5c31\u4f1a\u88ab\u89c6\u4f5c\u5168\u5c40\u53d8\u91cf\u3002
\u4e00\u4e2a\u4f7f\u7528\u8fd9\u4e2a\u7279\u6027\u7684\u4f8b\u5b50\uff1a
def get_adder(summand1):\n\"\"\"Returns a function that adds numbers to a given number.\"\"\"\ndef adder(summand2):\nreturn summand1 + summand2\nreturn adder\n
"},{"location":"standard/language_rules/#1162","title":"1.16.2 \u4f18\u70b9","text":"\u901a\u5e38\u53ef\u4ee5\u5e26\u6765\u66f4\u52a0\u6e05\u6670\uff0c\u4f18\u96c5\u7684\u4ee3\u7801\u3002\u5c24\u5176\u4f1a\u8ba9\u6709\u7ecf\u9a8c\u7684 Lisp \u548c Scheme \uff08\u8fd8\u6709 Haskell\uff0c ML \u7b49\uff09\u7a0b\u5e8f\u5458\u611f\u5230\u6b23\u6170\u3002
"},{"location":"standard/language_rules/#1163","title":"1.16.3 \u7f3a\u70b9","text":"\u53ef\u80fd\u5bfc\u81f4\u8ba9\u4eba\u8ff7\u60d1\u7684 bug\u3002\u4f8b\u5982\u4e0b\u9762\u8fd9\u4e2a\u4f9d\u636e PEP-0227 \u7684\u4f8b\u5b50\uff1a
i = 4\ndef foo(x):\ndef bar():\nprint(i, end='')\n# ...\n# A bunch of code here\n# ...\nfor i in x: # Ah, i *is* local to foo, so this is what bar sees\nprint(i, end='')\nbar()\n
\u56e0\u6b64 foo([1, 2, 3])
\u4f1a\u6253\u5370 1 2 3 3
\u800c\u4e0d\u662f 1 2 3 4
"},{"location":"standard/language_rules/#1164","title":"1.16.4 \u7ed3\u8bba","text":"\u9f13\u52b1\u4f7f\u7528\u3002
"},{"location":"standard/language_rules/#117","title":"1.17 \u51fd\u6570\u4e0e\u65b9\u6cd5\u88c5\u9970\u5668","text":"\u5f53\u6709\u660e\u663e\u4f18\u52bf\u65f6\uff0c\u5c31\u660e\u667a\u800c\u8c28\u614e\u7684\u4f7f\u7528\u88c5\u9970\u5668\u3002\u907f\u514d\u4f7f\u7528 staticmethod
\uff0c\u9650\u5236\u4f7f\u7528 classmethod
\u3002
"},{"location":"standard/language_rules/#1171","title":"1.17.1 \u5b9a\u4e49","text":"\u7528\u4e8e\u51fd\u6570\u53ca\u65b9\u6cd5\u7684\u88c5\u9970\u5668\uff08\u4e5f\u5c31\u662f @
\u6807\u8bb0\uff09\u3002 \u6700\u5e38\u89c1\u7684\u88c5\u9970\u5668\u662f @property
\uff0c\u7528\u4e8e\u5c06\u666e\u901a\u65b9\u6cd5\u8f6c\u6362\u4e3a\u52a8\u6001\u8fd0\u7b97\u7684\u5c5e\u6027\u3002\u4e0d\u8fc7\uff0c\u88c5\u9970\u5668\u8bed\u6cd5\u4e5f\u5141\u8bb8\u7528\u6237\u81ea\u5b9a\u4e49\u88c5\u9970\u5668\u3002 \u7279\u522b\u5730\uff0c\u5bf9\u4e8e\u67d0\u4e2a\u51fd\u6570 my_decorator
\uff0c\u4e0b\u9762\u7684\u4e24\u6bb5\u4ee3\u7801\u662f\u7b49\u6548\u7684\uff1a
class C:\n@my_decorator\ndef method(self):\n# method body ...\n
\u76f8\u5f53\u4e8e\uff1a
class C:\ndef method(self):\n# method body ...\nmethod = my_decorator(method)\n
"},{"location":"standard/language_rules/#1172","title":"1.17.2 \u4f18\u70b9","text":"\u4f18\u96c5\u7684\u5728\u51fd\u6570\u4e0a\u6307\u5b9a\u4e00\u4e9b\u8f6c\u6362\u3002\u8be5\u8f6c\u6362\u53ef\u80fd\u51cf\u5c11\u4e00\u4e9b\u91cd\u590d\u4ee3\u7801\uff0c\u4fdd\u6301\u5df2\u6709\u51fd\u6570\u4e0d\u53d8\uff08enforce invariants)\uff09\u7b49\u3002
"},{"location":"standard/language_rules/#1173","title":"1.17.3 \u7f3a\u70b9","text":"\u88c5\u9970\u5668\u53ef\u4ee5\u5728\u51fd\u6570\u7684\u53c2\u6570\u6216\u8fd4\u56de\u503c\u4e0a\u6267\u884c\u4efb\u4f55\u64cd\u4f5c\uff0c\u8fd9\u53ef\u80fd\u5bfc\u81f4\u8ba9\u4eba\u60ca\u5f02\u7684\u9690\u85cf\u884c\u4e3a\u3002\u6b64\u5916\uff0c\u88c5\u9970\u5668\u5728\u5bf9\u8c61\u5b9a\u4e49\u65f6\u6267\u884c\u3002 \u5bf9\u4e8e\u6a21\u5757\u7ea7\u522b\u7684\u5bf9\u8c61\uff08\u7c7b\u3001\u6a21\u5757\u51fd\u6570\u7b49\uff09\uff0c\u6b64\u8fc7\u7a0b\u53d1\u751f\u5728\u5bfc\u5165\u65f6\u3002\u4ece\u88c5\u9970\u5668\u4ee3\u7801\u7684\u5931\u8d25\u4e2d\u6062\u590d\u66f4\u52a0\u4e0d\u53ef\u80fd\u3002
"},{"location":"standard/language_rules/#1174","title":"1.17.4 \u7ed3\u8bba","text":" - \u5982\u679c\u597d\u5904\u5f88\u663e\u7136\uff0c\u5c31\u660e\u667a\u800c\u8c28\u614e\u7684\u4f7f\u7528\u88c5\u9970\u5668\u3002
- \u88c5\u9970\u5668\u5e94\u8be5\u9075\u5b88\u548c\u51fd\u6570\u4e00\u6837\u7684\u5bfc\u5165\u548c\u547d\u540d\u89c4\u5219\u3002
- \u88c5\u9970\u5668\u7684 Python \u6587\u6863\u5e94\u8be5\u6e05\u6670\u7684\u8bf4\u660e\u8be5\u51fd\u6570\u662f\u4e00\u4e2a\u88c5\u9970\u5668\u3002
- \u8bf7\u4e3a\u88c5\u9970\u5668\u7f16\u5199\u5355\u5143\u6d4b\u8bd5\u3002
\u907f\u514d\u88c5\u9970\u5668\u81ea\u8eab\u5bf9\u5916\u754c\u7684\u4f9d\u8d56\uff08\u5373\u4e0d\u8981\u4f9d\u8d56\u4e8e\u6587\u4ef6\uff0csocket
\uff0c\u6570\u636e\u5e93\u8fde\u63a5\u7b49\uff09\uff0c\u56e0\u4e3a\u88c5\u9970\u5668\u8fd0\u884c\u65f6\u8fd9\u4e9b\u8d44\u6e90\u53ef\u80fd\u4e0d\u53ef\u7528\uff08\u7531 pydoc
\u6216\u5176\u5b83\u5de5\u5177\u5bfc\u5165\uff09\u3002\u5e94\u8be5\u4fdd\u8bc1\u4e00\u4e2a\u7528\u6709\u6548\u53c2\u6570\u8c03\u7528\u7684\u88c5\u9970\u5668\u5728\u6240\u6709\u60c5\u51b5\u4e0b\u90fd\u662f\u6210\u529f\u7684\u3002
\u88c5\u9970\u5668\u662f\u4e00\u79cd\u7279\u6b8a\u5f62\u5f0f\u7684\u201c\u9876\u7ea7\u4ee3\u7801\u201d\u3002\u53c2\u8003 Main \u7684\u8bdd\u9898\u3002
\u6c38\u8fdc\u4e0d\u8981\u4f7f\u7528 staticmethod
\uff0c\u9664\u975e\u4e3a\u4e86\u4e0e\u73b0\u6709\u5e93\u4e2d\u5b9a\u4e49\u7684 API \u96c6\u6210\u800c\u88ab\u8feb\u4f7f\u7528\u3002\u53ef\u4ee5\u5199\u4e00\u4e2a\u6a21\u5757\u7ea7\u51fd\u6570\u4ee3\u66ff\u3002
\u53ea\u6709\u5728\u7f16\u5199\u547d\u540d\u6784\u9020\u51fd\u6570\u6216\u4fee\u6539\u5fc5\u8981\u7684\u5168\u5c40\u72b6\u6001\uff08\u5982\u8fdb\u7a0b\u7ea7\u7f13\u5b58\uff09\u7684\u7279\u5b9a\u7c7b\u64cd\u4f5c\u65f6\u624d\u4f7f\u7528 classmethod
\u3002
"},{"location":"standard/language_rules/#118","title":"1.18 \u7ebf\u7a0b","text":"\u4e0d\u8981\u4f9d\u8d56\u5185\u5efa\u7c7b\u578b\u7684\u539f\u5b50\u6027\u3002
\u867d\u7136 Python \u7684\u5185\u5efa\u7c7b\u578b\u4f8b\u5982\u5b57\u5178\u770b\u4e0a\u53bb\u62e5\u6709\u539f\u5b50\u64cd\u4f5c\uff0c\u4f46\u662f\u5728\u67d0\u4e9b\u60c5\u5f62\u4e0b\u5b83\u4eec\u4ecd\u7136\u4e0d\u662f\u539f\u5b50\u7684\uff08\u5373\uff0c\u5982\u679c __hash__
\u6216 __eq__
\u88ab\u5b9e\u73b0\u4e3a Python \u65b9\u6cd5\uff09\u4e14\u5b83\u4eec\u7684\u539f\u5b50\u6027\u662f\u9760\u4e0d\u4f4f\u7684\u3002\u4f60\u4e5f\u4e0d\u80fd\u6307\u671b\u539f\u5b50\u53d8\u91cf\u8d4b\u503c\uff08\u56e0\u4e3a\u8fd9\u4e2a\u53cd\u8fc7\u6765\u4f9d\u8d56\u5b57\u5178\uff09\u3002
\u4f18\u5148\u4f7f\u7528 Queue
\u6a21\u5757\u7684 Queue
\u6570\u636e\u7c7b\u578b\u4f5c\u4e3a\u7ebf\u7a0b\u95f4\u7684\u6570\u636e\u901a\u4fe1\u65b9\u5f0f\u3002\u53e6\u5916\uff0c\u4f7f\u7528 threading
\u6a21\u5757\u53ca\u5176\u9501\u539f\u8bed\uff08locking primitives
\uff09\u3002\u4e86\u89e3\u6761\u4ef6\u53d8\u91cf\u7684\u5408\u9002\u4f7f\u7528\u65b9\u5f0f\uff0c\u8fd9\u6837\u4f60\u5c31\u53ef\u4ee5\u4f7f\u7528 threading.Condition
\u6765\u53d6\u4ee3\u4f4e\u7ea7\u522b\u7684\u9501\u4e86\u3002
"},{"location":"standard/language_rules/#119","title":"1.19 \u5a01\u529b\u8fc7\u5927\u7684\u7279\u6027","text":"\u907f\u514d\u4f7f\u7528\u8fd9\u4e9b\u7279\u6027\u3002
"},{"location":"standard/language_rules/#1191","title":"1.19.1 \u5b9a\u4e49","text":"Python \u662f\u4e00\u79cd\u5f02\u5e38\u7075\u6d3b\u7684\u8bed\u8a00\uff0c\u5b83\u4e3a\u4f60\u63d0\u4f9b\u4e86\u5f88\u591a\u82b1\u54e8\u7684\u7279\u6027\uff0c\u8bf8\u5982\u5143\u7c7b\uff08metaclasses
\uff09\u3001\u5b57\u8282\u7801\u8bbf\u95ee\u3001 \u4efb\u610f\u7f16\u8bd1\uff08on-the-fly compilation
\uff09\u3001\u52a8\u6001\u7ee7\u627f\u3001\u5bf9\u8c61\u7236\u7c7b\u91cd\u5b9a\u4e49\uff08object reparenting
\uff09\u3001\u5bfc\u5165\u4fee\u6539\uff08import hacks
\uff09\u3001 \u53cd\u5c04\uff08\u4f8b\u5982 getattr()
\u7684\u4e00\u4e9b\u4f7f\u7528\uff09\u3001\u7cfb\u7edf\u5185\u4fee\u6539\uff08modification of system internals
\uff09\u3001\u65b9\u6cd5\u5b9e\u73b0\u81ea\u5b9a\u4e49\u6e05\u7406\uff08__del__
\uff09\u7b49\u7b49\u3002
"},{"location":"standard/language_rules/#1192","title":"1.19.2 \u4f18\u70b9","text":"\u5f3a\u5927\u7684\u8bed\u8a00\u7279\u6027\uff0c\u80fd\u8ba9\u4f60\u7684\u4ee3\u7801\u66f4\u7d27\u51d1\u3002
"},{"location":"standard/language_rules/#1193","title":"1.19.3 \u7f3a\u70b9","text":"\u4f7f\u7528\u8fd9\u4e9b\u5f88\u201c\u9177\u201d\u7684\u7279\u6027\u5341\u5206\u8bf1\u4eba\uff0c\u4f46\u4e0d\u662f\u7edd\u5bf9\u5fc5\u8981\u3002\u4f7f\u7528\u5947\u6280\u6deb\u5de7\u7684\u4ee3\u7801\u5c06\u66f4\u52a0\u96be\u4ee5\u9605\u8bfb\u548c\u8c03\u8bd5\u3002\u5f00\u59cb\u53ef\u80fd\u8fd8\u597d\uff08\u5bf9\u539f\u4f5c\u8005\u800c\u8a00\uff09, \u4f46\u5f53\u4f60\u56de\u987e\u4ee3\u7801, \u5b83\u4eec\u53ef\u80fd\u4f1a\u6bd4\u90a3\u4e9b\u7a0d\u957f\u4e00\u70b9\u4f46\u662f\u5f88\u76f4\u63a5\u7684\u4ee3\u7801\u66f4\u52a0\u96be\u4ee5\u7406\u89e3.
"},{"location":"standard/language_rules/#1194","title":"1.19.4 \u7ed3\u8bba","text":"\u5728\u4f60\u7684\u4ee3\u7801\u4e2d\u907f\u514d\u8fd9\u4e9b\u7279\u6027\u3002
\u5185\u90e8\u9700\u8981\u4f7f\u7528\u8fd9\u4e9b\u7279\u6027\u7684\u6807\u51c6\u5e93\u6a21\u5757\u548c\u7c7b\u53ef\u4ee5\u4f7f\u7528\uff08\u4f8b\u5982\uff0c abc.ABCMeta
\u3001 dataclasses
\u548c enum
\uff09\u3002
"},{"location":"standard/language_rules/#120-pythonfrom-__future__-imports","title":"1.20 \u65b0\u7248 Python:from __future__ imports
","text":"\u53ef\u4ee5\u4f7f\u7528\u5bfc\u5165 future \u8fd9\u79cd\u7279\u6b8a\u64cd\u5728\u8001\u7248\u672c\u4e2d\u4f7f\u7528\u65b0\u7248\u672c\u7684\u8bed\u6cd5\u7279\u6027\u3002
"},{"location":"standard/language_rules/#1201","title":"1.20.1 \u5b9a\u4e49","text":"\u4f7f\u7528 from __future__ import
\u8bed\u53e5\u53ef\u4ee5\u5728\u8001\u7248\u672c\u4e2d\u542f\u7528\u65b0\u7248\u672c\u7684\u529f\u80fd\u3002
"},{"location":"standard/language_rules/#1202","title":"1.20.2 \u4f18\u70b9","text":"\u7ecf\u9a8c\u8bc1\u660e\uff0c\u5728\u58f0\u660e\u517c\u5bb9\u6027\u5e76\u9632\u6b62\u8fd9\u4e9b\u6587\u4ef6\u4e2d\u7684\u56de\u5f52\u7684\u540c\u65f6\uff0c\u5bf9\u6bcf\u4e2a\u6587\u4ef6\u8fdb\u884c\u66f4\u6539\uff0c\u53ef\u4ee5\u4f7f\u8fd0\u884c\u65f6\u7248\u672c\u5347\u7ea7\u66f4\u52a0\u5e73\u6ed1\u3002 \u73b0\u4ee3\u4ee3\u7801\u66f4\u6613\u4e8e\u7ef4\u62a4\uff0c\u56e0\u4e3a\u5b83\u4e0d\u592a\u53ef\u80fd\u5728\u5c06\u6765\u7684\u8fd0\u884c\u65f6\u5347\u7ea7\u671f\u95f4\u79ef\u7d2f\u6280\u672f\u503a\u52a1\u3002
"},{"location":"standard/language_rules/#1203","title":"1.20.3 \u7f3a\u70b9","text":" - \u6ca1\u6709\u5f15\u5165\u6240\u9700\u7684 future \u8bed\u53e5\u65f6\uff0c\u8fd9\u4e9b\u4ee3\u7801\u53ef\u80fd\u65e0\u6cd5\u5728\u8001\u7248\u672c\u7684\u89e3\u91ca\u5668\u7248\u672c\u4e0a\u8fd0\u884c\u3002
- \u5728\u652f\u6301\u5404\u79cd\u73af\u5883\u7684\u9879\u76ee\u4e2d\uff0c\u8fd9\u79cd\u9700\u6c42\u66f4\u4e3a\u5e38\u89c1\u3002
"},{"location":"standard/language_rules/#1204","title":"1.20.4 \u7ed3\u8bba","text":"from __future__ imports
\u63a8\u8350\u4f7f\u7528 from __future__ import
\u8bed\u53e5\u3002\u6240\u6709\u7684\u65b0\u4ee3\u7801\u90fd\u5e94\u8be5\u5305\u542b\u4ee5\u4e0b\u5185\u5bb9\uff0c\u73b0\u6709\u7684\u4ee3\u7801\u4e5f\u5e94\u8be5\u5728\u6709\u6761\u4ef6\u7684\u60c5\u51b5\u4e0b\u8fdb\u884c\u517c\u5bb9\u66f4\u65b0\u3002
\u5728 3.5 \u6216\u66f4\u65e9\u7684\u7248\u672c\uff08\u800c\u4e0d\u662f >= 3.7\uff09\u4e0a\u6267\u884c\u7684\u4ee3\u7801\u4e2d\uff0c\u5bfc\u5165\uff1a
from __future__ import generator_stop\n
\u6709\u5173\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u9605\u8bfb Python future \u8bed\u53e5\u5b9a\u4e49\u6587\u6863\u3002
\u4e0d\u8981\u5220\u9664\u8fd9\u4e9b\u5bfc\u5165\uff0c\u9664\u975e\u60a8\u786e\u4fe1\u4ee3\u7801\u5728\u5f53\u524d\u73af\u5883\u8fd0\u884c\u6ca1\u6709\u95ee\u9898\u3002\u5373\u4f7f\u60a8\u73b0\u5728\u6ca1\u6709\u4f7f\u7528\u5f53\u524d\u4ee3\u7801\u4e2d\u7279\u5b9a\u7684 future \u5bfc\u5165\u542f\u7528\u7684\u7279\u6027\uff0c \u4fdd\u7559\u8fd9\u4e9b\u5bfc\u5165\u4fbf\u4e8e\u4ee5\u540e\u4fee\u6539\u4ee3\u7801\u65f6\u76f4\u63a5\u4f7f\u7528\u3002
\u8fd8\u6709\u4e00\u4e9b\u5176\u4ed6\u7684 from __future__
\u8bed\u53e5\uff0c\u53ef\u4ee5\u5728\u9700\u8981\u7684\u65f6\u5019\u4f7f\u7528\u3002
"},{"location":"standard/language_rules/#121_1","title":"1.21 \u4ee3\u7801\u7c7b\u578b\u6807\u6ce8","text":"\u53ef\u4ee5\u6839\u636e PEP-484 \u4f7f\u7528\u7c7b\u578b\u6807\u6ce8\uff0c\u5e76\u4f7f\u7528\u7c7b\u4f3c pytype \u7684\u7c7b\u578b\u68c0\u67e5\u5de5\u5177\u5728\u6784\u5efa\u65f6\u5bf9\u4ee3\u7801\u8fdb\u884c\u68c0\u67e5\u3002
\u7c7b\u578b\u6807\u6ce8\u53ef\u4ee5\u5728\u6e90\u7801\u4e2d\uff0c\u4e5f\u53ef\u4ee5\u5728 stub pyi\u6587\u4ef6\u4e2d\u3002 \u5c3d\u53ef\u80fd\u5728\u6e90\u4ee3\u7801\u4e2d\u8fdb\u884c\u6807\u6ce8\uff0c\u5bf9\u4e8e\u7b2c\u4e09\u65b9\u5e93\u6216\u6269\u5c55\u6a21\u5757\u53ef\u4ee5\u4f7f\u7528 pyi
\u6587\u4ef6\u3002
"},{"location":"standard/language_rules/#1211","title":"1.21.1 \u5b9a\u4e49","text":"\u7c7b\u578b\u6807\u6ce8\uff08\u6216\u7c7b\u578b\u63d0\u793a\uff09\u53ef\u4ee5\u7528\u4e8e\u51fd\u6570\u6216\u65b9\u6cd5\u7684\u53c2\u6570\u548c\u8fd4\u56de\u503c
def func(a: int) -> List[int]:\n
\u8fd8\u53ef\u4ee5\u4f7f\u7528\u7c7b\u4f3c PEP-526 \u7684\u8bed\u6cd5\u58f0\u660e\u53d8\u91cf\u7684\u7c7b\u578b\uff1a
a: SomeType = some_func()\n
"},{"location":"standard/language_rules/#1212","title":"1.21.2 \u4f18\u70b9","text":"\u7c7b\u578b\u6807\u6ce8\u53ef\u4ee5\u63d0\u9ad8\u4ee3\u7801\u7684\u53ef\u8bfb\u6027\u548c\u53ef\u7ef4\u62a4\u6027\u3002\u7c7b\u578b\u68c0\u67e5\u5668\u53ef\u4ee5\u628a\u8bb8\u591a\u8fd0\u884c\u65f6\u9519\u8bef\u8f6c\u6362\u4e3a\u6784\u5efa\u65f6\u9519\u8bef\uff0c\u5e76\u51cf\u5c11\u5a01\u529b\u8fc7\u5927\u7279\u6027\u5730\u4f7f\u7528\u3002
"},{"location":"standard/language_rules/#1213","title":"1.21.3 \u7f3a\u70b9","text":" - \u5fc5\u987b\u4fdd\u6301\u7c7b\u578b\u6807\u6ce8\u66f4\u65b0\u3002
- \u60a8\u53ef\u80fd\u4f1a\u770b\u5230\u60a8\u8ba4\u4e3a\u662f\u6b63\u786e\u4ee3\u7801\u7684\u9519\u8bef\u4fe1\u606f\u3002
- \u4f7f\u7528\u7c7b\u578b\u68c0\u67e5\u5668\u53ef\u80fd\u4f1a\u51cf\u5c11\u5a01\u529b\u8fc7\u5927\u7279\u6027\u5730\u4f7f\u7528\u3002
"},{"location":"standard/language_rules/#1214","title":"1.21.4 \u7ed3\u8bba","text":"\u5f3a\u70c8\u5efa\u8bae\u60a8\u5728\u66f4\u6539\u4ee3\u7801\u65f6\u542f\u7528 Python \u7c7b\u578b\u5206\u6790\u3002\u5f53\u6dfb\u52a0\u6216\u4fee\u6539\u516c\u5171 API \u65f6\uff0c\u8bf7\u5305\u542b\u7c7b\u578b\u6807\u6ce8\uff0c\u5e76\u5728\u6784\u5efa\u7cfb\u7edf\u4e2d\u542f\u7528 pytype
\u8fdb\u884c\u68c0\u67e5\u3002 \u7531\u4e8e\u9759\u6001\u5206\u6790\u5bf9 Python \u6765\u8bf4\u76f8\u5bf9\u8f83\u65b0\uff0c\u6211\u4eec\u627f\u8ba4\u4f1a\u6709\u4e00\u4e9b\u526f\u4f5c\u7528\uff08\u6bd4\u5982\u9519\u8bef\u7684\u7c7b\u578b\u63a8\u65ad\uff09\u53ef\u80fd\u4f1a\u963b\u6b62\u4e00\u4e9b\u9879\u76ee\u91c7\u7528\u3002 \u56e0\u6b64\uff0c\u6211\u4eec\u9f13\u52b1\u4f5c\u8005\u6dfb\u52a0\u4e00\u4e2a\u5e26\u6709 TODO
\u7684\u6ce8\u91ca\uff0c\u6216\u8005\u5728 BUILD
\u6587\u4ef6\u6216\u4ee3\u7801\u672c\u8eab\u4e2d\u901a\u8fc7 bug \u94fe\u63a5\u63cf\u8ff0\u5f53\u524d\u4e0d\u91c7\u7528\u7c7b\u578b\u6807\u6ce8\u7684\u95ee\u9898\u3002
"},{"location":"standard/style_rules/","title":"Python \u98ce\u683c\u89c4\u8303","text":"\u672c\u6587\u6863\u4e3a Google Python Style Guide \u7b2c\u4e09\u7ae0 Python Style Rules \u7684\u8bd1\u6587\u3002
\u6700\u540e\u66f4\u65b0\u65f6\u95f4\uff1a 2023-06-26
\u5982\u679c\u6709\u7ffb\u8bd1\u9519\u8bef\u6216\u8868\u8ff0\u4e0d\u51c6\u786e\u7684\u95ee\u9898\uff0c\u6b22\u8fce\u63d0\u4ea4 PR\uff0c\u611f\u8c22\u60a8\u7684\u53c2\u4e0e\u3002
"},{"location":"standard/style_rules/#31","title":"3.1 \u5206\u53f7","text":"\u4e0d\u8981\u5728\u884c\u5c3e\u52a0\u5206\u53f7\uff0c\u4e5f\u4e0d\u8981\u7528\u5206\u53f7\u5c06\u4e24\u6761\u547d\u4ee4\u653e\u5728\u540c\u4e00\u884c\u3002
"},{"location":"standard/style_rules/#32","title":"3.2 \u884c\u957f\u5ea6","text":"\u6bcf\u884c\u4e0d\u8d85\u8fc780\u4e2a\u5b57\u7b26\u3002
\u4f8b\u5916\uff1a
- \u957f\u7684\u5bfc\u5165\u6a21\u5757\u8bed\u53e5
- \u6ce8\u91ca\u91cc\u7684 URL \u3001\u8def\u5f84\u540d\u548c\u957f\u6807\u8bc6
- \u4e0d\u5305\u542b\u7a7a\u683c\uff0c\u4e0d\u65b9\u4fbf\u8de8\u884c\u62c6\u5206\u7684\u957f\u5b57\u7b26\u4e32\u6a21\u5757\u7ea7\u5e38\u91cf\uff0c\u5982 URL \u6216\u8def\u5f84\u540d
- Pylint \u7981\u7528\u6ce8\u91ca\u3002\uff08\u4f8b\u5982\uff1a
# pylint: disable=invalid-name
\uff09
\u4e0d\u8981\u4f7f\u7528\u53cd\u659c\u6760\u6765\u663e\u5f0f\u5ef6\u7eed\u884c\u3002
\u76f8\u53cd\uff0cPython \u4f1a\u5c06\u5706\u62ec\u53f7\u3001\u65b9\u62ec\u53f7\u548c\u82b1\u62ec\u53f7\u4e2d\u7684\u884c\u9690\u5f0f\u7684\u8fde\u63a5\u8d77\u6765\uff0c\u4f60\u53ef\u4ee5\u5229\u7528\u8fd9\u4e2a\u7279\u70b9\u3002\u5982\u679c\u9700\u8981\uff0c\u4f60\u53ef\u4ee5\u5728\u8868\u8fbe\u5f0f\u5916\u56f4\u589e\u52a0\u4e00\u5bf9\u989d\u5916\u7684\u5706\u62ec\u53f7\u3002
\u8bf7\u6ce8\u610f\uff0c\u6b64\u89c4\u5219\u5e76\u4e0d\u7981\u6b62\u5b57\u7b26\u4e32\u4e2d\u53cd\u659c\u6760\u8f6c\u4e49\u7684\u6362\u884c\u7b26\uff08\u89c1\u4e0b\u6587\uff09\u3002
\u63a8\u8350
foo_bar(self, width, height, color='black', design=None, x='foo',\nemphasis=None, highlight=0)\nif (width == 0 and height == 0 and\ncolor == 'red' and emphasis == 'strong'):\n(bridge_questions.clarification_on\n.average_airspeed_of.unladen_swallow) = 'African or European?'\nwith (\nvery_long_first_expression_function() as spam,\nvery_long_second_expression_function() as beans,\nthird_thing() as eggs,\n):\nplace_order(eggs, beans, spam, beans)\n
\u4e0d\u63a8\u8350
if width == 0 and height == 0 and \\\n color == 'red' and emphasis == 'strong':\nbridge_questions.clarification_on \\\n .average_airspeed_of.unladen_swallow = 'African or European?'\nwith very_long_first_expression_function() as spam, \\\n very_long_second_expression_function() as beans, \\\n third_thing() as eggs:\nplace_order(eggs, beans, spam, beans)\n
\u5982\u679c\u4e00\u4e2a\u6587\u672c\u5b57\u7b26\u4e32\u5728\u4e00\u884c\u653e\u4e0d\u4e0b\uff0c\u53ef\u4ee5\u4f7f\u7528\u5706\u62ec\u53f7\u6765\u5b9e\u73b0\u9690\u5f0f\u884c\u8fde\u63a5\u3002
x = ('This will build a very long long '\n'long long long long long long string')\n
\u5c3d\u53ef\u80fd\u9ad8\u7684\u5728\u53e5\u6cd5\u6c34\u5e73\u4e0a\u6362\u884c\uff0c\u5982\u679c\u5fc5\u987b\u6253\u65ad\u4e00\u884c\u4e24\u6b21\uff0c\u90a3\u4e48\u4e24\u6b21\u90fd\u8981\u5728\u76f8\u540c\u7684\u53e5\u6cd5\u6c34\u5e73\u4e0a\u6253\u65ad\u3002
\u63a8\u8350
bridgekeeper.answer(\nname=\"Arthur\", quest=questlib.find(owner=\"Arthur\", perilous=True))\nanswer = (a_long_line().of_chained_methods()\n.that_eventually_provides().an_answer())\nif (\nconfig is None\nor 'editor.language' not in config\nor config['editor.language'].use_spaces is False\n):\nuse_tabs()\n
\u4e0d\u63a8\u8350
bridgekeeper.answer(name=\"Arthur\", quest=questlib.find(\nowner=\"Arthur\", perilous=True))\nanswer = a_long_line().of_chained_methods().that_eventually_provides(\n).an_answer()\nif (config is None or 'editor.language' not in config or config[\n'editor.language'].use_spaces is False):\nuse_tabs()\n
\u5728\u6ce8\u91ca\u4e2d\uff0c\u5982\u679c\u5fc5\u8981\uff0c\u5c06\u957f\u7684 URL \u653e\u5728\u4e00\u884c\u4e0a\u3002
\u63a8\u8350
# See details at\n# http://www.example.com/us/developer/documentation/api/content/v2.0/csv_file_name_extension_full_specification.html\n
\u4e0d\u63a8\u8350
# See details at\n# http://www.example.com/us/developer/documentation/api/content/\\\n# v2.0/csv_file_name_extension_full_specification.html\n
\u6ce8\u610f\u4e0a\u9762\u4f8b\u5b50\u4e2d\u7684\u5143\u7d20\u7f29\u8fdb\u3002\u4f60\u53ef\u4ee5\u5728\u672c\u6587\u7684\u7f29\u8fdb\u90e8\u5206\u627e\u5230\u89e3\u91ca\u3002
\u5728\u6240\u6709\u5176\u4ed6\u60c5\u51b5\u4e0b\uff0c\u5982\u679c\u4e00\u884c\u8d85\u8fc780\u4e2a\u5b57\u7b26\uff0c\u5e76\u4e14 Black\u6216Pyink \u81ea\u52a8\u683c\u5f0f\u5316\u7a0b\u5e8f\u65e0\u6cd5\u5e2e\u52a9\u4f7f\u8be5\u884c\u4f4e\u4e8e\u9650\u5236\uff0c\u5219\u5141\u8bb8\u8be5\u884c\u8d85\u8fc7\u6b64\u6700\u5927\u503c\u3002\u5efa\u8bae\u4f5c\u8005\u5728\u5408\u7406\u7684\u60c5\u51b5\u4e0b\uff0c\u6839\u636e\u4e0a\u8ff0\u6ce8\u91ca\u624b\u52a8\u62c6\u5206\u884c\u3002
"},{"location":"standard/style_rules/#33","title":"3.3 \u62ec\u53f7","text":"\u5b81\u7f3a\u6bcb\u6ee5\u7684\u4f7f\u7528\u62ec\u53f7\u3002
\u9664\u975e\u662f\u7528\u4e8e\u5b9e\u73b0\u884c\u8fde\u63a5\uff0c\u5426\u5219\u4e0d\u8981\u5728\u8fd4\u56de\u8bed\u53e5\u6216\u6761\u4ef6\u8bed\u53e5\u4e2d\u4f7f\u7528\u62ec\u53f7\uff0c\u9690\u5f0f\u7684\u884c\u8fde\u63a5\u6216\u8005\u5143\u7ec4\u4e24\u8fb9\u4f7f\u7528\u62ec\u53f7\u9664\u5916\u3002
\u63a8\u8350
if foo:\nbar()\nwhile x:\nx = bar()\nif x and y:\nbar()\nif not x:\nbar()\n# For a 1 item tuple the ()s are more visually obvious than the comma.\nonesie = (foo,)\nreturn foo\nreturn spam, beans\nreturn (spam, beans)\nfor (x, y) in dict.items(): ...\n
\u4e0d\u63a8\u8350
if (x):\nbar()\nif not(x):\nbar()\nreturn (foo)\n
"},{"location":"standard/style_rules/#34","title":"3.4 \u7f29\u8fdb","text":"\u75284\u4e2a\u7a7a\u683c\u6765\u7f29\u8fdb\u4ee3\u7801\u3002
\u7edd\u5bf9\u4e0d\u8981\u7528 tab
\uff0c\u4e5f\u4e0d\u8981 tab
\u548c\u7a7a\u683c\u6df7\u7528\u3002\u5bf9\u4e8e\u884c\u8fde\u63a5\u7684\u60c5\u51b5\uff0c\u4f60\u5e94\u8be5\u8981\u4e48\u5782\u76f4\u5bf9\u9f50\u6362\u884c\u7684\u5143\u7d20\uff08\u89c1\u884c\u957f \u90e8\u5206\u7684\u793a\u4f8b\uff09\uff0c\u6216\u8005\u4f7f\u75284\u7a7a\u683c\u7684\u60ac\u6302\u5f0f\u7f29\u8fdb\uff08\u8fd9\u65f6\u7b2c\u4e00\u884c\u4e0d\u5e94\u8be5\u6709\u53c2\u6570\uff09\u3002
\u63a8\u8350
# Aligned with opening delimiter\nfoo = long_function_name(var_one, var_two,\nvar_three, var_four)\nmeal = (spam,\nbeans)\n# Aligned with opening delimiter in a dictionary\nfoo = {\nlong_dictionary_key: value1 +\nvalue2,\n...\n}\n# 4-space hanging indent; nothing on first line.\nfoo = long_function_name(\nvar_one, var_two, var_three,\nvar_four)\nmeal = (\nspam,\nbeans)\n# 4-space hanging indent; nothing on first line\n# closing parenthesis on a new line.\nfoo = long_function_name(\nvar_one, var_two, var_three,\nvar_four\n)\nmeal = (\nspam,\nbeans,\n)\n# 4-space hanging indent in a dictionary\nfoo = {\nlong_dictionary_key:\nlong_dictionary_value,\n...\n}\n
\u4e0d\u63a8\u8350
# Stuff on first line forbidden\nfoo = long_function_name(var_one, var_two,\nvar_three, var_four)\nmeal = (spam,\nbeans)\n# 2-space hanging indent forbidden\nfoo = long_function_name(\nvar_one, var_two, var_three,\nvar_four)\n# No hanging indent in a dictionary\nfoo = {\n'long_dictionary_key':\nlong_dictionary_value,\n...\n}\n
"},{"location":"standard/style_rules/#341","title":"3.4.1 \u5728\u5e8f\u5217\u7684\u672b\u5c3e\u662f\u5426\u52a0\u9017\u53f7\uff1f","text":"\u53ea\u6709\u5728\u5e8f\u5217\u7ed3\u675f\u7b26 ]
\u3001 )
\u6216 }
\u4e0e\u6700\u540e\u4e00\u4e2a\u5143\u7d20\u4e0d\u5728\u540c\u4e00\u884c\u65f6\u624d\u5efa\u8bae\u4f7f\u7528\u3002\u672b\u5c3e\u9017\u53f7\u7684\u5b58\u5728\u8fd8\u7528\u4f5c\u5bf9\u4ee3\u7801\u81ea\u52a8\u683c\u5f0f\u5316\u7a0b\u5e8f\u7684\u63d0\u793a\uff0c\u4ee5\u5f15\u5bfc\u5b83\u5728\u6700\u540e\u4e00\u4e2a\u5143\u7d20\u4e4b\u540e\u51fa\u73b0\u65f6\uff0c \u81ea\u52a8\u5c06\u5bb9\u5668\u4e2d\u6bcf\u4e2a\u6761\u76ee\u683c\u5f0f\u5316\u4e3a\u4e00\u884c\u3002
\u63a8\u8350
golomb3 = [0, 1, 3]\n
golomb4 = [\n0,\n1,\n4,\n6,\n]\n
\u4e0d\u63a8\u8350
golomb4 = [\n0,\n1,\n4,\n6\n]\n
"},{"location":"standard/style_rules/#35","title":"3.5 \u7a7a\u884c","text":"\u9876\u7ea7\u5b9a\u4e49\u4e4b\u95f4\u7a7a\u4e24\u884c, \u65b9\u6cd5\u5b9a\u4e49\u4e4b\u95f4\u7a7a\u4e00\u884c
- \u9876\u7ea7\u5b9a\u4e49\u4e4b\u95f4\u7a7a\u4e24\u884c\uff0c\u6bd4\u5982\u51fd\u6570\u6216\u8005\u7c7b\u5b9a\u4e49\u3002
- \u65b9\u6cd5\u5b9a\u4e49\uff0c\u7c7b\u5b9a\u4e49\u4e0e\u7b2c\u4e00\u4e2a\u65b9\u6cd5\u4e4b\u95f4\uff0c\u90fd\u5e94\u8be5\u7a7a\u4e00\u884c\u3002
- \u5728
def
\u51fd\u6570\u5b9a\u4e49\u4e4b\u540e\u4e0d\u9700\u8981\u7a7a\u884c\u3002 - \u51fd\u6570\u6216\u65b9\u6cd5\u4e2d\uff0c\u67d0\u4e9b\u5730\u65b9\u8981\u662f\u4f60\u89c9\u5f97\u5408\u9002\uff0c\u5c31\u7a7a\u4e00\u884c\u3002
"},{"location":"standard/style_rules/#36","title":"3.6 \u7a7a\u683c","text":"\u6309\u7167\u6807\u51c6\u7684\u6392\u7248\u89c4\u8303\u6765\u4f7f\u7528\u6807\u70b9\u4e24\u8fb9\u7684\u7a7a\u683c\u3002
\u62ec\u53f7\u5185\u4e0d\u8981\u6709\u7a7a\u683c\u3002
\u63a8\u8350
spam(ham[1], {eggs: 2}, [])\n
\u4e0d\u63a8\u8350
spam( ham[ 1 ], { eggs: 2 }, [ ] )\n
\u4e0d\u8981\u5728\u9017\u53f7\uff0c\u5206\u53f7\uff0c\u5192\u53f7\u524d\u9762\u52a0\u7a7a\u683c\u3002\u4f46\u5e94\u8be5\u5728\u5b83\u4eec\u540e\u9762\u52a0\uff08\u9664\u4e86\u5728\u884c\u5c3e\uff09\u3002
\u63a8\u8350
if x == 4:\nprint(x, y)\nx, y = y, x\n
\u4e0d\u63a8\u8350
if x == 4 :\nprint(x , y)\nx , y = y , x\n
\u53c2\u6570\u5217\u8868\u3001\u7d22\u5f15\u6216\u5207\u7247\u7684\u5de6\u62ec\u53f7\u524d\u4e0d\u5e94\u52a0\u7a7a\u683c\u3002
\u63a8\u8350
spam(1)\n
\u4e0d\u63a8\u8350
spam (1)\n
\u63a8\u8350
dict['key'] = list[index]\n
\u4e0d\u63a8\u8350
dict ['key'] = list [index]\n
\u884c\u5c3e\u4e0d\u9700\u8981\u7a7a\u683c\u3002
\u5728\u4e8c\u5143\u64cd\u4f5c\u7b26\u4e24\u8fb9\u90fd\u52a0\u4e0a\u4e00\u4e2a\u7a7a\u683c\uff0c\u6bd4\u5982\u8d4b\u503c\uff08=
\uff09\u3001\u6bd4\u8f83\uff08==
\u3001<
\u3001>
\u3001!=
\u3001<>
\u3001<=
\u3001>=
\u3001in
\u3001not in
\u3001is
\u3001is not
\uff09\uff0c\u5e03\u5c14\uff08and
\u3001or
\u3001not
\uff09\u3002 \u81f3\u4e8e\u7b97\u672f\u64cd\u4f5c\u7b26\uff08+
\u3001-
\u3001*
\u3001/
\u3001//
\u3001%
\u3001**
\u3001@
\uff09\u4e24\u8fb9\u7684\u7a7a\u683c\u8be5\u5982\u4f55\u4f7f\u7528\uff0c\u9700\u8981\u4f60\u81ea\u5df1\u597d\u597d\u5224\u65ad\u3002\u4e0d\u8fc7\u4e24\u4fa7\u52a1\u5fc5\u8981\u4fdd\u6301\u4e00\u81f4\u3002
\u63a8\u8350
x == 1\n
\u4e0d\u63a8\u8350
x<1\n
\u5f53 =
\u7528\u4e8e\u6307\u793a\u5173\u952e\u5b57\u53c2\u6570\u6216\u9ed8\u8ba4\u53c2\u6570\u503c\u65f6\uff0c\u4e0d\u8981\u5728\u5176\u4e24\u4fa7\u4f7f\u7528\u7a7a\u683c\u3002\u4f46\u6709\u4e00\u4e2a\u4f8b\u5916\uff1a\u5f53\u5b58\u5728\u7c7b\u578b\u6ce8\u91ca\u65f6\uff0c\u5728\u9ed8\u8ba4\u53c2\u6570\u503c\u7684 =
\u5468\u56f4\u4f7f\u7528\u7a7a\u683c\u3002
\u63a8\u8350
def complex(real, imag=0.0): return Magic(r=real, i=imag)\n
def complex(real, imag: float = 0.0): return Magic(r=real, i=imag)\n
\u4e0d\u63a8\u8350
def complex(real, imag = 0.0): return Magic(r = real, i = imag)\n
def complex(real, imag: float=0.0): return Magic(r = real, i = imag)\n
\u4e0d\u8981\u7528\u7a7a\u683c\u6765\u5782\u76f4\u5bf9\u9f50\u591a\u884c\u95f4\u7684\u6807\u8bb0\uff0c\u56e0\u4e3a\u8fd9\u4f1a\u9020\u6210\u7ef4\u62a4\u7684\u8d1f\u62c5\uff08\u9002\u7528\u4e8e :
\u3001#
\u3001=
\u7b49\uff09\uff1a
\u63a8\u8350
foo = 1000 # comment\nlong_name = 2 # comment that should not be aligned\ndictionary = {\n'foo': 1,\n'long_name': 2,\n}\n
\u4e0d\u63a8\u8350
foo = 1000 # comment\nlong_name = 2 # comment that should not be aligned\ndictionary = {\n'foo' : 1,\n'long_name': 2,\n}\n
"},{"location":"standard/style_rules/#37-shebang","title":"3.7 Shebang","text":"\u5927\u90e8\u5206 .py
\u6587\u4ef6\u4e0d\u5fc5\u4ee5 #!
\u4f5c\u4e3a\u6587\u4ef6\u7684\u5f00\u59cb\u3002\u6839\u636e PEP-394\uff0c\u7a0b\u5e8f\u7684 main
\u6587\u4ef6\u5e94\u8be5\u4ee5 #!/usr/bin/env python3
\uff08\u7528\u4e8e\u652f\u6301\u865a\u62df\u73af\u5883\uff09\u6216\u8005 #!/usr/bin/python3
\u5f00\u59cb\u3002
\u5185\u6838\u4f7f\u7528\u8fd9\u4e00\u884c\u6765\u67e5\u627e Python \u89e3\u91ca\u5668\uff0c\u4f46\u662f Python \u5728\u5bfc\u5165\u6a21\u5757\u65f6\u4f1a\u5ffd\u7565\u8fd9\u4e00\u884c\u3002\u56e0\u6b64\u53ea\u6709\u5728\u6253\u7b97\u76f4\u63a5\u6267\u884c\u7684\u6587\u4ef6\u4e0a\u6dfb\u52a0\u624d\u6709\u5fc5\u8981\u3002
"},{"location":"standard/style_rules/#38","title":"3.8 \u6ce8\u91ca\u548c\u6587\u6863\u5b57\u7b26\u4e32","text":"\u786e\u4fdd\u5bf9\u6a21\u5757, \u51fd\u6570, \u65b9\u6cd5\u548c\u884c\u5185\u6ce8\u91ca\u4f7f\u7528\u6b63\u786e\u7684\u98ce\u683c\u3002
"},{"location":"standard/style_rules/#381","title":"3.8.1 \u6587\u6863\u5b57\u7b26\u4e32","text":"Python \u6709\u4e00\u79cd\u72ec\u4e00\u65e0\u4e8c\u7684\u7684\u6ce8\u91ca\u65b9\u5f0f\uff1a \u4f7f\u7528\u6587\u6863\u5b57\u7b26\u4e32\u3002\u6587\u6863\u5b57\u7b26\u4e32\u662f\u5305\u3001\u6a21\u5757\u3001\u7c7b\u6216\u51fd\u6570\u91cc\u7684\u7b2c\u4e00\u4e2a\u8bed\u53e5\u3002\u8fd9\u4e9b\u5b57\u7b26\u4e32\u53ef\u4ee5\u901a\u8fc7\u5bf9\u8c61\u7684 __doc__
\u6210\u5458\u88ab\u81ea\u52a8\u63d0\u53d6\uff0c\u5e76\u4e14\u88ab pydoc
\u6240\u7528\uff08\u4f60\u53ef\u4ee5\u5728\u4f60\u7684\u6a21\u5757\u4e0a\u8fd0\u884c pydoc
\u8bd5\u4e00\u628a\uff0c\u770b\u770b\u5b83\u957f\u4ec0\u4e48\u6837\uff09\u3002 \u6211\u4eec\u5bf9\u6587\u6863\u5b57\u7b26\u4e32\u7684\u60ef\u4f8b\u662f\u4f7f\u7528\u4e09\u91cd\u53cc\u5f15\u53f7 \"\"\"
\uff08\u53c2\u89c1\uff1a PEP-257 \uff09\u3002\u4e00\u4e2a\u6587\u6863\u5b57\u7b26\u4e32\u5e94\u8be5\u8fd9\u6837\u7ec4\u7ec7\uff08\u901a\u5e38\u4e00\u884c\u4e0d\u8d85\u8fc7 80 \u4e2a\u5b57\u7b26\uff09\uff0c\u5148\u662f\u4e00\u884c\u4ee5\u53e5\u53f7\uff0c\u95ee\u53f7\u6216\u60ca\u53f9\u53f7\u7ed3\u5c3e\u7684\u6982\u8ff0\uff08\u6216\u8005\u8be5\u6587\u6863\u5b57\u7b26\u4e32\u5355\u7eaf\u53ea\u6709\u4e00\u884c\uff09\u3002\u63a5\u7740\u662f\u4e00\u4e2a\u7a7a\u884c\uff0c\u63a5\u7740\u662f\u6587\u6863\u5b57\u7b26\u4e32\u5269\u4e0b\u7684\u90e8\u5206\uff0c\u5b83\u5e94\u8be5\u4e0e\u6587\u6863\u5b57\u7b26\u4e32\u7684\u7b2c\u4e00\u884c\u7684\u7b2c\u4e00\u4e2a\u5f15\u53f7\u5bf9\u9f50\u3002\u4e0b\u9762\u6709\u66f4\u591a\u6587\u6863\u5b57\u7b26\u4e32\u7684\u683c\u5f0f\u5316\u89c4\u8303\u3002
"},{"location":"standard/style_rules/#382","title":"3.8.2 \u6a21\u5757","text":"\u6bcf\u4e2a\u6587\u4ef6\u5e94\u8be5\u5305\u542b\u4e00\u4e2a\u8bb8\u53ef\u6837\u677f\u3002\u6839\u636e\u9879\u76ee\u4f7f\u7528\u7684\u8bb8\u53ef\uff08\u4f8b\u5982\uff1aApache 2.0
\u3001BSD
\u3001LGPL
\u3001GPL
\uff09\uff0c\u9009\u62e9\u5408\u9002\u7684\u6837\u677f\u3002
\u6587\u4ef6\u5e94\u8be5\u4ee5\u63cf\u8ff0\u6a21\u5757\u5185\u5bb9\u548c\u7528\u6cd5\u7684\u6587\u6863\u5b57\u7b26\u4e32\u5f00\u59cb\u3002
\"\"\"A one line summary of the module or program, terminated by a period.\nLeave one blank line. The rest of this docstring should contain an\noverall description of the module or program. Optionally, it may also\ncontain a brief description of exported classes and functions and/or usage\nexamples.\n Typical usage example:\n foo = ClassFoo()\n bar = foo.FunctionBar()\n\"\"\"\n
\u6d4b\u8bd5\u6a21\u5757\uff0c\u6d4b\u8bd5\u6587\u4ef6\u7684\u6a21\u5757\u7ea7\u6587\u6863\u5b57\u7b26\u4e32\u4e0d\u662f\u5fc5\u987b\u7684\uff0c\u4ec5\u5f53\u53ef\u4ee5\u63d0\u4f9b\u9644\u52a0\u4fe1\u606f\u65f6\u53ef\u5305\u542b\u3002
\u793a\u4f8b\u5305\u62ec\u6709\u5173\u5982\u4f55\u8fd0\u884c\u6d4b\u8bd5\u7684\u4e00\u4e9b\u7ec6\u8282\u3001\u5bf9\u4e0d\u5bfb\u5e38\u8bbe\u7f6e\u6a21\u5f0f\u7684\u89e3\u91ca\u3001\u5bf9\u5916\u90e8\u73af\u5883\u7684\u4f9d\u8d56\u7b49\u3002
\"\"\"This blaze test uses golden files.\nYou can update those files by running\n`blaze run //foo/bar:foo_test -- --update_golden_files` from the `google3`\ndirectory.\n\"\"\"\n
\u4e0d\u5e94\u4f7f\u7528\u4e0d\u63d0\u4f9b\u4efb\u4f55\u65b0\u4fe1\u606f\u7684\u6587\u6863\u5b57\u7b26\u4e32\u3002
\"\"\"Tests for foo.bar.\"\"\"\n
"},{"location":"standard/style_rules/#383","title":"3.8.3 \u51fd\u6570\u548c\u65b9\u6cd5","text":"\u4e0b\u6587\u6240\u6307\u7684\u51fd\u6570\uff0c\u5305\u62ec\u51fd\u6570\uff0c\u65b9\u6cd5\uff0c\u751f\u6210\u5668\u4ee5\u53ca\u5c5e\u6027\u3002
\u6bcf\u4e2a\u5177\u6709\u4ee5\u4e0b\u4e00\u9879\u6216\u591a\u9879\u7279\u6027\u7684\u51fd\u6570\u90fd\u5fc5\u987b\u6709\u6587\u6863\u5b57\u7b26\u4e32\uff1a
- \u516c\u5171 API \u7684\u4e00\u90e8\u5206
- \u89c4\u6a21\u5927
- \u903b\u8f91\u590d\u6742
\u6587\u6863\u5b57\u7b26\u4e32\u5e94\u8be5\u63d0\u4f9b\u8db3\u591f\u7684\u4fe1\u606f\uff0c\u5f53\u522b\u4eba\u7f16\u5199\u4ee3\u7801\u8c03\u7528\u8be5\u51fd\u6570\u65f6\uff0c\u4ed6\u4e0d\u9700\u8981\u770b\u4e00\u884c\u4ee3\u7801\uff0c\u53ea\u8981\u770b\u6587\u6863\u5b57\u7b26\u4e32\u5c31\u53ef\u4ee5\u4e86\u3002 \u6587\u6863\u5b57\u7b26\u4e32\u5e94\u63cf\u8ff0\u51fd\u6570\u7684\u8c03\u7528\u8bed\u6cd5\u548c\u8bed\u4e49\uff0c\u4f46\u901a\u5e38\u4e0d\u63cf\u8ff0\u5176\u5b9e\u73b0\u7ec6\u8282\uff0c\u9664\u975e\u8fd9\u4e9b\u7ec6\u8282\u4e0e\u51fd\u6570\u7684\u4f7f\u7528\u65b9\u5f0f\u76f8\u5173\u3002 \u4f8b\u5982\uff0c\u4f5c\u4e3a\u526f\u4f5c\u7528\u4f1a\u6539\u53d8\u5176\u53c2\u6570\u7684\u51fd\u6570\u5e94\u5728\u5176\u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u6ce8\u660e\u8fd9\u4e00\u70b9\u3002\u5426\u5219\uff0c\u5bf9\u4e8e\u8c03\u7528\u8005\u4e0d\u76f8\u5173\u7684\u51fd\u6570\u5b9e\u73b0\u7684\u5fae\u5999\u4f46\u91cd\u8981\u7684\u7ec6\u8282\uff0c \u6700\u597d\u5c06\u5176\u8868\u8fbe\u4e3a\u4ee3\u7801\u65c1\u8fb9\u7684\u6ce8\u91ca\uff0c\u800c\u4e0d\u662f\u5728\u51fd\u6570\u7684\u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u3002
\u6587\u6863\u5b57\u7b26\u4e32\u5e94\u8be5\u662f\u63cf\u8ff0\u6027\u7684\uff08 \"\"\"Fetches rows from a Bigtable.\"\"\"
\uff09 \u6216\u8005\u547d\u4ee4\u5f0f\u7684\uff08 \"\"\"Fetch rows from a Bigtable.\"\"\"
\uff09\uff0c \u4f46\u662f\u5728\u4e00\u4e2a\u6587\u4ef6\u4e2d\uff0c\u98ce\u683c\u5e94\u8be5\u4fdd\u6301\u4e00\u76f4\u3002\u5bf9\u4e8e @property \u6570\u636e\u63cf\u8ff0\u7b26\u7684\u6587\u6863\u5b57\u7b26\u4e32\u5e94\u8be5\u4f7f\u7528\u4e0e\u5c5e\u6027\u6216\u51fd\u6570\u53c2\u6570\u7684\u6587\u6863\u5b57\u7b26\u4e32\u76f8\u540c\u7684\u98ce\u683c \uff08 \"\"\"The Bigtable path.\"\"\"
\u800c\u4e0d\u662f \"\"\"Returns the Bigtable path.\"\"\"
\uff09\u3002
\u91cd\u5199\u57fa\u7c7b\u4e2d\u7684\u65b9\u6cd5\u65f6\uff0c\u7528\u4e00\u4e2a\u7b80\u5355\u7684\u6587\u6863\u5b57\u7b26\u4e32\u5f15\u5bfc\u8bfb\u8005\u67e5\u770b\u88ab\u8986\u76d6\u65b9\u6cd5\u7684\u6587\u6863\u5b57\u7b26\u4e32\uff0c\u4f8b\u5982\uff1a \"\"\"See base class.\"\"\"
\u3002\u8fd9\u6837\u505a\u7684\u597d\u5904\u662f\uff0c\u65e0\u9700\u91cd\u590d\u57fa\u672c\u65b9\u6cd5\u4e2d\u7684\u6587\u6863\u5b57\u7b26\u4e32\u4fe1\u606f\u3002\u4f46\u662f\uff0c\u5982\u679c\u91cd\u5199\u65b9\u6cd5\u7684\u884c\u4e3a\u53d1\u751f\u4e86\u6539\u53d8\uff0c\u6216\u8005\u9700\u8981\u63d0\u4f9b\u8be6\u7ec6\u4fe1\u606f\uff08\u4f8b\u5982\uff1a\u8bb0\u5f55\u989d\u5916\u526f\u4f5c\u7528\uff09\uff0c\u90a3\u4e48\u91cd\u5199\u65b9\u6cd5\u81f3\u5c11\u9700\u8981\u901a\u8fc7\u6587\u6863\u5b57\u7b26\u4e32\u6765\u63cf\u8ff0\u8fd9\u4e9b\u5dee\u5f02\u3002
\u5173\u4e8e\u51fd\u6570\u7684\u51e0\u4e2a\u65b9\u9762\u5e94\u8be5\u5728\u7279\u5b9a\u7684\u5c0f\u8282\u4e2d\u8fdb\u884c\u63cf\u8ff0\u8bb0\u5f55\u3002\u8fd9\u51e0\u4e2a\u65b9\u9762\u5982\u4e0b\u6587\u6240\u8ff0\uff0c\u6bcf\u8282\u5e94\u8be5\u4ee5\u4e00\u4e2a\u6807\u9898\u884c\u5f00\u59cb\uff0c\u6807\u9898\u884c\u4ee5\u5192\u53f7\u7ed3\u5c3e\u3002\u9664\u6807\u9898\u884c\u5916\uff0c\u5c0f\u8282\u7684\u5176\u4ed6\u5185\u5bb9\u5e94\u88ab\u7f29\u8fdb\u4e24\u4e2a\u6216\u56db\u4e2a\u7a7a\u683c\uff08\u5728\u6587\u4ef6\u5185\u4fdd\u6301\u4e00\u81f4\uff09\u3002\u5982\u679c\u51fd\u6570\u7684\u540d\u79f0\u548c\u7b7e\u540d\u5177\u6709\u8db3\u591f\u7684\u4fe1\u606f\uff0c\u53ef\u4ee5\u4f7f\u7528\u5355\u884c\u6587\u6863\u5b57\u7b26\u4e32\u8fdb\u884c\u9002\u5f53\u63cf\u8ff0\uff0c\u90a3\u5c31\u53ef\u4ee5\u7701\u7565\u8fd9\u4e9b\u90e8\u5206\u3002
"},{"location":"standard/style_rules/#args","title":"Args:","text":"\u5217\u51fa\u6bcf\u4e2a\u53c2\u6570\u7684\u540d\u5b57\uff0c\u5728\u540d\u5b57\u540e\u4f7f\u7528\u4e00\u4e2a\u5192\u53f7\u548c\u4e00\u4e2a\u7a7a\u683c\uff0c\u5206\u9694\u5bf9\u8be5\u53c2\u6570\u7684\u63cf\u8ff0\u3002\u5982\u679c\u63cf\u8ff0\u592a\u957f\u8d85\u8fc7\u4e86\u5355\u884c80\u5b57\u7b26\uff0c\u4f7f\u75282\u6216\u80054\u4e2a\u7a7a\u683c\u7684\u60ac\u6302\u7f29\u8fdb\uff08\u4e0e\u6587\u4ef6\u5176\u4ed6\u90e8\u5206\u4fdd\u6301\u4e00\u81f4\uff09\u3002\u63cf\u8ff0\u5e94\u8be5\u5305\u62ec\u6240\u9700\u7684\u7c7b\u578b\u548c\u542b\u4e49\u3002\u5982\u679c\u4e00\u4e2a\u51fd\u6570\u63a5\u53d7 *foo
\uff08\u53ef\u53d8\u957f\u5ea6\u53c2\u6570\u5217\u8868\uff09\u6216\u8005 **bar
\uff08\u4efb\u610f\u5173\u952e\u5b57\u53c2\u6570\uff09\uff0c\u5e94\u8be5\u8be6\u7ec6\u5217\u51fa *foo
\u548c **bar
\u3002
"},{"location":"standard/style_rules/#returns-yields","title":"Returns:\uff08\u6216\u8005 Yields: \u7528\u4e8e\u751f\u6210\u5668\uff09","text":"\u8fd4\u56de\u503c\u7684\u8bed\u4e49\u5e94\u8be5\u88ab\u63cf\u8ff0\u6e05\u695a\uff0c\u5305\u62ec\u7c7b\u578b\u6ce8\u91ca\u6240\u4e0d\u80fd\u63d0\u4f9b\u7684\u4efb\u4f55\u7c7b\u578b\u4fe1\u606f\u3002 \u5982\u679c\u51fd\u6570\u53ea\u8fd4\u56de None\uff0c\u5219\u4e0d\u9700\u8981\u6b64\u90e8\u5206\u3002\u5982\u679c\u6587\u6863\u5b57\u7b26\u4e32\u4ee5 Returns
\u6216 Yields
\u5f00\u5934 \uff08\u4f8b\u5982 \"\"\"Returns row from Bigtable as a tuple of strings.\"\"\"
\uff09\uff0c\u5e76\u4e14\u5f00\u5934\u7684\u53e5\u5b50\u8db3\u4ee5\u63cf\u8ff0\u8fd4\u56de\u503c\uff0c\u5219\u53ef\u4ee5\u7701\u7565\u6b64\u90e8\u5206\u3002 \u4e0d\u8981\u6a21\u4eff\u50cf NumPy\u98ce\u683c
\uff0c\u8be5\u98ce\u683c\u901a\u5e38\u5c06\u5143\u7ec4\u8fd4\u56de\u503c\u8bb0\u5f55\u4e3a\u591a\u4e2a\u5e26\u6709\u5355\u72ec\u540d\u79f0\u7684\u8fd4\u56de\u503c\uff08\u4ece\u4e0d\u63d0\u5230\u5143\u7ec4\uff09\u3002 \u76f8\u53cd\uff0c\u5e94\u5c06\u6b64\u7c7b\u8fd4\u56de\u503c\u63cf\u8ff0\u4e3a\uff1aReturns: A tuple (mat_a, mat_b), where mat_a is \u2026, and \u2026
\u3002 \u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u7684\u8f85\u52a9\u540d\u79f0\u4e0d\u4e00\u5b9a\u9700\u8981\u4e0e\u51fd\u6570\u4f53\u4e2d\u4f7f\u7528\u7684\u4efb\u4f55\u5185\u90e8\u540d\u79f0\u76f8\u5bf9\u5e94\uff08\u56e0\u4e3a\u5b83\u4eec\u4e0d\u662f API \u7684\u4e00\u90e8\u5206\uff09\u3002
"},{"location":"standard/style_rules/#raises","title":"Raises:","text":"\u5217\u51fa\u4e0e\u63a5\u53e3\u6709\u5173\u7684\u6240\u6709\u5f02\u5e38\uff0c\u7136\u540e\u7ed9\u51fa\u8bf4\u660e\u3002\u4f7f\u7528\u7c7b\u4f3c\u7684\u5f02\u5e38\u540d\u79f0 + \u5192\u53f7 + \u7a7a\u683c\u6216\u6362\u884c\u7b26\uff0c\u5e76\u6309 Args\uff1a
\u4e2d\u6240\u8ff0\u60ac\u6302\u7f29\u8fdb\u6837\u5f0f\u3002\u5982\u679c\u8fdd\u53cd\u4e86\u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u6307\u5b9a\u7684 API\uff0c\u5219\u4e0d\u5e94\u8be5\u8bb0\u5f55\u5f15\u53d1\u7684\u5f02\u5e38\uff08\u56e0\u4e3a\u8fd9\u4f1a\u4f7f\u8fdd\u53cd API \u7684\u884c\u4e3a\u6210\u4e3a API \u7684\u4e00\u90e8\u5206\uff09\u3002
def fetch_smalltable_rows(\ntable_handle: smalltable.Table,\nkeys: Sequence[bytes | str],\nrequire_all_keys: bool = False,\n) -> Mapping[bytes, tuple[str, ...]]:\n\"\"\"Fetches rows from a Smalltable.\n Retrieves rows pertaining to the given keys from the Table instance\n represented by table_handle. String keys will be UTF-8 encoded.\n Args:\n table_handle: An open smalltable.Table instance.\n keys: A sequence of strings representing the key of each table\n row to fetch. String keys will be UTF-8 encoded.\n require_all_keys: If True only rows with values set for all keys will be\n returned.\n Returns:\n A dict mapping keys to the corresponding table row data\n fetched. Each row is represented as a tuple of strings. For\n example:\n {b'Serak': ('Rigel VII', 'Preparer'),\n b'Zim': ('Irk', 'Invader'),\n b'Lrrr': ('Omicron Persei 8', 'Emperor')}\n Returned keys are always bytes. If a key from the keys argument is\n missing from the dictionary, then that row was not found in the\n table (and require_all_keys must have been False).\n Raises:\n IOError: An error occurred accessing the smalltable.\n \"\"\"\n
\u5982\u4e0b\u6240\u793a\uff0c Args \u4e2d\u53c2\u6570\u6362\u884c\u4e5f\u662f\u5141\u8bb8\u7684\uff1a
def fetch_smalltable_rows(\ntable_handle: smalltable.Table,\nkeys: Sequence[bytes | str],\nrequire_all_keys: bool = False,\n) -> Mapping[bytes, tuple[str, ...]]:\n\"\"\"Fetches rows from a Smalltable.\n Retrieves rows pertaining to the given keys from the Table instance\n represented by table_handle. String keys will be UTF-8 encoded.\n Args:\n table_handle:\n An open smalltable.Table instance.\n keys:\n A sequence of strings representing the key of each table row to\n fetch. String keys will be UTF-8 encoded.\n require_all_keys:\n If True only rows with values set for all keys will be returned.\n Returns:\n A dict mapping keys to the corresponding table row data\n fetched. Each row is represented as a tuple of strings. For\n example:\n {b'Serak': ('Rigel VII', 'Preparer'),\n b'Zim': ('Irk', 'Invader'),\n b'Lrrr': ('Omicron Persei 8', 'Emperor')}\n Returned keys are always bytes. If a key from the keys argument is\n missing from the dictionary, then that row was not found in the\n table (and require_all_keys must have been False).\n Raises:\n IOError: An error occurred accessing the smalltable.\n \"\"\"\n
"},{"location":"standard/style_rules/#384","title":"3.8.4 \u7c7b","text":"\u7c7b\u5e94\u8be5\u5728\u5176\u5b9a\u4e49\u4e0b\u6709\u4e00\u4e2a\u7528\u4e8e\u63cf\u8ff0\u8be5\u7c7b\u7684\u6587\u6863\u5b57\u7b26\u4e32\u3002\u5982\u679c\u4f60\u7684\u7c7b\u6709\u516c\u5171\u5c5e\u6027\uff08Attributes
\uff09\uff0c\u90a3\u4e48\u6587\u6863\u4e2d\u5e94\u8be5\u6709\u4e00\u4e2a\u5c5e\u6027\uff08Attributes
\uff09\u6bb5\uff0c\u5e76\u4e14\u5e94\u8be5\u9075\u5b88\u548c\u51fd\u6570\u53c2\u6570\u76f8\u540c\u7684\u683c\u5f0f\uff1a
class SampleClass:\n\"\"\"Summary of class here.\n Longer class information...\n Longer class information...\n Attributes:\n likes_spam: A boolean indicating if we like SPAM or not.\n eggs: An integer count of the eggs we have laid.\n \"\"\"\ndef __init__(self, likes_spam: bool = False):\n\"\"\"Initializes the instance based on spam preference.\n Args:\n likes_spam: Defines if instance exhibits this preference.\n \"\"\"\nself.likes_spam = likes_spam\nself.eggs = 0\ndef public_method(self):\n\"\"\"Performs operation blah.\"\"\"\n
\u6240\u6709\u7c7b\u6587\u6863\u5b57\u7b26\u4e32\u90fd\u5e94\u4ee5\u4e00\u884c\u6458\u8981\u5f00\u5934\uff0c\u63cf\u8ff0\u7c7b\u5b9e\u4f8b\u6240\u4ee3\u8868\u7684\u5185\u5bb9\u3002\u8fd9\u610f\u5473\u7740 Exception \u7684\u5b50\u7c7b\u8fd8\u5e94\u8be5\u63cf\u8ff0\u5f02\u5e38\u4ee3\u8868\u4ec0\u4e48\uff0c\u800c\u4e0d\u662f\u5b83\u53ef\u80fd\u53d1\u751f\u7684\u4e0a\u4e0b\u6587\u3002 \u7c7b\u6587\u6863\u5b57\u7b26\u4e32\u4e0d\u5e94\u91cd\u590d\u4e0d\u5fc5\u8981\u7684\u4fe1\u606f\uff0c\u4f8b\u5982\u8be5\u7c7b\u662f\u4e00\u4e2a\u7c7b\u3002
\u63a8\u8350
class CheeseShopAddress:\n\"\"\"The address of a cheese shop.\n ...\n \"\"\"\nclass OutOfCheeseError(Exception):\n\"\"\"No more cheese is available.\"\"\"\n
!!! fail \"\u4e0d\u63a8\u8350
```python\nclass CheeseShopAddress:\n \"\"\"Class that describes the address of a cheese shop.\n\n ...\n \"\"\"\n\nclass OutOfCheeseError(Exception):\n \"\"\"Raised when no more cheese is available.\"\"\"\n```\n
"},{"location":"standard/style_rules/#385","title":"3.8.5 \u5757\u6ce8\u91ca\u548c\u884c\u6ce8\u91ca","text":"\u6700\u9700\u8981\u5199\u6ce8\u91ca\u7684\u662f\u4ee3\u7801\u4e2d\u90a3\u4e9b\u6280\u5de7\u6027\u7684\u90e8\u5206\u3002\u5982\u679c\u4f60\u5728\u4e0b\u6b21\u4ee3\u7801\u5ba1\u67e5 \u7684\u65f6\u5019\u5fc5\u987b\u89e3\u91ca\u4e00\u4e0b\uff0c\u90a3\u4e48\u4f60\u5e94\u8be5\u73b0\u5728\u5c31\u7ed9\u5b83\u5199\u6ce8\u91ca\u3002\u5bf9\u4e8e\u590d\u6742\u7684\u64cd\u4f5c\uff0c\u5e94\u8be5\u5728\u5176\u64cd\u4f5c\u5f00\u59cb\u524d\u5199\u4e0a\u82e5\u5e72\u884c\u6ce8\u91ca\uff0c\u5bf9\u4e8e\u4e0d\u662f\u4e00\u76ee\u4e86\u7136\u7684\u4ee3\u7801\uff0c\u5e94\u5728\u5176\u884c\u5c3e\u6dfb\u52a0\u6ce8\u91ca\u3002
# We use a weighted dictionary search to find out where i is in\n# the array. We extrapolate position based on the largest num\n# in the array and the array size and then do binary search to\n# get the exact number.\nif i & (i - 1) == 0: # True if i is 0 or a power of 2.\n
\u4e3a\u4e86\u63d0\u9ad8\u53ef\u8bfb\u6027\uff0c\u6ce8\u91ca\u5b57\u7b26 #
\u5e94\u8be5\u81f3\u5c11\u79bb\u5f00\u4ee3\u7801\u4e24\u4e2a\u7a7a\u683c\uff0c\u7136\u540e\u5728\u6ce8\u91ca\u672c\u8eab\u7684\u6587\u672c\u4e4b\u524d\u81f3\u5c11\u6709\u4e00\u4e2a\u7a7a\u683c\u3002
\u53e6\u4e00\u65b9\u9762\uff0c\u7edd\u4e0d\u8981\u63cf\u8ff0\u4ee3\u7801\u3002\u5047\u8bbe\u9605\u8bfb\u4ee3\u7801\u7684\u4eba\u6bd4\u4f60\u66f4\u61c2 Python\uff0c\u4ed6\u53ea\u662f\u4e0d\u77e5\u9053\u4f60\u7684\u4ee3\u7801\u8981\u505a\u4ec0\u4e48\u3002
# BAD COMMENT: Now go through the b array and make sure whenever i occurs\n# the next element is i+1\n
"},{"location":"standard/style_rules/#386","title":"3.8.6 \u6807\u70b9\u7b26\u53f7\u3001\u62fc\u5199\u548c\u8bed\u6cd5","text":"\u6ce8\u610f\u6807\u70b9\u7b26\u53f7\u3001\u62fc\u5199\u548c\u8bed\u6cd5\u3002\u597d\u7684\u6ce8\u91ca\u66f4\u5bb9\u6613\u9605\u8bfb\u3002
\u6ce8\u91ca\u5e94\u8be5\u50cf\u53d9\u4e8b\u6587\u672c\u4e00\u6837\u53ef\u8bfb\uff0c\u6709\u9002\u5f53\u7684\u5927\u5199\u548c\u6807\u70b9\u7b26\u53f7\u3002\u5728\u8bb8\u591a\u60c5\u51b5\u4e0b\uff0c\u5b8c\u6574\u7684\u53e5\u5b50\u6bd4\u53e5\u5b50\u7247\u6bb5\u66f4\u5177\u53ef\u8bfb\u6027\u3002\u8f83\u77ed\u7684\u6ce8\u91ca\uff0c\u4f8b\u5982\u4ee3\u7801\u884c\u672b\u5c3e\u7684\u6ce8\u91ca\uff0c\u6709\u65f6\u53ef\u80fd\u4e0d\u90a3\u4e48\u6b63\u5f0f\uff0c\u4f46\u5e94\u8be5\u4e0e\u4f60\u7684\u98ce\u683c\u4fdd\u6301\u4e00\u81f4\u3002
\u867d\u7136\u88ab\u4ee3\u7801\u5ba1\u9605\u8005\u6307\u51fa\u6807\u70b9\u7b26\u53f7\u4f7f\u7528\u4e0d\u51c6\u786e\uff08\u5728\u7528\u5206\u53f7\u7684\u5730\u65b9\u7528\u4e86\u9017\u53f7\uff09\u7684\u611f\u89c9\u4f1a\u5f88\u4e0d\u723d\uff0c\u4f46\u6e90\u4ee3\u7801\u4fdd\u6301\u9ad8\u5ea6\u7684\u6e05\u6670\u6027\u548c\u53ef\u8bfb\u6027\u662f\u975e\u5e38\u91cd\u8981\u7684\u3002\u6b63\u786e\u7684\u6807\u70b9\u3001\u62fc\u5199\u548c\u8bed\u6cd5\u6709\u52a9\u4e8e\u5b9e\u73b0\u8fd9\u4e00\u76ee\u6807\u3002
"},{"location":"standard/style_rules/#310","title":"3.10 \u5b57\u7b26\u4e32","text":"\u5373\u4f7f\u53c2\u6570\u90fd\u662f\u5b57\u7b26\u4e32\uff0c\u4e5f\u8981\u4f7f\u7528 f-string\uff0c %
\u64cd\u4f5c\u7b26\u6216\u8005 format
\u65b9\u6cd5\u683c\u5f0f\u5316\u5b57\u7b26\u4e32\u3002\u4e0d\u8fc7\u4e5f\u4e0d\u80fd\u4e00\u6982\u800c\u8bba\uff0c\u4f60\u9700\u8981\u5728 +
\u548c %
\uff08\u6216 format
\uff09\u4e4b\u95f4\u597d\u597d\u5224\u5b9a\u3002\u4e0d\u8981\u5c06 %
\u6216 format
\u65b9\u6cd5\u7528\u4e8e\u7eaf\u8fde\u63a5\u3002
\u63a8\u8350
x = f'name: {name}; score: {n}'\nx = '%s, %s!' % (imperative, expletive)\nx = '{}, {}'.format(first, second)\nx = 'name: %s; score: %d' % (name, n)\nx = 'name: %(name)s; score: %(score)d' % {'name':name, 'score':n}\nx = 'name: {}; score: {}'.format(name, n)\nx = a + b\n
\u4e0d\u63a8\u8350
x = first + ', ' + second\nx = 'name: ' + name + '; score: ' + str(n)\n
\u907f\u514d\u5728\u5faa\u73af\u4e2d\u7528 +
\u548c +=
\u64cd\u4f5c\u7b26\u6765\u7d2f\u52a0\u5b57\u7b26\u4e32\u3002
\u7531\u4e8e\u5b57\u7b26\u4e32\u662f\u4e0d\u53ef\u53d8\u7684\uff0c\u8fd9\u6837\u505a\u4f1a\u521b\u5efa\u4e0d\u5fc5\u8981\u7684\u4e34\u65f6\u5bf9\u8c61\uff0c\u4e14\u5bfc\u81f4\u4e8c\u6b21\u65b9\u800c\u4e0d\u662f\u7ebf\u6027\u7684\u8fd0\u884c\u65f6\u95f4\u3002\u5c3d\u7ba1\u8fd9\u79cd\u5e38\u89c1\u7684\u7d2f\u52a0\u53ef\u4ee5\u5728 CPython \u4e0a\u8fdb\u884c\u4f18\u5316\uff0c\u4f46\u8fd9\u662f\u4e00\u4e2a\u5b9e\u73b0\u7ec6\u8282\u3002\u5e94\u7528\u4f18\u5316\u7684\u6761\u4ef6\u4e0d\u5bb9\u6613\u9884\u6d4b\uff0c\u5e76\u4e14\u53ef\u80fd\u4f1a\u6539\u53d8\u3002\u4f5c\u4e3a\u66ff\u4ee3\u65b9\u6848\uff0c\u4f60\u53ef\u4ee5\u5c06\u6bcf\u4e2a\u5b50\u4e32\u52a0\u5165\u5217\u8868\uff0c\u7136\u540e\u5728\u5faa\u73af\u7ed3\u675f\u540e\u7528 ''.join
\u8fde\u63a5\u5217\u8868\uff08\u4e5f\u53ef\u4ee5\u5c06\u6bcf\u4e2a\u5b50\u4e32\u5199\u5165\u4e00\u4e2a io.StringIO
\u7f13\u5b58\u4e2d\uff09\u3002
\u63a8\u8350
items = ['<table>']\nfor last_name, first_name in employee_list:\nitems.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))\nitems.append('</table>')\nemployee_table = ''.join(items)\n
\u4e0d\u63a8\u8350
employee_table = '<table>'\nfor last_name, first_name in employee_list:\nemployee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)\nemployee_table += '</table>'\n
\u5728\u540c\u4e00\u4e2a\u6587\u4ef6\u4e2d\uff0c\u4fdd\u6301\u4f7f\u7528\u5b57\u7b26\u4e32\u5f15\u53f7\u7684\u4e00\u81f4\u6027\u3002\u4f7f\u7528\u5355\u5f15\u53f7 '
\u6216\u8005\u53cc\u5f15\u53f7 \"
\u4e4b\u4e00\u7528\u4ee5\u5f15\u7528\u5b57\u7b26\u4e32\uff0c\u5e76\u5728\u540c\u4e00\u6587\u4ef6\u4e2d\u6cbf\u7528\u3002\u5728\u5b57\u7b26\u4e32\u5185\u53ef\u4ee5\u4f7f\u7528\u53e6\u5916\u4e00\u79cd\u5f15\u53f7\uff0c\u4ee5\u907f\u514d\u5728\u5b57\u7b26\u4e32\u4e2d\u4f7f\u7528 \\\\
\u8f6c\u4e49\u3002
\u63a8\u8350
Python('Why are you hiding your eyes?')\nGollum(\"I'm scared of lint errors.\")\nNarrator('\"Good!\" thought a happy Python reviewer.')\n
\u4e0d\u63a8\u8350
Python(\"Why are you hiding your eyes?\")\nGollum('The lint. It burns. It burns us.')\nGollum(\"Always the great lint. Watching. Watching.\")\n
\u4e3a\u591a\u884c\u5b57\u7b26\u4e32\u4f7f\u7528\u4e09\u91cd\u53cc\u5f15\u53f7 \"\"\"
\u800c\u975e\u4e09\u91cd\u5355\u5f15\u53f7 '''
\u3002\u5f53\u4e14\u4ec5\u5f53\u9879\u76ee\u4e2d\u4f7f\u7528\u5355\u5f15\u53f7 '
\u6765\u5f15\u7528\u5b57\u7b26\u4e32\u65f6\uff0c\u624d\u53ef\u80fd\u4f1a\u4f7f\u7528\u4e09\u91cd '''
\u4e3a\u975e\u6587\u6863\u5b57\u7b26\u4e32\u7684\u591a\u884c\u5b57\u7b26\u4e32\u6765\u6807\u8bc6\u5f15\u7528\u3002\u6587\u6863\u5b57\u7b26\u4e32\u5fc5\u987b\u4f7f\u7528\u4e09\u91cd\u53cc\u5f15\u53f7 \"\"\"
\u3002
\u591a\u884c\u5b57\u7b26\u4e32\u4e0d\u4f1a\u968f\u7a0b\u5e8f\u5176\u4f59\u90e8\u5206\u7684\u7f29\u8fdb\u800c\u7f29\u8fdb\u3002\u5982\u679c\u8981\u907f\u514d\u5728\u5b57\u7b26\u4e32\u4e2d\u5d4c\u5165\u989d\u5916\u7684\u7a7a\u767d\uff0c\u53ef\u4ee5\u4f7f\u7528\u4e32\u8054\u7684\u5355\u884c\u5b57\u7b26\u4e32\u6216\u5e26\u6709 textwrap.dedent()
\u7684\u591a\u884c\u5b57\u7b26\u4e32\u6765\u5220\u9664\u6bcf\u884c\u4e0a\u7684\u521d\u59cb\u7a7a\u767d\u3002
\u63a8\u8350
long_string = \"\"\"This is fine if your use case can accept\n extraneous leading spaces.\"\"\"\n
long_string = (\"And this is fine if you cannot accept\\n\" +\n\"extraneous leading spaces.\")\n
long_string = textwrap.dedent(\"\"\"\\\n This is also fine, because textwrap.dedent()\n will collapse common leading spaces in each line.\"\"\")\n
import textwrap\nlong_string = textwrap.dedent(\"\"\"\\\n This is also fine, because textwrap.dedent()\n will collapse common leading spaces in each line.\"\"\")\n
\u4e0d\u63a8\u8350
long_string = \"\"\"This is pretty ugly.\nDon't do this.\n\"\"\"\n
\u8bf7\u6ce8\u610f\uff0c\u6b64\u5904\u4f7f\u7528\u53cd\u659c\u6760\u5e76\u4e0d\u8fdd\u53cd\u7981\u6b62\u663e\u5f0f\u7eed\u884c\u7684\u89c4\u5b9a\uff1b\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u53cd\u659c\u6760\u6b63\u5728\u8f6c\u4e49\u5b57\u7b26\u4e32\u6587\u5b57\u4e2d\u7684\u6362\u884c\u7b26\u3002
"},{"location":"standard/style_rules/#3101","title":"3.10.1 \u65e5\u5fd7","text":"\u5bf9\u4e8e\u671f\u671b\u4ee5\u6a21\u5f0f\u5b57\u7b26\u4e32\uff08\u5e26\u6709 %
- \u5360\u4f4d\u7b26\uff09\u4f5c\u4e3a\u7b2c\u4e00\u4e2a\u53c2\u6570\u7684\u65e5\u5fd7\u51fd\u6570\uff1a\u59cb\u7ec8\u4f7f\u7528\u5b57\u7b26\u4e32\u6587\u672c\uff08\u800c\u4e0d\u662f f-string
\uff09\u4f5c\u4e3a\u5b83\u4eec\u7684\u7b2c\u4e00\u4e2a\u53c2\u6570\uff0c\u5e76\u4f7f\u7528\u6a21\u5f0f\u53c2\u6570\uff08pattern-parameters\uff09\u4f5c\u4e3a\u540e\u7eed\u53c2\u6570\u3002\u4e00\u4e9b\u65e5\u5fd7\u5b9e\u73b0\u5c06\u672a\u5c55\u5f00\u7684\u6a21\u5f0f\u5b57\u7b26\u4e32\u6536\u96c6\u4e3a\u53ef\u67e5\u8be2\u5b57\u6bb5\u3002\u5b83\u8fd8\u9632\u6b62\u82b1\u8d39\u65f6\u95f4\u5448\u73b0\u6ca1\u6709\u914d\u7f6e\u8bb0\u5f55\u5668\u8f93\u51fa\u7684\u6d88\u606f\u3002
\u63a8\u8350
import tensorflow as tf\nlogger = tf.get_logger()\nlogger.info('TensorFlow Version is: %s', tf.__version__)\n
\u63a8\u8350
import os\nfrom absl import logging\nlogging.info('Current $PAGER is: %s', os.getenv('PAGER', default=''))\nhomedir = os.getenv('HOME')\nif homedir is None or not os.access(homedir, os.W_OK):\nlogging.error('Cannot write to home directory, $HOME=%r', homedir)\n
\u4e0d\u63a8\u8350
import os\nfrom absl import logging\nlogging.info('Current $PAGER is:')\nlogging.info(os.getenv('PAGER', default=''))\nhomedir = os.getenv('HOME')\nif homedir is None or not os.access(homedir, os.W_OK):\nlogging.error(f'Cannot write to home directory, $HOME={homedir!r}')\n
"},{"location":"standard/style_rules/#3102","title":"3.10.2 \u9519\u8bef\u6d88\u606f","text":"\u9519\u8bef\u6d88\u606f\uff08\u4f8b\u5982\uff1aValueError
\u7b49\u5f02\u5e38\u7684\u6d88\u606f\u5b57\u7b26\u4e32\uff0c\u6216\u663e\u793a\u7ed9\u7528\u6237\u7684\u6d88\u606f\uff09\u5e94\u9075\u5faa\u4e09\u4e2a\u51c6\u5219\uff1a
- \u6d88\u606f\u9700\u8981\u4e0e\u5b9e\u9645\u9519\u8bef\u6761\u4ef6\u7cbe\u786e\u5339\u914d\u3002
- \u63d2\u5165\u7684\u7247\u6bb5\u5fc5\u987b\u59cb\u7ec8\u80fd\u591f\u6e05\u695a\u5730\u8bc6\u522b\u3002
- \u5b83\u4eec\u5e94\u8be5\u5141\u8bb8\u7b80\u5355\u7684\u81ea\u52a8\u5316\u5904\u7406\uff08\u4f8b\u5982
grepping
\uff09\u3002
\u63a8\u8350
if not 0 <= p <= 1:\nraise ValueError(f'Not a probability: {p!r}')\ntry:\nos.rmdir(workdir)\nexcept OSError as error:\nlogging.warning('Could not remove directory (reason: %r): %r',\nerror, workdir)\n
\u4e0d\u63a8\u8350
if p < 0 or p > 1: # PROBLEM: also false for float('nan')!\nraise ValueError(f'Not a probability: {p!r}')\ntry:\nos.rmdir(workdir)\nexcept OSError:\n# PROBLEM: Message makes an assumption that might not be true:\n# Deletion might have failed for some other reason, misleading\n# whoever has to debug this.\nlogging.warning('Directory already was deleted: %s', workdir)\ntry:\nos.rmdir(workdir)\nexcept OSError:\n# PROBLEM: The message is harder to grep for than necessary, and\n# not universally non-confusing for all possible values of `workdir`.\n# Imagine someone calling a library function with such code\n# using a name such as workdir = 'deleted'. The warning would read:\n# \"The deleted directory could not be deleted.\"\nlogging.warning('The %s directory could not be deleted.', workdir)\n
"},{"location":"standard/style_rules/#311-sockets","title":"3.11 \u6587\u4ef6,Sockets\u548c\u7c7b\u4f3c\u7684\u72b6\u6001\u8d44\u6e90","text":"\u5728\u6587\u4ef6\u548c sockets \u7ed3\u675f\u65f6\uff0c\u663e\u5f0f\u7684\u5173\u95ed\u5b83\u3002 \u6b64\u89c4\u5219\u81ea\u7136\u6269\u5c55\u5230\u5185\u90e8\u4f7f\u7528\u5957\u63a5\u5b57\u7684\u53ef\u5173\u95ed\u8d44\u6e90\uff0c\u4f8b\u5982\u6570\u636e\u5e93\u8fde\u63a5\uff0c\u4ee5\u53ca\u9700\u8981\u4ee5\u7c7b\u4f3c\u65b9\u5f0f\u5173\u95ed\u7684\u5176\u4ed6\u8d44\u6e90\u3002\u4ec5\u4e3e\u51e0\u4e2a\u4f8b\u5b50\uff0c \u8fd9\u8fd8\u5305\u62ec mmap mappings, h5py File objects\u548c matplotlib.pyplot figure windows\u3002
\u9664\u6587\u4ef6\u5916\uff0csockets \u6216\u5176\u4ed6\u7c7b\u4f3c\u6587\u4ef6\u7684\u5bf9\u8c61\u5728\u6ca1\u6709\u5fc5\u8981\u7684\u60c5\u51b5\u4e0b\u6253\u5f00\uff0c\u4f1a\u6709\u8bb8\u591a\u526f\u4f5c\u7528\uff0c\u4f8b\u5982\uff1a
- \u5b83\u4eec\u53ef\u80fd\u4f1a\u6d88\u8017\u6709\u9650\u7684\u7cfb\u7edf\u8d44\u6e90\u3002\u5982\u6587\u4ef6\u63cf\u8ff0\u7b26\u3002\u5982\u679c\u8fd9\u4e9b\u8d44\u6e90\u5728\u4f7f\u7528\u540e\u6ca1\u6709\u53ca\u65f6\u5f52\u8fd8\u7cfb\u7edf\uff0c\u90a3\u4e48\u7528\u4e8e\u5904\u7406\u8fd9\u4e9b\u5bf9\u8c61\u7684\u4ee3\u7801\u4f1a\u5c06\u8d44\u6e90\u6d88\u8017\u6b86\u5c3d\u3002
- \u6301\u6709\u6587\u4ef6\u5c06\u4f1a\u963b\u6b62\u5bf9\u4e8e\u6587\u4ef6\u7684\u5176\u4ed6\u8bf8\u5982\u79fb\u52a8\u3001\u5220\u9664\u4e4b\u7c7b\u7684\u64cd\u4f5c\u3002
- \u4ec5\u4ec5\u662f\u4ece\u903b\u8f91\u4e0a\u5173\u95ed\u6587\u4ef6\u548c Sockets\uff0c\u90a3\u4e48\u5b83\u4eec\u4ecd\u7136\u53ef\u80fd\u4f1a\u88ab\u5176\u5171\u4eab\u7684\u7a0b\u5e8f\u5728\u65e0\u610f\u4e2d\u8fdb\u884c\u8bfb\u6216\u8005\u5199\u64cd\u4f5c\u3002\u53ea\u6709\u5f53\u5b83\u4eec\u771f\u6b63\u88ab\u5173\u95ed\u540e\uff0c\u5bf9\u4e8e\u5b83\u4eec\u5c1d\u8bd5\u8fdb\u884c\u8bfb\u6216\u8005\u5199\u64cd\u4f5c\u5c06\u4f1a\u629b\u51fa\u5f02\u5e38\uff0c\u5e76\u4f7f\u5f97\u95ee\u9898\u5feb\u901f\u663e\u73b0\u51fa\u6765\u3002
\u800c\u4e14\uff0c\u5e7b\u60f3\u5f53\u6587\u4ef6\u5bf9\u8c61\u6790\u6784\u65f6\uff0c\u6587\u4ef6\u548c sockets \u4f1a\u81ea\u52a8\u5173\u95ed\uff0c \u8bd5\u56fe\u5c06\u6587\u4ef6\u5bf9\u8c61\u7684\u751f\u547d\u5468\u671f\u548c\u6587\u4ef6\u7684\u72b6\u6001\u7ed1\u5b9a\u5728\u4e00\u8d77\u7684\u60f3\u6cd5\uff0c\u90fd\u662f\u4e0d\u73b0\u5b9e\u7684\u3002\u56e0\u4e3a\u6709\u5982\u4e0b\u539f\u56e0\uff1a
- \u6ca1\u6709\u4efb\u4f55\u65b9\u6cd5\u53ef\u4ee5\u786e\u4fdd\u8fd0\u884c\u73af\u5883\u4f1a\u771f\u6b63\u7684\u6267\u884c\u6587\u4ef6\u7684\u6790\u6784\u3002\u4e0d\u540c\u7684 Python \u5b9e\u73b0\u91c7\u7528\u4e0d\u540c\u7684\u5185\u5b58\u7ba1\u7406\u6280\u672f\uff0c\u6bd4\u5982\u5ef6\u65f6\u5783\u573e\u5904\u7406\u673a\u5236\u3002\u5ef6\u65f6\u5783\u573e\u5904\u7406\u673a\u5236\u53ef\u80fd\u4f1a\u5bfc\u81f4\u5bf9\u8c61\u751f\u547d\u5468\u671f\u88ab\u4efb\u610f\u65e0\u9650\u5236\u7684\u5ef6\u957f\u3002
- \u5bf9\u4e8e\u6587\u4ef6\u610f\u5916\u7684\u5f15\u7528\uff0c\u4f1a\u5bfc\u81f4\u5bf9\u4e8e\u6587\u4ef6\u7684\u6301\u6709\u65f6\u95f4\u8d85\u51fa\u9884\u671f\uff08\u6bd4\u5982\u5bf9\u4e8e\u5f02\u5e38\u7684\u8ddf\u8e2a\uff0c\u5305\u542b\u6709\u5168\u5c40\u53d8\u91cf\u7b49\uff09\u3002
\u591a\u6b21\u53d1\u73b0\uff0c\u4f9d\u8d56\u4e8e\u81ea\u52a8\u6e05\u7406\u673a\u5236\u5728\u8fd1\u51e0\u5341\u5e74\u5185\u7684\u591a\u4e2a\u8bed\u8a00\u4e2d\u5bfc\u81f4\u4e86\u91cd\u5927\u95ee\u9898\uff08\u4f8b\u5982: java\u4e2d\u7684 this article\uff09
\u7ba1\u7406\u6587\u4ef6\u7684\u9996\u9009\u65b9\u6cd5\u662f\u4f7f\u7528 with
\u8bed\u53e5\uff1a
with open(\"hello.txt\") as hello_file:\nfor line in hello_file:\nprint(line)\n
\u5bf9\u4e8e\u4e0d\u652f\u6301 with
\u8bed\u53e5\u7684\u7c7b\u6587\u4ef6\u5bf9\u8c61\uff0c\u8bf7\u4f7f\u7528 contextlib.closing()
\uff1a
import contextlib\nwith contextlib.closing(urllib.urlopen(\"http://www.python.org/\")) as front_page:\nfor line in front_page:\nprint(line)\n
"},{"location":"standard/style_rules/#312-todo","title":"3.12 TODO \u6ce8\u91ca","text":"\u4e3a\u4e34\u65f6\u4ee3\u7801\u4f7f\u7528 TODO
\u6ce8\u91ca\uff0c\u5b83\u662f\u4e00\u79cd\u77ed\u671f\u89e3\u51b3\u65b9\u6848\uff0c\u4e0d\u7b97\u5b8c\u7f8e\uff0c\u4f46\u591f\u597d\u4e86\u3002
TODO
\u6ce8\u91ca\u5e94\u8be5\u5728\u6240\u6709\u5f00\u5934\u5904\u5305\u542b TODO
\u5b57\u7b26\u4e32\uff0c\u7d27\u8ddf\u7740\u662f\u7528\u62ec\u53f7\u62ec\u8d77\u6765\u7684\u4f60\u7684\u540d\u5b57\uff0c\u90ae\u7bb1\u5730\u5740\u6216\u5176\u5b83\u6807\u8bc6\u7b26\u3002\u7136\u540e\u662f\u4e00\u4e2a\u53ef\u9009\u7684\u5192\u53f7\u3002\u63a5\u7740\u5fc5\u987b\u6709\u4e00\u884c\u6ce8\u91ca\uff0c\u89e3\u91ca\u8981\u505a\u4ec0\u4e48\u3002
\u4e3b\u8981\u76ee\u7684\u662f\u4e3a\u4e86\u6709\u4e00\u4e2a\u7edf\u4e00\u7684 TODO
\u683c\u5f0f\uff0c\u8fd9\u6837\u6dfb\u52a0\u6ce8\u91ca\u7684\u4eba\u5c31\u53ef\u4ee5\u641c\u7d22\u5230\uff08\u5e76\u53ef\u4ee5\u6309\u9700\u63d0\u4f9b\u66f4\u591a\u7ec6\u8282\uff09\u3002\u5199\u4e86 TODO
\u6ce8\u91ca\u5e76\u4e0d\u4fdd\u8bc1\u5199\u7684\u4eba\u4f1a\u4eb2\u81ea\u89e3\u51b3\u95ee\u9898\u3002\u5f53\u4f60\u5199\u4e86\u4e00\u4e2a TODO
\uff0c\u8bf7\u6ce8\u4e0a\u4f60\u7684\u540d\u5b57\u3002
# TODO(crbug.com/192795): Investigate cpufreq optimizations.\n# TODO(yourusername): File an issue and use a '*' for repetition.\n
\u5982\u679c\u4f60\u7684 TODO
\u662f \u201c\u5c06\u6765\u505a\u67d0\u4e8b\u201d \u7684\u5f62\u5f0f\uff0c\u90a3\u4e48\u8bf7\u786e\u4fdd\u4f60\u5305\u542b\u4e86\u4e00\u4e2a\u6307\u5b9a\u7684\u65e5\u671f\uff082009\u5e7411\u6708\u89e3\u51b3\uff09\u6216\u8005\u4e00\u4e2a\u7279\u5b9a\u7684\u4e8b\u4ef6\uff08\u7b49\u5230\u6240\u6709\u7684\u5ba2\u6237\u90fd\u53ef\u4ee5\u5904\u7406 XML \u8bf7\u6c42\u5c31\u79fb\u9664\u8fd9\u4e9b\u4ee3\u7801\uff09\u3002
"},{"location":"standard/style_rules/#313","title":"3.13 \u5bfc\u5165\u683c\u5f0f","text":"\u6bcf\u4e2a\u5bfc\u5165\u5e94\u8be5\u72ec\u5360\u4e00\u884c\uff0ctyping
\u5bfc\u5165\u662f\u4e2a\u4f8b\u5916\u3002
\u63a8\u8350
from collections.abc import Mapping, Sequence\nimport os\nimport sys\nfrom typing import Any, NewType\n
\u4e0d\u63a8\u8350
import os, sys\n
\u5bfc\u5165\u603b\u5e94\u8be5\u653e\u5728\u6587\u4ef6\u9876\u90e8\uff0c\u4f4d\u4e8e\u6a21\u5757\u6ce8\u91ca\u548c\u6587\u6863\u5b57\u7b26\u4e32\u4e4b\u540e\uff0c\u6a21\u5757\u5168\u5c40\u53d8\u91cf\u548c\u5e38\u91cf\u4e4b\u524d\u3002\u5bfc\u5165\u5e94\u8be5\u6309\u7167\u4ece\u6700\u901a\u7528\u5230\u6700\u4e0d\u901a\u7528\u7684\u987a\u5e8f\u5206\u7ec4\uff1a
-
Future \u5bfc\u5165\u8bed\u53e5\uff1a
from __future__ import annotations\n
\u8bf7\u53c2\u9605\u4e0a\u9762\u7684\u66f4\u591a\u4fe1\u606f\u3002
-
\u6807\u51c6\u5e93\u5bfc\u5165\uff1a
import sys\n
-
\u7b2c\u4e09\u65b9\u6a21\u5757\u6216\u5305\u5bfc\u5165\uff1a
import tensorflow as tf\n
-
\u4ee3\u7801\u5e93\u5b50\u5305\u5bfc\u5165\uff1a
from otherproject.ai import mind\n
-
\u5df2\u5f03\u7528\uff1a \u4e0e\u6b64\u6587\u4ef6\u5c5e\u4e8e\u540c\u4e00\u9876\u7ea7\u5b50\u5305\u7684\u5e94\u7528\u7a0b\u5e8f\u7279\u5b9a\u5bfc\u5165\u3002\u4f8b\u5982\uff1a
from myproject.backend.hgwells import time_machine\n
\u60a8\u53ef\u80fd\u4f1a\u53d1\u73b0\u4e4b\u524d\u7684 Google Python \u98ce\u683c\u662f\u8fd9\u4e48\u505a\u7684\uff0c\u4f46\u73b0\u5728\u5df2\u7ecf\u4e0d\u63a8\u8350\u4e86\u3002\u65b0\u7684\u4ee3\u7801\u4e0d\u8981\u8fd9\u4e48\u505a \u3002\u53ea\u9700\u5c06\u7279\u5b9a\u4e8e\u5e94\u7528\u7a0b\u5e8f\u7684\u5b50\u5305\u5bfc\u5165\u4e0e\u5176\u4ed6\u5b50\u5305\u5bfc\u5165\u4e00\u6837\u5bf9\u5f85\u5373\u53ef\u3002
\u6bcf\u79cd\u5206\u7ec4\u4e2d\uff0c\u5e94\u8be5\u6839\u636e\u6bcf\u4e2a\u6a21\u5757\u7684\u5b8c\u6574\u5305\u8def\u5f84\uff08from path import ...
\u4e2d\u7684 path
\uff09\u6309\u5b57\u5178\u5e8f\u6392\u5e8f\uff0c\u5ffd\u7565\u5927\u5c0f\u5199\u3002\u4ee3\u7801\u53ef\u4ee5\u9009\u62e9\u5728\u5bfc\u5165\u8282\u4e4b\u95f4\u653e\u7f6e\u4e00\u4e2a\u7a7a\u884c\u3002
import collections\nimport queue\nimport sys\nfrom absl import app\nfrom absl import flags\nimport bs4\nimport cryptography\nimport tensorflow as tf\nfrom book.genres import scifi\nfrom myproject.backend import huxley\nfrom myproject.backend.hgwells import time_machine\nfrom myproject.backend.state_machine import main_loop\nfrom otherproject.ai import body\nfrom otherproject.ai import mind\nfrom otherproject.ai import soul\n# \u4e4b\u524d\u98ce\u683c\u7684\u4ee3\u7801\u53ef\u80fd\u4f1a\u5c06\u4e00\u4e9b\u5bfc\u5165\u653e\u5728\u8fd9\u91cc:\n# from myproject.backend.hgwells import time_machine\n# from myproject.backend.state_machine import main_loop\n
"},{"location":"standard/style_rules/#314","title":"3.14 \u8bed\u53e5","text":"\u901a\u5e38\u6bcf\u4e2a\u8bed\u53e5\u5e94\u8be5\u72ec\u5360\u4e00\u884c\u3002
\u4e0d\u8fc7\uff0c\u5982\u679c\u6d4b\u8bd5\u7ed3\u679c\u4e0e\u6d4b\u8bd5\u8bed\u53e5\u5728\u4e00\u884c\u653e\u5f97\u4e0b\uff0c\u4f60\u4e5f\u53ef\u4ee5\u5c06\u5b83\u4eec\u653e\u5728\u540c\u4e00\u884c\u3002\u5982\u679c\u662f if
\u8bed\u53e5\uff0c\u53ea\u6709\u5728\u6ca1\u6709 else
\u65f6\u624d\u80fd\u8fd9\u6837\u505a\u3002\u7279\u522b\u5730\uff0c\u7edd\u4e0d\u8981\u5bf9 try/except
\u8fd9\u6837\u505a\uff0c\u56e0\u4e3a try
\u548c except
\u4e0d\u80fd\u653e\u5728\u540c\u4e00\u884c\u3002
\u63a8\u8350
if foo: bar(foo)\n
\u4e0d\u63a8\u8350
if foo: bar(foo)\nelse: baz(foo)\ntry: bar(foo)\nexcept ValueError: baz(foo)\ntry:\nbar(foo)\nexcept ValueError: baz(foo)\n
"},{"location":"standard/style_rules/#315-getters-and-setters","title":"3.15 Getters and Setters","text":"\u5f53 getter \u548c setter \u51fd\u6570\uff08\u4e5f\u79f0\u4e3a\u8bbf\u95ee\u5668\u548c\u4fee\u6539\u5668\uff09\u4e3a\u83b7\u53d6\u6216\u8bbe\u7f6e\u53d8\u91cf\u7684\u503c\u63d0\u4f9b\u6709\u610f\u4e49\u7684\u89d2\u8272\u6216\u884c\u4e3a\u65f6\uff0c\u5219\u5e94\u8be5\u4f7f\u7528\u5b83\u4eec\u3002
\u7279\u522b\u662f\uff0c\u5f53\u5f53\u524d\u6216\u5408\u7406\u7684\u672a\u6765\u83b7\u53d6\u6216\u8bbe\u7f6e\u53d8\u91cf\u5f88\u590d\u6742\u6216\u6210\u672c\u5f88\u9ad8\u65f6\uff0c\u5e94\u8be5\u4f7f\u7528\u5b83\u4eec\u3002
\u4f8b\u5982\uff0c\u5982\u679c\u4e00\u5bf9 getter/setter \u53ea\u662f\u8bfb\u53d6\u548c\u5199\u5165\u5185\u90e8\u5c5e\u6027\uff0c\u5219\u5e94\u5c06\u5185\u90e8\u5c5e\u6027\u516c\u5f00\u3002\u76f8\u6bd4\u4e4b\u4e0b\uff0c\u5982\u679c\u8bbe\u7f6e\u4e00\u4e2a\u53d8\u91cf\u610f\u5473\u7740\u67d0\u4e9b\u72b6\u6001\u65e0\u6548\u6216\u91cd\u5efa\uff0c \u90a3\u4e48\u5b83\u5e94\u8be5\u662f\u4e00\u4e2a setter \u51fd\u6570\u3002\u51fd\u6570\u8c03\u7528\u6697\u793a\u6b63\u5728\u53d1\u751f\u6f5c\u5728\u7684\u91cd\u8981\u64cd\u4f5c\u3002\u6216\u8005\uff0c\u5f53\u9700\u8981\u7b80\u5355\u903b\u8f91\u6216\u91cd\u6784\u4ee5\u4e0d\u518d\u9700\u8981 getter \u548c setter \u65f6\uff0c property
\u53ef\u80fd\u662f\u4e00\u4e2a\u9009\u9879\u3002
Getter \u548c setter \u5e94\u8be5\u9075\u5faa\u547d\u540d\u89c4\u8303\uff0c\u4f8b\u5982\uff1a get_foo()
\u548c set_foo()
\u3002
\u5982\u679c\u4e4b\u524d\u7684\u4ee3\u7801\u884c\u4e3a\u5141\u8bb8\u901a\u8fc7\u5c5e\u6027\uff08property
\uff09\u8bbf\u95ee\uff0c\u90a3\u4e48\u5c31\u4e0d\u8981\u5c06\u65b0\u7684\u8bbf\u95ee\u51fd\u6570\u4e0e\u5c5e\u6027\u7ed1\u5b9a\u3002\u8fd9\u6837\uff0c\u4efb\u4f55\u8bd5\u56fe\u901a\u8fc7\u8001\u65b9\u6cd5\u8bbf\u95ee\u53d8\u91cf\u7684\u4ee3\u7801\u5c31\u6ca1\u6cd5\u8fd0\u884c\uff0c\u4f7f\u7528\u8005\u4e5f\u5c31\u4f1a\u610f\u8bc6\u5230\u590d\u6742\u6027\u53d1\u751f\u4e86\u53d8\u5316\u3002
"},{"location":"standard/style_rules/#316","title":"3.16 \u547d\u540d","text":"module_name
\u3001package_name
\u3001ClassName
\u3001method_name
\u3001ExceptionName
\u3001function_name
\u3001GLOBAL_CONSTANT_NAME
\u3001global_var_name
\u3001instance_var_name
\u3001function_parameter_name
\u3001local_var_name
, query_proper_noun_for_thing
\u3001send_acronym_via_https
\u3002
\u51fd\u6570\u540d\u3001\u53d8\u91cf\u540d\u548c\u6587\u4ef6\u540d\u5e94\u8be5\u90fd\u662f\u63cf\u8ff0\u6027\u7684\uff0c\u907f\u514d\u4f7f\u7528\u7f29\u5199\u3002\u7279\u522b\u662f\uff0c\u4e0d\u8981\u4f7f\u7528\u5bf9\u9879\u76ee\u4ee5\u5916\u7684\u8bfb\u8005\u6765\u8bf4\u6a21\u68f1\u4e24\u53ef\u6216\u4e0d\u719f\u6089\u7684\u7f29\u5199\uff0c\u4e5f\u4e0d\u8981\u901a\u8fc7\u5220\u9664\u5355\u8bcd\u4e2d\u7684\u5b57\u6bcd\u6765\u7f29\u5199\u3002
\u603b\u662f\u4f7f\u7528 .py
\u6587\u4ef6\u6269\u5c55\u540d\uff0c\u4e0d\u8981\u4f7f\u7528\u8fde\u5b57\u7b26\u3002
"},{"location":"standard/style_rules/#3161","title":"3.16.1 \u5e94\u8be5\u907f\u514d\u7684\u540d\u79f0","text":" -
\u5355\u5b57\u7b26\u540d\u79f0\uff0c\u9664\u4e86\u4ee5\u4e0b\u7279\u6b8a\u60c5\u51b5\uff1a
- \u8ba1\u6570\u5668\u548c\u8fed\u4ee3\u5668\uff08\u4f8b\u5982\uff1a
i
\uff0c j
\uff0c k
\uff0c v
\u7b49\u7b49\uff09 - \u4f5c\u4e3a
try/except
\u8bed\u53e5\u7684\u5f02\u5e38\u6807\u8bc6\u7b26 e
\u3002 - \u4f5c\u4e3a
with
\u8bed\u53e5\u58f0\u660e\u7684\u6587\u4ef6\u5bf9\u8c61 f
- \u6ca1\u6709\u7ea6\u675f\u7684\u79c1\u6709\u7c7b\u578b\u53d8\u91cf\uff08\u4f8b\u5982
_T = TypeVar(\"_T\")\u3001_P = ParamSpec(\"_P\")
\uff09
\u6ce8\u610f\u4e0d\u8981\u6ee5\u7528\u5355\u5b57\u7b26\u547d\u540d\u3002\u4e00\u822c\u6765\u8bf4\uff0c\u63cf\u8ff0\u6027\u5e94\u4e0e\u540d\u79f0\u7684\u53ef\u89c1\u6027\u8303\u56f4\u6210\u6bd4\u4f8b\u3002\u4f8b\u5982\uff1a i
\u53ef\u80fd\u662f\u4e94\u884c\u4ee3\u7801\u5757\u7684\u597d\u540d\u79f0\uff0c\u4f46\u5728\u591a\u4e2a\u5d4c\u5957\u8303\u56f4\u5185\uff0c\u5b83\u53ef\u80fd\u592a\u6a21\u7cca\u4e86\u3002
- \u5305/\u6a21\u5757\u540d\u4e2d\u7684\u8fde\u5b57\u7b26\uff08
-
\uff09 __double_leading_and_trailing_underscore__
\u53cc\u4e0b\u5212\u7ebf\u5f00\u5934\u5e76\u7ed3\u5c3e\u7684\u540d\u79f0\uff08Python\u4fdd\u7559\uff09 - \u4e0d\u793c\u8c8c\u7684\u7528\u8bed
- \u4e0d\u9700\u8981\u5305\u542b\u53d8\u91cf\u7c7b\u578b\u7684\u540d\u79f0\uff08\u4f8b\u5982\uff1a
id_to_name_dict
\uff09
"},{"location":"standard/style_rules/#3162","title":"3.16.2 \u547d\u540d\u7ea6\u5b9a","text":" - \u6240\u8c13\u201c\u5185\u90e8\uff08
Internal
\uff09\u201d\u8868\u793a\u4ec5\u6a21\u5757\u5185\u53ef\u7528\uff0c\u6216\u8005\u5728\u7c7b\u5185\u662f\u4fdd\u62a4\u6216\u79c1\u6709\u7684\u3002 - \u5728\u6a21\u5757\u53d8\u91cf\u548c\u51fd\u6570\u524d\u52a0\u4e00\u4e2a\u4e0b\u5212\u7ebf(
_
)\uff0c\u53ef\u4ee5\u5728\u4e00\u5b9a\u7a0b\u5ea6\u4e0a\u4fdd\u62a4\u5b83\u4eec\uff08\u4ee3\u7801\u68c0\u67e5\u5de5\u5177\u4f1a\u6807\u8bb0\u8bbf\u95ee\u53d7\u4fdd\u62a4\u7684\u6210\u5458\uff09\u3002 - \u7528\u53cc\u4e0b\u5212\u7ebf\uff08
__
\uff09\u5f00\u5934\u7684\u5b9e\u4f8b\u53d8\u91cf\u6216\u65b9\u6cd5\u8868\u793a\u7c7b\u5185\u79c1\u6709\uff0c\u4f46\u5e76\u4e0d\u63a8\u8350\u8fd9\u4e48\u505a\uff0c\u56e0\u4e3a\u4f1a\u5f71\u54cd\u4ee3\u7801\u7684\u53ef\u8bfb\u6027\u6216\u53ef\u6d4b\u8bd5\u6027\uff0c\u800c\u4e14\u4e5f\u4e0d\u662f\u771f\u6b63\u7684\u79c1\u6709\u3002\u5efa\u8bae\u4f7f\u7528 \u5355\u4e0b\u5212\u7ebf_
\u3002 - \u5c06\u76f8\u5173\u7684\u7c7b\u548c\u9876\u7ea7\u51fd\u6570\u653e\u5728\u540c\u4e00\u4e2a\u6a21\u5757\u91cc\u3002\u4e0d\u50cf Java \uff0c\u6ca1\u5fc5\u8981\u9650\u5236\u4e00\u4e2a\u7c7b\u4e00\u4e2a\u6a21\u5757\u3002
- \u5bf9\u7c7b\u540d\u4f7f\u7528\u5927\u5199\u5b57\u6bcd\u5f00\u5934\u7684\u5355\u8bcd\uff08\u5982
CapWords
\uff0c\u5373 Pascal \u98ce\u683c\uff09\uff0c\u4f46\u662f\u6a21\u5757\u540d\u5e94\u8be5\u7528\u5c0f\u5199\u52a0\u4e0b\u5212\u7ebf\u7684\u65b9\u5f0f\uff08\u5982 lower_with_under.py
\uff09\u3002 \u5c3d\u7ba1\u5df2\u7ecf\u6709\u5f88\u591a\u73b0\u5b58\u7684\u6a21\u5757\u4f7f\u7528\u7c7b\u4f3c\u4e8e CapWords.py
\u8fd9\u6837\u7684\u547d\u540d\uff0c\u4f46\u73b0\u5728\u5df2\u7ecf\u4e0d\u9f13\u52b1\u8fd9\u6837\u505a\uff0c\u56e0\u4e3a\u5982\u679c\u6a21\u5757\u540d\u78b0\u5de7\u548c\u7c7b\u540d\u4e00\u81f4\uff0c\u8fd9\u4f1a\u8ba9\u4eba\u56f0\u6270\u3002\uff08\u201c\u60f3\u60f3 \u6211\u5e94\u8be5\u7528 import StringIO
\u8fd8\u662f from StringIO import StringIO
\uff1f\u201d\uff09 - \u65b0\u7684\u5355\u5143\u6d4b\u8bd5\u6587\u4ef6\u9075\u5faa PEP 8 \u517c\u5bb9\u7684\u4e0b\u5212\u7ebf\u547d\u540d\u6cd5\uff0c\u4f8b\u5982\uff0c
test_<\u88ab\u6d4b\u8bd5\u7684\u65b9\u6cd5><\u72b6\u6001>
\u3002\u4e3a\u4e86\u4e0e\u9075\u5faa CapWords
\u51fd\u6570\u540d\u79f0\u7684\u65e7\u6a21\u5757\u4fdd\u6301\u4e00\u81f4\uff0c\u65b9\u6cd5\u540d\u79f0\u4e2d\u53ef\u4ee5\u51fa\u73b0\u4e0b\u5212\u7ebf\uff0c\u4ee5\u4fbf\u5206\u9694\u540d\u79f0\u7684\u903b\u8f91\u7ec4\u4ef6\uff0c\u5176\u4e2d\u4ee5 test \u5f00\u5934\u7684\u65b9\u6cd5\u540d\u53ef\u80fd\u91c7\u7528 test<\u88ab\u6d4b\u8bd5\u7684\u65b9\u6cd5><\u72b6\u6001
> \u7684\u6a21\u5f0f\u3002
"},{"location":"standard/style_rules/#3163","title":"3.16.3 \u6587\u4ef6\u547d\u540d","text":"Python \u6587\u4ef6\u540d\u5fc5\u987b\u4ee5 .py
\u6269\u5c55\u540d\u7ed3\u5c3e\uff0c\u5e76\u4e14\u4e0d\u8981\u5305\u542b\u8fde\u5b57\u7b26\uff08-
\uff09\u3002\u8fd9\u6837\u53ef\u4ee5\u65b9\u4fbf\u5bfc\u5165\u548c\u5355\u5143\u6d4b\u8bd5\u3002\u5982\u679c\u4f60\u5e0c\u671b\u4f7f\u7528\u6ca1\u6709\u6269\u5c55\u540d\u7684\u53ef\u6267\u884c\u6587\u4ef6\uff0c\u53ef\u4ee5\u4f7f\u7528\u8f6f\u8fde\u63a5\u65b9\u5f0f\u6216\u8005\u5305\u542b exec \"$0.py\" \"$@\"
\u7684\u7b80\u5355\u5305\u88c5\u811a\u672c\u3002
"},{"location":"standard/style_rules/#3164-guidos","title":"3.16.4 \u57fa\u4e8e Guido\u2019s \u63a8\u8350\u7684\u6d3e\u751f\u51c6\u5219","text":"Type Public Internal Packages lower_with_under Modules lower_with_under _lower_with_under Classes CapWords _CapWords Exceptions CapWords Functions lower_with_under() _lower_with_under() Global/Class Constants CAPS_WITH_UNDER _CAPS_WITH_UNDER Global/Class Variables lower_with_under _lower_with_under Instance Variables lower_with_under _lower_with_under (protected) Method Names lower_with_under() _lower_with_under() (protected) Function/Method Parameters lower_with_under Local Variables lower_with_under"},{"location":"standard/style_rules/#3165","title":"3.16.5 \u6570\u5b66\u7b26\u53f7","text":"\u5bf9\u4e8e\u504f\u6570\u5b66\u8fd0\u7b97\u7684\u4ee3\u7801\uff0c\u5f53\u5b83\u4eec\u5339\u914d\u53c2\u8003\u8bba\u6587\u6216\u7b97\u6cd5\u4e2d\u5df2\u5efa\u7acb\u7684\u7b26\u53f7\u65f6\uff0c\u8f83\u77ed\u7684\u53d8\u91cf\u540d\u4f1a\u8fdd\u53cd\u6837\u5f0f\u6307\u5357\u3002\u6267\u884c\u6b64\u64cd\u4f5c\u65f6\uff0c\u8bf7\u5728\u6ce8\u91ca\u6216\u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u5f15\u7528\u6240\u6709\u547d\u540d\u7ea6\u5b9a\u7684\u6765\u6e90\uff0c\u5982\u679c\u6765\u6e90\u65e0\u6cd5\u8bbf\u95ee\uff0c\u8bf7\u6e05\u695a\u5730\u8bb0\u5f55\u547d\u540d\u7ea6\u5b9a\u3002\u5bf9\u4e8e\u516c\u5171 API\uff0c\u6700\u597d\u4f7f\u7528\u7b26\u5408 PEP8 \u7684\u63cf\u8ff0\u6027\u540d\u79f0\uff08descriptive_names
\uff09\uff0c\u8fd9\u6837\u66f4\u5bb9\u6613\u8131\u79bb\u4e0a\u4e0b\u6587\u3002
"},{"location":"standard/style_rules/#317-main","title":"3.17 Main","text":"\u5728 Python \u4e2d\uff0c pydoc
\u4ee5\u53ca\u5355\u5143\u6d4b\u8bd5\u8981\u6c42\u6a21\u5757\u5fc5\u987b\u662f\u53ef\u5bfc\u5165\u7684\u3002\u5982\u679c\u6587\u4ef6\u6253\u7b97\u4f5c\u4e3a\u53ef\u6267\u884c\u6587\u4ef6\u4f7f\u7528\uff0c\u90a3\u4e48\u5b83\u7684\u4e3b\u8981\u529f\u80fd\u5e94\u8be5\u653e\u5728 main()
\u51fd\u6570\u4e2d\u3002\u4f60\u7684\u4ee3\u7801\u5e94\u8be5\u5728\u6267\u884c\u4e3b\u7a0b\u5e8f\u524d\u603b\u662f\u68c0\u67e5 if __name__ == '__main__'
\uff0c\u8fd9\u6837\u5f53\u6a21\u5757\u88ab\u5bfc\u5165\u65f6\u4e3b\u7a0b\u5e8f\u5c31\u4e0d\u4f1a\u88ab\u6267\u884c\u3002
\u5f53\u4f7f\u7528 absl \u65f6\uff0c\u8bf7\u4f7f\u7528 app.run
\uff1a
from absl import app\n...\ndef main(argv):\n# process non-flag arguments\n...\nif __name__ == '__main__':\napp.run(main)\n
\u6216\u8005\uff1a
def main():\n...\nif __name__ == '__main__':\nmain()\n
\u6240\u6709\u7684\u9876\u7ea7\u4ee3\u7801\u5728\u6a21\u5757\u5bfc\u5165\u65f6\u90fd\u4f1a\u88ab\u6267\u884c\u3002\u8981\u5c0f\u5fc3\u4e0d\u8981\u53bb\u8c03\u7528\u51fd\u6570\u3001\u521b\u5efa\u5bf9\u8c61\u6216\u8005\u6267\u884c\u90a3\u4e9b\u4e0d\u5e94\u8be5\u5728\u4f7f\u7528 pydoc
\u65f6\u6267\u884c\u7684\u64cd\u4f5c\u3002
"},{"location":"standard/style_rules/#318","title":"3.18 \u51fd\u6570\u957f\u5ea6","text":"\u559c\u6b22\u5c0f\u800c\u7f8e\u7684\u51fd\u6570\u3002
\u957f\u51fd\u6570\u6709\u65f6\u5019\u662f\u53ef\u4ee5\u63a5\u53d7\u7684\uff0c\u5bf9\u51fd\u6570\u7684\u957f\u5ea6\u6ca1\u6709\u786c\u6027\u9650\u5236\u3002\u5982\u679c\u4e00\u4e2a\u51fd\u6570\u8d85\u8fc7\u4e8640\u884c\uff0c\u5c31\u9700\u8981\u601d\u8003\u4e00\u4e0b\uff0c\u5728\u4e0d\u7834\u574f\u7a0b\u5e8f\u7ed3\u6784\u7684\u60c5\u51b5\u4e0b\u662f\u5426\u9700\u8981\u62c6\u5206\u3002
\u5373\u4f7f\u4f60\u7684\u957f\u51fd\u6570\u73b0\u5728\u8fd0\u884c\u826f\u597d\uff0c\u5c06\u6765\u4fee\u6539\u5b83\u7684\u4eba\u4e5f\u53ef\u80fd\u4f1a\u6dfb\u52a0\u65b0\u7684\u529f\u80fd\uff0c\u8fd9\u53ef\u80fd\u4f1a\u5bfc\u81f4 BUG \u5f88\u96be\u67e5\u627e\u3002\u4fdd\u6301\u51fd\u6570\u7684\u7b80\u77ed\u548c\u7b80\u5355\u53ef\u4ee5\u4f7f\u5176\u4ed6\u4eba\u66f4\u5bb9\u6613\u9605\u8bfb\u548c\u4fee\u6539\u4f60\u7684\u4ee3\u7801\u3002
\u5728\u5904\u7406\u67d0\u4e9b\u4ee3\u7801\u65f6\uff0c\u60a8\u53ef\u80fd\u4f1a\u53d1\u73b0\u957f\u5e76\u4e14\u590d\u6742\u7684\u51fd\u6570\u3002\u5148\u4e0d\u8981\u88ab\u4fee\u6539\u8fd9\u4e9b\u4ee3\u7801\u6240\u5413\u5012\uff1a\u5982\u679c\u611f\u5230\u51fd\u6570\u4f7f\u7528\u56f0\u96be\uff0c\u9519\u8bef\u4e5f\u5f88\u96be\u8c03\u8bd5\uff0c\u6216\u8005\u60f3\u5728\u51e0\u4e2a\u4e0d\u540c\u7684\u5730\u65b9\u4f7f\u7528\u76f8\u540c\u7684\u529f\u80fd\uff0c\u53ef\u4ee5\u8003\u8651\u5c06\u51fd\u6570\u62c6\u5206\u6210\u66f4\u5c0f\u548c\u66f4\u6613\u4e8e\u7ba1\u7406\u7684\u4ee3\u7801\u6bb5\u3002
"},{"location":"standard/style_rules/#319","title":"3.19 \u7c7b\u578b\u6807\u6ce8","text":""},{"location":"standard/style_rules/#3191","title":"3.19.1 \u901a\u7528\u89c4\u5219","text":" - \u719f\u6089 PEP-484\u3002
-
\u5728\u65b9\u6cd5\u4e2d\uff0c\u53ea\u6709\u5728\u9700\u8981\u6b63\u786e\u7684\u7c7b\u578b\u4fe1\u606f\u65f6\u624d\u6807\u6ce8 self
\u6216 cls
\u3002\u4f8b\u5982\uff1a
@classmethod \ndef create(cls: Type[T]) -> T: \nreturn cls()\n
-
\u540c\u6837\uff0c\u4e0d\u5fc5\u5f3a\u5236\u6ce8\u91ca __init__
\u7684\u8fd4\u56de\u503c\uff08\u5176\u4e2d None \u662f\u552f\u4e00\u6709\u6548\u7684\u9009\u9879\uff09\u3002
- \u5982\u679c\u65e0\u6cd5\u8868\u793a\u4efb\u4f55\u5176\u4ed6\u53d8\u91cf\u6216\u8fd4\u56de\u7c7b\u578b\uff0c\u8bf7\u4f7f\u7528
Any
\u3002 - \u4f60\u4e0d\u9700\u8981\u6807\u6ce8\u6a21\u5757\u4e2d\u7684\u6240\u6709\u51fd\u6570\u3002
- \u81f3\u5c11\u8981\u6807\u6ce8\u516c\u5171 API\u3002
- \u5728\u5b89\u5168\u6027\u548c\u6e05\u6670\u6027\u4e0e\u7075\u6d3b\u6027\u4e4b\u95f4\u627e\u5230\u4e00\u4e2a\u5e73\u8861\u70b9\u3002
- \u6807\u6ce8\u90a3\u4e9b\u5bb9\u6613\u51fa\u73b0\u7c7b\u578b\u76f8\u5173\u9519\u8bef\uff08\u4ee5\u524d\u7684 BUG \u6216\u590d\u6742\u6027\uff09\u7684\u4ee3\u7801
- \u6807\u6ce8\u90a3\u4e9b\u96be\u4ee5\u7406\u89e3\u7684\u4ee3\u7801\u3002
- \u6807\u6ce8\u90a3\u4e9b\u4ece\u7c7b\u578b\u7684\u89d2\u5ea6\u6765\u770b\u5df2\u7ecf\u7a33\u5b9a\u7684\u4ee3\u7801\u3002\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u4f60\u53ef\u4ee5\u5728\u7a33\u5b9a\u7684\u4ee3\u7801\u4e2d\u6807\u6ce8\u6240\u6709\u51fd\u6570\uff0c\u800c\u4e0d\u4f1a\u5931\u53bb\u592a\u591a\u7075\u6d3b\u6027\u3002
"},{"location":"standard/style_rules/#3192","title":"3.19.2 \u65ad\u884c","text":"\u9075\u5faa\u73b0\u6709\u7f29\u8fdb\u89c4\u5219\u3002
\u5728\u6ce8\u91ca\u4e4b\u540e\uff0c\u8bb8\u591a\u51fd\u6570\u7b7e\u540d\u5c06\u53d8\u4e3a\u201c\u6bcf\u884c\u4e00\u4e2a\u53c2\u6570\u201d\u3002\u4e3a\u786e\u4fdd\u8fd4\u56de\u7c7b\u578b\u4e5f\u6709\u81ea\u5df1\u7684\u4e00\u884c\uff0c\u53ef\u4ee5\u5728\u6700\u540e\u4e00\u4e2a\u53c2\u6570\u540e\u9762\u52a0\u4e0a\u4e00\u4e2a\u9017\u53f7\u3002
def my_method(\nself,\nfirst_var: int,\nsecond_var: Foo,\nthird_var: Bar | None,\n) -> int:\n...\n
\u5c3d\u91cf\u5728\u53d8\u91cf\u4e4b\u95f4\u65ad\u884c\uff0c\u4e0d\u8981\u5728\u53d8\u91cf\u540d\u548c\u7c7b\u578b\u6807\u6ce8\u4e4b\u95f4\u65ad\u884c\u3002\u5982\u679c\u6240\u6709\u5185\u5bb9\u90fd\u5728\u4e00\u884c\u4e0a\uff0c\u5c31\u4e0d\u8981\u7ba1\u4e86\u3002
def my_method(self, first_var: int) -> int:\n...\n
\u5982\u679c\u51fd\u6570\u540d\u3001\u6700\u540e\u4e00\u4e2a\u53c2\u6570\u548c\u8fd4\u56de\u7c7b\u578b\u7ec4\u5408\u8d77\u6765\u592a\u957f\u4e86\uff0c\u53ef\u4ee5\u65b0\u6362\u4e00\u884c\u5e76\u7f29\u8fdb4\u4e2a\u5b57\u7b26\u3002 \u5728\u4f7f\u7528\u6362\u884c\u7b26\u65f6\uff0c\u5efa\u8bae\u5c06\u6bcf\u4e2a\u53c2\u6570\u548c\u8fd4\u56de\u7c7b\u578b\u653e\u5728\u81ea\u5df1\u7684\u884c\u4e0a\uff0c\u5e76\u5c06\u53f3\u62ec\u53f7\u4e0e def
\u5bf9\u9f50\u3002
def my_method(\nself,\nother_arg: MyLongType | None,\n) -> tuple[MyLongType1, MyLongType1]:\n...\n
\u6216\u8005\uff0c\u8fd4\u56de\u7c7b\u578b\u53ef\u4ee5\u4e0e\u6700\u540e\u4e00\u4e2a\u53c2\u6570\u653e\u5728\u540c\u4e00\u884c\uff1a
\u63a8\u8350
def my_method(\nself,\nfirst_var: int,\nsecond_var: int) -> dict[OtherLongType, MyLongType]:\n...\n
Pylint \u5141\u8bb8\u60a8\u5c06\u53f3\u62ec\u53f7\u79fb\u5230\u65b0\u884c\uff0c\u5e76\u4e0e\u5de6\u62ec\u53f7\u5bf9\u9f50\uff0c\u4f46\u8fd9\u4e48\u505a\u53ef\u8bfb\u6027\u4f1a\u6bd4\u8f83\u5dee\u3002
\u4e0d\u63a8\u8350
def my_method(self,\nother_arg: Optional[MyLongType]\n) -> Dict[OtherLongType, MyLongType]:\n...\n
\u5c31\u50cf\u4e0a\u9762\u7684\u4f8b\u5b50\u4e00\u6837\uff0c\u6211\u4eec\u4e0d\u5e0c\u671b\u622a\u65ad\u7c7b\u578b\u3002\u4f46\u662f\uff0c\u6709\u65f6\u5019\u5b83\u4eec\u653e\u5728\u4e00\u884c\u4e0a\u5b9e\u5728\u592a\u957f\u4e86\uff08\u5c3d\u91cf\u4fdd\u6301\u5b50\u7c7b\u578b\u4e0d\u88ab\u622a\u65ad\uff09\uff1a
def my_method(\nself,\nfirst_var: Tuple[List[MyLongType1],\nList[MyLongType2]],\nsecond_var: List[Dict[\nMyLongType3, MyLongType4]],\n) -> None:\n...\n
\u5982\u679c\u5355\u4e2a\u540d\u79f0\u548c\u7c7b\u578b\u592a\u957f\uff0c\u8bf7\u8003\u8651\u4f7f\u7528\u7c7b\u578b\u7684\u522b\u540d\u3002\u6700\u540e\u4e00\u79cd\u65b9\u6cd5\u662f\u5728\u5192\u53f7\u540e\u9762\u622a\u65ad\uff0c\u5e76\u7f29\u8fdb4\u4e2a\u5b57\u7b26\u3002
\u63a8\u8350
def my_function(\nlong_variable_name:\nlong_module_name.LongTypeName,\n) -> None:\n...\n
\u4e0d\u63a8\u8350
def my_function(\nlong_variable_name: long_module_name.\nLongTypeName,\n) -> None:\n...\n
"},{"location":"standard/style_rules/#3193","title":"3.19.3 \u524d\u7f6e\u58f0\u660e","text":"\u5982\u679c\u4f60\u9700\u8981\u4f7f\u7528\u4e00\u4e2a\u5c1a\u672a\u5b9a\u4e49\u7684\u7c7b\u540d\uff08\u6765\u81ea\u540c\u4e00\u6a21\u5757\uff09\uff0c\u4f8b\u5982\uff0c\u5982\u679c\u4f60\u9700\u8981\u5728\u8be5\u7c7b\u7684\u58f0\u660e\u5185\u90e8\u4f7f\u7528\u7c7b\u540d\uff0c\u6216\u8005\u5982\u679c\u4f60\u4f7f\u7528\u7684\u7c7b\u662f\u5728\u4ee3\u7801\u540e\u9762\u5b9a\u4e49\u7684\uff0c \u90a3\u4e48\u53ef\u4ee5\u4f7f\u7528 from future import annotations
\u6216\u4f7f\u7528\u5b57\u7b26\u4e32\u4f5c\u4e3a\u7c7b\u540d\u3002
\u63a8\u8350
from __future__ import annotations\nclass MyClass:\ndef __init__(self, stack: Sequence[MyClass], item: OtherClass) -> None:\nclass OtherClass:\n
\u63a8\u8350
class MyClass:\ndef __init__(self, stack: Sequence['MyClass'], item: 'OtherClass') -> None:\nclass OtherClass:\n...\n
"},{"location":"standard/style_rules/#3194","title":"3.19.4 \u9ed8\u8ba4\u503c","text":"\u6839\u636e PEP-008 \uff0c\u4ec5\u5728\u540c\u65f6\u5177\u6709\u7c7b\u578b\u6807\u6ce8\u548c\u9ed8\u8ba4\u503c\u53c2\u6570\u7684 =
\u5de6\u53f3\u4e24\u8fb9\u4f7f\u7528\u7a7a\u683c\u3002
\u63a8\u8350
def func(a: int = 0) -> int:\n...\n
\u4e0d\u63a8\u8350
def func(a:int=0) -> int:\n...\n
"},{"location":"standard/style_rules/#3195-nonetype","title":"3.19.5 NoneType","text":"\u5728 Python \u7c7b\u578b\u7cfb\u7edf\u4e2d\uff0cNoneType
\u662f\u201c\u4e00\u7b49\u516c\u6c11\u201d\u7c7b\u578b\uff0c\u800c\u5728\u7c7b\u578b\u6ce8\u91ca\u4e2d\uff0cNone \u662f NoneType \u7684\u522b\u540d\u3002\u5982\u679c\u4e00\u4e2a\u53c2\u6570\u53ef\u4ee5\u662f None\uff0c \u90a3\u4e48\u5fc5\u987b\u58f0\u660e\u5b83\uff01\u60a8\u53ef\u4ee5\u4f7f\u7528 |
\u8054\u5408\u7c7b\u578b\u8868\u8fbe\u5f0f\uff08\u5efa\u8bae\u5728\u65b0\u7684 Python 3.10+ \u4ee3\u7801\u4e2d\u4f7f\u7528\uff09\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528\u65e7\u7684 Optional
\u548c Union
\u8bed\u6cd5\u3002
\u8bf7\u4f7f\u7528\u663e\u5f0f\u7684 X | None
\u800c\u4e0d\u662f\u9690\u5f0f\u7684\u3002PEP 484 \u7684\u65e9\u671f\u7248\u672c\u5141\u8bb8\u5c06 a: str = None
\u89e3\u91ca\u4e3a a: str | None = None
\uff0c\u4f46\u73b0\u5728\u5df2\u7ecf\u4e0d\u63a8\u8350\u4e86\u3002
\u63a8\u8350
def modern_or_union(a: str | int | None, b: str | None = None) -> str:\n...\ndef union_optional(a: Union[str, int, None], b: Optional[str] = None) -> str:\n...\n
\u4e0d\u63a8\u8350
def nullable_union(a: Union[None, str]) -> str:\n...\ndef implicit_optional(a: str = None) -> str:\n...\n
"},{"location":"standard/style_rules/#3196","title":"3.19.6 \u7c7b\u578b\u522b\u540d","text":"\u53ef\u4ee5\u4e3a\u590d\u6742\u7c7b\u578b\u58f0\u660e\u522b\u540d\u3002\u522b\u540d\u5e94\u8be5\u662f\u5927\u5199\u7684\uff08CapWorded
\uff09\u3002\u5982\u679c\u522b\u540d\u4ec5\u5728\u6a21\u5757\u4e2d\u4f7f\u7528\uff0c\u90a3\u4e48\u5e94\u8be5\u4f7f\u7528\u524d\u7f6e\u4e0b\u5212\u7ebf\u8ba9\u5176\u53d8\u6210\u79c1\u6709\u7684\uff08\u5982 _Private
\uff09\u3002
\u8bf7\u6ce8\u610f\uff0c\u4ec5 3.10+ \u7248\u672c\u652f\u6301 :TypeAlias
\u6ce8\u91ca\u3002
from typing import TypeAlias\n_LossAndGradient: TypeAlias = tuple[tf.Tensor, tf.Tensor]\nComplexTFMap: TypeAlias = Mapping[str, _LossAndGradient]\n
\u5176\u4ed6\u4f8b\u5b50\u8fd8\u6709\u590d\u6742\u7684\u5d4c\u5957\u7c7b\u578b\u548c\u51fd\u6570\u7684\u591a\u4e2a\u8fd4\u56de\u53d8\u91cf\uff08\u4f5c\u4e3a\u5143\u7ec4\uff09\u3002
"},{"location":"standard/style_rules/#3197","title":"3.19.7 \u5ffd\u7565\u7c7b\u578b","text":"\u53ef\u4ee5\u5728\u884c\u4e0a\u4f7f\u7528\u7279\u6b8a\u6ce8\u91ca # type: ignore
\u7981\u7528\u7c7b\u578b\u68c0\u67e5\u3002
pytype
\u6709\u4e00\u4e2a\u9488\u5bf9\u7279\u5b9a\u9519\u8bef\u7684\u7981\u7528\u9009\u9879\uff08\u7c7b\u4f3c\u4e8e lint\uff09
# pytype: disable=attribute-error\n
"},{"location":"standard/style_rules/#3198","title":"3.19.8 \u6807\u6ce8\u53d8\u91cf","text":"\u8d4b\u503c\u6807\u6ce8
\u5982\u679c\u4e00\u4e2a\u5185\u90e8\u53d8\u91cf\u7684\u7c7b\u578b\u5f88\u96be\u6216\u65e0\u6cd5\u63a8\u65ad\u51fa\uff0c\u5219\u4f7f\u7528\u5e26\u6ce8\u91ca\u7684\u8d4b\u503c\u6307\u5b9a\u5b83\u7684\u7c7b\u578b - \u5728\u53d8\u91cf\u540d\u548c\u503c\u4e4b\u95f4\u4f7f\u7528 :
\u548c type
\uff08\u4e0e\u5177\u6709\u9ed8\u8ba4\u503c\u7684\u51fd\u6570\u53c2\u6570\u76f8\u540c\u7684\u505a\u6cd5\uff09\uff1a
a: Foo = SomeUndecoratedFunction()\n
\u7c7b\u578b\u6ce8\u91ca
\u867d\u7136\u4f60\u53ef\u80fd\u4f1a\u770b\u5230\u8fd9\u4e9b\u6ce8\u91ca\u5728\u4ee3\u7801\u5e93\u4e2d\u4ecd\u7136\u5b58\u5728\uff08\u5b83\u4eec\u5728 Python 3.6 \u4e4b\u524d\u662f\u5fc5\u8981\u7684\uff09\uff0c\u4f46\u4e0d\u8981\u518d\u5728\u884c\u672b\u6dfb\u52a0\u4efb\u4f55 # type: <type name>
\u7684\u6ce8\u91ca\u4e86\uff1a
a = SomeUndecoratedFunction() # type: Foo\n
"},{"location":"standard/style_rules/#3199-vs","title":"3.19.9 \u5143\u7ec4 vs \u5217\u8868","text":"\u7c7b\u578b\u5316\u5217\u8868\u53ea\u80fd\u5305\u542b\u5355\u4e00\u7c7b\u578b\u7684\u5bf9\u8c61\u3002\u7c7b\u578b\u5316\u5143\u7ec4\u53ef\u4ee5\u5177\u6709\u5355\u4e2a\u91cd\u590d\u7c7b\u578b\uff0c\u4e5f\u53ef\u4ee5\u5177\u6709\u4e00\u7ec4\u4e0d\u540c\u7c7b\u578b\u7684\u5143\u7d20\u3002\u540e\u8005\u901a\u5e38\u7528\u4f5c\u51fd\u6570\u7684\u8fd4\u56de\u7c7b\u578b\u3002
a: list[int] = [1, 2, 3]\nb: tuple[int, ...] = (1, 2, 3)\nc: tuple[int, str, float] = (1, \"2\", 3.5)\n
"},{"location":"standard/style_rules/#31910-typevars","title":"3.19.10 TypeVars","text":"Python \u7c7b\u578b\u7cfb\u7edf\u4e2d\u6709\u6cdb\u578b\uff0c\u5de5\u5382\u51fd\u6570 TypeVar
\u662f\u4f7f\u7528\u5b83\u4eec\u7684\u5e38\u7528\u65b9\u6cd5\u3002
\u4f8b\u5982\uff1a
from collections.abc import Callable\nfrom typing import ParamSpec, TypeVar\n_P = ParamSpec(\"_P\")\n_T = TypeVar(\"_T\")\n...\ndef next(l: list[_T]) -> _T:\nreturn l.pop()\ndef print_when_called(f: Callable[_P, _T]) -> Callable[_P, _T]:\ndef inner(*args: P.args, **kwargs: P.kwargs) -> R:\nprint('Function was called')\nreturn f(*args, **kwargs)\nreturn inner\n
TypeVar \u53ef\u4ee5\u88ab\u7ea6\u675f\uff1a
AddableType = TypeVar(\"AddableType\", int, float, str)\ndef add(a: AddableType, b: AddableType) -> AddableType:\nreturn a + b\n
typing
\u6a21\u5757\u4e2d\u4e00\u4e2a\u5e38\u89c1\u7684\u9884\u5b9a\u4e49\u7c7b\u578b\u53d8\u91cf\u662f AnyStr
\u3002\u53ef\u4ee5\u7528\u4e8e\u6807\u6ce8 bytes
\u6216 unicode
\uff0c\u4f46\u662f\u5fc5\u987b\u662f\u5728\u76f8\u540c\u7c7b\u578b\u4e2d\u4f7f\u7528\u3002
from typing import AnyStr\ndef check_length(x: AnyStr) -> AnyStr:\nif len(x) <= 42:\nreturn x\nraise ValueError()\n
\u7c7b\u578b\u53d8\u91cf\u5fc5\u987b\u5177\u6709\u63cf\u8ff0\u6027\u540d\u79f0\uff0c\u9664\u975e\u5b83\u6ee1\u8db3\u4ee5\u4e0b\u6240\u6709\u6761\u4ef6\uff1a
- \u5916\u90e8\u4e0d\u53ef\u89c1
- \u4e0d\u53d7\u7ea6\u675f
\u63a8\u8350
_T = TypeVar(\"_T\")\n_P = ParamSpec(\"_P\")\nAddableType = TypeVar(\"AddableType\", int, float, str)\nAnyFunction = TypeVar(\"AnyFunction\", bound=Callable)\n
\u4e0d\u63a8\u8350
T = TypeVar(\"T\")\nP = ParamSpec(\"P\")\n_T = TypeVar(\"_T\", int, float, str)\n_F = TypeVar(\"_F\", bound=Callable)\n
"},{"location":"standard/style_rules/#31911","title":"3.19.11 \u5b57\u7b26\u4e32\u7c7b\u578b","text":"\u4e0d\u8981\u5728\u65b0\u4ee3\u7801\u4e2d\u4f7f\u7528 typing.Text
\u3002\u5b83\u4ec5\u7528\u4e8e Python 2/3 \u517c\u5bb9\u6027\u3002
\u4f7f\u7528 str
\u4f5c\u4e3astring
/ text
\u6570\u636e\u3002\u5bf9\u4e8e\u5904\u7406\u4e8c\u8fdb\u5236\u6570\u636e\u7684\u4ee3\u7801\uff0c\u8bf7\u4f7f\u7528 bytes
\u3002
def deals_with_text_data(x: str) -> str:\n...\ndef deals_with_binary_data(x: bytes) -> bytes:\n...\n
\u5982\u679c\u51fd\u6570\u7684\u6240\u6709\u5b57\u7b26\u4e32\u7c7b\u578b\u59cb\u7ec8\u76f8\u540c\uff0c\u4f8b\u5982\uff0c\u5982\u679c\u8fd4\u56de\u7c7b\u578b\u4e0e\u4e0a\u9762\u4ee3\u7801\u4e2d\u7684\u53c2\u6570\u7c7b\u578b\u76f8\u540c\uff0c\u8bf7\u4f7f\u7528 AnyStr\u3002
"},{"location":"standard/style_rules/#31912","title":"3.19.12 \u7c7b\u578b\u5bfc\u5165","text":"\u5bf9\u4e8e\u7528\u4e8e\u652f\u6301\u9759\u6001\u5206\u6790\u548c\u7c7b\u578b\u68c0\u67e5\u7684 types
\u548c collections.abc
\u6a21\u5757\u4e2d\u7684\u7b26\u53f7\uff0c\u8bf7\u59cb\u7ec8\u5bfc\u5165\u7b26\u53f7\u672c\u8eab\u3002 \u8fd9\u4f7f\u5f97\u5e38\u89c1\u6ce8\u91ca\u66f4\u52a0\u7b80\u6d01\uff0c\u5e76\u7b26\u5408\u4e16\u754c\u5404\u5730\u4f7f\u7528\u7684\u6253\u5b57\u4e60\u60ef\u3002\u660e\u786e\u5141\u8bb8\u4ece typing
\u548c collections.abc
\u6a21\u5757\u5728\u4e00\u884c\u4e2d\u5bfc\u5165\u591a\u4e2a\u7279\u5b9a\u7c7b\u3002
from collections.abc import Mapping, Sequence\nfrom typing import Any, Generic\n
\u65e2\u7136\u8fd9\u79cd\u4ece typing
\u6a21\u5757\u5bfc\u5165\u7684\u65b9\u5f0f\u4f1a\u5c06\u5bfc\u5165\u9879\u6dfb\u52a0\u5230\u672c\u5730\u547d\u540d\u7a7a\u95f4\uff0c \u90a3\u4e48 typing
\u6216 collections.abc
\u4e2d\u7684\u4efb\u4f55\u540d\u79f0\u90fd\u5e94\u8be5\u7c7b\u4f3c\u4e8e\u5173\u952e\u5b57\uff0c\u800c\u4e14\u4e0d\u8981\u5728\u4f60\u7684 Python \u4ee3\u7801\u4e2d\u53bb\u5b9a\u4e49\uff08\u65e0\u8bba\u662f\u5426\u6709\u7c7b\u578b\uff09\u3002\u5982\u679c\u6a21\u5757\u4e2d\u7684\u7c7b\u578b\u548c\u73b0\u6709\u540d\u79f0\u4e4b\u95f4\u5b58\u5728\u51b2\u7a81\uff0c\u8bf7\u4f7f\u7528 import x as y
\u5bfc\u5165\u3002
from typing import Any as AnyType\n
\u63a8\u8350\u4f7f\u7528\u5185\u7f6e\u7c7b\u578b\u4f5c\u4e3a\u6ce8\u91ca\uff08\u5982\u679c\u53ef\u7528\uff09\u3002 Python \u901a\u8fc7 Python 3.9 \u4e2d\u5f15\u5165\u7684 PEP-585 \u652f\u6301\u4f7f\u7528\u53c2\u6570\u5bb9\u5668\u7c7b\u578b\u7684\u7c7b\u578b\u6ce8\u91ca\u3002
def generate_foo_scores(foo: set[str]) -> list[float]:\n...\n
"},{"location":"standard/style_rules/#31913","title":"3.19.13 \u6761\u4ef6\u5bfc\u5165","text":"\u4ec5\u5728\u7279\u6b8a\u60c5\u51b5\u4e0b\u624d\u4f7f\u7528\u6761\u4ef6\u5bfc\u5165\uff0c\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u5fc5\u987b\u5728\u8fd0\u884c\u65f6\u907f\u514d\u7c7b\u578b\u68c0\u67e5\u6240\u9700\u7684\u5176\u4ed6\u5bfc\u5165\u3002\u4e0d\u63a8\u8350\u8fd9\u79cd\u65b9\u5f0f\uff1b\u5e94\u8be5\u9996\u9009\u5176\u4ed6\u65b9\u6cd5\uff0c\u6bd4\u5982\u91cd\u6784\u4ee3\u7801\u4ee5\u5141\u8bb8\u9876\u7ea7\u5bfc\u5165\u3002
\u53ef\u4ee5\u5c06\u4ec5\u7528\u4e8e\u7c7b\u578b\u6807\u6ce8\u7684\u5bfc\u5165\u653e\u5728 if TYPE_CHECKING:
\u4ee3\u7801\u5757\u4e2d\u3002
- \u6709\u6761\u4ef6\u5bfc\u5165\u7684\u7c7b\u578b\u9700\u8981\u4f5c\u4e3a\u5b57\u7b26\u4e32\u5f15\u7528\uff0c\u4ee5\u4fbf\u6807\u6ce8\u8868\u8fbe\u5f0f\u5b9e\u9645\u8fd0\u884c\u65f6\u80fd\u5411\u524d\u517c\u5bb9 Python 3.6\u3002
- \u8fd9\u91cc\u53ea\u5e94\u8be5\u5b9a\u4e49\u7528\u4e8e\u7c7b\u578b\u6807\u6ce8\u7684\u5b9e\u4f53\uff1b\u5305\u62ec\u522b\u540d\u3002\u5426\u5219\u5c06\u4f1a\u6709\u4e00\u4e2a\u8fd0\u884c\u65f6\u9519\u8bef\uff0c\u56e0\u4e3a\u6a21\u5757\u5c06\u4e0d\u4f1a\u5728\u8fd0\u884c\u65f6\u5bfc\u5165\u3002
- \u6240\u6709\u6b63\u5e38\u5bfc\u5165\u540e\u7684\u4ee3\u7801\u5757\u5e94\u8be5\u662f\u6b63\u786e\u7684\u3002
- \u7c7b\u578b\u5bfc\u5165\u5217\u8868\u4e2d\u4e0d\u5e94\u8be5\u6709\u7a7a\u884c\u3002
- \u5c06\u6b64\u5217\u8868\u6309\u7167\u5e38\u89c4\u5bfc\u5165\u5217\u8868\u8fdb\u884c\u6392\u5e8f\u3002
import typing\nif typing.TYPE_CHECKING:\nimport sketch\ndef f(x: \"sketch.Sketch\"): ...\n
"},{"location":"standard/style_rules/#31914","title":"3.19.14 \u5faa\u73af\u4f9d\u8d56","text":"\u7531\u7c7b\u578b\u5f15\u8d77\u7684\u5faa\u73af\u4f9d\u8d56\u662f\u4e00\u79cd\u4ee3\u7801\u5473\u9053\u3002\u8fd9\u4e9b\u4ee3\u7801\u9700\u8981\u8fdb\u884c\u91cd\u6784\u3002\u867d\u7136\u5728\u6280\u672f\u4e0a\u53ef\u4ee5\u4fdd\u6301\u5faa\u73af\u4f9d\u8d56\u5173\u7cfb\uff0c\u4f46\u662f\u5404\u79cd\u6784\u5efa\u7cfb\u7edf\u4e0d\u5141\u8bb8\u8fd9\u6837\u505a\uff0c\u56e0\u4e3a\u6bcf\u4e2a\u6a21\u5757\u90fd\u5fc5\u987b\u4f9d\u8d56\u4e8e\u5176\u4ed6\u6a21\u5757\u3002
\u5c06\u5f15\u8d77\u5faa\u73af\u4f9d\u8d56\u5bfc\u5165\u7684\u6a21\u5757\u66ff\u6362\u4e3a Any
\u3002\u8bbe\u7f6e\u4e00\u4e2a\u6709\u610f\u4e49\u7684\u522b\u540d \uff0c\u5e76\u4f7f\u7528\u6b64\u6a21\u5757\u4e2d\u7684\u5b9e\u9645\u7c7b\u578b\u540d\u79f0\uff08Any \u7684\u4efb\u4f55\u5c5e\u6027\u90fd\u662f Any\uff09\u3002\u522b\u540d\u5b9a\u4e49\u5e94\u8be5\u4e0e\u6700\u540e\u5bfc\u5165\u5206\u5f00\u4e00\u884c\u3002
from typing import Any\nsome_mod = Any # some_mod.py imports this module.\n...\ndef my_method(self, var: \"some_mod.SomeType\") -> None:\n...\n
"},{"location":"standard/style_rules/#31915","title":"3.19.15 \u6cdb\u578b","text":"\u8fdb\u884c\u6807\u6ce8\u65f6\uff0c\u6700\u597d\u4e3a\u6cdb\u578b\u7c7b\u578b\u6307\u5b9a\u7c7b\u578b\u53c2\u6570\uff1b\u5426\u5219\uff0c\u6cdb\u578b\u53c2\u6570\u5c06\u88ab\u5047\u5b9a\u4e3a Any
\u3002
\u63a8\u8350
def get_names(employee_ids: Sequence[int]) -> Mapping[int, str]:\n...\n
\u4e0d\u63a8\u8350
# This is interpreted as get_names(employee_ids: Sequence[Any]) -> Mapping[Any, Any]\ndef get_names(employee_ids: Sequence) -> Mapping:\n...\n
\u5982\u679c\u6cdb\u578b\u7684\u6700\u4f73\u7c7b\u578b\u53c2\u6570\u662f Any
\uff0c\u8bf7\u4f7f\u7528\u663e\u5f0f\u8bbe\u7f6e\u3002\u4f46\u8bf7\u8bb0\u4f4f\uff0c\u5728\u8bb8\u591a\u60c5\u51b5\u4e0b TypeVar
\u53ef\u80fd\u66f4\u5408\u9002\u3002
\u4e0d\u63a8\u8350
def get_names(employee_ids: Sequence[Any]) -> Mapping[Any, str]:\n\"\"\"Returns a mapping from employee ID to employee name for given IDs.\"\"\"\n
\u63a8\u8350
_T = TypeVar('_T')\ndef get_names(employee_ids: Sequence[_T]) -> Mapping[_T, str]:\n\"\"\"Returns a mapping from employee ID to employee name for given IDs.\"\"\"\n
"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Python \u9879\u76ee\u5de5\u7a0b\u5316\u5f00\u53d1\u6307\u5357","text":"\u6587\u6863\u76ee\u6807\uff1a
\u4ee5\u901a\u4fd7\u6613\u61c2\u7ed3\u6784\u6e05\u6670\u7684\u6587\u6863\u5411\u8bfb\u8005\u5c55\u793a\u5982\u4f55\u505a Python \u5de5\u7a0b\u5316
\u53d7\u4f17\u76ee\u6807\uff1a
- Python \u521d\u5b66\u8005
- Python \u521d\u7ea7\u5f00\u53d1
- Python \u4e2d\u7ea7\u5f00\u53d1
\u6307\u5357\u4e3b\u8981\u5305\u542b\u4ee5\u4e0b\u4e3b\u9898\uff1a
- \u5feb\u901f\u4e0a\u624b\uff08\u4e00\u4e2a\u6700\u901a\u7528\uff0c\u6700\u521d\u7ea7\u7684\u793a\u4f8b\u9879\u76ee\uff09
- \u5f00\u53d1\u524d\u51c6\u5907
- Python \u73af\u5883\u7684\u5b89\u88c5
- \u865a\u62df\u73af\u5883\u7ba1\u7406
- IDE \u7684\u9009\u62e9
- Python \u89c4\u8303
- \u98ce\u683c\u89c4\u8303
- \u8bed\u8a00\u89c4\u8303
- \u5e94\u7528\u5f00\u53d1\u5b9e\u8df5
- \u521d\u7ea7\u6559\u7a0b(\u4e00\u4e2a\u5305\u542b\u5b8c\u6574\u5f00\u53d1\u6d41\u7a0b\u7684\u793a\u4f8b\u9879\u76ee)
- \u521d\u59cb\u5316\u9879\u76ee
- \u529f\u80fd\u5f00\u53d1
- \u6d4b\u8bd5
- \u6253\u5305\u53d1\u5e03
- \u8fdb\u9636\u6559\u7a0b
- \u7c7b\u578b\u6807\u6ce8
- \u4f7f\u7528\u914d\u7f6e\u7cfb\u7edf
- \u5982\u4f55\u7528\u597d\u65e5\u5fd7
- \u5f02\u5e38\u7ba1\u7406
- \u5982\u4f55\u66f4\u597d\u5f97\u6d4b\u8bd5
- \u7528\u4fe1\u53f7\u89e3\u8026\u903b\u8f91
- \u652f\u6301\u63d2\u4ef6\u5316
- \u9879\u76ee\u7ba1\u7406
- \u4ee3\u7801\u68c0\u6d4b
- \u9879\u76ee\u7ed3\u6784
- \u6587\u6863\u7ba1\u7406
- \u6253\u5305\u53d1\u5e03
- \u5f00\u53d1\u5b9e\u8df5
- Web
- Fastapi
- Django
- Flask
- \u722c\u866b
- Scrapy
- aiohttp
- \u6570\u636e\u5e93
- SQLAlchemy
- \u6570\u636e\u5f00\u53d1\u5b9e\u8df5
- \u521d\u7ea7\u6559\u7a0b
\u5982\u679c\u60a8\u5bf9\u6587\u6863\u6709\u4efb\u4f55\u5efa\u8bae\u6216\u610f\u89c1\uff0c\u6b22\u8fce\u63d0\u4ea4 issues \u8fdb\u884c\u8ba8\u8bba\u3002\u5f53\u7136\u6211\u4eec\u66f4\u671f\u5f85\u4e0e\u60a8\u5171\u540c\u534f\u4f5c\u5f00\u53d1\uff0c\u8ba9\u6587\u6863\u53d8\u5f97\u66f4\u52a0\u5b8c\u5584\u3002
"},{"location":"#_1","title":"\u4f7f\u7528\u65b9\u5f0f","text":""},{"location":"#1","title":"1. \u514b\u9686\u9879\u76ee","text":"git clone https://github.com/pyloong/pythonic-project-guidelines\n
"},{"location":"#2","title":"2. \u521d\u59cb\u5316\u73af\u5883","text":"\u9879\u76ee\u9884\u89c8\u9700\u8981\u5b89\u88c5 Python \u73af\u5883\u6765\u542f\u52a8 server\uff0c\u5f3a\u70c8\u5efa\u8bae\u4f7f\u7528 Python 3.10+ \u7684\u7248\u672c\u3002\u5982\u679c\u672c\u5730\u6ca1\u6709 Python \u73af\u5883\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528 Docker\u9884\u89c8\u670d\u52a1\u5668 \u6765\u542f\u52a8\u3002
"},{"location":"#21","title":"2.1 \u672c\u5730\u521d\u59cb\u5316","text":"\u521b\u5efa\u865a\u62df\u73af\u5883\uff1a
python3 -m venv .venv\nsource .venv/bin/activate\n
\u5b89\u88c5\u4f9d\u8d56\uff1a
pip install -r requirements.txt\n
"},{"location":"#22-docker","title":"2.2 \u4f7f\u7528 Docker \u521d\u59cb\u5316","text":"docker pull squidfunk/mkdocs-material:9.1.11\n
"},{"location":"#3","title":"3. \u9884\u89c8","text":""},{"location":"#31","title":"3.1 \u672c\u5730\u9884\u89c8","text":"mkdocs serve\n
"},{"location":"#32-docker","title":"3.2 \u4f7f\u7528 Docker \u9884\u89c8","text":"unix:
docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material:9.1.11\n
Windows:
docker run --rm -it -p 8000:8000 -v \"%cd%\":/docs squidfunk/mkdocs-material:9.1.11\n
"},{"location":"#_2","title":"\u534f\u4f5c\u89c4\u8303","text":"\u6587\u6863\u4f7f\u7528 Markdown \u7f16\u5199\uff0c\u4f7f\u7528 mkdocs \u914d\u5408 mkdocs-material \u4e3b\u9898\u6784\u5efa\u3002
- fork
- code
- pr
"},{"location":"quick_start/","title":"\u5feb\u901f\u4e0a\u624b","text":"\u8fd9\u662f\u4e00\u4e2a\u5feb\u901f\u4e0a\u624b\u7684\u5f00\u53d1\u6307\u5357\uff0c\u672c\u6587\u901a\u8fc7\u4e00\u4e2a\u5305\u542b\u4e3b\u8981\u77e5\u8bc6\u70b9\u7684\u7b80\u5355\u9879\u76ee\uff0c\u5411\u5f00\u53d1\u8005\u5c55\u793a\u4e00\u4e2a\u66f4\u7b26\u5408 Python \u89c4\u8303\u548c\u98ce\u683c\uff08Pythonic\uff09\u7684\u9879\u76ee\u5f00\u53d1\u6d41\u7a0b\u3002
\u793a\u4f8b\u9879\u76ee\u662f\u4e00\u4e2a\u5355\u8bcd\u7edf\u8ba1\u7684\u6f14\u793a\u7a0b\u5e8f\uff0c\u5982\u679c\u4f60\u60f3\u67e5\u770b\u5b8c\u6574\u793a\u4f8b\uff0c\u53ef\u4ee5\u6d4f\u89c8 Word Count \u9879\u76ee\u6e90\u7801\u3002
"},{"location":"quick_start/#1","title":"1. \u5f00\u53d1\u73af\u5883\u642d\u5efa","text":""},{"location":"quick_start/#11-python","title":"1.1 Python \u5f00\u53d1\u73af\u5883","text":"\u672c\u9879\u76ee\u4f7f\u7528 Python 3.10 \u3002\u5177\u4f53\u7248\u672c\u7684 Python \u73af\u5883\u53ef\u4ee5\u5728\u5b98\u7f51\u4e0b\u8f7d\u3002
"},{"location":"quick_start/#12","title":"1.2 \u5f00\u53d1\u5de5\u5177","text":"\u63a8\u8350\u4f7f\u7528 Pycharm \u5f00\u53d1\u5de5\u5177\uff0c\u53ef\u4ee5\u9009\u62e9\u514d\u8d39\u7684\u793e\u533a\u7248\u672c\u3002
Visual Studio Code \u662f\u5fae\u8f6f\u5f00\u53d1\u7684\u4e00\u6b3e\u514d\u8d39\u8f7b\u91cf\u7ea7\u6587\u672c\u7f16\u8f91\u5668\uff0c\u901a\u8fc7\u5b89\u88c5\u63d2\u4ef6\u53ef\u4ee5\u81ea\u5b9a\u4e49\u6210\u4e00\u6b3e\u529f\u80fd\u5f3a\u5927\u7684 IDE \u5f00\u53d1\u5de5\u5177\u3002\u76ee\u524d\u652f\u6301 Python \u7684\u63d2\u4ef6\u4f53\u7cfb\u5df2\u7ecf\u8f83\u4e3a\u5b8c\u5584\uff0c\u6b64\u65b9\u6848\u4e5f\u53ef\u4ee5\u4f5c\u4e3a\u5907\u7528\u3002
"},{"location":"quick_start/#13","title":"1.3 \u865a\u62df\u73af\u5883\u5de5\u5177","text":"\u63a8\u8350\u4f7f\u7528 Poetry \uff0c\u65e2\u5305\u542b\u4e86\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\u4e5f\u652f\u6301\u6253\u5305\u53d1\u5e03\u7b49\u529f\u80fd\u3002
\u5728\u5b89\u88c5\u597d Python \u73af\u5883\u540e\uff0c\u5e94\u8be5\u5728\u5168\u5c40\u73af\u5883\u4e2d\u5b89\u88c5 Poetry \u3002
sudo python -m pip install -U pip\nsudo pip install -U poetry\n
"},{"location":"quick_start/#14","title":"1.4 \u521d\u59cb\u5316\u9879\u76ee","text":"cookiecutter \u662f\u4e00\u4e2a\u901a\u8fc7\u9879\u76ee\u6a21\u677f\u521b\u5efa\u9879\u76ee\u7684\u547d\u4ee4\u884c\u5de5\u5177\u3002
\u5b89\u88c5 cookiecutter
sudo pip3 install -U cookiecutter\n
\u521d\u59cb\u5316\u9879\u76ee
cd workspace\ncookiecutter https://github.com/pyloong/cookiecutter-pythonic-project\n
\u8fd0\u884c\u547d\u4ee4\u540e\u4f1a\u51fa\u73b0\u4e0b\u9762\u7684\u914d\u7f6e\u8fc7\u7a0b\uff0c\u5982\u679c\u4f60\u4e0d\u6e05\u695a\u914d\u7f6e\u7684\u5177\u4f53\u7528\u9014\uff0c\u53ef\u4ee5\u76f4\u63a5\u6309\u56de\u8f66\u4f7f\u7528\u9ed8\u8ba4\u914d\u7f6e\uff0c\u9ed8\u8ba4\u914d\u7f6e\u4f7f\u7528\u9879\u76ee\u6a21\u677f\u521d\u59cb\u503c\u3002
\u276f cookiecutter https://github.com/pyloong/cookiecutter-pythonic-project\nproject_name [My Project]: Word Count\nproject_slug [word_count]: project_description [My Awesome Project!]: Word Count Project.\nauthor_name [Author]: test\nauthor_email [author@example.com]: test@example.com\nversion [0.1.0]: Select python_version:\n1 - 3.10\n2 - 3.11\nChoose from 1, 2 [1]: use_src_layout [y]: use_poetry [y]: use_docker [n]: Select ci_tools:\n1 - none\n2 - Gitlab\n3 - Github\nChoose from 1, 2, 3 [1]: init_skeleton [n]:\n
\u5982\u679c\u4f60\u5728\u4f7f\u7528\u9879\u76ee\u6a21\u677f\u8fc7\u7a0b\u4e2d\u6709\u4efb\u4f55\u95ee\u9898\u6216\u7591\u95ee\uff0c\u53ef\u4ee5\u901a\u8fc7\u53d1\u8d77 issues \u8fdb\u884c\u53cd\u9988\u3002
\u751f\u6210\u540e\u7684\u9879\u76ee\u7ed3\u6784\u5982\u4e0b\uff1a
word_count\n\u251c\u2500\u2500 .editorconfig\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 .pre-commit-config.yaml\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 docs\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 development.md\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 src\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 word_count\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 tests\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 conftest.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 settings.yml\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 test_version.py\n\u2514\u2500\u2500 tox.ini\n\n5 directories, 13 files\n
\u751f\u6210\u9879\u76ee\u7684 src
\u76ee\u5f55\u4e0b\u6709\u4e00\u4e2a\u9879\u76ee\u6a21\u5757\uff0c\u7528\u6765\u5b58\u653e\u9879\u76ee\u6e90\u4ee3\u7801\uff0c tests
\u76ee\u5f55\u7528\u6765\u7f16\u5199\u6a21\u5757\u7684\u76f8\u5173\u6d4b\u8bd5\u4ee3\u7801\u3002
pyproject.toml
\u5305\u542b\u9879\u76ee\u521d\u59cb\u4f9d\u8d56\uff0c\u548c\u9879\u76ee\u7684\u63cf\u8ff0\u4fe1\u606f\uff0ctox.ini
\u5b9a\u4e49\u4e86\u4efb\u52a1\u81ea\u52a8\u5316\u6267\u884c\u903b\u8f91\u3002
"},{"location":"quick_start/#15","title":"1.5 \u521d\u59cb\u5316\u9879\u76ee\u73af\u5883","text":"\u4f7f\u7528 poetry \u521d\u59cb\u5316\u4e00\u4e2a\u865a\u62df\u73af\u5883\u3002
cd word_count\npoetry install -v\n
\u521d\u59cb\u5316\u5b8c\u6210\u540e\uff0c\u4f1a\u751f\u6210\u4e00\u4e2a poetry.lock
\uff0c\u53ef\u4ee5\u7528\u6765\u9501\u5b9a\u751f\u4ea7\u73af\u5883\u5b89\u88c5\u5305\u7684\u7248\u672c\u548c\u4f9d\u8d56\u4fe1\u606f\u3002
"},{"location":"quick_start/#16-git","title":"1.6 \u521d\u59cb\u5316 Git","text":"\u63a8\u8350\u4f7f\u7528 Git \u5bf9\u9879\u76ee\u8fdb\u884c\u7248\u672c\u7ba1\u7406\u3002\u6240\u4ee5\u9700\u8981\u63d0\u524d\u5b89\u88c5 Git \uff0c\u5e76\u719f\u6089\u5e38\u7528\u7684 Git \u6982\u5ff5\u548c Git \u547d\u4ee4\u3002
git init\ngit config user.name test\ngit config user.email test@example.com\n\n# \u521d\u59cb\u5316\u9879\u76ee\u63d0\u4ea4\ngit add .\ngit commit -m \"feat: \u521d\u59cb\u5316\u9879\u76ee\u63d0\u4ea4\"\n
"},{"location":"quick_start/#17","title":"1.7 \u4f1a\u7528\u5230\u7684\u5176\u4ed6\u5de5\u5177","text":"\u5728\u751f\u6210\u7684 pyproject.toml
\u6587\u4ef6\u4e2d\uff0c\u9ed8\u8ba4\u6dfb\u52a0\u4e86\u4e00\u4e9b\u5f00\u53d1\u73af\u5883\u4e2d\u5e38\u7528\u7684\u5de5\u5177\u3002
isort
: isort \u662f\u4e00\u4e2a\u81ea\u52a8\u683c\u5f0f\u5316\u5bfc\u5165\u5de5\u5177 pylint
: pylint \u662f\u4e00\u4e2a\u68c0\u6d4b\u4ee3\u7801\u98ce\u683c\u5de5\u5177 pytest
: pytest \u662f\u4e00\u4e2a\u66f4\u52a0\u6613\u7528\u7684\u6d4b\u8bd5\u6846\u67b6\uff0c\u517c\u5bb9 unittest
\u6d4b\u8bd5\u6846\u67b6 pytest-cov
: pytest-cov \u662f pytest
\u7684 Coverage \u63d2\u4ef6\uff0c\u7528\u6765\u7edf\u8ba1\u6d4b\u8bd5\u8986\u76d6\u7387 mkdocs
: mkdocs \u662f\u4e00\u4e2a\u9879\u76ee\u6587\u6863\u6784\u5efa\u5de5\u5177\uff0c\u4f7f\u7528 markdown \u7f16\u5199\u5185\u5bb9\uff0c\u6784\u5efa\u751f\u6210\u6587\u6863\u9875\u9762\u3002 mkdocs-material
: mkdocs-material \u662f\u57fa\u4e8e mkdocs \u6784\u5efa\u6587\u6863\uff0c\u5e76\u63d0\u4f9b\u73b0\u4ee3\u5316\u4e3b\u9898\u7684\u5e93\u3002 tox
: tox \u662f\u4e00\u4e2a\u4efb\u52a1\u81ea\u52a8\u5316\u5de5\u5177
\u5982\u679c\u60f3\u8981\u4e86\u89e3\u76f8\u5173\u7684\u529f\u80fd\uff0c\u53ef\u4ee5\u9605\u8bfb\u5bf9\u5e94\u7684\u6280\u672f\u8bf4\u660e\u6587\u6863\u3002
"},{"location":"quick_start/#2","title":"2. \u529f\u80fd\u5f00\u53d1","text":"\u9996\u5148\u5c06\u9879\u76ee\u4ee5\u53ef\u7f16\u8f91\u65b9\u5f0f\u5b89\u88c5\u5230\u73af\u5883\u4e2d\uff1a
poetry install\n
\u8fd9\u6837\u505a\u7684\u76ee\u7684\u662f\u5c06 src
\u4e0b\u7684\u5305\u5b89\u88c5\u5230 Python \u73af\u5883\u4e2d\uff0c\u5426\u5219\u65e0\u6cd5\u6b63\u5e38\u5bfc\u5165\u5305\u4e2d\u7684\u6a21\u5757\u3002
"},{"location":"quick_start/#21","title":"2.1 \u529f\u80fd\u9700\u6c42","text":"\u63d0\u4f9b\u4e00\u4e2a\u4ece\u6587\u672c\u6587\u4ef6\u8bfb\u53d6\u6570\u636e\uff0c\u6570\u636e\u4ee5\u7a7a\u683c\u5206\u5272\u5355\u8bcd\uff0c\u7136\u540e\u7edf\u8ba1\u6587\u4ef6\u4e2d\u7684\u5355\u8bcd\u6570\u91cf\uff0c\u5e76\u5c06\u7ed3\u679c\u5199\u5165\u5230\u76ee\u6807\u6587\u4ef6\u4e2d\u3002
"},{"location":"quick_start/#22","title":"2.2 \u7f16\u5199\u8ba1\u6570\u5668","text":"\u5728 src/word_count/
\u4e0b\u521b\u5efa counter.py
\u6587\u4ef6\uff0c\u540c\u65f6\u52a0\u5165\u5982\u4e0b\u5185\u5bb9\uff1a
\"\"\"Count a file \"\"\"\nimport logging\nfrom collections.abc import Generator\nfrom pathlib import Path\n# Config root logger\nlogging.basicConfig(\nlevel=logging.DEBUG,\nformat='%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n)\ndef count(source_file: str, dest_file: str):\n\"\"\"\n Count source\n :param source_file:\n :param dest_file:\n :return:\n \"\"\"\nwords = read_from_file(Path(source_file))\ntotal = 0\nfor _ in words:\ntotal += 1\nwrite_to_file(Path(dest_file), total)\ndef read_from_file(source_file: Path) -> Generator[str, None, None]:\n\"\"\"\n :param source_file:\n :return:\n \"\"\"\n# Read source_file\nlogging.debug('Read file: %s', source_file)\nwith open(source_file, 'r', encoding='utf-8') as source_obj:\nfor line in source_obj:\nfor word in line.split(' '):\nyield word\ndef write_to_file(dest_file: Path, total_words: int) -> None:\n\"\"\"\n Write result to file\n :param dest_file:\n :param total_words:\n :return:\n \"\"\"\nlogging.debug('Count %s words, write to %d', dest_file, total_words)\nwith open(dest_file, 'w', encoding='utf-8') as dest_obj:\ndest_obj.write(f'Total count: {total_words}')\n
"},{"location":"quick_start/#221","title":"2.2.1 \u5bfc\u5165\u683c\u5f0f\u5316","text":"\u5728\u9879\u76ee\u6839\u76ee\u5f55\u8fd0\u884c isort \u5bf9\u5bfc\u5165\u8fdb\u884c\u683c\u5f0f\u5316\u3002
isort .\n
\u6b64\u64cd\u4f5c\u4f1a\u81ea\u52a8\u4fee\u6539\u4ee3\u7801\uff0c\u5c06\u5bfc\u5165\u7684\u5305\u683c\u5f0f\u5316\u3002\u5982\u679c\u60f3\u67e5\u770b\u533a\u522b\uff0c\u53ef\u4ee5\u8fd0\u884c\u5982\u4e0b\u547d\u4ee4\uff1a
isort . --check-only --diff\n
"},{"location":"quick_start/#222","title":"2.2.2 \u4ee3\u7801\u98ce\u683c\u68c0\u67e5","text":"\u5728\u9879\u76ee\u6839\u76ee\u5f55\u8fd0\u884c pylint \u68c0\u67e5\u4ee3\u7801\u662f\u5426\u89c4\u8303\uff0c\u662f\u5426\u7b26\u5408 PEP8 \u6807\u51c6\u3002
pylint tests src\n
\u6b64\u64cd\u4f5c\u4f1a\u5217\u51fa\u4ee3\u7801\u4e2d\u4e0d\u7b26\u5408\u89c4\u8303\u7684\u90e8\u5206\uff0c\u5e76\u663e\u793a\u5bf9\u5e94\u7684\u89c4\u8303\u540d\u79f0\u3002\u53ef\u4ee5\u5728\u8fd9\u91cc\u627e\u5230\u6240\u6709\u89c4\u5219\u3002
\u5728\u5b8c\u6210\u4fee\u6539\u540e\u518d\u6b21\u8fd0\u884c\u4e24\u4e2a\u547d\u4ee4\uff0c\u76f4\u5230\u90fd\u6ca1\u6709\u5f02\u5e38\u8f93\u51fa\u4e3a\u6b62\u3002
"},{"location":"quick_start/#223","title":"2.2.3 \u6d4b\u8bd5","text":"\u5982\u679c\u4f60\u4f7f\u7528\u7684\u662f Pycharm \u5f00\u53d1\uff0c\u53ef\u4ee5\u901a\u8fc7\u70b9\u51fb File
--> Settings
--> Tools
--> Python Integrated Tools
--> Testing
--> Default runner
\u9009\u62e9\u6d4b\u8bd5\u6846\u67b6\uff0c\u63a8\u8350\u4f7f\u7528 pytest
\u3002
\u4e3a\u4e86\u65b9\u4fbf\u4f7f\u7528 mock
\u9700\u8981\u5b89\u88c5 pytest-mock
\u6a21\u5757\uff0c\u53ef\u4ee5\u5728 pytest
\u7684 fixture
\u7279\u6027\u4e0a\u4f7f\u7528 mock
\u3002
\u5b89\u88c5\u5f00\u53d1\u73af\u5883\u4f9d\u8d56\uff1a
poetry add --group dev pytest-mock\n
\u6dfb\u52a0\u6d4b\u8bd5\u914d\u7f6e\uff0c\u5728 tests/conftest.py
\u4e2d\u52a0\u5165\uff1a
\"\"\"Test config\"\"\"\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\nimport pytest\n@pytest.fixture\ndef mock_path() -> Path:\n\"\"\"Mock a path, and clean when unit test done.\"\"\"\nwith TemporaryDirectory() as temp_path:\nyield Path(temp_path)\n
\u5728 tests/
\u4e0b\u6dfb\u52a0\u4e0e src/word_count
\u76ee\u5f55\u4e2d\u6587\u4ef6\u540d\u76f8\u540c\u7684\u6587\u4ef6\uff0c\u5e76\u5728\u6587\u4ef6\u540d\u524d\u6dfb\u52a0 test_
\u524d\u7f00\u3002
\u6dfb\u52a0\u6587\u4ef6 tests/test_counter.py
\uff1a
\"\"\"Test counter\"\"\"\nfrom pathlib import Path\nimport pytest\nfrom word_count.counter import count, read_from_file, write_to_file\n@pytest.fixture(name='mock_source_file')\ndef fixture_mock_source_file(mock_path) -> Path:\n\"\"\"mock source_file, this file has two words.\"\"\"\nwords = ['hello', ' ', 'words']\nsource_file = mock_path / 'source.txt'\nwith open(source_file, 'w', encoding='utf-8') as obj:\nobj.write(''.join(words))\nyield source_file\ndef test_read_from_file(mock_source_file):\n\"\"\"Test read_from_file\"\"\"\nresult = read_from_file(mock_source_file)\nassert sum(1 for _ in result) == 2\ndef test_write_to_file(mock_path):\n\"\"\"Test write_to_file\"\"\"\ndest_file = mock_path / 'dest.txt'\nwrite_to_file(dest_file, 100)\nwith open(dest_file, 'r', encoding='utf-8') as obj:\ntxt = obj.read()\nassert 'Total count: 100' in txt\ndef test_count(mocker, mock_path, mock_source_file):\n\"\"\"Test count\"\"\"\nmock_read_from_file = mocker.patch(\n'word_count.counter.read_from_file',\nreturn_value=list(range(10))\n)\nmock_write_to_file = mocker.patch(\n'word_count.counter.write_to_file'\n)\ndest_file = mock_path / 'dest.txt'\ncount(str(mock_source_file), str(dest_file))\nmock_read_from_file.assert_called_once_with(mock_source_file)\nmock_write_to_file.assert_called_once_with(dest_file, 10)\n
\u8fd0\u884c pytest
\uff0c\u8ba9\u6d4b\u8bd5\u6b63\u786e\u8fd0\u884c\u3002\u5982\u679c\u6d4b\u8bd5\u7528\u4f8b\u5931\u8d25\uff0c\u9700\u8981\u6839\u636e\u51fa\u9519\u5806\u6808\u627e\u5230\u95ee\u9898\u539f\u56e0\uff0c\u89e3\u51b3\u6389\u540e\u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u547d\u4ee4\uff0c\u76f4\u5230\u4ee3\u7801\u6d4b\u8bd5\u901a\u8fc7\u3002
\u7136\u540e\u8fd0\u884c isort
\u548c pylint src tests
\u683c\u5f0f\u5316\u4ee3\u7801\u5e76\u68c0\u67e5\u4ee3\u7801\u98ce\u683c\u3002
"},{"location":"quick_start/#224","title":"2.2.4 \u63d0\u4ea4\u4ee3\u7801","text":"\u4e00\u4e2a\u529f\u80fd\u7279\u6027\u5f00\u53d1\u5b8c\u6210\u540e\uff0c\u9700\u8981\u63d0\u4ea4\u4ee3\u7801\u6765\u4fdd\u5b58\u8bb0\u5f55\uff0c\u907f\u514d\u610f\u5916\u64cd\u4f5c\u3002
git add .\ngit commit -m \"feat(counter): \u589e\u52a0 Counter \u903b\u8f91\uff0c\u5e76\u5b8c\u6210\u6d4b\u8bd5\u3002\"\n
"},{"location":"quick_start/#23","title":"2.3 \u7f16\u5199\u547d\u4ee4\u884c\u5165\u53e3","text":"\u5728 src/word_count/
\u76ee\u5f55\u4e0b\uff0c\u521b\u5efa cmdline.py
\u6587\u4ef6\uff0c\u52a0\u5165\u5982\u4e0b\u5185\u5bb9\uff1a
\"\"\"Cmdline\"\"\"\nimport argparse\nimport sys\nfrom word_count.counter import count\ndef init_args() -> argparse.Namespace:\n\"\"\"Init argument and parse\"\"\"\nparser = argparse.ArgumentParser()\nparser.add_argument('-s', '--source', required=True, help='Source file used for count.')\nparser.add_argument('-d', '--dest', required=True, help='Dest file used for count result')\nreturn parser.parse_args(sys.argv[1:])\ndef main():\n\"\"\"Execute\"\"\"\nargs = init_args()\ncount(args.source, args.dest)\nif __name__ == '__main__':\nmain()\n
\u8fd0\u884c isort
\u548c pylint
\u683c\u5f0f\u5316\u4ee3\u7801\u5e76\u68c0\u67e5\u4ee3\u7801\u98ce\u683c\u3002
"},{"location":"quick_start/#231","title":"2.3.1 \u6d4b\u8bd5","text":"\u521b\u5efa tests/test_cmdline.py
\u6587\u4ef6\uff0c\u52a0\u5165\u5982\u4e0b\u5185\u5bb9\uff1a
\"\"\"Test cmdline\"\"\"\nimport sys\nimport pytest\nfrom word_count import cmdline\ndef test_help(mocker, capsys):\n\"\"\"test help command\"\"\"\nargs = ['word_count', '-h']\nmocker.patch.object(sys, 'argv', args)\nwith pytest.raises(SystemExit) as ex:\ncmdline.main()\nassert ex.value.code == 0\nouterr = capsys.readouterr()\nassert '-s SOURCE' in outerr.out\nassert '-d DEST' in outerr.out\ndef test_only_pass_source(mocker, capsys):\n\"\"\"test only pass -s \"\"\"\nargs = ['word_count', '-s', 'foo']\nmocker.patch.object(sys, 'argv', args)\nwith pytest.raises(SystemExit) as ex:\ncmdline.main()\nassert ex.value.code == 2\nouterr = capsys.readouterr()\nassert 'the following arguments are required: -d' in outerr.err\ndef test_only_pass_dest(mocker, capsys):\n\"\"\"test only pass -d\"\"\"\nargs = ['word_count', '-d', 'foo']\nmocker.patch.object(sys, 'argv', args)\nwith pytest.raises(SystemExit) as ex:\ncmdline.main()\nassert ex.value.code == 2\nouterr = capsys.readouterr()\nassert 'the following arguments are required: -s' in outerr.err\ndef test_main(mocker):\n\"\"\"test cmdline, and everything is fine.\"\"\"\nargs = ['word_count', '-s', 'foo', '-d', 'bar']\nmocker.patch.object(sys, 'argv', args)\nmock_count = mocker.patch('word_count.cmdline.count')\ncmdline.main()\nmock_count.assert_called_once()\n
\u8fd0\u884c pytest
\uff0c\u8ba9\u6d4b\u8bd5\u6b63\u786e\u8fd0\u884c\u3002
\u8fd0\u884c isort
\u548c pylint
\u683c\u5f0f\u5316\u4ee3\u7801\u5e76\u68c0\u67e5\u4ee3\u7801\u98ce\u683c\u3002
"},{"location":"quick_start/#232","title":"2.3.2 \u63d0\u4ea4\u4ee3\u7801","text":"git add .\ngit commit -m \"feat(cmdline): \u589e\u52a0 cmdline \u903b\u8f91\uff0c\u5e76\u5b8c\u6210\u6d4b\u8bd5\u3002\"\n
"},{"location":"quick_start/#24","title":"2.4 \u603b\u7ed3","text":"\u81f3\u6b64\uff0c\u6211\u4eec\u7684\u529f\u80fd\u5df2\u7ecf\u5f00\u53d1\u5b8c\u6210\u3002\u5728\u6574\u4e2a\u5f00\u53d1\u8fc7\u7a0b\u4e2d\uff0c\u6211\u4eec\u9075\u5faa\u4e86 \u201c\u6dfb\u52a0\u529f\u80fd\u7279\u6027\u201d => \u201c\u4ee3\u7801\u98ce\u683c\u68c0\u67e5\u201d => \u201c\u5355\u5143\u6d4b\u8bd5\u201d \u7684\u5f00\u53d1\u6d41\u7a0b\u3002
\u5982\u679c\u611f\u89c9\u6bcf\u6b21\u8fd0\u884c\u591a\u4e2a\u547d\u4ee4\u6bd4\u8f83\u7e41\u7410\uff0c\u53ef\u4ee5\u5728\u9879\u76ee\u6839\u76ee\u5f55\u4e2d\u8fd0\u884c tox
\u81ea\u52a8\u5316\u5b8c\u6210\u4ee3\u7801\u6d4b\u8bd5\u3001\u5bfc\u5305\u68c0\u67e5\u548c\u4ee3\u7801\u98ce\u683c\u68c0\u67e5\u3002
tox\n
\u73b0\u5728\u53ef\u4ee5\u5728\u7ec8\u7aef\u4e2d\u8fd0\u884c\u5355\u8bcd\u7edf\u8ba1\uff1a
python src/word_count/cmdline.py -s LICENSE -d /tmp/res.txt\n
"},{"location":"quick_start/#25","title":"2.5 \u6253\u5305\u53d1\u5e03","text":"\u5982\u679c\u5e0c\u671b\u522b\u4eba\u80fd\u66f4\u65b9\u4fbf\u7684\u4f7f\u7528\u9879\u76ee\uff0c\u53ef\u4ee5\u5c06\u9879\u76ee\u6253\u5305\u53d1\u5e03\u5230 pypi \u4e2d\uff0c\u7136\u540e\u5728\u9700\u8981\u4f7f\u7528\u7684\u5730\u65b9\u8fd0\u884c pip install -U word-count
\u3002
\u4f46\u662f\u5b89\u88c5\u5230\u73af\u5883\u540e\u53bb\u8fd0\u884c cmdline.py
\u4f1a\u6bd4\u8f83\u9ebb\u70e6\uff0c\u6240\u4ee5\u9700\u8981\u5c06 cmdline.py
\u6ce8\u518c\u6210\u53ef\u6267\u884c\u547d\u4ee4\u3002
\u4fee\u6539 pyproject.toml
\uff0c\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
[tool.poetry.plugins.console_scripts]\nword_count = \"word_count.cmdline:main\"\n
\u5f53\u4f7f\u7528 pip
\u547d\u4ee4\u5c06\u9879\u76ee\u5305\u5b89\u88c5\u5230\u73af\u5883\u540e\uff0c\u4f1a\u81ea\u52a8\u6ce8\u518c\u4e00\u4e2a word_count
\u7684\u53ef\u6267\u884c\u547d\u4ee4\u3002
\u518d\u6b21\u5c06\u672c\u5730\u9879\u76ee\u4ee5\u53ef\u7f16\u8f91\u65b9\u5f0f\u5b89\u88c5\u5230\u5f53\u524d Python \u73af\u5883\uff1a
poetry install\n
\u7136\u540e\u5c31\u53ef\u4ee5\u6b63\u5e38\u4f7f\u7528 word_count
\u547d\u4ee4\uff1a
$ word_count -h\nusage: word_count [-h] -s SOURCE -d DEST\n\noptional arguments:\n -h, --help show this help message and exit\n -s SOURCE, --source SOURCE\n Source file used for count.\n -d DEST, --dest DEST Dest file used for count result\n
"},{"location":"quick_start/#251","title":"2.5.1 \u6253\u5305","text":"\u8fd0\u884c\u6253\u5305\u547d\u4ee4\uff1a
poetry build
sdist
\u4f1a\u5c06\u9879\u76ee\u6253\u5305\u6210\u6e90\u7801\u5305\uff0c bdist_wheel
\u4f1a\u5c06\u9879\u76ee\u6253\u5305\u6210\u7f16\u8bd1\u540e\u7684\u4e8c\u8fdb\u5236\u5305\u3002
\u6253\u5305\u540e\u7684\u6587\u4ef6\u5728 dist
\u76ee\u5f55\u4e2d\u3002\u53ef\u4ee5\u76f4\u63a5\u5728\u5176\u4ed6\u5730\u65b9\u8fd0\u884c pip install word_count.wheel
\u5b89\u88c5\u3002
"},{"location":"quick_start/#252","title":"2.5.2 \u53d1\u5e03","text":"\u5c06\u5f00\u53d1\u597d\u7684\u9879\u76ee\u53d1\u5e03\u5230\u7d22\u5f15\u4ed3\u5e93\uff0c\u6216\u5185\u7f51\u7684\u79c1\u6709\u4ed3\u5e93\u3002
poetry publish\n
\u9ed8\u8ba4\u4f1a\u5c06\u9879\u76ee\u53d1\u5e03\u5230 pypi \u4e2d\uff0c\u6240\u4ee5\u9700\u8981\u6709\u5bf9\u5e94\u7684\u767b\u5f55\u8d26\u53f7\u3002
"},{"location":"datadevelop/quick_start/etl_develop/","title":"\u5e94\u7528\u5f00\u53d1","text":"\u63d0\u4f9bETL\u5de5\u7a0b\u5316\u7684\u9879\u76ee\u793a\u4f8b\uff0c\u5e2e\u52a9\u521d\u5b66\u8005\u5feb\u901f\u7406\u89e3\u548c\u5b66\u4e60ETL\u5b8c\u6574\u7684\u5de5\u7a0b\u5316\u5f00\u53d1\u3002
"},{"location":"datadevelop/quick_start/etl_develop/#_2","title":"\u4efb\u52a1\u9700\u6c42","text":"\u73b0\u6709\u6c7d\u8f66\u4fe1\u606f\u6570\u636ecar_price.csv
-
\u5bf9CarName
\u5b57\u6bb5\u5305\u542b[dirty data]
\u6570\u636e\u8fdb\u884c\u7ea0\u6b63\uff0c\u53bb\u9664\u5b57\u7b26\u4e32[dirty data]
-
\u5220\u9664price
\u5c0f\u4e8e10000
\u7684\u6c7d\u8f66\u6570\u636e
-
\u6700\u7ec8\u7ed3\u679cSchema\uff1acar_id
, symboling
, car_name
, price
, \u5c06\u7ed3\u679c\u5bfc\u51fajson
\u6587\u4ef6
\u5728\u547d\u4ee4\u884c\u4f7f\u7528cookiecutter
\u521b\u5efa\u9879\u76ee\u9aa8\u67b6:
\u276f cookiecutter https://github.com/pyloong/cookiecutter-pythonic-project-bigdata-etl\nproject_name [My Project]: Automotive Data Etl\nproject_slug [automotive_data_etl]:\nproject_description [My Awesome Project!]: This is my first etl package, i love it.\nauthor_name [Author]: ming\nauthor_email [ming@example.com]: ming@gmail.com\nversion [0.1.0]:\nSelect python_version:\n1 - 3.10\n2 - 3.9\nChoose from 1, 2 [1]:\nuse_src_layout [y]:\nuse_poetry [y]:\nuse_docker [n]:\nSelect ci_tools:\n1 - none\n2 - Gitlab\n3 - Github\nChoose from 1, 2, 3 [1]:\nSelect use_framework:\n1 - none\n2 - pyspark\nChoose from 1, 2 [1]: 2\n
"},{"location":"datadevelop/quick_start/etl_develop/#task","title":"Task\u7c7b","text":"\u521b\u5efa\u6c7d\u8f66\u6570\u636eETL\u4efb\u52a1AutomotiveDataTask
\u7c7b\uff0csrc/automotive_data_etl/tasks/automotive_task/task.py
task.py\"\"\"Processing car data task.\"\"\"\nimport logging\nfrom pyspark.sql import DataFrame\nfrom automotive_data_etl.tasks.abstract.task import AbstractTask\nfrom automotive_data_etl.tasks.automotive_task.automotive_transform import AutomotiveDataTransform\nclass AutomotiveDataTask(AbstractTask):\n\"\"\"Processing car data task.\"\"\"\ndef __init__(self):\nsuper().__init__()\nself.spark = self.ctx.get_spark_session()\ndef _extract(self) -> DataFrame:\n\"\"\"Read CSV file return DataFrame\"\"\"\ndf: DataFrame = self.spark.read.csv(\nself.settings.input_path,\nencoding='utf-8',\nheader=True,\ninferSchema=True\n)\nthis.logger.info(f'Extract data from {self.settings.input_path}')\nreturn df\ndef _transform(self, df: DataFrame) -> DataFrame:\n\"\"\"execute CarTransform transform function\"\"\"\nreturn AutomotiveDataTransform().transform(df)\ndef _load(self, df: DataFrame) -> None:\n\"\"\"Load final data to output path\"\"\"\ndf.write.json(self.settings.output_path, mode='overwrite', encoding='utf-8')\nthis.logger.info(f'Load data to {self.settings.output_path}')\n
\u6bcf\u4e00\u4e2aTask
\u4efb\u52a1\u90fd\u4f1a\u7ecf\u8fc7\u201c\u8f93\u5165\u201d\u3001\u201c\u8f6c\u6362\u201d\u548c\u201c\u8f93\u51fa\u201d\u7684\u8fc7\u7a0b\uff0c\u5b9e\u73b0AbstractTask
\u4e2d\u7684_extract
\u3001_transform
\u3001_load
\u62bd\u8c61\u65b9\u6cd5
_extract
\uff1a\u8bfb\u53d6tmp/input/car_price.csv
CSV\u6587\u4ef6 _transform
\uff1a\u6267\u884c\u5c06\u5b9e\u73b0\u7684Transform
\u7c7b\u7684transform
\u65b9\u6cd5 _load
\uff1a\u5c06DataFrame\u4ee5Json\u683c\u5f0f\u5199\u5165tmp/output
\u76ee\u5f55\u4e0b
"},{"location":"datadevelop/quick_start/etl_develop/#transform","title":"Transform\u7c7b","text":"\u521b\u5efa\u6c7d\u8f66\u6570\u636eAutomotiveDataTransform
\u7c7b\uff0csrc/automotive_data_etl/tasks/automotive_task/automotive_transform.py
automotive_transform.py\"\"\"Car data Transformation.\"\"\"\nfrom functools import reduce\nfrom pyspark.sql import DataFrame\nfrom pyspark.sql.functions import col\nfrom pyspark.sql.functions import udf\nfrom pyspark.sql.types import StringType\nfrom automotive_data_etl.tasks.abstract.transform import AbstractTransform\nclass AutomotiveDataTransform(AbstractTransform):\n\"\"\"Car data Transformation.\"\"\"\ndef transform(self, df: DataFrame) -> DataFrame:\n\"\"\"Execute the transform process\"\"\"\ntransformations = (\nself._filter_price,\nself._process_car_name,\nself._select_final_columns,\n)\nreturn reduce(DataFrame.transform, transformations, df) # type: ignore\n@staticmethod\ndef _filter_price(df: DataFrame) -> DataFrame:\n\"\"\"Filter results price > 10000\"\"\"\nreturn df.filter(col('price') > 10000)\n@staticmethod\ndef _process_car_name(df: DataFrame) -> DataFrame:\n\"\"\"Clean [dirty data] from CarName\"\"\"\nres_df = df.withColumn('CarName', _name_replace_udf(col('CarName')).alias('CarName'))\nreturn res_df\n@staticmethod\ndef _select_final_columns(df: DataFrame) -> DataFrame:\n\"\"\"Car price data dataframe select final columns\"\"\"\nreturn df.select(\ncol('car_ID').alias('car_id'),\ncol('symboling'),\ncol('CarName').alias('car_name'),\ncol('price'),\n)\n@udf(returnType=StringType())\ndef _name_replace_udf(car_name):\n\"\"\"Clean [dirty data] udf\"\"\"\nif not car_name:\nreturn None\nerr_str = '[dirty data]'\nif err_str not in car_name:\nreturn car_name\ncar_name = car_name.replace(err_str, '')\nreturn car_name\n
AutomotiveDataTransform
\u7c7b\uff0c\u5b9e\u73b0\u4ee5\u4e0b\u65b9\u6cd5\uff1a
_filter_price
\u7684\u529f\u80fd\u662f\u7b5b\u9009 price
> 10000 \u7684\u6570\u636e _process_car_name
\u7684\u529f\u80fd\u662f\u4f7f\u7528udf\u65b9\u6cd5_name_replace_udf
\uff0c\u5c06CarName
\u5b57\u6bb5\u4e2d\u5305\u542b\u810f\u6570\u636e[dirty_data]
\u7684\u5185\u5bb9\u8fdb\u884c\u5904\u7406 _select_final_columns
\u65b9\u6cd5\u662f\u67e5\u8be2\u5e76\u8fd4\u56decar_id
,symboling
,car_name
,price
\u6570\u636e transform
\u7684\u529f\u80fd\u662f\u6267\u884c\u5904\u7406\u6d41\u7a0b\uff0c\u8fd4\u56de\u6570\u636e\u7ed3\u679c\uff0c\u81f3\u6b64\u5b8c\u6210\u6c7d\u8f66\u6570\u636e\u8f6c\u6362\u8fc7\u7a0b
"},{"location":"datadevelop/quick_start/etl_develop/#_3","title":"\u914d\u7f6e","text":"\u5c06\u5982\u4e0b\u914d\u7f6e\u66f4\u65b0\u5230\u914d\u7f6e\u6587\u4ef6\u4e2d\uff0c\u56e0\u4e3a\u9879\u76ee\u9ed8\u8ba4\u4f7f\u7528dev\u73af\u5883\u914d\u7f6e\uff0c\u5219\u9700\u8981\u5728configs/dev.toml
\u4e2d\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
# spark configs\nspark_master = 'local[*]'\nspark_config.spark.driver.memory = '3G'\nspark_config.spark.executor.memory = '16G'\nspark_config.spark.sql.debug.maxToStringFields = 100\n
"},{"location":"datadevelop/quick_start/initialization/","title":"\u521d\u59cb\u5316\u9879\u76ee","text":"\u672c\u6587\u901a\u8fc7\u4e00\u4e2a\u5305\u542b\u4e3b\u8981\u77e5\u8bc6\u70b9\u7684\u7b80\u5355\u9879\u76ee\uff0c\u5411\u5f00\u53d1\u8005\u5c55\u793a\u4e00\u4e2a\u901a\u7528\u3001\u89c4\u8303\u548c\u6613\u4e8e\u7406\u89e3\u7684ETL\u7684\u9879\u76ee\u5f00\u53d1\u6d41\u7a0b\u3002 \u793a\u4f8b\u9879\u76ee\u4f7f\u7528Pyspark
\u5c06\u672c\u5730\u6587\u4ef6\u8fdb\u884c\u9884\u5904\u7406\uff0c\u5e76\u5c06\u7ed3\u679c\u5bfc\u51fa\u6587\u4ef6\u7684\u6f14\u793a\u7a0b\u5e8f\u3002
"},{"location":"datadevelop/quick_start/initialization/#_2","title":"\u521b\u5efa\u9879\u76ee\u9aa8\u67b6","text":"\u4f7f\u7528 cookiecutter \u52a0\u8f7d\u9879\u76ee\u6a21\u677f\u3002\u901a\u8fc7\u4ea4\u4e92\u64cd\u4f5c\uff0c\u53ef\u4ee5\u9009\u62e9\u4f7f\u7528\u7684\u529f\u80fd\u3002
cookiecutter https://github.com/pyloong/cookiecutter-pythonic-project-bigdata-etl\n
"},{"location":"datadevelop/quick_start/initialization/#_3","title":"\u521b\u5efa\u865a\u62df\u73af\u5883","text":"\u5207\u6362\u5230\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\uff0c\u9879\u76ee\u4f7f\u7528 poetry \u7ba1\u7406\u865a\u62df\u73af\u5883\uff0c\u8fd0\u884c\u547d\u4ee4\u81ea\u52a8\u521b\u5efa\u865a\u62df\u73af\u5883\uff0c\u540c\u65f6\u5b89\u88c5\u5f00\u53d1\u73af\u5883\u4f9d\u8d56
poetry install\n
"},{"location":"datadevelop/quick_start/initialization/#ide","title":"IDE\u9879\u76ee\u521d\u59cb\u5316","text":""},{"location":"datadevelop/quick_start/initialization/#_4","title":"\u52a0\u8f7d\u865a\u62df\u73af\u5883","text":"\u4f7f\u7528Pycharm\u6253\u5f00\u9879\u76ee File
| Settings
| Project
| Python Interpreter
\u9009\u62e9 Poetry Environment
\u6dfb\u52a0\u521a\u624d\u521b\u5efa\u7684\u865a\u62df\u73af\u5883(\u9009\u62e9Existing
)
"},{"location":"datadevelop/quick_start/initialization/#_5","title":"\u4fee\u6539\u9879\u76ee\u7ed3\u6784","text":"\u4f7f\u7528Pycharm\u6253\u5f00\u9879\u76ee File
| Settings
| Project
| Project Structure
\u5c06src
\u548ctests
\u76ee\u5f55\u8bbe\u7f6e\u4e3aSources
\u6e90\u4ee3\u7801\u8def\u5f84
"},{"location":"datadevelop/quick_start/initialization/#etl","title":"ETL\u9879\u76ee\u6982\u8ff0","text":"\u5728\u5f00\u53d1ETL\u4efb\u52a1\u7684\u65f6\u5019\uff0c\u5efa\u8bae\u5728tasks
\u76ee\u5f55\u4e0b\u65b0\u5efa\u6587\u4ef6\u5939\u6765\u5b58\u653e\u5bf9\u5e94ETL\u4efb\u52a1\u4ee3\u7801\uff0c\u8fd9\u6837\u505a\u7684\u597d\u5904\u662f\u65b9\u4fbf\u7ba1\u7406\u548c\u9605\u8bfb\u3002
"},{"location":"datadevelop/quick_start/initialization/#executor","title":"Executor","text":"\u6267\u884c\u5668executor
\uff0c\u5b83\u53ea\u5173\u5fc3AbstractTask
\u7684run
\u65b9\u6cd5\uff0c\u5f00\u53d1\u8005\u4e0d\u9700\u8981\u91cd\u590d\u5f00\u53d1\u8c03\u7528Task
\u76f8\u5173\u7684\u529f\u80fd\u3002
_load_task
\uff1a\u901a\u8fc7stevedore \u63d2\u4ef6\u6846\u67b6\uff0c\u67e5\u627e\u5728namespace
\u4e2d\u6ce8\u518c\u7684Task
\uff0c\u5e76\u8fdb\u884c\u5b9e\u4f8b\u5316\u3002
run
\uff1a\u8c03\u7528AbstractTask
\u7684run
\u65b9\u6cd5\u3002
\"\"\"\nLoads a Task class and calls its `run()` method.\n\"\"\"\nimport logging\nfrom typing import Callable\nfrom stevedore import ExtensionManager\nfrom etl_project.constants import TASK_NAMESPACE\nfrom etl_project.context import Context\nfrom etl_project.utils.exception import PluginNotFoundError\nclass Executor:\n\"\"\"\n Loads a Task class and calls its `run()` method.\n \"\"\"\n# pylint: disable=too-few-public-methods\ndef __init__(self, ctx: Context, task: str):\nself.ctx = ctx\nself.task = task\ndef run(self) -> None:\n\"\"\"calls its `run()` method in the task class\"\"\"\ntask_class = self._load_task(TASK_NAMESPACE, self.task)\nlogging.info(f\"Running task: {task_class}\")\ntask_class().run()\n@staticmethod\ndef _load_task(namespace: str, name: str) -> Callable:\n\"\"\"Get extension by name from namespace, return task obj\"\"\"\nextension_manager = ExtensionManager(namespace=namespace, invoke_on_load=False)\nfor ext in extension_manager.extensions:\nif ext.name == name:\nreturn ext.plugin\nlogging.warning(f'Load plugin: {ext.plugin} in namespace \"{namespace}\"')\nraise PluginNotFoundError(namespace=namespace, name=name)\n
"},{"location":"datadevelop/quick_start/initialization/#abstracttask","title":"AbstractTask","text":"\u5728\u5f00\u53d1\u65f6\u5efa\u8bae\u5b9e\u73b0AbstractTransform
\u548cAbstractTask
\uff0c\u8fd9\u4e24\u4e2a\u62bd\u8c61\u7c7b\u5c06\u4efb\u52a1\u7684\u901a\u7528\u65b9\u6cd5\u62bd\u8c61\u63d0\u51fa\uff0c\u5f00\u53d1\u8005\u53ea\u9700\u8981\u5173\u5fc3\u4e1a\u52a1\u5c31\u53ef\u4ee5\u4e86\u3002
AbstractTask
: Task\u4efb\u52a1\u62bd\u8c61\u7c7b\uff0cexecutor
\u6267\u884c\u5668\u5b9e\u4f8b\u5316Task
\uff0c\u6267\u884cAbstractTask
\u62bd\u8c61\u7236\u7c7b\u7684run
\u65b9\u6cd5
run
: \u6267\u884cTask\u4efb\u52a1\u6d41\u7a0b _extract
: \u6570\u636e\u6e90\u62bd\u53d6(\u62bd\u8c61\u65b9\u6cd5) _transform
: \u6570\u636e\u8f6c\u6362(\u62bd\u8c61\u65b9\u6cd5)\uff0c\u6267\u884c\u8f6c\u6362\u6d41\u7a0b\uff0c\u8c03\u7528AbstractTransform
\u5b50\u7c7b _load
: \u6570\u636e\u52a0\u8f7d(\u62bd\u8c61\u65b9\u6cd5)
\"\"\"Base Task\"\"\"\nfrom abc import ABC, abstractmethod\nfrom etl_project.context import Context\nclass AbstractTask(ABC):\n\"\"\"\n Base class to read a dataset, transform it, and save it to a table.\n \"\"\"\n# pylint: disable=[too-few-public-methods]\ndef __init__(self):\nself.ctx = Context()\nself.settings = self.ctx.settings\ndef run(self) -> None:\n\"\"\"Execute task module\"\"\"\ndata = self._extract()\ndata_transformed = self._transform(data)\nself._load(data_transformed)\n@abstractmethod\ndef _extract(self):\n\"\"\"extract tmp from file/database/other.\"\"\"\nraise NotImplementedError\n@abstractmethod\ndef _transform(self, data):\n\"\"\"Transform incoming tmp, and output the transform result\"\"\"\nraise NotImplementedError\n@abstractmethod\ndef _load(self, data) -> None:\n\"\"\"Load tmp to file/database/other.\"\"\"\nraise NotImplementedError\n
"},{"location":"datadevelop/quick_start/initialization/#abstracttransform","title":"AbstractTransform","text":"AbstractTransform
: Transform\u62bd\u8c61\u7c7b\uff0c\u63d0\u4f9bAbstractTask
\u4e2d_transform
\u8fdb\u884c\u8c03\u7528\uff0c\u540c\u4e00\u4e2aTask\u53ef\u4ee5\u5b9e\u73b0\u591a\u4e2a_transform
_transform
: \u6570\u636e\u8f6c\u6362(\u62bd\u8c61\u65b9\u6cd5)\uff0c\u6307\u5b9a\u8f6c\u6362\u6d41\u7a0b\uff0c\u5904\u7406\u8f93\u5165\u6570\u636e(data
)
\"\"\"Base Transform\"\"\"\nfrom abc import ABC, abstractmethod\nfrom etl_project.context import Context\nclass AbstractTransform(ABC):\n\"\"\"\n Base class to define a DataFrame transformation.\n \"\"\"\n# pylint: disable=[too-few-public-methods]\ndef __init__(self):\nself.ctx = Context()\nself.settings = self.ctx.settings\n@abstractmethod\ndef transform(self, data):\n\"\"\"Transform original dataset.\"\"\"\nraise NotImplementedError\n
"},{"location":"datadevelop/quick_start/initialization/#context","title":"Context","text":"context.py
\u8d1f\u8d23\u6574\u4e2a\u9879\u76ee\u7684\u4e0a\u4e0b\u6587\u5185\u5bb9\u7ba1\u7406\uff0c\u5355\u4f8b\u6a21\u5f0f\u5b9e\u73b0\uff0c\u5f00\u53d1\u8005\u53ef\u4ee5\u5c06\u516c\u5171\u7684\u5c5e\u6027\u6216\u65b9\u6cd5\u5728Context
\u4e2d\u8fdb\u884c\u5b9e\u73b0\u3002\u8fd9\u6837\u53ef\u4ee5\u907f\u514d\u5bf9\u8c61\u7684\u91cd\u590d\u5b9e\u4f8b\u5316\uff0c\u59cb\u7ec8\u4f7f\u7528\u540c\u4e00\u4e2aContext
\u5e76\u8c03\u7528\u5176\u4e2d\u7684\u5c5e\u6027\u548c\u65b9\u6cd5\u3002
\"\"\"Context\"\"\"\nfrom dynaconf.base import Settings\nfrom etl_project.constants import ENV_DEVELOPMENT\nfrom etl_project.dependencies.config import config_manager\nfrom etl_project.dependencies.logger import LoggerManager\n@singleton\nclass Context:\n\"\"\"\n Context for project, Provide properties and methods\n \"\"\"\nenvironment = ENV_DEVELOPMENT\ndef __init__(self):\n\"\"\"Context Parameters\"\"\"\nself.settings = config_manager.from_env(self.environment)\nself.logger = LoggerManager(self.settings).get_logger()\n
Context
\u6240\u4f7f\u7528\u7684@singleton
\u5355\u4f8b\u6a21\u5f0f\u88c5\u9970\u5668\u5b9e\u73b0\u5982\u4e0b\uff1a
\"\"\"Singleton pattern decorator\"\"\"\n_instance = {}\ndef singleton(cls):\n# \u521b\u5efa\u4e00\u4e2a\u5b57\u5178\u7528\u6765\u4fdd\u5b58\u88ab\u88c5\u9970\u7c7b\u7684\u5b9e\u4f8b\u5bf9\u8c61 _instance = {}\ndef _singleton(*args, **kwargs):\n# \u5224\u65ad\u8fd9\u4e2a\u7c7b\u6709\u6ca1\u6709\u521b\u5efa\u8fc7\u5bf9\u8c61\uff0c\u6ca1\u6709\u65b0\u521b\u5efa\u4e00\u4e2a\uff0c\u6709\u5219\u8fd4\u56de\u4e4b\u524d\u521b\u5efa\u7684\nif cls not in _instance:\n_instance[cls] = cls(*args, **kwargs)\nreturn _instance[cls]\nreturn _singleton\n
"},{"location":"datadevelop/quick_start/initialization/#_6","title":"\u6ce8\u518c\u63d2\u4ef6","text":"ETL\u4efb\u52a1\u5b8c\u6210\u540e\u9700\u8981\u6ce8\u518c\u63d2\u4ef6\uff1a
\u56e0\u4e3a\u9879\u76ee\u9ed8\u8ba4\u4f7f\u7528poetry \u7ba1\u7406\u865a\u62df\u73af\u5883\uff0c\u5219\u9700\u8981\u5728pyproject.toml
\u6587\u4ef6\u589e\u52a0\u4e2d\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
[tool.poetry.plugins.\"etl_tasks\"]\ntask_name = \"{task_class_path}:TaskExample\"\n
\u4f7f\u7528\u5982\u4e0b\u547d\u4ee4\u5c06\u9879\u76ee\u63d2\u4ef6\u66f4\u65b0\u5230\u73af\u5883\u4e2d\uff1a
poetry install\n
"},{"location":"datadevelop/quick_start/preparation/","title":"\u73af\u5883\u51c6\u5907","text":""},{"location":"datadevelop/quick_start/preparation/#_2","title":"\u5f00\u53d1\u73af\u5883","text":"\u672c\u9875\u603b\u7ed3\u4e86\u6570\u636e\u5f00\u53d1\u9879\u76ee\u6240\u9700\u7684\u51c6\u5907\u5de5\u4f5c\uff0c\u524d\u63d0\u5df2\u7ecf\u5b8c\u6210\u5f00\u53d1\u524d\u51c6\u5907 \u548c\u5b89\u88c5Cookiecutter\u3002
\u5feb\u901f\u4e0a\u624b\u793a\u4f8b\u9879\u76ee\u4f7f\u7528Pyspark\u8fdb\u884c\u5f00\u53d1\uff0c\u53ef\u4ee5\u901a\u8fc7Pypi \u5b89\u88c5PySpark\u5982\u4e0b\uff1a
pip install pyspark\n
\u672c\u5730\u8fd0\u884cPySpark\u9879\u76ee\u65f6\u73af\u5883\u4f9d\u8d56Hadoop \u548cJDK\uff0c\u9700\u8981\u5b89\u88c5\u5e76\u914d\u7f6e\u73af\u5883\u53d8\u91cf
\u73af\u5883\u53d8\u91cf\u8def\u5f84\u95ee\u9898
\u5982JAVA_HOME\uff0cHADOOP_HOME\u73af\u5883\u53d8\u91cf\uff0c\u8def\u5f84\u4e2d\u4e0d\u8981\u5e26\u6709\u7a7a\u683c\u6216\u4e2d\u6587\uff0c\u907f\u514d\u52a0\u8f7d\u65f6\u62a5\u9519
"},{"location":"datadevelop/quick_start/preparation/#hadoop","title":"\u5b89\u88c5Hadoop","text":"\u4e0b\u8f7dHadoop\u4e8c\u8fdb\u5236\u5305
\u5efa\u8bae\u4f7f\u7528\u89e3\u538b\u5de5\u5177\u5bf9.tar.gz
\u6587\u4ef6\u683c\u5f0f\u8fdb\u884c\u89e3\u538b\uff0c\u5982Bandizip
\u5728Windows PowerShell \u8fd0\u884ctar -zxvf
\u4e2d\u53ef\u80fd\u53d1\u751fMaximum Path Length Limitation
"},{"location":"datadevelop/quick_start/preparation/#jdk","title":"\u5b89\u88c5JDK","text":"\u4e0b\u8f7dJDK\u5b89\u88c5\u5305\uff0c\u5efa\u8bae\u7edf\u4e00\u7ba1\u7406\u5f00\u53d1\u73af\u5883\uff0c\u66f4\u6539\u5b89\u88c5\u8def\u5f84\u3002
"},{"location":"datadevelop/quick_start/preparation/#_3","title":"\u914d\u7f6e\u73af\u5883\u53d8\u91cf","text":"\u914d\u7f6eJAVA_HOME
\u914d\u7f6eHADOOP_HOME
\u914d\u7f6e%JAVA_HOME%/bin
\uff0c%HADOOP_HOME%/bin
"},{"location":"datadevelop/quick_start/preparation/#_4","title":"\u5e38\u89c1\u95ee\u9898\u603b\u7ed3","text":""},{"location":"datadevelop/quick_start/preparation/#1-winutilsexe-hadoopdll","title":"\u95ee\u98981 \uff08\u7f3a\u5c11winutils.exe
, hadoop.dll
\uff09","text":"22/08/25 13:51:47 tid: [main] WARN org.apache.hadoop.util.Shell - Did not find winutils.exe: {}\njava.io.FileNotFoundException: java.io.FileNotFoundException: HADOOP_HOME and hadoop.home.dir are unset. -see https://wiki.apache.org/hadoop/WindowsProblems\n at org.apache.hadoop.util.Shell.fileNotFoundException(Shell.java:548)\nat org.apache.hadoop.util.Shell.getHadoopHomeDir(Shell.java:569)\nat org.apache.hadoop.util.Shell.getQualifiedBin(Shell.java:592)\nat org.apache.hadoop.util.Shell.<clinit>(Shell.java:689)\nat org.apache.hadoop.util.StringUtils.<clinit>(StringUtils.java:78)\nat org.apache.hadoop.fs.FileSystem$Cache$Key.<init>(FileSystem.java:3609)\nat org.apache.hadoop.fs.FileSystem$Cache$Key.<init>(FileSystem.java:3604)\nat org.apache.hadoop.fs.FileSystem$Cache.get(FileSystem.java:3441)\nat org.apache.hadoop.fs.FileSystem.get(FileSystem.java:524)\nat org.puppy.hadoop.app.HDFSApplication.main(HDFSApplication.java:26)\nCaused by: java.io.FileNotFoundException: HADOOP_HOME and hadoop.home.dir are unset.\n at org.apache.hadoop.util.Shell.checkHadoopHomeInner(Shell.java:468)\nat org.apache.hadoop.util.Shell.checkHadoopHome(Shell.java:439)\nat org.apache.hadoop.util.Shell.<clinit>(Shell.java:516)\n... 6 common frames omitted\n
\u89e3\u51b3\u65b9\u6848:
Windows\u5728\u5b89\u88c5Hadoop\u73af\u5883\u65f6\u53ef\u80fd\u4f1a\u9047\u5230\u7f3a\u5c11\u6587\u4ef6winutils.exe
\u548chadoop.dll
\uff0c\u53ef\u4ee5\u901a\u8fc7github\u4e0b\u8f7dHadoop\u6587\u4ef6 \uff0c\u5c06\u5b89\u88c5\u65f6\u7f3a\u5c11\u7684\u6587\u4ef6\uff0c\u653e\u5165%Hadoop%/bin\u76ee\u5f55\u4e0b\uff0c\u91cd\u542fIDE\u3002
\uff08\u5982\u679c\u8fd8\u4e0d\u6210\u529f\u7684\u8bdd\u53ef\u4ee5\u5c1d\u8bd5\uff09\u5c06hadoop.dll\u590d\u5236\u5230C:\\Window\\System32\u4e0b
"},{"location":"datadevelop/quick_start/preparation/#2-python-worker-failed-to-connect-back","title":"\u95ee\u98982 \uff08Python worker failed to connect back.\uff09","text":"22/08/25 13:51:47 ERROR Executor: Exception in task 0.0 in stage 2.0 (TID 2)\norg.apache.spark.SparkException: Python worker failed to connect back.\n at org.apache.spark.api.python.PythonWorkerFactory.createSimpleWorker(PythonWorkerFactory.scala:189)\nat org.apache.spark.api.python.PythonWorkerFactory.create(PythonWorkerFactory.scala:109)\nat org.apache.spark.SparkEnv.createPythonWorker(SparkEnv.scala:124)\nat org.apache.spark.api.python.BasePythonRunner.compute(PythonRunner.scala:164)\nat org.apache.spark.sql.execution.python.BatchEvalPythonExec.evaluate(BatchEvalPythonExec.scala:81)\nat org.apache.spark.sql.execution.python.EvalPythonExec.$anonfun$doExecute$2(EvalPythonExec.scala:130)\nat org.apache.spark.rdd.RDD.$anonfun$mapPartitions$2(RDD.scala:855)\nat org.apache.spark.rdd.RDD.$anonfun$mapPartitions$2$adapted(RDD.scala:855)\nat org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:52)\nat org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:365)\nat org.apache.spark.rdd.RDD.iterator(RDD.scala:329)\nat org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:52)\nat org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:365)\nat org.apache.spark.rdd.RDD.iterator(RDD.scala:329)\nat org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:52)\nat org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:365)\nat org.apache.spark.rdd.RDD.iterator(RDD.scala:329)\nat org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:90)\nat org.apache.spark.scheduler.Task.run(Task.scala:136)\nat org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$3(Executor.scala:548)\nat org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1504)\nat org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:551)\nat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\nat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\nat java.lang.Thread.run(Thread.java:748)\nCaused by: java.net.SocketTimeoutException: Accept timed out\n at java.net.DualStackPlainSocketImpl.waitForNewConnection(Native Method)\nat java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:135)\nat java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)\nat java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)\nat java.net.ServerSocket.implAccept(ServerSocket.java:545)\nat java.net.ServerSocket.accept(ServerSocket.java:513)\nat org.apache.spark.api.python.PythonWorkerFactory.createSimpleWorker(PythonWorkerFactory.scala:176)\n... 24 more\n
\u89e3\u51b3\u65b9\u6848:
\u5168\u5c40\u589e\u52a0\u73af\u5883\u53d8\u91cf\u6216IDE\u589e\u52a0\u73af\u5883\u53d8\u91cf\uff0c\u589e\u52a0\u5185\u5bb9\u5982\u4e0b\uff1a
PYSPARK_DRIVER_PYTHON=jupyter;\nPYSPARK_PYTHON=python\n
"},{"location":"datadevelop/quick_start/preparation/#3-poetrygbk","title":"\u95ee\u98983 \uff08Poetry\u4e0b\u8f7d\u8d44\u6e90\"gbk\" \u683c\u5f0f\u5f02\u5e38\uff09","text":"The following packages are already present in the pyproject.toml and will be skipped: 'gbk' codec can't encode character '\\u2022' in position 2: illegal multibyte sequence\n
\u89e3\u51b3\u65b9\u6848:
Windows\u7cfb\u7edf\u8bed\u8a00\u8bbe\u7f6e\u4e3autf-8
\u683c\u5f0f
"},{"location":"datadevelop/quick_start/release/","title":"\u53d1\u5e03","text":""},{"location":"datadevelop/quick_start/release/#_2","title":"\u672c\u5730\u8fd0\u884c","text":""},{"location":"datadevelop/quick_start/release/#task","title":"\u6ce8\u518cTask","text":"\u5c06main
\u547d\u4ee4\u884c\u5165\u53e3\u548c\u4e0a\u8ff0\u5b9e\u73b0\u7684Task
\u7c7b\u6ce8\u518c\u5230\u547d\u540d\u7a7a\u95f4\u4e2d\u3002
\u7f16\u8f91pyproject.toml
\u6587\u4ef6\uff0c\u589e\u52a0poetry\u63d2\u4ef6\u5982\u4e0b\u5185\u5bb9\uff1a
[tool.poetry.plugins.console_scripts]\nautomotive_data_etl = \"automotive_data_etl.cmdline:main\"\n[tool.poetry.plugins.\"etl_tasks\"]\nautomotive_task = \"automotive_data_etl.tasks.automotive_task.task:AutomotiveDataTask\"\n
\u8fd9\u4e48\u505a\u7684\u76ee\u7684\u662f\u5c06AutomotiveDataTask
\u6ce8\u518c\u5230entry_points
\u4e2d\uff0c \u7136\u540e\u5728\u7a0b\u5e8f\u4e2d\u4f7f\u7528importlib.metadata
\u6839\u636e\u540d\u79f0\u7a7a\u95f4\u67e5\u627e\u3002\u800c stevedore
\u5219\u662f\u5c01\u88c5\u4e86\u67e5\u627e\u7684\u590d\u6742\u903b\u8f91\uff0c\u8ba9\u4f7f\u7528\u63d2\u4ef6\u66f4\u7b80\u5355\u3002
\u5c06\u9879\u76ee\u4ee5\u53ef\u7f16\u8f91\u6a21\u5f0f\u5b89\u88c5\u5230\u5f53\u524d\u73af\u5883\uff1a
poetry install\n
\u53ef\u4ee5\u5728 Python Console
\u4e0b\u67e5\u770b\u6ce8\u518c\u63d2\u4ef6\u4fe1\u606f\uff1a
>>> from importlib.metadata import entry_points\n\n>>> entry_points(group='etl_tasks')\n[EntryPoint(name='automotive_task', value='automotive_data_etl.tasks.automotive_task.task:AutomotiveDataTask', group='etl_tasks')]\n
\u5c06\u672c\u5730\u9879\u76ee\u4ee5\u53ef\u7f16\u8f91\u65b9\u5f0f\u5b89\u88c5\u5230\u5f53\u524d Python \u73af\u5883\uff1a
pip install -e .\n
"},{"location":"datadevelop/quick_start/release/#task_1","title":"\u8fd0\u884cTask","text":"\u7136\u540e\u901a\u8fc7\u547d\u4ee4\u884c\u7684\u65b9\u5f0f\u8fd0\u884cTask
\uff0c\u901a\u8fc7\u547d\u4ee4\u884c\u53c2\u6570\u7684\u65b9\u5f0f\u66f4\u65b0\u8f93\u5165\u8f93\u51fa\u8def\u5f84\uff1a
automotive_data_etl \\\n--env=development \\\n--task=automotive_task \\\n--input=tmp/input/car_price.csv \\\n--output=tmp/output/\n
"},{"location":"datadevelop/quick_start/tests/","title":"\u6d4b\u8bd5","text":""},{"location":"datadevelop/quick_start/tests/#_2","title":"\u5355\u5143\u6d4b\u8bd5","text":"\u5355\u5143\u6d4b\u8bd5\uff08unit test\uff09\u5c31\u662f\u7f16\u5199\u6d4b\u8bd5\u6765\u9a8c\u8bc1\u67d0\u4e00\u6a21\u5757\u7684\u529f\u80fd\u6b63\u786e\u6027\u3002\u4e00\u822c\u4f1a\u6307\u5b9a\u8f93\u5165\uff0c\u9a8c\u8bc1\u8f93\u51fa\u662f\u5426\u7b26\u5408\u9884\u671f\uff0c\u53ef\u4ee5\u5e2e\u52a9\u6211\u4eec\u5f88\u5feb\u51c6\u786e\u7684\u5b9a\u4f4d\u5230\u95ee\u9898\u7684\u4f4d\u7f6e\uff0c\u51fa\u73b0\u95ee\u9898\u7684\u6a21\u5757\u548c\u5355\u5143\u3002 \u6211\u4eec\u5c06\u4f7f\u7528Pytest\u8fdb\u884c\u6d4b\u8bd5\u3002
"},{"location":"datadevelop/quick_start/tests/#_3","title":"\u914d\u7f6e\u6587\u4ef6\u6d4b\u8bd5","text":"test_settings
\uff1a\u6d4b\u8bd5dynaconf
\u914d\u7f6e\u662f\u5426\u4f7f\u7528testing
\u73af\u5883\u914d\u7f6e
@pytest.fixture()\ndef context():\n\"\"\"Fixture context for the tests\"\"\"\nContext().environment = 'testing'\nctx = Context()\nreturn ctx\ndef test_settings(context):\n\"\"\"Test: Setting init by \"testing\" env\"\"\"\nsettings = context.settings\nassert settings.message == 'This is in testing env'\n# tmp path\nassert settings.input_path == '../tmp/input/car_price.csv'\nassert settings.output_path == '../tmp/output'\n# spark configs\nassert settings.spark_master == 'local[*]'\nassert settings.spark_config.spark.driver.memory == '3G'\nassert settings.spark_config.spark.executor.memory == '16G'\nassert settings.spark_config.spark.sql.debug.maxToStringFields == 100\n
"},{"location":"datadevelop/quick_start/tests/#extract","title":"\u4efb\u52a1Extract\u6d4b\u8bd5","text":"test_extract
\uff1a\u6d4b\u8bd5car_price.csv
\u6587\u4ef6\u662f\u5426\u88ab\u6b63\u786e\u8bfb\u53d6
def test_extract(context):\n\"\"\"Test: Read CSV file return DataFrame\"\"\"\ntask = AutomotiveDataTask()\ndf = task._extract()\nassert df.count() == 205\nreturn df\n
"},{"location":"datadevelop/quick_start/tests/#transform","title":"\u4efb\u52a1Transform\u6d4b\u8bd5","text":"test_transform_filter_price
\uff1a\u6d4b\u8bd5automotive_data_etl
\u8f6c\u6362\u8fc7\u7a0b\uff0c\u662f\u5426\u8fc7\u6ee4price
\u5b57\u6bb510000
\u4ee5\u4e0a\u7684\u6570\u636e
def test_transform_filter_price(test_extract):\n\"\"\"Test: Filter results price > 10000\"\"\"\ntransform = AutomotiveDataTransform()\ndf = transform._filter_price(test_extract)\nassert df.filter(col('price') <= 10000).count() == 0\nreturn df\n
test_transform_process_car_name
\uff1a\u6d4b\u8bd5CarName
\u5217\u662f\u5426\u5c06[dirty tmp]
\u5b57\u7b26\u4e32\u6e05\u9664
def test_transform_process_car_name(test_transform_filter_price):\n\"\"\"Test: Clean [dirty tmp] from CarName\"\"\"\ntransform = AutomotiveDataTransform()\ndf = transform._process_car_name(test_transform_filter_price)\nassert df.filter(col('CarName').contains('[dirty tmp]')).count() == 0\nreturn df\n
test_transform_select_final_columns
\uff1a\u6d4b\u8bd5\u6700\u7ec8\u7ed3\u679cColumns
\u662f\u5426\u4e3a\uff1acar_id
, car_name
, symboling
, price
def test_transform_select_final_columns(test_transform_process_car_name):\n\"\"\"Test: Final columns is ['car_id', 'car_name', 'symboling', 'price']\"\"\"\nfinal_columns = ['car_id', 'car_name', 'symboling', 'price']\ntransform = AutomotiveDataTransform()\ndf = transform._select_final_columns(test_transform_process_car_name)\nnames = df.schema.names\nassert names.sort() == final_columns.sort()\nreturn df\n
"},{"location":"datadevelop/quick_start/tests/#load","title":"\u4efb\u52a1Load\u6d4b\u8bd5","text":"test_load
\uff1a\u6d4b\u8bd5\u7ed3\u679c\u6570\u636e\u662f\u5426\u53ef\u4ee5\u6b63\u786e\u88ab\u5199\u5165JSON
\u6587\u4ef6
def test_load(test_transform_select_final_columns):\n\"\"\"Test: Load csv file\"\"\"\ntask = AutomotiveDataTask()\ntask._load(test_transform_select_final_columns)\n
"},{"location":"datadevelop/quick_start/tests/#_4","title":"\u4efb\u52a1\u5b8c\u6574\u6d41\u7a0b\u6d4b\u8bd5","text":"\u521b\u5efa\u6d4b\u8bd5\u6587\u4ef6tests/test_task.py
\"\"\"Test log\"\"\"\nimport pytest\nfrom pyspark.sql.functions import col\nfrom automotive_data_etl.context import Context\nfrom automotive_data_etl.tasks.automotive_task.automotive_transform import AutomotiveDataTransform\nfrom automotive_data_etl.tasks.automotive_task.task import AutomotiveDataTask\n@pytest.fixture()\ndef context():\n\"\"\"Fixture context for the tests\"\"\"\nContext().environment = 'testing'\nctx = Context()\nreturn ctx\ndef test_settings(context):\n\"\"\"Test: Setting init by \"testing\" env\"\"\"\nsettings = context.settings\nassert settings.message == 'This is in testing env'\n# tmp path\nassert settings.input_path == '../tmp/input/car_price.csv'\nassert settings.output_path == '../tmp/output'\n# spark configs\nassert settings.spark_master == 'local[*]'\nassert settings.spark_config.spark.driver.memory == '3G'\nassert settings.spark_config.spark.executor.memory == '16G'\nassert settings.spark_config.spark.sql.debug.maxToStringFields == 100\n@pytest.fixture()\ndef test_extract(context):\n\"\"\"Test: Read CSV file return DataFrame\"\"\"\ntask = AutomotiveDataTask()\ndf = task._extract()\nassert df.count() == 205\nreturn df\n@pytest.fixture()\ndef test_transform_filter_price(test_extract):\n\"\"\"Test: Filter results price > 10000\"\"\"\ntransform = AutomotiveDataTransform()\ndf = transform._filter_price(test_extract)\nassert df.filter(col('price') <= 10000).count() == 0\nreturn df\n@pytest.fixture()\ndef test_transform_process_car_name(test_transform_filter_price):\n\"\"\"Test: Clean [dirty tmp] from CarName\"\"\"\ntransform = AutomotiveDataTransform()\ndf = transform._process_car_name(test_transform_filter_price)\nassert df.filter(col('CarName').contains('[dirty tmp]')).count() == 0\nreturn df\n@pytest.fixture()\ndef test_transform_select_final_columns(test_transform_process_car_name):\n\"\"\"Test: Final columns is ['car_id', 'car_name', 'symboling', 'price']\"\"\"\nfinal_columns = ['car_id', 'car_name', 'symboling', 'price']\ntransform = AutomotiveDataTransform()\ndf = transform._select_final_columns(test_transform_process_car_name)\nnames = df.schema.names\nassert names.sort() == final_columns.sort()\nreturn df\ndef test_load(test_transform_select_final_columns):\n\"\"\"Test: Load csv file\"\"\"\ntask = AutomotiveDataTask()\ntask._load(test_transform_select_final_columns)\n
"},{"location":"datadevelop/quick_start/tests/#_5","title":"\u6570\u636e\u6d4b\u8bd5","text":""},{"location":"datadevelop/quick_start/tests/#step-1","title":"Step 1: \u6570\u636e\u8f93\u5165\u6d4b\u8bd5","text":" -
\u6570\u636e\u8d44\u6e90\u5e94\u8be5\u88ab\u9a8c\u8bc1\uff0c\u6765\u786e\u4fdd\u6b63\u786e\u7684\u6570\u636e\u88ab\u52a0\u8f7d\u8fdb\u7cfb\u7edf
-
\u4efb\u52a1\u9700\u6c42\u4f7f\u7528CSV\u6587\u4ef6\u6570\u636e\u6e90\uff0c\u52a0\u8f7d\u5185\u5bb9\u4e0e\u6e90\u6570\u636e\u8fdb\u884c\u5339\u914d
"},{"location":"datadevelop/quick_start/tests/#step-2-transform","title":"Step 2: Transform \u9636\u6bb5\u6d4b\u8bd5","text":" -
\u6d4b\u8bd5\u8f6c\u6362\u89c4\u5219\u53ef\u6b63\u786e\u6267\u884c
-
\u8f6c\u6362\u6570\u636e\u91cf\u4e0e\u88ab\u8f6c\u6362\u6570\u636e\u91cf\u662f\u5426\u5339\u914d
"},{"location":"datadevelop/quick_start/tests/#step-3","title":"Step 3: \u8f93\u51fa\u7ed3\u679c\u6d4b\u8bd5","text":" -
\u68c0\u67e5\u8f6c\u6362(Transformation)\u89c4\u5219\u88ab\u6b63\u786e\u5e94\u7528
-
\u901a\u8fc7\u5176\u4ed6\u65b9\u5f0f\u8ba1\u7b97\u6e90\u6570\u636e\u91cf\u8fdb\u884c\u6bd4\u8f83
"},{"location":"guidelines/advanced/configuration/","title":"\u914d\u7f6e","text":"\u914d\u7f6e\u662f\u4e00\u4e2a\u9879\u76ee\u7684\u6838\u5fc3\u9a71\u52a8\uff0c\u53ef\u4ee5\u5728\u4e0d\u66f4\u6539\u6e90\u4ee3\u7801\u6216\u51cf\u5c11\u6e90\u4ee3\u7801\u4fee\u6539\u7684\u60c5\u51b5\u4e0b\u5feb\u901f\u8c03\u6574\u9879\u76ee\u7684\u8fd0\u884c\u3002 \u4f7f\u7528\u4e2d\u5fc3\u914d\u7f6e\u9a71\u52a8\u9879\u76ee\uff0c\u80fd\u8ba9\u9879\u76ee\u7684\u4f7f\u7528\u66f4\u52a0\u7075\u6d3b\uff0c\u8fd0\u7ef4\u5de5\u4f5c\u66f4\u8f7b\u677e\u3002
\u4f8b\u5982 Django \u6846\u67b6\u4f1a\u81ea\u5e26\u4e00\u4e2a settings.py
\u6587\u4ef6\uff0c\u5728 settings.py
\u4e2d\u7684\u914d\u7f6e\u9879\u90fd\u4f1a\u8986\u76d6\u6846\u67b6 \u7ea7\u522b\u7684\u9ed8\u8ba4\u914d\u7f6e\uff0c\u65b9\u4fbf\u7528\u6237\u81ea\u5b9a\u4e49\u4fee\u6539\u3002\u5728\u4ee3\u7801\u4e2d\uff0c\u53ef\u4ee5\u4f7f\u7528 django.settings
\u5bf9\u8c61\u83b7\u53d6\u6240\u6709 \u914d\u7f6e\u9879\u3002 Scrapy \u6846\u67b6\u540c\u6837\u4e5f\u6709\u8fd9\u79cd\u673a\u5236\u3002
\u8fd9\u4e9b\u6210\u719f\u7684\u6846\u67b6\u589e\u52a0\u4e86\u4e2d\u5fc3\u914d\u7f6e\uff0c\u5c31\u662f\u4e3a\u4e86\u901a\u8fc7\u5f00\u653e\u51fa\u6765\u7684\u914d\u7f6e\u9879\u6765\u7075\u6d3b\u63a7\u5236\u6846\u67b6\u6240\u652f\u6301\u7684\u5185\u5bb9\u3002 \u800c\u5728\u4e00\u822c\u9879\u76ee\u4e2d\uff0c\u4e5f\u53ef\u4ee5\u53c2\u7167\u8fd9\u79cd\u8bbe\u8ba1\uff0c\u8ba9\u9879\u76ee\u90e8\u7f72\u66f4\u52a0\u7075\u6d3b\u3002
"},{"location":"guidelines/advanced/configuration/#1","title":"1. \u4e00\u822c\u505a\u6cd5","text":"\u5e38\u89c1\u589e\u52a0\u4e2d\u5fc3\u914d\u7f6e\u7684\u505a\u6cd5\u662f\u5728\u9879\u76ee\u4e2d\u589e\u52a0\u4e00\u4e2a settings.py
\u6587\u4ef6\uff0c\u8be5\u6587\u4ef6\u4e2d\u6a21\u5757\u7ea7\u522b\u5e38\u91cf\u5b9a\u4e49\u914d\u7f6e\u9879\u3002 \u5728\u4f7f\u7528\u65f6\uff0c\u901a\u8fc7\u5bfc\u5165\u6a21\u5757\u4e2d\u7684\u5185\u5bb9\u4f7f\u7528\u3002
\u4f8b\u5982\uff1a
\u5728\u9879\u76ee\u4e2d\u521b\u5efa\u4e00\u4e2a settings.py
\u6587\u4ef6\uff0c\u5728\u6587\u4ef6\u4e2d\u5b9a\u4e49\u6a21\u5757\u7ea7\u5e38\u91cf
## Settings\n# File config\nSOURCE_FILE = '/tmp/foo.txt'\n# Log config\nLOG_LEVEL = 'DEBUG'\nLOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n
\u521b\u5efa app.py
\u6587\u4ef6\uff0c\u5728\u6587\u4ef6\u4e2d\u5bfc\u5165 settings
\u6a21\u5757\uff0c\u5e76\u4f7f\u7528\u8be5\u6a21\u5757\u4e2d\u7684\u5e38\u91cf\u3002
\"\"\"Count a file \"\"\"\nimport logging\nfrom pathlib import Path \nimport settings\n# Config root logger\nlogging.basicConfig(\nlevel=settings.LOG_LEVEL,\nformat=settings.LOG_FORMAT,\n)\ndef count_word(source_file: Path) -> None:\n\"\"\"\n :param source_file:\n :return: None\n \"\"\"\ntotal_words = 0\n# Read source_file\nlogging.debug('Read file: %s', source_file)\nwith open(source_file, mode='r', encoding='utf-8') as source_obj:\nfor line in source_obj.readlines():\ntotal_words += len(line.split(' '))\nlogging.info('File has %s words', total_words)\ndef main():\ncount_word(Path(settings.SOURCE_FILE))\nif __name__ == '__main__':\nmain()\n
"},{"location":"guidelines/advanced/configuration/#2","title":"2. \u52a8\u6001\u914d\u7f6e\u793a\u4f8b","text":"Dynaconf \u662f\u4e00\u4e2a\u7075\u6d3b\u7684\u4e2d\u5fc3\u914d\u7f6e\u7ba1\u7406\u5de5\u5177\uff0c\u5e95\u5c42\u8bbe\u8ba1\u548c Django \u4e00\u81f4\uff0c\u4f1a\u5ef6\u8fdf\u52a0\u8f7d\u914d\u7f6e\u3002
\u5176\u5177\u6709\u5982\u4e0b\u7279\u70b9\uff1a
- \u52a0\u8f7d\u591a\u4e2a\u914d\u7f6e\u6e90
- \u914d\u7f6e\u5206\u5c42
- Django Flask \u6269\u5c55
- \u652f\u6301 Redis \u548c Vault
\u5728\u9879\u76ee\u4e2d\u65b0\u5efa\u914d\u7f6e\u6587\u4ef6 settings.yml
settings.yml \uff1a
foo: 1\nbar: 2\n
\u65b0\u5efa\u914d\u7f6e\u6a21\u5757 config.py
config.py \uff1a
from dynaconf import Dynaconf\nsettings = Dynaconf(\nsettings_files=['settings.yml'],\n)\n
\u65b0\u5efa\u4e00\u4e2a app.py
\u6587\u4ef6\uff0c\u4f7f\u7528\u914d\u7f6e
app.py \uff1a
from config import settings\nprint(settings.FOO)\nprint(settings.BAR)\n
\u7136\u540e\u8fd0\u884c python app.py
\u53ef\u4ee5\u770b\u5230\u5df2\u7ecf\u80fd\u591f\u81ea\u52a8\u83b7\u53d6 settings.yml
\u914d\u7f6e\u6587\u4ef6\u4e2d\u7684\u503c\u3002
\u589e\u52a0\u672c\u5730\u914d\u7f6e\u6587\u4ef6 settings.local.yml
settings.local.yml :
foo: 10\nbar: 20\n
\u518d\u6b21\u8fd0\u884c python app.py
\uff0c\u7a0b\u5e8f\u4f1a\u81ea\u52a8\u83b7\u53d6 settings.local.yml
\u3002
\u8fd9\u662f\u56e0\u4e3a Dynaconf
\u5728\u521d\u59cb\u5316\u662f\u4f20\u5165\u4e86\u914d\u7f6e\u6587\u4ef6\u683c\u5f0f\u4e3a settings.yml
\uff0c\u5728\u52a0\u8f7d\u914d\u7f6e\u65f6\uff0c\u4f1a\u540c\u65f6\u67e5\u627e settings.local.yml
\u7684\u914d\u7f6e\u6587\u4ef6\u3002 \u5e76\u5c06\u4e24\u4e2a\u914d\u7f6e\u6587\u4ef6\u7684\u5185\u5bb9\u5408\u5e76\uff0c\u5982\u679c\u5b58\u5728\u76f8\u540c\u53d8\u91cf\uff0c settings.local.yml
\u4f1a\u8986\u76d6 settings.yml
\u4e2d\u7684\u914d\u7f6e\u3002
"},{"location":"guidelines/advanced/configuration/#_2","title":"\u9879\u76ee\u5b9e\u8df5","text":""},{"location":"guidelines/advanced/configuration/#django","title":"Django \u9879\u76ee","text":"Dynaconf \u53ef\u4ee5\u642d\u914d Django \u4e00\u8d77\u4f7f\u7528\u3002\u867d\u7136 Django \u6709\u81ea\u5df1\u7684\u914d\u7f6e\u6a21\u5757\uff0c\u4f46\u662f\u5e76\u4e0d\u7075\u6d3b\u3002
\u642d\u914d Dynaconf \uff0c\u53ef\u4ee5\u542f\u52a8\u5c42\u7ea7\u914d\u7f6e\uff0c\u4f8b\u5982\u652f\u6301 Dev
\u3001 prod
\u548c test
\u591a\u79cd\u73af\u5883\u7684\u914d\u7f6e\uff0c\u800c\u4e14\u53ef\u4ee5\u901a\u8fc7\u73af\u5883\u53d8\u91cf\u5f88\u65b9\u4fbf\u7684 \u4fee\u6539\u914d\u7f6e\uff0c\u5305\u62ec\u52a0\u8f7d\u5176\u4ed6\u5730\u65b9\u7684\u914d\u7f6e\u3002
\u5728 Django \u9879\u76ee\u7684 settings.py
\u6587\u4ef6\u6700\u540e\u6dfb\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
import dynaconf # pylint: disable=wrong-import-position\nsettings = dynaconf.DjangoDynaconf(\n__name__,\nenvvar_prefix='BLOG',\nsettings_files=[\nBASE_DIR / 'settings.local.yml'\n],\nenvironments=False,\nload_dotenv=True,\nENVVAR_FOR_DYNACONF='BLOG_SETTINGS',\nincludes=[\nPath(sys.prefix, 'etc', 'blog', 'settings.yml'),\n]\n)\n
\u5f53 Django \u52a0\u8f7d settings.py
\u6a21\u5757\u7684\u65f6\u5019\uff0c\u4f1a\u521d\u59cb\u5316 Dynaconf \u3002 Dynaconf \u4f1a\u5c06 Django \u7684 settings
\u5bf9\u8c61\u4e2d\u7684\u914d\u7f6e\u52a0\u8f7d\u5230 Dynaconf \u4e2d\uff0c \u7136\u540e\u5c06\u81ea\u8eab\u7684\u6240\u6709\u914d\u7f6e\u518d\u91cd\u65b0\u52a0\u8f7d\u5230 Django \u7684 settings
\u5bf9\u8c61\u4e2d\u3002
Dynaconf \u4e0d\u4ec5\u4f1a\u52a0\u8f7d\u914d\u7f6e\u6587\u4ef6\uff0c\u4e5f\u4f1a\u52a0\u8f7d\u4ee5 BLOG_
\u5f00\u5934\u7684\u73af\u5883\u53d8\u91cf\u3002
"},{"location":"guidelines/advanced/configuration/#_3","title":"\u4f7f\u7528\u914d\u7f6e\u6587\u4ef6","text":"\u5728\u521d\u59cb\u5316 Dynaconf \u65f6\uff0c\u4f1a\u52a0\u8f7d\u9879\u76ee\u6839\u76ee\u5f55\u7684 settings.local.yml
\u914d\u7f6e\u6587\u4ef6\uff0c\u6b64\u6587\u4ef6\u4e00\u822c\u662f\u5f00\u53d1\u65f6\u4f7f\u7528\u7684\u672c\u5730\u914d\u7f6e\uff0c\u5e76\u4e14\u4e0d\u5e94\u8be5\u88ab Git \u8ffd\u8e2a\u3002 \u5728\u4e0d\u540c\u7684\u5f00\u53d1\u4eba\u5458\u4f7f\u7528\u6216\u8005\u4e0d\u540c\u7684\u73af\u5883\u4e2d\uff0c\u53ef\u4ee5\u4f7f\u7528\u591a\u6837\u5316\u7684\u672c\u5730\u914d\u7f6e\u3002\u5bf9\u4e8e\u9700\u8981\u7edf\u4e00\u7684\u9ed8\u8ba4\u914d\u7f6e\uff0c\u76f4\u63a5\u653e\u5728 settings.py
\u4e2d\u5c31\u53ef\u4ee5\u4e86\u3002
\u540c\u65f6\u8fd8\u4f1a\u8bfb\u53d6 <sys.prefix>/etc/blog/settings.yml
\u3002\u8fd9\u4e2a\u4e00\u822c\u4f5c\u4e3a\u751f\u4ea7\u73af\u5883\u7684\u7cfb\u7edf\u914d\u7f6e\u3002\u5982\u679c\u4f7f\u7528\u7684\u662f\u7cfb\u7edf Python \u73af\u5883\uff0c\u53ef\u80fd\u76ee\u5f55\u662f\u5728 /usr/local/etc/blog/settings.yml
\uff0c\u5982\u679c\u662f\u865a\u62df\u73af\u5883\uff0c\u5219\u53ef\u80fd\u662f /home/foo/.virtualenvs/blog-fxage/etc/blog/settings.yml
\u3002
\u5982\u679c\u60f3\u81ea\u5b9a\u4e49\u914d\u7f6e\u6587\u4ef6\u4f4d\u7f6e\uff0c\u53ef\u4ee5\u901a\u8fc7\u8bbe\u7f6e\u73af\u5883\u53d8\u91cf BLOG_SETTINGS=/tmp/settings.yml
\u6307\u5b9a Dynaconf \u52a0\u8f7d\u6587\u4ef6\u7684\u4f4d\u7f6e\u3002
DEBUG: true\nALLOWED_HOSTS:\n- '*'\nINSTALLED_APPS:\n- dynaconf_merge_unique # \u6307\u793a Dynaconf \u5c06 INSTALLED_APPS \u4e0e\u9ed8\u8ba4\u914d\u7f6e\u5408\u5e76\u800c\u4e0d\u662f\u8986\u76d6\uff0c\u5e76\u4e14\u8fdb\u884c\u53bb\u91cd\n- debug_toolbar # \u6307\u793a Dynaconf \u5c06 debug_toolbar \u6dfb\u52a0\u5230 INSTALLED_APPS \u5217\u8868\u4e2d\nMIDDLEWARE:\n- dynaconf_merge_unique\n- debug_toolbar.middleware.DebugToolbarMiddleware\nDATABASES:\ndefault:\nENGINE: 'django.db.backends.mysql'\nNAME: blo\nUSER: root\nPASSWORD: '000000'\nHOST: 127.0.0.1\nPORT: 3306\nREST_FRAMEWORK:\n# \u6307\u793a Dynaconf \u5c06 REST_FRAMEWORK \u4e0e\u9ed8\u8ba4\u914d\u7f6e\u5408\u5e76\uff0c\u800c\u4e0d\u662f\u8986\u76d6\ndynaconf_merge_unique: true\n# \u6307\u793a Dynaconf \u53ea\u4fee\u6539 PAGE_SIZE \u7684\u503c\uff0c\u5176\u4ed6\u4e0d\u53d8\nPAGE_SIZE: 10\n}\n
\u4e0a\u8ff0\u914d\u7f6e\u5728 Dynaconf \u8bfb\u53d6\u540e\uff0c\u53ef\u4ee5\u8986\u76d6 settings.py
\u4e2d\u7684\u9ed8\u8ba4\u914d\u7f6e\u3002\u5176\u4e2d\u6709\u51e0\u4e2a\u70b9\u9700\u8981\u6ce8\u610f\uff1a
- \u5982\u679c\u76f4\u63a5\u6587\u4ef6\u4e2d\u5b9a\u4e49\u914d\u7f6e\uff0c\u4f1a\u8986\u76d6\u9ed8\u8ba4\u914d\u7f6e\u3002
- \u5982\u679c\u9700\u8981\u548c\u9ed8\u8ba4\u914d\u7f6e\u5408\u5e76\uff0c\u53ef\u4ee5\u4f7f\u7528
dynaconf_merge
\u3002
"},{"location":"guidelines/advanced/configuration/#_4","title":"\u4f7f\u7528\u73af\u5883\u53d8\u91cf","text":"Dynaconf \u652f\u6301\u52a0\u8f7d\u73af\u5883\u53d8\u91cf\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528 .env \u6587\u4ef6\u3002
\u5728\u4f7f\u7528\u73af\u5883\u53d8\u91cf\u65f6\uff0c\u540c\u6837\u548c\u914d\u7f6e\u6587\u4ef6\u4e00\u6837\uff0c\u652f\u6301\u5b8c\u5168\u8986\u76d6\uff0c\u548c\u81ea\u52a8\u5408\u5e76\u3002
\u9700\u8981\u989d\u5916\u5f3a\u8c03\u4e00\u70b9\u7684\u662f\uff0c Dynaconf \u521d\u59cb\u5316\u7684\u65f6\u5019\uff0c\u4f7f\u7528\u4e86 envvar_prefix=BLOG
\u3002 Dynaconf \u4f1a\u81ea\u52a8\u52a0\u8f7d\u4ee5 BLOG_
\u5f00\u5934\u7684 \u73af\u5883\u53d8\u91cf\u3002\u5305\u62ec ENVVAR_FOR_DYNACONF='BLOG_SETTINGS'
\u914d\u7f6e\u7684 Dynaconf \u52a0\u8f7d\u914d\u7f6e\u6587\u4ef6\u7684\u73af\u5883\u53d8\u91cf BLOG_SETTINGS
\u3002
\u6240\u4ee5\u5728\u4f7f\u7528\u73af\u5883\u53d8\u91cf\u7684\u65f6\u5019\uff0c\u4e0d\u8981\u9519\u8bef\u7684\u5c06 BLOG_SETTINGS
\u73af\u5883\u53d8\u91cf\u6307\u5b9a\u5176\u4ed6\u5185\u5bb9\uff0c\u800c\u9020\u6210\u4e0d\u5fc5\u8981\u7684\u9519\u8bef\u3002
# \u4f7f\u7528\u73af\u5883\u53d8\u91cf\u914d\u7f6e\u5355\u503c\nexport BLOG_DEBUG='True'\n# \u4f7f\u7528\u73af\u5883\u53d8\u91cf\u914d\u7f6e\u5bf9\u8c61\nexport BLOG_DATABASES=\"{'default'={'ENGINE'='django.db.backends.mysql', 'NAME'='blog', 'USER'='root', 'PASSWORD'='000000', 'HOST'='localhost', 'POST'=3306}}\"\n# \u4f7f\u7528\u73af\u5883\u53d8\u91cf\u914d\u7f6e\u5408\u5e76\u5185\u5bb9\nexport BLOG_MIDDLEWARE='[\"dynaconf_merge_unique\", \"debug_toolbar.middleware.DebugToolbarMiddleware\"]' # \u4f7f\u7528 dynaconf_merge_unique \u5408\u5e76\u5e76\u53bb\u91cd\nexport BLOG_MIDDLEWARE='@merge [\"debug_toolbar.middleware.DebugToolbarMiddleware\"]' # \u4f7f\u7528 merge \u5173\u952e\u5b57\nexport BLOG_MIDDLEWARE='@merge debug_toolbar.middleware.DebugToolbarMiddleware' # \u7b80\u5199\nexport BLOG_REST_FRAMEWORK='{PAGE_SIZE=10, dynaconf_merge=true}' # \u4f7f\u7528 dynaconf_merge \u5408\u5e76\nexport BLOG_REST_FRAMEWORK='@merge {PAGE_SIZE=10}' # \u4f7f\u7528 merge \u5173\u952e\u5b57\nexport BLOG_REST_FRAMEWORK='@merge PAGE_SIZE=10' # \u7b80\u5199\nexport BLOG_DATABASES__default__PASSWORD='123456' # \u4f7f\u7528\u4e24\u4e2a\u4e0b\u5212\u7ebf (__) \u4f5c\u4e3a\u5b50\u7ea7\n
"},{"location":"guidelines/advanced/exception/","title":"\u5f02\u5e38\u7ba1\u7406","text":"\u51e0\u4e4e\u6240\u6709\u7f16\u7a0b\u8bed\u8a00\u4e2d\u90fd\u6709\u5f02\u5e38\u3002\u5f02\u5e38\u53ef\u4ee5\u5feb\u901f\u6307\u51fa\u7a0b\u5e8f\u51fa\u73b0\u7684\u95ee\u9898\uff0c\u4fbf\u4e8e\u6392\u67e5\u3002\u5f00\u53d1\u4eba\u5458\u4e5f\u53ef\u4ee5\u6839\u636e\u60c5\u51b5\u629b\u51fa\u81ea\u5b9a\u4e49\u5f02\u5e38\uff0c \u4ee5\u6307\u793a\u671f\u671b\u7684\u5185\u5bb9\u548c\u5b9e\u9645\u4e0d\u76f8\u7b26\u3002\u826f\u597d\u7684\u5f02\u5e38\u8bbe\u8ba1\u548c\u4f7f\u7528\u4e60\u60ef\uff0c\u53ef\u4ee5\u63d0\u9ad8\u7a0b\u5e8f\u7684\u8d28\u91cf\u3002
"},{"location":"guidelines/advanced/exception/#_2","title":"\u4ecb\u7ecd","text":"Python \u4e2d\u7684\u5f02\u5e38\u5206\u4e3a\u4e24\u7c7b\uff0c\u4e00\u662f\u8bed\u6cd5\u9519\u8bef\uff0c\u4e00\u7c7b\u662f\u5f02\u5e38\u3002
"},{"location":"guidelines/advanced/exception/#_3","title":"\u53e5\u6cd5\u9519\u8bef","text":"\u8bed\u6cd5\u9519\u8bef\u662f\u7528\u6765\u6307\u793a Python \u7f16\u7801\u4e0d\u7b26\u5408\u8bed\u6cd5\u89c4\u8303\u7684\uff1a
>>> while True print('Hello world')\nFile \"<stdin>\", line 1\nwhile True print('Hello world')\n^\nSyntaxError: invalid syntax\n
\u5982\u4e0a\u6240\u793a\uff0c\u4f7f\u7528 ^
\u6307\u793a\u9519\u8bef\u7684\u4f4d\u7f6e\u3002
"},{"location":"guidelines/advanced/exception/#_4","title":"\u5f02\u5e38","text":"\u5373\u4f7f\u8bed\u53e5\u6216\u8868\u8fbe\u5f0f\u4f7f\u7528\u4e86\u6b63\u786e\u7684\u8bed\u6cd5\uff0c\u6267\u884c\u65f6\u4ecd\u53ef\u80fd\u89e6\u53d1\u9519\u8bef\u3002\u6267\u884c\u65f6\u68c0\u6d4b\u5230\u7684\u9519\u8bef\u79f0\u4e3a \u5f02\u5e38\uff0c \u5f02\u5e38\u4e0d\u4e00\u5b9a\u5bfc\u81f4\u4e25\u91cd\u7684\u540e\u679c\uff1a\u5f88\u5feb\u6211\u4eec\u5c31\u80fd\u5b66\u4f1a\u5982\u4f55\u5904\u7406 Python \u7684\u5f02\u5e38\u3002\u5927\u591a\u6570\u5f02\u5e38\u4e0d\u4f1a\u88ab\u7a0b\u5e8f\u5904\u7406\uff0c \u800c\u662f\u663e\u793a\u4e0b\u5217\u9519\u8bef\u4fe1\u606f\uff1a
>>> 10 * (1/0)\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in <module>\nZeroDivisionError: division by zero\n>>> 4 + spam*3\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in <module>\nNameError: name 'spam' is not defined\n>>> '2' + 2\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in <module>\nTypeError: can only concatenate str (not \"int\") to str\n
\u5185\u7f6e\u5f02\u5e38\u7ed3\u6784\u5982\u4e0b\uff1a
BaseException\n+-- SystemExit\n+-- KeyboardInterrupt\n+-- GeneratorExit\n+-- Exception\n+-- StopIteration\n+-- StopAsyncIteration\n+-- ArithmeticError\n| +-- FloatingPointError\n| +-- OverflowError\n| +-- ZeroDivisionError\n+-- AssertionError\n+-- AttributeError\n+-- BufferError\n+-- EOFError\n+-- ImportError\n| +-- ModuleNotFoundError\n+-- LookupError\n| +-- IndexError\n| +-- KeyError\n+-- MemoryError\n+-- NameError\n| +-- UnboundLocalError\n+-- OSError\n| +-- BlockingIOError\n| +-- ChildProcessError\n| +-- ConnectionError\n| | +-- BrokenPipeError\n| | +-- ConnectionAbortedError\n| | +-- ConnectionRefusedError\n| | +-- ConnectionResetError\n| +-- FileExistsError\n| +-- FileNotFoundError\n| +-- InterruptedError\n| +-- IsADirectoryError\n| +-- NotADirectoryError\n| +-- PermissionError\n| +-- ProcessLookupError\n| +-- TimeoutError\n+-- ReferenceError\n+-- RuntimeError\n| +-- NotImplementedError\n| +-- RecursionError\n+-- SyntaxError\n| +-- IndentationError\n| +-- TabError\n+-- SystemError\n+-- TypeError\n+-- ValueError\n| +-- UnicodeError\n| +-- UnicodeDecodeError\n| +-- UnicodeEncodeError\n| +-- UnicodeTranslateError\n+-- Warning\n+-- DeprecationWarning\n+-- PendingDeprecationWarning\n+-- RuntimeWarning\n+-- SyntaxWarning\n+-- UserWarning\n+-- FutureWarning\n+-- ImportWarning\n+-- UnicodeWarning\n+-- BytesWarning\n+-- EncodingWarning\n+-- ResourceWarning\n
"},{"location":"guidelines/advanced/exception/#_5","title":"\u4f7f\u7528","text":""},{"location":"guidelines/advanced/exception/#_6","title":"\u6355\u83b7\u5f02\u5e38","text":"\u5728\u903b\u8f91\u4e2d\uff0c\u53ef\u80fd\u51fa\u73b0\u4e0d\u7b26\u5408\u9884\u671f\u7684\u903b\u8f91\uff0c\u4f1a\u629b\u51fa\u76f8\u5173\u5f02\u5e38\u3002\u6b64\u65f6\u5728\u7f16\u7801\u65f6\uff0c\u4e3a\u4e86\u903b\u8f91\u7684\u6b63\u5e38\u8fd0\u884c\uff0c\u9700\u8981\u5bf9\u903b\u8f91\u8fdb\u884c\u5904\u7406\uff1a
import sys\ntry:\nf = open('myfile.txt')\ns = f.readline()\ni = int(s.strip())\nexcept OSError as err:\nprint(\"OS error: {0}\".format(err))\nexcept ValueError:\nprint(\"Could not convert data to an integer.\")\nexcept BaseException as err:\nprint(f\"Unexpected {err=}, {type(err)=}\")\nraise\n
\u5982\u4e0a\u8ff0\u903b\u8f91\uff0c\u5bf9\u4e8e\u5df2\u77e5\u80fd\u5224\u65ad\u7684\u60c5\u51b5\uff0c\u53ef\u4ee5\u901a\u8fc7\u65e5\u5fd7\u8f93\u51fa\u663e\u793a\u53cb\u597d\u4fe1\u606f\uff0c\u907f\u514d\u7a0b\u5e8f\u7acb\u5373\u505c\u6b62\u3002\u5f53\u65e0\u6cd5\u5224\u65ad\u5f02\u5e38\u65f6\uff0c\u5219 \u7ee7\u7eed\u629b\u51fa\u5f02\u5e38\u3002
\u6355\u83b7\u5f02\u5e38\u662f\uff0c\u4f7f\u7528 try...except
\u4ee3\u7801\u5757\u5305\u88f9\u9700\u8981\u5904\u7406\u5f02\u5e38\u7684\u4ee3\u7801\u3002 expect
\u6355\u83b7\u6307\u5b9a\u7684\u5f02\u5e38\u7c7b\u578b\uff0c\u5982\u679c\u51fa\u73b0\uff0c\u8fdb\u5165 \u5bf9\u5e94\u7684\u4ee3\u7801\u903b\u8f91\u3002\u5bf9\u4e8e\u4e00\u4e9b\u4e0d\u60f3\u5904\u7406\u7684\uff0c\u901a\u8fc7 raise
\u629b\u51fa\u5f02\u5e38\u3002
"},{"location":"guidelines/advanced/exception/#_7","title":"\u5f02\u5e38\u94fe","text":"\u5f53\u629b\u51fa\u5f02\u5e38\u65f6\uff0c raise
\u8bed\u53e5\u652f\u6301 from
\u5b50\u53e5\u542f\u7528\u94fe\u5f0f\u5f02\u5e38\u3002
>>> def func():\n... raise ConnectionError\n...\n>>> try:\n... func()\n... except ConnectionError as exc:\n... raise RuntimeError('Failed to open database') from exc\n...\nTraceback (most recent call last):\nFile \"<stdin>\", line 2, in <module>\nFile \"<stdin>\", line 2, in func\nConnectionError\nThe above exception was the direct cause of the following exception:\nTraceback (most recent call last):\nFile \"<stdin>\", line 4, in <module>\nRuntimeError: Failed to open database\n
\u4e0a\u8ff0\u793a\u4f8b\u4e2d\uff0c\u5f02\u5e38\u4fe1\u606f\u4e2d\u542b\u6709\u4e24\u6b21\u629b\u51fa\u7684\u5f02\u5e38\u3002\u8fd9\u5bf9\u4e8e\u8c03\u8bd5\u5f88\u6709\u5e2e\u52a9\u3002
\u5982\u679c\u4e0d\u60f3\u629b\u51fa\u94fe\u5f0f\u5f02\u5e38\uff0c\u53ef\u4ee5\u4f7f\u7528 from None
\uff1a
>>> try:\n... open('database.sqlite')\n... except OSError:\n... raise RuntimeError from None\n...\nTraceback (most recent call last):\nFile \"<stdin>\", line 4, in <module>\nRuntimeError\n
"},{"location":"guidelines/advanced/exception/#_8","title":"\u81ea\u5b9a\u4e49\u5f02\u5e38","text":"\u7a0b\u5e8f\u53ef\u4ee5\u901a\u8fc7\u521b\u5efa\u65b0\u7684\u5f02\u5e38\u7c7b\u547d\u540d\u81ea\u5df1\u7684\u5f02\u5e38\uff08Python \u7c7b\u7684\u5185\u5bb9\u8be6\u89c1 \u7c7b\uff09\u3002\u4e0d\u8bba\u662f\u4ee5\u76f4\u63a5\u8fd8\u662f\u95f4\u63a5\u7684\u65b9\u5f0f\uff0c\u5f02\u5e38\u90fd\u5e94\u4ece Exception \u7c7b\u6d3e\u751f\u3002
\u5f02\u5e38\u7c7b\u548c\u5176\u4ed6\u7c7b\u4e00\u6837\uff0c\u53ef\u4ee5\u6267\u884c\u4efb\u4f55\u64cd\u4f5c\u3002\u4f46\u901a\u5e38\u4f1a\u6bd4\u8f83\u7b80\u5355\uff0c\u53ea\u63d0\u4f9b\u8ba9\u5904\u7406\u5f02\u5e38\u7684\u7a0b\u5e8f\u63d0\u53d6\u9519\u8bef\u4fe1\u606f\u7684\u4e00\u4e9b\u5c5e\u6027\u3002 \u521b\u5efa\u80fd\u89e6\u53d1\u591a\u4e2a\u4e0d\u540c\u9519\u8bef\u7684\u6a21\u5757\u65f6\uff0c\u4e00\u822c\u53ea\u4e3a\u8be5\u6a21\u5757\u5b9a\u4e49\u5f02\u5e38\u57fa\u7c7b\uff0c\u7136\u540e\u518d\u6839\u636e\u4e0d\u540c\u7684\u9519\u8bef\u6761\u4ef6\uff0c\u521b\u5efa\u6307\u5b9a\u5f02\u5e38\u7c7b\u7684\u5b50\u7c7b\uff1a
class Error(Exception):\n\"\"\"Base class for exceptions in this module.\"\"\"\npass\nclass InputError(Error):\n\"\"\"Exception raised for errors in the input.\n Attributes:\n expression -- input expression in which the error occurred\n message -- explanation of the error\n \"\"\"\ndef __init__(self, expression, message):\nself.expression = expression\nself.message = message\nclass TransitionError(Error):\n\"\"\"Raised when an operation attempts a state transition that's not\n allowed.\n Attributes:\n previous -- state at beginning of transition\n next -- attempted new state\n message -- explanation of why the specific transition is not allowed\n \"\"\"\ndef __init__(self, previous, next, message):\nself.previous = previous\nself.next = next\nself.message = message\n
\u5927\u591a\u6570\u5f02\u5e38\u547d\u540d\u90fd\u4ee5 \u201cError\u201d \u7ed3\u5c3e\uff0c\u7c7b\u4f3c\u6807\u51c6\u5f02\u5e38\u7684\u547d\u540d\u3002
\u8bb8\u591a\u6807\u51c6\u6a21\u5757\u90fd\u9700\u8981\u81ea\u5b9a\u4e49\u5f02\u5e38\uff0c\u4ee5\u62a5\u544a\u7531\u5176\u5b9a\u4e49\u7684\u51fd\u6570\u4e2d\u51fa\u73b0\u7684\u9519\u8bef\u3002
"},{"location":"guidelines/advanced/exception/#_9","title":"\u5f02\u5e38\u6e05\u7406","text":"\u5bf9\u4e8e\u50cf\u6587\u4ef6\u6216\u8005\u8fde\u63a5\u5bf9\u8c61\u7684\u64cd\u4f5c\uff0c\u5728\u6253\u5f00\u540e\uff0c\u9700\u8981\u5728\u5f02\u5e38\u6700\u540e\u5173\u95ed\uff0c\u5c31\u9700\u8981\u7528\u5230\u5f02\u5e38\u6e05\u7406\u3002
import sys\ntry:\nf = open('myfile.txt')\ns = f.readline()\ni = int(s.strip())\nexcept OSError as err:\nprint(\"OS error: {0}\".format(err))\nraise\nfinally:\nf.close()\n
\u4e0a\u8ff0\u903b\u8f91\u4e2d\uff0c\u4f7f\u7528 try...expect...finally
\u505a\u629b\u51fa\u5f02\u5e38\u540e\u7684\u6e05\u7406\u5de5\u4f5c\u3002\u5176\u4e2d finally
\u4ee3\u7801\u5757\u4e2d\uff0c\u5173\u95ed\u4e86\u524d\u9762 \u6253\u5f00\u7684\u6587\u4ef6\u5bf9\u8c61\u3002
def divide(x, y):\ntry:\nresult = x / y\nexcept ZeroDivisionError:\nprint(\"division by zero!\")\nelse:\nprint(\"result is\", result)\nfinally:\nprint(\"executing finally clause\")\n
\u4e0a\u8ff0\u793a\u4f8b\u4ee3\u7801\u901a\u8fc7 else
\u903b\u8f91\u5757\u6267\u884c\u6ca1\u6709\u89e6\u53d1\u5f02\u5e38\u65f6\u7684\u903b\u8f91\u3002
\u5bf9\u4e8e\u4e00\u4e9b\u6e05\u7406\u6027\u7684\u5de5\u4f5c\uff0c\u63a8\u8350\u4f7f\u7528 with \u8bed\u53e5\u81ea\u52a8\u7ba1\u7406\u4e0a\u4e0b\u6587\u3002
"},{"location":"guidelines/advanced/exception/#_10","title":"\u5b9e\u8df5","text":"\u5f00\u53d1\u5b9e\u8df5\u4e2d\uff0c\u5f02\u5e38\u4fe1\u606f\u5bf9\u8bca\u65ad\u7a0b\u5e8f\u975e\u5e38\u91cd\u8981\u3002\u6240\u4ee5\u5728\u4f7f\u7528\u548c\u5904\u7406\u5f02\u5e38\u65f6\uff0c\u8bf7\u9075\u5faa\u5982\u4e0b\u51e0\u70b9\uff1a
- \u9700\u8981\u5904\u7406\u5f02\u5e38\u65f6\u4f7f\u7528
try...except...finally
\u6355\u83b7 - \u5904\u7406\u5f02\u5e38\u65f6\uff0c\u5982\u679c\u6ca1\u6709\u7ee7\u7eed\u629b\u51fa\u5f02\u5e38\uff0c\u9700\u8981\u8f93\u5165\u65e5\u5fd7\u4fe1\u606f\u3002\u9664\u975e\u4f60\u77e5\u9053\u4e0d\u8f93\u51fa\u4efb\u4f55\u4fe1\u606f\u4e0d\u4f1a\u9020\u6210\u6392\u9519\u56f0\u96be\u3002
- \u9879\u76ee\u7ea7\u522b\uff0c\u4e00\u5b9a\u8981\u5b9a\u4e49\u4e00\u4e2a\u9879\u76ee\u7684\u57fa\u7c7b\u5f02\u5e38\u3002\u9879\u76ee\u4e2d\u5176\u4ed6\u81ea\u5b9a\u4e49\u5f02\u5e38\u5fc5\u987b\u7ee7\u627f\u8be5\u57fa\u7c7b\u5f02\u5e38\u3002\u8fd9\u4e48\u505a\u7684\u76ee\u7684\u662f\u53ef\u4ee5\u5728\u5916\u5c42\u903b\u8f91\u901a\u8fc7\u6355\u83b7\u57fa\u7c7b \u5f02\u5e38\u6765\u53ea\u6355\u83b7\u629b\u51fa\u7684\u81ea\u5b9a\u4e49\u5f02\u5e38\u3002
- \u9879\u76ee\u5f02\u5e38\u8981\u4ee5
ERROR
\u7ed3\u5c3e\u3002\u548c\u6807\u51c6\u5f02\u5e38\u547d\u540d\u7c7b\u4f3c\u3002
"},{"location":"guidelines/advanced/logging/","title":"Logging","text":"\u5728\u5f00\u53d1\u4e2d\uff0c\u901a\u5e38\u4f1a\u4f7f\u7528 print
\u8f93\u51fa\u4e00\u4e9b\u4fe1\u606f\uff0c\u6216\u8005\u8bca\u65ad\u4fe1\u606f\u3002\u5728\u4fe1\u606f\u7684\u5b8c\u6574\u6027\u548c\u4fe1\u606f\u683c\u5f0f\u4e0a\u90fd\u4e0d\u80fd\u7b80\u4fbf\u4e14\u7075\u6d3b\u63a7\u5236\u3002\u6b64\u65f6\u4f7f\u7528 logging
\u662f\u4e2a\u66f4\u597d\u7684\u9009\u62e9\uff0c \u800c\u4e14\u4e5f\u9f13\u52b1\u5f00\u53d1\u4eba\u5458\u5c3d\u53ef\u80fd\u7684\u4f18\u5148\u9009\u7528\u6253\u5370\u65e5\u5fd7\u7684\u65b9\u5f0f\u5728\u63a7\u5236\u53f0\u8f93\u51fa\u4fe1\u606f\u3002
\u65e5\u5fd7\u662f\u5bf9\u8f6f\u4ef6\u6267\u884c\u65f6\u6240\u53d1\u751f\u4e8b\u4ef6\u7684\u4e00\u79cd\u8ffd\u8e2a\u65b9\u5f0f\u3002\u8f6f\u4ef6\u5f00\u53d1\u4eba\u5458\u5bf9\u4ed6\u4eec\u7684\u4ee3\u7801\u6dfb\u52a0\u65e5\u5fd7\u8c03\u7528\uff0c\u501f\u6b64\u6765\u6307\u793a\u67d0\u4e8b\u4ef6\u7684\u53d1\u751f\u3002 \u4e00\u4e2a\u4e8b\u4ef6\u901a\u8fc7\u4e00\u4e9b\u5305\u542b\u53d8\u91cf\u6570\u636e\u7684\u63cf\u8ff0\u4fe1\u606f\u6765\u63cf\u8ff0\uff08\u6bd4\u5982\uff1a\u6bcf\u4e2a\u4e8b\u4ef6\u53d1\u751f\u65f6\u7684\u6570\u636e\u90fd\u662f\u4e0d\u540c\u7684\uff09\u3002\u5f00\u53d1\u8005\u8fd8\u4f1a\u533a\u5206\u4e8b\u4ef6\u7684\u91cd\u8981\u6027\uff0c \u91cd\u8981\u6027\u4e5f\u88ab\u79f0\u4e3a \u7b49\u7ea7 \u6216 \u4e25\u91cd\u6027\u3002\u6709\u4e00\u4e2a\u597d\u7684\u65e5\u5fd7\u5b9e\u8df5\uff0c\u80fd\u8ba9\u5f00\u53d1\u8c03\u8bd5\u6d41\u7a0b\u66f4\u987a\u7545\uff0c\u51fa\u73b0\u95ee\u9898\u80fd\u66f4\u5feb\u901f\u7cbe\u51c6\u5b9a\u4f4d\u3002
\u672c\u6587\u4e0d\u4f1a\u4ee5\u6700\u57fa\u7840\u7684\u65b9\u5f0f\u8bb2\u8ff0 Python logging \u7684\u4f7f\u7528\uff0c\u800c\u662f\u4ee5\u5f53\u524d\u603b\u7ed3\u7684\u5b9e\u8df5\u65b9\u5f0f\u7ed3\u5408\u5b9e\u9645\u64cd\u4f5c\u6848\u4f8b\u5c55\u793a Logging \u7684\u4f7f\u7528\uff0c\u6240\u4ee5\u5728\u9605\u8bfb\u6587\u7ae0\u524d\uff0c \u4f60\u5e94\u8be5\u63d0\u524d\u4e86\u89e3 \u65e5\u5fd7 HOWTO \u548c Python \u7684\u65e5\u5fd7\u8bb0\u5f55\u5de5\u5177 \u4e24\u7bc7\u6587\u6863\u3002
"},{"location":"guidelines/advanced/logging/#1","title":"1. \u7b80\u5355\u4f7f\u7528","text":"\u5728\u4e00\u822c\u5f00\u53d1\u4e2d\uff0c\u5bf9\u4e8e\u4e34\u65f6\u5f00\u53d1\u7684\u9879\u76ee\uff0c\u53ef\u80fd\u4e3a\u4e86\u5feb\u901f\u5b8c\u6210\u4efb\u52a1\uff0c\u9879\u76ee\u4e2d\u5927\u91cf\u4f7f\u7528\u4e86 print
\u5c06\u8c03\u8bd5\u4fe1\u606f\u8f93\u51fa\u5230\u63a7\u5236\u53f0\u3002 \u9879\u76ee\u540e\u671f\u5c31\u4f1a\u51fa\u73b0\u8c03\u8bd5\u56f0\u96be\u7b49\u95ee\u9898\u3002\u672c\u8282\u4f1a\u63d0\u4f9b\u5728\u7b80\u5355\u73af\u5883\u4e0b\u5feb\u901f\u4f7f\u7528\u65e5\u5fd7\u7684\u65b9\u5f0f\u3002
"},{"location":"guidelines/advanced/logging/#11","title":"1.1 \u5355\u6587\u4ef6\u4f7f\u7528","text":"\u5bf9\u4e8e\u5355\u6587\u4ef6\u7684\u4f7f\u7528\uff0c\u76f4\u63a5\u4f7f\u7528\u6839\u65e5\u5fd7\u5bf9\u8c61\u5373\u53ef\u3002\u7531\u4e8e\u9ed8\u8ba4\u7684\u65e5\u5fd7\u7ea7\u522b\u4e3a WARNING
\uff0c\u6240\u4ee5\u4f7f\u7528\u66f4\u4f4e\u7ea7\u522b\u7684\u65e5\u5fd7\u662f\u65e0\u6cd5\u663e\u793a\u7684\u3002
\"\"\"Simple logging\"\"\"\nimport logging\nlogging.warning('I love you ~')\n
\u5982\u679c\u540e\u7eed\u5f00\u53d1\u6709\u8981\u63a7\u5236\u65e5\u5fd7\u7ea7\u522b\u7684\u9700\u6c42\uff0c\u76f4\u63a5\u5728\u5f00\u59cb\u7684\u65f6\u5019\u521d\u59cb\u5316\u65e5\u5fd7\u914d\u7f6e\u5c31\u53ef\u4ee5\u4e86\u3002
\"\"\"Simple logging\"\"\"\nimport logging\nlogging.basicConfig(level=logging.DEBUG)\nlogging.warning('I love you ~')\nlogging.debug('I love you too ~')\n
\u5f53\u9700\u8981\u8f93\u51fa\u66f4\u8be6\u7ec6\u7684\u65e5\u5fd7\u4fe1\u606f\uff0c\u5982\u6267\u884c\u65f6\u95f4\u3001\u65e5\u5fd7\u7ea7\u522b\u3001\u7ebf\u7a0b\u6216\u8fdb\u7a0b\u4fe1\u606f\u7b49\uff0c\u90fd\u53ef\u4ee5\u5f88\u65b9\u4fbf\u7684\u63a7\u5236\u3002
\"\"\"Simple logging\"\"\"\nimport logging\nlogging.basicConfig(\nlevel=logging.DEBUG,\n# format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',\n# format='%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(process)d %(thread)d - %(message)s',\nformat='%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(process)d %(thread)d - %(pathname)s:%(lineno)d %(message)s',\ndatefmt='%Y-%m-%dT%H:%M:%S.%s+0800',\n)\nlogging.warning('I love you ~')\nlogging.debug('I love you too ~')\n
\u867d\u7136\u4f7f\u7528 print
\u80fd\u66f4\u5feb\u901f\u7684\u5728\u63a7\u5236\u53f0\u8f93\u51fa\u60f3\u8981\u770b\u5230\u7684\u5185\u5bb9\uff0c\u4f46\u4ece\u4e0a\u9762\u7684\u793a\u4f8b\u6765\u770b\uff0c\u76f4\u63a5\u4f7f\u7528\u9ed8\u8ba4\u7684\u65e5\u5fd7\u8f93\u51fa\u4e5f\u662f\u5f88\u65b9\u4fbf\u7684\uff0c\u552f\u4e00\u7684\u533a\u522b\u53ef\u80fd \u5c31\u662f\u9700\u8981\u5bfc\u5165\u4e86\u3002\u800c\u4f7f\u7528\u65e5\u5fd7\u7684\u8bdd\uff0c\u60f3\u8981\u5728\u540e\u7eed\u589e\u52a0\u8f93\u51fa\u66f4\u7cbe\u786e\u7684\u4fe1\u606f\u5c31\u663e\u5f97\u6bd4\u8f83\u7075\u6d3b\u3002
\u65e5\u5fd7\u683c\u5f0f\u6240\u652f\u6301\u7684\u5b57\u6bb5\u8bf7\u53c2\u8003 LogRecord \u5c5e\u6027 \u3002
\u8fd9\u4e5f\u662f\u4e3a\u4ec0\u4e48\u5efa\u8bae\u4f18\u5148\u4f7f\u7528\u65e5\u5fd7\u7684\u539f\u56e0\u3002
"},{"location":"guidelines/advanced/logging/#12","title":"1.2 \u4e00\u822c\u9879\u76ee\u4e2d\u4f7f\u7528","text":"\u5bf9\u4e8e\u4e00\u822c\u7684\u9879\u76ee\uff0c\u53ef\u80fd\u4f1a\u6709\u591a\u4e2a\u6a21\u5757\u6216\u8005\u5305\uff0c\u5728\u6bcf\u4e2a\u6a21\u5757\u4e2d\u90fd\u521d\u59cb\u5316\u4e00\u4e0b\u65e5\u5fd7\u914d\u7f6e\u663e\u7136\u8fdd\u53cd\u4e86 DRY \u539f\u5219\u3002 \u6240\u4ee5\u6700\u597d\u662f\u6709\u4e00\u4e2a\u516c\u5171\u7684\u65e5\u5fd7\u6a21\u5757\uff0c\u5728\u8be5\u6a21\u5757\u4e2d\u521d\u59cb\u5316\u65e5\u5fd7\u3002
log.py \uff1a
\"\"\"Log config\"\"\"\nimport logging\nlogging.basicConfig(\nlevel=logging.DEBUG,\nformat='%(asctime)s - %(name)s - %(levelname)s - %(message)s',\ndatefmt='%Y-%m-%dT%H:%M:%S.%s+0800',\n)\nlogger = logging.getLogger()\n
\u5728\u5176\u4ed6\u6a21\u5757\u4e2d\u76f4\u63a5\u5bfc\u5165 from log import logger
\u5373\u53ef\u4f7f\u7528\u3002
\u63d0\u793a \u5728 log.py
\u4e2d\u521d\u59cb\u5316\u65e5\u5fd7\u914d\u7f6e\u540e\uff0c\u867d\u7136\u53ef\u4ee5\u5728\u5176\u4ed6\u6a21\u5757\u4e2d\u76f4\u63a5\u4f7f\u7528 logging
\u6253\u5370\u65e5\u5fd7\uff0c\u4f46\u5982\u679c\u542f\u52a8\u811a\u672c\u7684\u65f6\u5019\u6ca1\u6709\u5bfc\u5165\u8be5\u6a21\u5757\uff0c \u8be5\u6a21\u5757\u4e2d\u7684\u5185\u5bb9\u662f\u4e0d\u4f1a\u52a0\u8f7d\u7684\uff0c\u4e5f\u5c31\u662f\u6240\u65e5\u5fd7\u914d\u7f6e\u5e76\u6ca1\u6709\u5b9e\u9645\u6267\u884c\u3002
\u6b64\u65f6\u53ef\u4ee5\u91cd\u6784\u4ee3\u7801\uff1a
\"\"\"Log config\"\"\"\nimport logging\ndef config_logging() -> None:\n\"\"\"Config logging\"\"\"\nlogging.basicConfig(\nlevel=logging.DEBUG,\nformat='%(asctime)s - %(name)s - %(levelname)s - %(message)s',\ndatefmt='%Y-%m-%dT%H:%M:%S.%s+0800',\n)\n
\u7136\u540e\u5728\u9879\u76ee\u5b9e\u9645\u6267\u884c\u4e4b\u524d\u8c03\u7528\u65b9\u6cd5\u521d\u59cb\u5316\u914d\u7f6e\u5c31\u53ef\u4ee5\u4e86\u3002
\u5bf9\u4e8e\u9700\u8981\u7075\u6d3b\u63a7\u5236\u65e5\u5fd7\u914d\u7f6e\u7684\uff0c\u53ef\u4ee5\u5c06\u65e5\u5fd7\u7ea7\u522b\u653e\u5230\u914d\u7f6e\u4e2d\uff0c\u901a\u8fc7\u5de5\u5382\u65b9\u5f0f\u521d\u59cb\u5316\uff1a
\"\"\"Log config\"\"\"\nimport logging\nfrom typing import Optional\ndef config_logging(\nlevel: Optional[int, str] = logging.DEBUG,\nlog_format: Optional[str] = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n) -> None:\n\"\"\"\n Config logging\n :param level:\n :param log_format:\n :return:\n \"\"\"\nlogging.basicConfig(\nlevel=level,\nformat=log_format,\ndatefmt='%Y-%m-%dT%H:%M:%S.%s+0800',\n)\n
\u5728\u903b\u8f91\u6267\u884c\u4e4b\u524d\u8c03\u7528\u65b9\u6cd5\u901a\u8fc7\u53c2\u6570\u5de5\u5382\u5316\u65e5\u5fd7\u914d\u7f6e\u3002
"},{"location":"guidelines/advanced/logging/#2","title":"2. \u901a\u7528\u5b9e\u8df5","text":"\u672c\u8282\u5185\u5bb9\u662f\u5728\u901a\u7528\u9879\u76ee\u4e2d\u53ef\u4ee5\u4f7f\u7528\u7684\u4e00\u822c\u5b9e\u8df5\u65b9\u6cd5\u3002
"},{"location":"guidelines/advanced/logging/#21-ini","title":"2.1 \u4f7f\u7528 ini
\u683c\u5f0f\u6587\u4ef6\u914d\u7f6e\u65e5\u5fd7","text":"\u5728\u9879\u76ee\u4e2d\u65b0\u5efa\u4e00\u4e2a log.ini
\u7684\u914d\u7f6e\u6587\u4ef6\uff1a
log.ini \uff1a
[loggers]\nkeys = root,simpleExample\n[handlers]\nkeys = consoleHandler\n[formatters]\nkeys = simpleFormatter\n[logger_root]\nlevel = DEBUG\nhandlers = consoleHandler\n[logger_simpleExample]\nlevel = DEBUG\nhandlers = consoleHandler\nqualname = simpleExample\npropagate = 0\n[handler_consoleHandler]\nclass = StreamHandler\nlevel = DEBUG\nformatter = simpleFormatter\nargs = (sys.stdout,)\n[formatter_simpleFormatter]\nformat = %(asctime)s - %(name)s - %(levelname)s - %(message)s\ndatefmt =\n
\u65b0\u5efa\u4e00\u4e2a log.py
\u7684\u5305\uff1a
log.py \uff1a
from logging.config import fileConfig\ndef init_ini_log() -> None:\nfileConfig('log.ini')\n
\u6700\u540e\u5728\u7a0b\u5e8f\u6267\u884c\u524d\uff0c\u521d\u59cb\u5316\u65e5\u5fd7\u914d\u7f6e\u3002
logging \u9ed8\u8ba4\u4f7f\u7528 configparser \u89e3\u6790 ini
\u683c\u5f0f\u6587\u4ef6\u3002 \u5982\u679c\u4f60\u60f3\u4f7f\u7528\u5176\u4ed6\u683c\u5f0f\uff0c\u5982 toml
\u6216\u8005 yaml
\uff0c\u5219\u9700\u8981\u81ea\u5df1\u624b\u52a8\u8bfb\u53d6\u5e76\u89e3\u6790\u6587\u4ef6\u5185\u5bb9\u4e3a Dict \u5373\u53ef\u3002
"},{"location":"guidelines/advanced/logging/#22-yaml","title":"2.2 \u4f7f\u7528 yaml
\u683c\u5f0f\u6587\u4ef6\u914d\u7f6e\u65e5\u5fd7","text":"\u65b0\u5efa log.yml
\u6587\u4ef6\uff1a
log.yml \uff1a
version: 1\nformatters:\nsimple:\nformat: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'\nhandlers:\nconsole:\nclass: logging.StreamHandler\nlevel: DEBUG\nformatter: simple\nstream: ext://sys.stdout\nloggers:\nsimpleExample:\nlevel: DEBUG\nhandlers:\n- console\npropagate: false\nroot:\nlevel: DEBUG\nhandlers:\n- console\n
YAML \u683c\u5f0f\u89e3\u6790\u5de5\u5177\u6709\u4e09\u4e2a\uff0c\u90fd\u5728\u6587\u6863\u4e2d\u6709\u76f8\u5e94\u5730\u5740\u3002\u5728\u8fd9\u91cc\u9009\u7528 PyYAML \u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
pip install pyyaml\n
\u65b0\u5efa log.py
\u6a21\u5757\uff1a
log.py \uff1a
from logging.config import dictConfig\nfrom yaml import load\ntry:\nfrom yaml import CLoader as Loader, CDumper as Dumper\nexcept ImportError:\nfrom yaml import Loader, Dumper\ndef init_yml_log() -> None:\nwith open('log.yml', mode='r') as obj:\nlogging_config = load(obj, Loader=Loader)\ndictConfig(logging_config)\n
\u6700\u540e\u5728\u7a0b\u5e8f\u6267\u884c\u524d\uff0c\u521d\u59cb\u5316\u65e5\u5fd7\u914d\u7f6e\u3002
"},{"location":"guidelines/advanced/logging/#23","title":"2.3 \u6ce8\u610f\u4e8b\u9879","text":""},{"location":"guidelines/advanced/logging/#231","title":"2.3.1 \u4f18\u5316","text":"\u6839\u636e\u6587\u6863\u4e2d \u4f18\u5316 \u4e00\u8282\u5185\u5bb9\u63cf\u8ff0\uff0c\u65e5\u5fd7\u4e2d\u7684\u53c2\u6570\u5316\u6d88\u606f\uff0c \u5e94\u8be5\u5ef6\u8fdf\u52a0\u8f7d\u3002\u8fd9\u4e48\u505a\u662f\u4e3a\u4e86\u51cf\u5c11\u5728\u8ba1\u7b97\u65e5\u5fd7\u53c2\u6570\u65f6\u6240\u6d88\u8017\u7684\u8d44\u6e90\uff0c\u56e0\u4e3a\u5982\u679c\u65e5\u5fd7\u8bb0\u5f55\u975e\u4e22\u5f03\uff0c\u5219\u4e0d\u9700\u8981\u6d88\u8017\u8fd9\u90e8\u5206\u8d44\u6e90\u3002\u6240\u4ee5\u5728 \u65e5\u5fd7\u8bb0\u5f55\u4e0a\uff0c\u5e94\u91c7\u7528 %
\u7684\u65b9\u5f0f\uff0c\u800c\u4e0d\u662f\u5176\u4ed6\u5b57\u7b26\u4e32\u683c\u5f0f\u5316\u3002
\u5173\u4e8e\u6027\u80fd\u7684\u8ba8\u8bba\u53ef\u4ee5\u53c2\u8003 W1202 - logging-fstring-interpolation is not useful \u3002
"},{"location":"guidelines/advanced/plugin/","title":"\u63d2\u4ef6","text":"plug-in \u5728\u7ef4\u57fa\u767e\u79d1\u4e2d\u662f\u8fd9\u4e48\u5b9a\u4e49\u7684\uff1a\u201c\u5728\u8ba1\u7b97\u4e2d\uff0c\u63d2\u4ef6\u662f\u8f6f\u4ef6\u7ec4\u4ef6\uff0c\u4e3a\u73b0\u6709\u8ba1\u7b97\u673a\u7a0b\u5e8f\u589e\u52a0\u4e00\u4e2a\u7279\u5b9a\u7684\u7279\u5f81\u3002\u201d \u6240\u4ee5\u63d2\u4ef6\u5e94\u8be5\u662f\u4e00\u4e2a\u80fd\u591f\u7075\u6d3b\u914d\u7f6e\uff0c\u5e76\u5f88\u65b9\u4fbf\u7684\u8f7d\u5165\u914d\u7f6e\u4e2d\u7684\u5185\u5bb9\u3002
\u7531\u4e8e Python \u672c\u8eab\u7684\u52a8\u6001\u7279\u6027\uff0c\u63d2\u4ef6\u5316\u7684\u5b9e\u73b0\u5c31\u66f4\u7075\u6d3b\u3002\u73b0\u6709\u7684\u52a8\u6001\u63d2\u4ef6\u90fd\u662f\u57fa\u4e8e Python \u7684\u547d\u540d\u7a7a\u95f4\u548c\u52a8\u6001\u5bfc\u5165\u529f\u80fd\u6765\u67e5\u627e\u5e76\u5bfc\u5165\u5916\u90e8\u4f9d\u8d56\u3002 \u5177\u4f53\u539f\u7406\u53ef\u4ee5\u67e5\u770b Creating and discovering plugins \u3002
"},{"location":"guidelines/advanced/plugin/#_2","title":"\u63d2\u4ef6\u6846\u67b6","text":""},{"location":"guidelines/advanced/plugin/#pluggy","title":"pluggy","text":"pluggy \u662f\u4ece pytest \u4e2d\u6f14\u5316\u51fa\u6765\u7684\u4e00\u4e2a\u63d2\u4ef6\u5de5\u5177\u3002\u5b83\u4e3a pytest \u63d0\u4f9b\u5916\u56f4\u63d2\u4ef6\u652f\u6301\uff0c\u5f53\u5f00\u53d1\u4eba\u5458\u9700\u8981\u6269\u5c55 pytest \u7684\u529f\u80fd\u65f6\uff0c\u57fa\u4e8e pytest \u7684\u89c4\u8303\u505a\u51fa\u5bf9\u5e94\u7684\u63d2\u4ef6\u7136\u540e\u5c06\u5176\u5b89\u88c5\u5230\u73af\u5883\u4e2d\u540e\uff0c pytest \u5c31\u53ef\u4ee5\u81ea\u52a8\u8bc6\u522b\u5df2\u6709\u63d2\u4ef6\u3002
\u5176\u5177\u4f53\u539f\u7406\u662f\u901a\u8fc7\u521b\u5efa\u4e00\u4e2a hookspec = pluggy.HookspecMarker(\"eggsample\")
\u6765\u6807\u8bb0\u63d2\u4ef6\u4e8b\u5148\u7684\u89c4\u8303\uff0c\u7136\u540e\u4f7f\u7528 hookimpl = pluggy.HookimplMarker(\"eggsample\")
\u6807\u8bb0 \u63d2\u4ef6\u7684\u5b9e\u73b0\u3002
\u89c4\u8303\uff1a
import pluggy\nhookspec = pluggy.HookspecMarker(\"eggsample\")\n@hookspec\ndef eggsample_add_ingredients(ingredients: tuple):\n\"\"\"Have a look at the ingredients and offer your own.\n :param ingredients: the ingredients, don't touch them!\n :return: a list of ingredients\n \"\"\"\n@hookspec\ndef eggsample_prep_condiments(condiments: dict):\n\"\"\"Reorganize the condiments tray to your heart's content.\n :param condiments: some sauces and stuff\n :return: a witty comment about your activity\n
\u5b9e\u73b0\uff1a
import pluggy\nhookimpl = pluggy.HookimplMarker(\"eggsample\")\n\"\"\"Marker to be imported and used in plugins (and for own implementations)\"\"\"\nclass ExamplePluggy:\n@hookimpl\ndef eggsample_add_ingredients(self):\nspices = [\"salt\", \"pepper\"]\nyou_can_never_have_enough_eggs = [\"egg\", \"egg\"]\ningredients = spices + you_can_never_have_enough_eggs\nreturn ingredients\n@hookimpl\ndef eggsample_prep_condiments(self, condiments):\ncondiments[\"mint sauce\"] = 1\n
\u7136\u540e\u5c06\u63d2\u4ef6\u89c4\u8303\u548c\u5b9e\u73b0\u88c5\u8f7d\u5230\u63d2\u4ef6\u7ba1\u7406\u7c7b\u4e2d\uff0c\u4e3a\u4e86\u53ef\u4ee5\u627e\u5230\u5176\u4ed6\u4eba\u5f00\u53d1\u7684\u63d2\u4ef6\uff0c\u9700\u8981\u8c03\u7528 load_setuptools_entrypoints
\u65b9\u6cd5\u4ece\u547d\u540d\u7a7a\u95f4 \u67e5\u627e\u5df2\u7ecf\u5728\u6307\u5b9a\u547d\u540d\u7a7a\u95f4\u4e0b\u7684\u5176\u4ed6\u63d2\u4ef6\u3002
import itertools\nimport random\nimport pluggy\ndef get_plugin_manager():\npm = pluggy.PluginManager(\"eggsample\")\npm.add_hookspecs(hookspecs)\npm.load_setuptools_entrypoints(\"eggsample\")\npm.register(ExamplePluggy)\nreturn pm\n
\u5728\u4f7f\u7528\u65f6\uff0c\u8c03\u7528 pm.hook.eggsample_add_ingredients
\u4f20\u9012\u53c2\u6570\u5373\u53ef\u3002
\u5916\u90e8\u5f00\u53d1\u7684\u63d2\u4ef6\uff0c\u53ea\u9700\u8981\u9075\u5faa\u63d2\u4ef6\u89c4\u8303\u505a\u5b9e\u73b0\uff1a
import eggsample\n@eggsample.hookimpl\ndef eggsample_add_ingredients(ingredients):\n\"\"\"Here the caller expects us to return a list.\"\"\"\nif \"egg\" in ingredients:\nspam = [\"lovely spam\", \"wonderous spam\"]\nelse:\nspam = [\"splendiferous spam\", \"magnificent spam\"]\nreturn spam\n@eggsample.hookimpl\ndef eggsample_prep_condiments(condiments):\n\"\"\"Here the caller passes a mutable object, so we mess with it directly.\"\"\"\ntry:\ndel condiments[\"steak sauce\"]\nexcept KeyError:\npass\ncondiments[\"spam sauce\"] = 42\nreturn \"Now this is what I call a condiments tray!\"\n
\u5e76\u5728\u6253\u5305\u4fe1\u606f\u4e2d\u6807\u6ce8\u76f8\u540c\u7684\u547d\u540d\u7a7a\u95f4\uff1a
from setuptools import setup\nsetup(\nname=\"eggsample-spam\",\ninstall_requires=\"eggsample\",\nentry_points={\"eggsample\": [\"spam = eggsample_spam\"]},\npy_modules=[\"eggsample_spam\"],\n)\n
\u5176\u539f\u7406\u4e5f\u662f\u901a\u8fc7 Python \u7684 from importlib.metadata import entry_points
\u627e\u5230\u6ce8\u518c\u5230 Python \u89e3\u91ca\u5668 entry_points
\u4e2d\u7684\u5305\uff0c\u5e76 \u6839\u636e\u547d\u540d\u7a7a\u95f4\u83b7\u53d6\u9700\u8981\u7684\u5185\u5bb9\u3002
"},{"location":"guidelines/advanced/plugin/#stevedore","title":"stevedore","text":"stevedore \u662f Openstack \u5f00\u53d1\u548c\u7ef4\u62a4\u7684\u4e00\u4e2a\u63d2\u4ef6\u5de5\u5177\u3002\u8be5 \u63d2\u4ef6\u4e3a Openstack \u7684 ceilometer \u63d0\u4f9b\u63d2\u4ef6\u529f\u80fd\u3002
stevedore \u5219\u662f\u63a8\u8350\u4f7f\u7528\u7ee7\u627f\u7684\u65b9\u5f0f\u89c4\u8303\u63d2\u4ef6\u63a5\u53e3\u3002
\u9996\u5148\u521b\u5efa\u4e00\u4e2a\u63d2\u4ef6\u57fa\u7c7b\uff1a
# stevedore/example/base.py\n# -*- coding: utf-8 -*-\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport abc\nclass FormatterBase(metaclass=abc.ABCMeta):\n\"\"\"Base class for example plugin used in the tutorial.\n \"\"\"\ndef __init__(self, max_width=60):\nself.max_width = max_width\n@abc.abstractmethod\ndef format(self, data):\n\"\"\"Format the data and return unicode text.\n :param data: A dictionary with string keys and simple types as\n values.\n :type data: dict(str:?)\n :returns: Iterable producing the formatted text.\n \"\"\"\n
\u7136\u540e\u5b9e\u73b0\u4e00\u4e2a\u7b80\u5355\u7684\u63d2\u4ef6\uff1a
# stevedore/example/simple.py\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\nfrom stevedore.example import base\nclass Simple(base.FormatterBase):\n\"\"\"A very basic formatter.\"\"\"\ndef format(self, data):\n\"\"\"Format the data and return unicode text.\n :param data: A dictionary with string keys and simple types as\n values.\n :type data: dict(str:?)\n \"\"\"\nfor name, value in sorted(data.items()):\nline = '{name} = {value}\\n'.format(\nname=name,\nvalue=value,\n)\nyield line\n
\u6700\u540e\u6253\u5305\u3002\u6253\u5305\u7684\u65f6\u5019\uff0c\u5c06\u63d2\u4ef6\u6ce8\u518c\u5230 entry_points
\u4e2d\u3002
# stevedore/example/setup.py\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom setuptools import find_packages\nfrom setuptools import setup\nsetup(\nname='stevedore-examples',\nversion='1.0',\ndescription='Demonstration package for stevedore',\nauthor='Doug Hellmann',\nauthor_email='doug@doughellmann.com',\nurl='http://opendev.org/openstack/stevedore',\nclassifiers=['Development Status :: 3 - Alpha',\n'License :: OSI Approved :: Apache Software License',\n'Programming Language :: Python',\n'Programming Language :: Python :: 2',\n'Programming Language :: Python :: 2.7',\n'Programming Language :: Python :: 3',\n'Programming Language :: Python :: 3.5',\n'Intended Audience :: Developers',\n'Environment :: Console',\n],\nplatforms=['Any'],\nscripts=[],\nprovides=['stevedore.examples',\n],\npackages=find_packages(),\ninclude_package_data=True,\nentry_points={\n'stevedore.example.formatter': [\n'simple = stevedore.example.simple:Simple',\n'plain = stevedore.example.simple:Simple',\n],\n},\nzip_safe=False,\n)\n
\u521b\u5efa\u4e00\u4e2a\u63d2\u4ef6\u9879\u76ee\uff0c\u5e76\u5b9e\u73b0\u63d2\u4ef6\uff1a
# stevedore/example2/fields.py\n# -*- coding: utf-8 -*-\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport textwrap\nfrom stevedore.example import base\nclass FieldList(base.FormatterBase):\n\"\"\"Format values as a reStructuredText field list.\n For example::\n : name1 : value\n : name2 : value\n : name3 : a long value\n will be wrapped with\n a hanging indent\n \"\"\"\ndef format(self, data):\n\"\"\"Format the data and return unicode text.\n :param data: A dictionary with string keys and simple types as\n values.\n :type data: dict(str:?)\n \"\"\"\nfor name, value in sorted(data.items()):\nfull_text = ': {name} : {value}'.format(\nname=name,\nvalue=value,\n)\nwrapped_text = textwrap.fill(\nfull_text,\ninitial_indent='',\nsubsequent_indent=' ',\nwidth=self.max_width,\n)\nyield wrapped_text + '\\n'\n
\u540c\u6837\u6253\u5305\uff0c\u5e76\u914d\u7f6e\u6ce8\u518c\u4fe1\u606f\uff0c\u5c06\u63d2\u4ef6\u6ce8\u518c\u5230\u540c\u4e00\u4e2a\u547d\u540d\u7a7a\u95f4\u4e2d\uff1a
# stevedore/example2/setup.py\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom setuptools import find_packages\nfrom setuptools import setup\nsetup(\nname='stevedore-examples2',\nversion='1.0',\ndescription='Demonstration package for stevedore',\nauthor='Doug Hellmann',\nauthor_email='doug@doughellmann.com',\nurl='http://opendev.org/openstack/stevedore',\nclassifiers=['Development Status :: 3 - Alpha',\n'License :: OSI Approved :: Apache Software License',\n'Programming Language :: Python',\n'Programming Language :: Python :: 2',\n'Programming Language :: Python :: 2.7',\n'Programming Language :: Python :: 3',\n'Programming Language :: Python :: 3.5',\n'Intended Audience :: Developers',\n'Environment :: Console',\n],\nplatforms=['Any'],\nscripts=[],\nprovides=['stevedore.examples2',\n],\npackages=find_packages(),\ninclude_package_data=True,\nentry_points={\n'stevedore.example.formatter': [\n'field = stevedore.example2.fields:FieldList',\n],\n},\nzip_safe=False,\n)\n
\u5728\u4f7f\u7528\u662f\uff0c\u53ef\u4ee5\u901a\u8fc7 driver \u65b9\u5f0f\u8c03\u7528\uff1a
# stevedore/example/load_as_driver.py\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport argparse\nfrom stevedore import driver\nif __name__ == '__main__':\nparser = argparse.ArgumentParser()\nparser.add_argument(\n'format',\nnargs='?',\ndefault='simple',\nhelp='the output format',\n)\nparser.add_argument(\n'--width',\ndefault=60,\ntype=int,\nhelp='maximum output width for text',\n)\nparsed_args = parser.parse_args()\ndata = {\n'a': 'A',\n'b': 'B',\n'long': 'word ' * 80,\n}\nmgr = driver.DriverManager(\nnamespace='stevedore.example.formatter',\nname=parsed_args.format,\ninvoke_on_load=True,\ninvoke_args=(parsed_args.width,),\n)\nfor chunk in mgr.driver.format(data):\nprint(chunk, end='')\n
\u6216\u8005\u901a\u8fc7 extensions \u7684\u65b9\u5f0f\u8c03\u7528\uff1a
# stevedore/example/load_as_extension.py\n# Copyright (C) 2020 Red Hat, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport argparse\nfrom stevedore import extension\nif __name__ == '__main__':\nparser = argparse.ArgumentParser()\nparser.add_argument(\n'--width',\ndefault=60,\ntype=int,\nhelp='maximum output width for text',\n)\nparsed_args = parser.parse_args()\ndata = {\n'a': 'A',\n'b': 'B',\n'long': 'word ' * 80,\n}\nmgr = extension.ExtensionManager(\nnamespace='stevedore.example.formatter',\ninvoke_on_load=True,\ninvoke_args=(parsed_args.width,),\n)\ndef format_data(ext, data):\nreturn (ext.name, ext.obj.format(data))\nresults = mgr.map(format_data, data)\nfor name, result in results:\nprint('Formatter: {0}'.format(name))\nfor chunk in result:\nprint(chunk, end='')\nprint('')\n
"},{"location":"guidelines/advanced/plugin/#_3","title":"\u5b9e\u8df5","text":"\u901a\u8fc7\u5b9e\u9645\u5f00\u53d1\u4f53\u9a8c\uff0c\u63a8\u8350\u4f7f\u7528 stevedore \u3002\u603b\u7ed3\u4f18\u70b9\u5982\u4e0b\uff1a
- \u901a\u8fc7\u8bbe\u8ba1\u57fa\u7c7b\u5b9a\u4e49\u63a5\u53e3\u89c4\u8303
- \u63d2\u4ef6\u8c03\u7528\u66f4\u52a0\u7075\u6d3b\u3002
- \u4f7f\u7528\u590d\u6742\u5ea6\u4e0a stevedore \u66f4\u80dc\u4e00\u7b79
"},{"location":"guidelines/advanced/signal_decouple/","title":"\u4fe1\u53f7\u89e3\u8026","text":"\u8fd9\u91cc\u6240\u8bf4\u7684\u4fe1\u53f7\u5e76\u4e0d\u662f\u64cd\u4f5c\u7cfb\u7edf\u7684\u4fe1\u53f7\uff0c\u800c\u662f \u4e8b\u4ef6\u9a71\u52a8\u67b6\u6784 \u7684\u4e00\u79cd\u7b80\u5355\u5b9e\u73b0\u3002
\u4e8b\u4ef6\u9a71\u52a8\u67b6\u6784\u53ef\u4ee5\u57fa\u4e8e\u53d1\u5e03/\u8ba2\u9605\u6a21\u578b\u6216\u8005\u4e8b\u4ef6\u6d41\u6a21\u578b\u3002
\u540e\u9762\u8c08\u5230\u7684\u90fd\u662f\u57fa\u4e8e\u53d1\u5e03/\u8ba2\u9605\u6a21\u578b\u5b9e\u73b0\u7684\u3002
"},{"location":"guidelines/advanced/signal_decouple/#_2","title":"\u5386\u53f2","text":"Python \u4e2d\u7684\u4fe1\u53f7\u89e3\u8026\u673a\u5236\u53ef\u4ee5\u901a\u8fc7 pydispatcher \u5b9e\u73b0\u3002\u800c\u4e14 Django Web \u6846\u67b6\u4e2d\u7684\u4fe1\u53f7\u673a\u5236\u4e5f\u662f \u57fa\u4e8e\u8fd9\u4e2a\u9879\u76ee\u884d\u751f\u7684\u3002
\u8be5\u9879\u76ee\u7684\u6838\u5fc3\u903b\u8f91 ---- \u5f31\u5f15\u7528\uff0c\u4e5f\u5728\u540e\u6765\u5f15\u5165\u5230 Python \u5b98\u65b9\u5e93\u4e2d\u3002\u6b64\u540e\u8be5\u9879\u76ee\u4e5f\u5728 2015 \u5e74\u4e0d\u518d\u66f4\u65b0\u3002
\u800c\u4e4b\u540e\u793e\u533a\u4e5f\u51fa\u73b0\u4e00\u4e9b\u4fe1\u53f7\u6846\u67b6\uff0c\u548c\u5728\u5e95\u5c42\u5b9e\u73b0\u7c7b\u4f3c\u4e8e pydispatcher \u529f\u80fd\u7684\u903b\u8f91\u3002
"},{"location":"guidelines/advanced/signal_decouple/#_3","title":"\u4fe1\u53f7\u6846\u67b6","text":""},{"location":"guidelines/advanced/signal_decouple/#pydispatcher","title":"pydispatcher","text":"pydispatcher \u63d0\u4f9b\u591a\u751f\u4ea7\u8005-\u591a\u6d88\u8d39\u8005\u4fe1\u53f7\u6ce8\u518c\u548c\u8def\u7531\u57fa\u7840\u8bbe\u65bd\uff0c\u4ee5\u5728\u591a\u4e2a\u4e0a\u4e0b\u6587\u4e2d\u4f7f\u7528\u3002
"},{"location":"guidelines/advanced/signal_decouple/#pydispatcher_1","title":"pydispatcher \u4f7f\u7528\u793a\u4f8b","text":"from pydispatch import dispatcher\nstart_process = 'process'\ndef audit(name):\nprint(f'{name} processing ......')\ndispatcher.connect(audit, signal=start_process, sender=dispatcher.Any)\nclass ETL:\nname = 'foo'\ndef process(self):\n\"\"\"\"\"\"\ndispatcher.send(signal=start_process, sender=self, name=self.name)\nif __name__ == '__main__':\nETL().process()\n
\u4e0a\u8ff0\u793a\u4f8b\u4e2d start_process
\u8ba2\u9605\u4e86 audit
\u4e8b\u4ef6\uff0c\u7136\u540e\u5728\u6267\u884c ETL.process
\u7684\u65f6\u5019\uff0c\u901a\u8fc7 dispatcher.send
\u4e00\u6761\u8bb0\u5f55\uff0c \u540c\u65f6\u89e6\u53d1\u8be5\u4e8b\u4ef6\u6267\u884c\u3002
pydispatcher \u652f\u6301\u6307\u5b9a\u7279\u5b9a\u4fe1\u53f7\uff0c\u548c\u53d1\u9001\u8005\u6216\u533f\u540d\u3002\u4fe1\u53f7\u53ef\u4ee5\u662f\u7279\u5b9a\u6216\u8005\u533f\u540d\u3002\u5bf9\u8c61\u7531 Python \u89e3\u91ca\u5668 \u89e3\u91ca\u5668\u7ba1\u7406\uff0c\u5982\u679c\u5bf9\u8c61\u88ab\u56de\u6536\uff0c\u5219\u4e0d\u4f1a\u5728\u89e6\u53d1\u3002
"},{"location":"guidelines/advanced/signal_decouple/#blinker","title":"blinker","text":"blinker \u4e3aPython\u5bf9\u8c61\u63d0\u4f9b\u5feb\u901f\u548c\u7b80\u5355\u7684\u5bf9\u8c61\u548c\u5e7f\u64ad\u4fe1\u53f7\u3002\u5176\u5185\u90e8\u903b\u8f91\u4f9d\u7136\u4f7f\u7528\u7684\u662f\u5f31\u5f15\u7528\u3002\u4f7f\u7528\u8d77\u6765\u548c pydispatcher \u7c7b\u4f3c\u3002
"},{"location":"guidelines/advanced/signal_decouple/#blinker_1","title":"blinker \u4f7f\u7528\u793a\u4f8b","text":"from blinker import Signal\nclass AltProcessor:\non_ready = Signal()\non_complete = Signal()\ndef __init__(self, name):\nself.name = name\ndef go(self):\nself.on_ready.send(self)\nprint(\"Alternate processing.\")\nself.on_complete.send(self)\ndef __repr__(self):\nreturn '<AltProcessor %s>' % self.name\napc = AltProcessor('c')\n@apc.on_complete.connect\ndef completed(sender):\nprint \"AltProcessor %s completed!\" % sender.name\nif __name__ == '__main__':\napc.go()\n
blinker \u540c\u6837\u652f\u6301\u533f\u540d\u4fe1\u53f7\uff0c\u5e95\u5c42\u7684\u5f31\u5f15\u7528\u673a\u5236\u53ef\u4ee5\u51cf\u5c11\u5bf9\u8c61\u7684\u5f15\u7528\u3002\u5b83\u6709\u4e00\u4e2a\u597d\u5904\u662f\u652f\u6301 \u88c5\u9970\u5668\u8ba2\u9605\u4e8b\u4ef6\uff0c\u4f7f\u7528\u8d77\u6765\u6bd4\u8f83\u65b9\u4fbf\u3002
"},{"location":"guidelines/advanced/signal_decouple/#aiosignal","title":"aiosignal","text":"aiosignal \u662f\u4ece aiohttp \u4e2d\u72ec\u7acb\u51fa\u6765\u7684\u5f02\u6b65\u4fe1\u53f7\u6846\u67b6\u3002 \u5b83\u548c\u4e0a\u8ff0\u4e24\u4e2a\u4fe1\u53f7\u6846\u67b6\u533a\u522b\u6709\uff1a\u4e00\uff0c\u5b83\u662f\u4e00\u4e2a\u5f02\u6b65\u4fe1\u53f7\u6846\u67b6\uff0c\u53ef\u4ee5\u8ba2\u9605\u5f02\u6b65\u4e8b\u4ef6\uff1b\u4e8c\uff0c\u5728\u8ba2\u9605\u4e8b\u4ef6\u65f6\uff0c\u5c5e\u4e8e\u5f3a\u5f15\u7528\u3002
"},{"location":"guidelines/advanced/signal_decouple/#aiosignal_1","title":"aiosignal \u4f7f\u7528\u793a\u4f8b","text":"import asyncio\nfrom aiosignal import Signal\nsignal = Signal('signal')\nasync def receiver(message: str):\nprint(f'I receive message: {message}')\nsignal.append(receiver)\nsignal.freeze()\nasync def main():\nawait signal.send('I am god!')\nif __name__ == '__main__':\nasyncio.run(main())\n
\u5728\u5e95\u5c42\uff0c Signal
\u662f\u7ee7\u627f\u4e86 MutableSequence
\u7c7b\uff0c\u4f7f\u7528 Signal.append
\u65b9\u6cd5\u5c06\u8ba2\u9605\u7684\u4e8b\u4ef6\u4fdd\u5b58\u5728\u5bf9\u8c61\u7684\u5c5e\u6027\u4e2d\u3002 \u5f53\u8c03\u7528 Signal.send
\u65b9\u6cd5\u65f6\uff0c\u4f1a\u904d\u5386\u8ba2\u9605\u7684\u4e8b\u4ef6\u5217\u8868\uff0c\u7136\u540e\u6267\u884c\u3002
"},{"location":"guidelines/advanced/signal_decouple/#_4","title":"\u5b9e\u73b0\u81ea\u5b9a\u4e49\u7684\u5f02\u6b65\u4fe1\u53f7","text":"aio-pydispatch
"},{"location":"guidelines/advanced/signal_decouple/#_5","title":"\u6e90\u4ee3\u7801","text":"aio_signal.signal.py
\"\"\"\nAsyncio pydispatch (Signal Manager)\nThis is based on [pyDispatcher](http://pydispatcher.sourceforge.net/) reference\n[scrapy SignalManager](https://docs.scrapy.org/en/latest/topics/signals.html) implementation on\n[Asyncio](https://docs.python.org/3/library/asyncio.html)\n\"\"\"\nimport asyncio\nimport functools\nimport logging\nimport threading\nimport weakref\nfrom typing import (Any, Awaitable, Callable, List, Optional, Tuple, TypeVar,\nUnion)\nfrom aio_pydispatch.utils import id_maker, safe_ref\nT = TypeVar('T') # pylint: disable=invalid-name\nlogger = logging.getLogger(__name__)\nclass _IgnoredException(Exception):\n\"\"\"Ignore exception\"\"\"\nclass Signal:\n\"\"\"\n The signal manager, you can register functions to a signal,\n and store in it.\n Then you can touch off all function that registered on the\n signal where you want.\n example:\n import asyncio\n from aio_pydispatch import Signal\n server_start = Signal('server_start')\n server_stop = Signal('server_stop')\n def ppp(value: str) -> None:\n print(value)\n async def main():\n server_start.connect(ppp)\n server_stop.connect(ppp)\n await server_start.send('server start')\n await asyncio.sleep(1)\n await server_stop.send('server stop')\n if __name__ == '__main__':\n asyncio.run(main())\n \"\"\"\ndef __init__(\nself,\nname: Optional[str] = None,\ndoc: Optional[str] = None,\n):\nself._name = name\nself._doc = doc\nself.__lock = threading.Lock()\nself.__should_clear_receiver = False\nself._receivers = {}\n@property\ndef receivers(self):\n\"\"\"Receivers\"\"\"\nreturn self._receivers\ndef connect(\nself,\nreceiver: Callable[..., Union[T, Awaitable]],\n) -> Callable[..., Union[T, Awaitable]]:\n\"\"\"\n Connect a receiver on this signal.\n :param receiver:\n :return:\n \"\"\"\nassert callable(receiver), \"Signal receivers must be callable.\"\nreferenced_receiver = safe_ref(receiver, self._set_should_clear_receiver, value=True)\nlookup_key = id_maker(receiver)\nwith self.__lock:\nself._clear_dead_receivers()\nif lookup_key not in self._receivers:\nself._receivers.setdefault(lookup_key, referenced_receiver)\nself._set_should_clear_receiver(False)\nreturn receiver\nasync def send(self, *args, **kwargs) -> List[Tuple[Any, Any]]:\n\"\"\"Send signal, touch off all registered function.\"\"\"\n_dont_log = kwargs.pop('_ignored_exception', _IgnoredException)\nresponses = []\nloop = asyncio.get_running_loop()\nfor receiver in self.live_receivers:\nfunc = functools.partial(\nreceiver,\n*args,\n**kwargs\n)\ntry:\nif asyncio.iscoroutinefunction(receiver):\nresponse = await func()\nelse:\nresponse = await loop.run_in_executor(None, func)\nexcept _dont_log as ex:\nresponse = ex\nexcept Exception as ex: # pylint: disable=broad-except\nresponse = ex\nlogger.error('Caught an error on %s', receiver, exc_info=True)\nresponses.append((receiver, response))\nreturn responses\ndef sync_send(self, *args, **kwargs) -> List[Tuple[Any, Any]]:\n\"\"\"\n Can only trigger sync func. If func is coroutine function,\n it will return a awaitable object.\n :param args:\n :param kwargs:\n :return:\n \"\"\"\n_dont_log = kwargs.pop('_ignored_exception', _IgnoredException)\nresponses = []\nfor receiver in self.live_receivers:\ntry:\nif asyncio.iscoroutinefunction(receiver):\nlogger.warning('%s is coroutine, but it not awaited', receiver)\nresponse = receiver(*args, **kwargs)\nexcept _dont_log as ex:\nresponse = ex\nexcept Exception as ex: # pylint: disable=broad-except\nresponse = ex\nlogger.error('Caught an error on %s', receiver, exc_info=True)\nresponses.append((receiver, response))\nreturn responses\n@property\ndef live_receivers(self) -> List[Callable[..., Union[T, Awaitable]]]:\n\"\"\"Get all live receiver.\"\"\"\nwith self.__lock:\nreceivers = []\n_receiver = self._receivers.copy()\nfor lookup_key, receiver in _receiver.items():\nif isinstance(receiver, weakref.ReferenceType):\nreal_receiver = receiver()\nif real_receiver is None:\nself._receivers.pop(lookup_key)\nelse:\nreceivers.append(real_receiver)\nreturn receivers\ndef _set_should_clear_receiver(self, value: bool) -> None:\n\"\"\"Register to the receiver weakerf finalize callback\"\"\"\nself.__should_clear_receiver = value\ndef _clear_dead_receivers(self) -> None:\nif self.__should_clear_receiver:\n_receiver = self._receivers.copy()\nfor lookup_key, receiver in _receiver.items():\nif isinstance(receiver, weakref.ReferenceType) and receiver() is not None:\ncontinue\nself._receivers.pop(lookup_key)\ndef disconnect(self, receiver) -> None:\n\"\"\"clean a receiver\"\"\"\nself._receivers.pop(id_maker(receiver))\ndef disconnect_all(self) -> None:\n\"\"\"Clean all receiver.\"\"\"\nself._receivers.clear()\n
aio_signal.utils.py
\"\"\"Utils\"\"\"\nimport weakref\nfrom typing import Any, Callable, Tuple\nfrom weakref import ReferenceType, WeakMethod\ndef ref_adapter(receiver: Any) -> Tuple[Any, ReferenceType]:\n\"\"\"\n Adapt a receiver to ref object.\n :param receiver:\n :return:\n \"\"\"\nref = weakref.ref\nreceiver_obj = receiver\n# Check if receiver is a ref.\nif hasattr(receiver, '__self__') and hasattr(receiver, '__func__'):\nref = WeakMethod\nreceiver_obj = receiver.__self__\nreferenced_receiver = ref(receiver)\nreturn receiver_obj, referenced_receiver\ndef safe_ref(receiver: Any, callback: Callable[..., None], *args, **kwargs) -> ReferenceType:\n\"\"\"\n Save ref a receiver.\n Register a callback function to the object finalizer\n :param receiver: A ref object\n :param callback: Register the callback function to the object finalizer\n :param args: Callback args\n :param kwargs: Callback kwargs\n :return:\n \"\"\"\nreceiver_obj, receiver = ref_adapter(receiver)\nweakref.finalize(receiver_obj, callback, *args, **kwargs)\nreturn receiver\ndef id_maker(receiver: Any) -> int:\n\"\"\"\n Get receiver id.\n If receiver is ref object, will return a ref object id.\n :param receiver:\n :return: Any\n \"\"\"\nif not isinstance(receiver, ReferenceType):\n_, receiver = ref_adapter(receiver)\nreturn id(receiver)\n
\u81ea\u5b9a\u4e49\u7684 aio_signal
\u652f\u6301\u8ba2\u9605\u540c\u6b65\u4e8b\u4ef6\u548c\u5f02\u6b65\u4e8b\u4ef6\u3002\u53d1\u5e03\u65f6\u652f\u6301\u540c\u6b65\u53d1\u5e03\u548c\u5f02\u6b65\u53d1\u5e03\u3002\u5f02\u6b65\u53d1\u5e03\u652f\u6301\u89e6\u53d1\u540c\u6b65\u4e8b\u4ef6\u548c \u5f02\u6b65\u4e8b\u4ef6\uff0c\u540c\u6b65\u53d1\u5e03\u53ea\u652f\u6301\u89e6\u53d1\u540c\u6b65\u4e8b\u4ef6\u3002
"},{"location":"guidelines/advanced/signal_decouple/#_6","title":"\u4f7f\u7528","text":"import asyncio\nfrom aio_signal import Signal\nserver_start = Signal('server_start')\nserver_stop = Signal('server_stop')\ndef ppp(value: str) -> None:\nprint(value)\nasync def main():\nserver_start.connect(ppp)\nserver_stop.connect(ppp)\nawait server_start.send('server start')\nawait asyncio.sleep(1)\nawait server_stop.send('server stop')\nif __name__ == '__main__':\nasyncio.run(main())\n
"},{"location":"guidelines/advanced/signal_decouple/#_7","title":"\u5b9e\u8df5","text":"\u5728\u5f00\u53d1\u5b9e\u8df5\u4e2d\uff0c\u63a8\u8350\u4f7f\u7528\u5e26\u6709\u5f31\u5f15\u7528\u7684\u4fe1\u53f7\u5e93\u3002\u8fd9\u6837\u53ef\u4ee5\u907f\u514d\u8d44\u6e90\u5360\u7528\u3002
"},{"location":"guidelines/advanced/test/","title":"Test - \u6d4b\u8bd5","text":"\u6d4b\u8bd5\u662f\u8f6f\u4ef6\u5f00\u53d1\u4e2d\u4e00\u4e2a\u4e0d\u53ef\u907f\u514d\u7684\u73af\u8282\uff0c\u5728\u4ee3\u7801\u7ea7\u522b\u8fdb\u884c\u6d4b\u8bd5\uff0c\u80fd\u591f\u5728\u90e8\u7f72\u94b1\u5c3d\u65e9\u53d1\u73b0\u7a0b\u5e8f\u4e2d\u7684\u5f02\u5e38\uff0c\u589e\u5f3a\u8f6f\u4ef6\u7684\u5065\u58ee\u6027\u3002
\u5728\u9075\u5faa TDD \u539f\u5219\u6765\u5f00\u53d1\uff0c\u80fd\u6709\u6548\u63d0\u9ad8\u4ee3\u7801\u7684\u8bbe\u8ba1\u3002
"},{"location":"guidelines/advanced/test/#1","title":"1. \u6d4b\u8bd5\u5de5\u5177","text":"\u5728 Python \u4e2d\u9664\u4e86\u6709\u8bed\u8a00\u5185\u7f6e\u7684\u6d4b\u8bd5\u6846\u67b6\u4e4b\u5916\uff0c\u8fd8\u6709\u8bb8\u591a\u7b2c\u4e09\u65b9\u6d4b\u8bd5\u6846\u67b6\uff0c\u4e00\u4e9b\u975e\u6d4b\u8bd5\u6846\u67b6\u5185\u90e8\u4e5f\u4f1a\u5185\u7f6e\u6d4b\u8bd5\u6846\u67b6\u3002\u5176\u76ee\u7684\u90fd\u662f\u5728\u5185\u7f6e\u6d4b\u8bd5\u6846\u67b6\u7684\u57fa\u7840\u4e0a \u589e\u52a0\u4e86\u4e00\u4e9b\u7279\u6027\uff0c\u8ba9\u7f16\u5199\u6d4b\u8bd5\u66f4\u52a0\u65b9\u4fbf\uff0c\u6d4b\u8bd5\u8fc7\u7a0b\u66f4\u52a0\u987a\u7545\u3002
\u4e3a\u4e86\u65b9\u4fbf\u6d4b\u8bd5\u6846\u67b6\u67e5\u627e\u6d4b\u8bd5\u7528\u4f8b\uff0c\u5728\u7f16\u5199\u6d4b\u8bd5\u65f6\u5e94\u9075\u5faa\u4e00\u5b9a\u7684\u89c4\u8303\uff1a
- \u6d4b\u8bd5\u6a21\u5757\u8981\u4ee5
test_
\u5f00\u5934 - \u6d4b\u8bd5\u65b9\u6cd5\u8981\u4ee5
test_
\u5f00\u5934 - \u6d4b\u8bd5\u7c7b\u540d\u8981\u4ee5
Test
\u5f00\u5934
"},{"location":"guidelines/advanced/test/#11","title":"1.1 \u5185\u7f6e\u6d4b\u8bd5\u6846\u67b6","text":"Python \u5185\u7f6e\u6d4b\u8bd5\u6846\u67b6\u662f unittest \uff0c\u662f\u53d7\u5230\u4e86 JUnit \u7684\u542f\u53d1\uff0c\u5e76\u4e14\u5728\u4f7f\u7528\u4e0a\u4e0e\u5176\u4ed6\u8bed\u8a00\u7684 \u5355\u5143\u6d4b\u8bd5\u6846\u67b6\u7c7b\u4f3c\u3002
\u9762\u5411\u5bf9\u8c61\u7684\u65b9\u5f0f\u6240\u652f\u6301\u7684\u51e0\u4e2a\u6982\u5ff5\uff1a
- \u6d4b\u8bd5\u811a\u624b\u67b6\uff1a
test fixture
\u8868\u793a\u4e3a\u4e86\u5c55\u5f00\u4e00\u9879\u6216\u591a\u9879\u6d4b\u8bd5\u6240\u9700\u8981\u51c6\u5907\u7684\u5de5\u4f5c\uff0c\u4ee5\u53ca\u76f8\u5173\u7684\u6e05\u7406\u5de5\u4f5c - \u6d4b\u8bd5\u7528\u4f8b\uff1a\u4e00\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u662f\u4e00\u4e2a\u72ec\u7acb\u7684\u5355\u5143\u6d4b\u8bd5\u3002
- \u6d4b\u8bd5\u5957\u4ef6\uff1a\u662f\u4e00\u7cfb\u5217\u7684\u6d4b\u8bd5\u7528\u4f8b\uff0c\u6216\u6d4b\u8bd5\u5957\u4ef6\u3002
- \u6d4b\u8bd5\u8fd0\u884c\u5668\uff1a\u7528\u4e8e\u6267\u884c\u548c\u8f93\u51fa\u6d4b\u8bd5\u7ed3\u679c\u3002
\u4e0b\u9762\u662f\u4e00\u4e2a\u6700\u7b80\u5355\u7684\u6d4b\u8bd5\u7528\u4f8b\uff1a
# Test\nimport unittest\nfrom unittest import TestCase\nclass TestSum(TestCase):\ndef test_sum(self):\n\"\"\"Test sum\"\"\"\nassert sum([1, 1]) == 2\nif __name__ == '__main__':\nunittest.main()\n
\u53ef\u4ee5\u901a\u8fc7\u8fd0\u884c\u8be5\u6587\u4ef6\u8fd0\u884c\u6d4b\u8bd5\uff0c\u4e5f\u53ef\u4ee5\u7528 python -m test_xxx.py
\u8fd0\u884c\u6d4b\u8bd5\u6a21\u5757\u3002
\u7ec4\u7ec7\u6d4b\u8bd5\u7684\u6d4b\u8bd5\u4ee3\u7801\uff1a
# Test\nfrom csv import DictReader\nimport unittest\nfrom unittest import TestCase\nfrom tempfile import NamedTemporaryFile\nclass TestSum(TestCase):\ndef test_sum(self):\n\"\"\"Test sum\"\"\"\nassert sum([1, 1]) == 2\nclass TestCsv(TestCase):\ndef setUp(self) -> None:\nself.temp_file = NamedTemporaryFile(suffix='csv')\nself.filename = self.temp_file.name\nwith open(self.filename, 'w') as obj:\nobj.writelines([\n'name,age\\n',\n'foo,12\\n',\n'bar,15\\n'\n])\ndef test_csv(self):\nwith open(self.filename, 'r') as obj:\nreader = DictReader(obj)\ndata = list(reader)\nself.assertEqual(len(data), 2)\ndef tearDown(self) -> None:\nself.temp_file.close()\ndef suite():\n_suite = unittest.TestSuite()\n_suite.addTest(TestSum())\n_suite.addTest(TestCsv())\nreturn _suite\nif __name__ == '__main__':\nrunner = unittest.TextTestRunner()\nrunner.run(suite())\n
\u4f7f\u7528 TestSuite
\u548c TextTestRunner
\u7ec4\u7ec7\u6d4b\u8bd5\uff0c\u53ef\u4ee5\u8ba9\u4ee3\u7801\u7684\u903b\u8f91\u66f4\u52a0\u6e05\u6670\u3002
"},{"location":"guidelines/advanced/test/#111-mock","title":"1.1.1 Mock \u5bf9\u8c61","text":"\u5728\u8fdb\u884c\u5355\u5143\u6d4b\u8bd5\u7684\u65f6\u5019\uff0c\u96be\u514d\u4f1a\u9047\u5230\u4f9d\u8d56\u5177\u4f53\u7684\u5bf9\u8c61\u6216\u8d44\u6e90\u7684\u60c5\u51b5\u3002\u4e3a\u4e86\u53ea\u6d4b\u8bd5\u5177\u4f53\u5355\u5143\u7684\u903b\u8f91\uff0c\u5c31\u9700\u8981\u6a21\u62df\u5355\u5143\u903b\u8f91\u6240\u4f9d\u8d56\u7684\u5185\u5bb9\u3002
\u4f7f\u7528 unittest.mock \u53ef\u4ee5\u5f88\u597d\u89e3\u51b3\u8fd9\u4e2d\u95ee\u9898\u3002
\u5982\u4e0b\u4f8b\u5b50\uff1a
# Test\nimport unittest\nfrom typing import Any\nfrom unittest import TestCase\nfrom unittest.mock import Mock\nclass Session:\ndef close(self, connection: Any):\nconnection.close()\nclass TestSession(TestCase):\ndef setUp(self) -> None:\nself.session = Session()\ndef test_close(self):\nmock = Mock()\nself.session.close(mock)\nmock.close.assert_called_with()\nif __name__ == '__main__':\nunittest.main()\n
\u5728\u6d4b\u8bd5 Session.close
\u8fd9\u4e2a\u5355\u5143\u903b\u8f91\u7684\u65f6\u5019\uff0c\u4f9d\u8d56\u4e00\u4e2a connection
\u5bf9\u8c61\u3002\u56e0\u4e3a\u5355\u5143\u6d4b\u8bd5\u4ec5\u5173\u6ce8\u5355\u5143\u5185\u90e8\u903b\u8f91\u662f\u5426\u6b63\u786e\uff0c\u5373\u7ed9\u5b9a\u8f93\u5165\uff0c\u6d4b\u8bd5\u5176\u5185\u90e8\u903b\u8f91\u3002 \u6240\u4ee5\u4f7f\u7528\u4e00\u4e2a Mock
\u5bf9\u8c61\u6a21\u62df\u5165\u53c2\uff0c\u7136\u540e\u5224\u65ad\u5165\u53c2\u662f\u5426\u5728\u903b\u8f91\u5185\u88ab\u8c03\u7528\u3002
\u9664\u4e86\u6a21\u62df\u5bf9\u8c61\uff0c\u8fd8\u53ef\u4ee5\u6a21\u62df\u7c7b\uff1a
# Test\nimport unittest\nfrom unittest import TestCase\nfrom unittest.mock import patch\nclass Session:\ndef exist(self):\n\"\"\"\"\"\"\ndef delete(self):\nif self.exist():\nreturn True\nreturn False\nclass TestSession(TestCase):\ndef setUp(self) -> None:\nself.session = Session()\ndef test_delete(self):\nwith patch.object(Session, 'exist', return_value=True) as mock_session:\nsession = Session()\nself.assertTrue(session.delete())\nmock_session.assert_called_once()\nif __name__ == '__main__':\nunittest.main()\n
\u6848\u4f8b\u4e2d\uff0c\u6d4b\u8bd5\u7684\u662f Session.delete
\u65b9\u6cd5\uff0c\u5185\u90e8\u903b\u8f91\u4f9d\u8d56 Session.exist
\u3002\u56e0\u4e3a\u4ec5\u6d4b\u8bd5\u5355\u5143\u903b\u8f91\u6240\u4ee5\u5c06\u5b83\u4f9d\u8d56 \u8c03\u7528\u7684 Session.exist
\u6a21\u62df\u6389\u3002
"},{"location":"guidelines/advanced/test/#12-pytest","title":"1.2 Pytest","text":"Pytest \u662f\u5728 unittest \u7684\u57fa\u7840\u4e0a \u589e\u52a0\u4e86\u5927\u91cf\u8bed\u6cd5\u7cd6\uff0c\u8ba9\u6d4b\u8bd5\u66f4\u52a0\u7b80\u4fbf\u548c\u7075\u6d3b\u3002\u5e76\u4e14\u5e26\u6709\u63d2\u4ef6\u529f\u80fd\uff0c\u65b9\u4fbf\u96c6\u6210\u5176\u4ed6\u529f\u80fd\u3002
\u7531\u4e8e Pytest \u80fd\u517c\u5bb9\u5176\u4ed6\u5927\u591a\u6570\u6d4b\u8bd5\u6846\u67b6\uff0c\u800c\u4e14\u5b83\u4e5f\u5177\u6709\u5f3a\u5927\u7684\u529f\u80fd\uff0c\u6240\u4ee5\u63a8\u8350\u4f7f\u7528 Pytest \u4f5c\u4e3a\u4e3b\u8981\u6d4b\u8bd5\u6846\u67b6\u4f7f\u7528\u3002
\u5b89\u88c5\uff1a
pip install pytest\n
\u7f16\u5199\u6d4b\u8bd5\u6587\u4ef6\uff1a
# content of test_sample.py\ndef inc(x):\nreturn x + 1\ndef test_answer():\nassert inc(3) == 5\n
\u5728\u7ec8\u7aef\u8fd0\u884c pytest
\u5373\u53ef\u81ea\u52a8\u53d1\u73b0\u6d4b\u8bd5\uff0c\u5e76\u8fd0\u884c\u3002
\u63d0\u793a pytest
\u4f1a\u81ea\u52a8\u53d1\u73b0\u6240\u6709\u4ee5 test_
\u5f00\u5934\u548c _test.py
\u7ed3\u5c3e\u7684\u6587\u4ef6\uff0c\u5e76\u52a0\u8f7d\u6240\u6709\u4ee5 test_
\u5f00\u5934\u7684\u65b9\u6cd5\u548c Test
\u5f00\u5934\u7684\u7c7b\u3002
"},{"location":"guidelines/advanced/test/#122","title":"1.2.2 \u76ee\u5f55\u7ed3\u6784\u7684\u9009\u62e9","text":"\u5728\u9879\u76ee\u7ed3\u6784\u4e0a\uff0c\u63a8\u8350\u4f7f\u7528 src
\u76ee\u5f55\u653e\u6e90\u4ee3\u7801\uff0c\u540c\u7ea7\u7684 tests
\u653e\u6d4b\u8bd5\u6a21\u5757\uff0c\u6d4b\u8bd5\u6a21\u5757\u7684\u7ec4\u7ec7\u548c src
\u7684\u5305\u7ed3\u6784\u4e00\u81f4\uff0c\u6d4b\u8bd5\u7684\u529f\u80fd \u76f8\u5bf9\u5e94\u3002
setup.py\nsrc/\n mypkg/\n __init__.py\n app.py\n view.py\ntests/\n __init__.py\n foo/\n __init__.py\n test_view.py\n bar/\n __init__.py\n test_view.py\n
\u5176\u4ed6\u98ce\u683c\u7684\u4f7f\u7528\u53ef\u4ee5\u53c2\u8003 Choosing a test layout / import rules
"},{"location":"guidelines/advanced/test/#121-fixture","title":"1.2.1 fixture","text":"Pytest \u7684 fixture \u53ef\u4ee5\u4e3a\u6d4b\u8bd5\u63d0\u4f9b\u4e00\u5b9a\u7684\u73af\u5883\u3002
import pytest\nclass Fruit:\ndef __init__(self, name):\nself.name = name\ndef __eq__(self, other):\nreturn self.name == other.name\n@pytest.fixture\ndef my_fruit():\nreturn Fruit(\"apple\")\n@pytest.fixture\ndef fruit_basket(my_fruit):\nreturn [Fruit(\"banana\"), my_fruit]\ndef test_my_fruit_in_basket(my_fruit, fruit_basket):\nassert my_fruit in fruit_basket\n
\u5728\u6d4b\u8bd5\u662f\uff0c\u516c\u5171\u7684 fixture
\u4e00\u822c\u63a8\u8350\u653e\u5728 conftest.py
\u4e2d\u3002
\u66f4\u590d\u6742\u7684 fixture
\uff1a
import pytest\n@pytest.fixture\ndef order():\nreturn []\n@pytest.fixture\ndef a(order):\norder.append(\"a\")\n@pytest.fixture\ndef b(a, order):\norder.append(\"b\")\n@pytest.fixture\ndef c(a, b, order):\norder.append(\"c\")\n@pytest.fixture\ndef d(c, b, order):\norder.append(\"d\")\n@pytest.fixture\ndef e(d, b, order):\norder.append(\"e\")\n@pytest.fixture\ndef f(e, order):\norder.append(\"f\")\n@pytest.fixture\ndef g(f, c, order):\norder.append(\"g\")\ndef test_order(g, order):\nassert order == [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\"]\n
"},{"location":"guidelines/advanced/test/#123","title":"1.2.3 \u53c2\u6570\u5316\u6d4b\u8bd5","text":"\u5728\u9488\u5bf9\u540c\u4e00\u4e2a\u903b\u8f91\u6709\u591a\u79cd\u4e0d\u540c\u8f93\u5165\u8fdb\u884c\u6d4b\u8bd5\u65f6\uff0c\u76f4\u63a5\u60f3\u5230\u7684\u5904\u7406\u65b9\u5f0f\u5c31\u662f\u505a\u6210\u5de5\u5382\uff0c\u7136\u540e\u5728\u6d4b\u8bd5\u65b9\u6cd5\u4e2d \u6784\u9020\u53c2\u6570\u5217\u8868\u4f20\u9012\u7ed9\u5de5\u5382\u3002\u8fd9\u5728 unittest \u4e2d\u79f0\u4f5c\u590d\u7528\u6d4b\u8bd5\u903b\u8f91\u3002\u5426\u5219\u5c31\u9700\u8981\u4e3a\u6d4b\u8bd5\u903b\u8f91\u7f16\u5199\u4e0d\u540c\u53c2\u6570 \u7684\u6d4b\u8bd5\u65b9\u6cd5\u3002
\u4f46\u5728 Pytest \u4e2d\u53ef\u4ee5\u4f7f\u7528\u53c2\u6570\u5316\u6d4b\u8bd5\uff0c \u8f7b\u677e\u89e3\u51b3\u8fd9\u79cd\u95ee\u9898\u3002
\"\"\"Test log\"\"\"\nimport pytest\ndef update_log_level(debug: bool, level: str) -> str:\nif debug:\nreturn 'DEBUG'\nreturn level\n@pytest.mark.parametrize(\n['debug', 'level', 'expect_value'],\n[\n(True, '', 'DEBUG'),\n(True, 'INFO', 'DEBUG'),\n(False, 'DEBUG', 'DEBUG'),\n(False, 'INFO', 'INFO'),\n]\n)\ndef test_log_level(debug: bool, level: str, expect_value):\n\"\"\"Test log level\"\"\"\nlog_level_name = update_log_level(debug, level)\nassert log_level_name == expect_value\n
\u53c2\u6570\u5316\u6d4b\u8bd5\u5e26\u6765\u7684\u597d\u5904\u975e\u5e38\u76f4\u89c2\uff0c\u800c\u4e14\u6d4b\u8bd5\u7f16\u5199\u4e5f\u53d8\u5f97\u7b80\u5355\u3002
"},{"location":"guidelines/advanced/test/#124","title":"1.2.4 \u63d2\u4ef6","text":"Pytest \u62e5\u6709\u5927\u91cf\u7684\u63d2\u4ef6 \uff0c\u800c\u4e14\u57fa\u672c\u4e0a\u662f\u5b89\u88c5\u5373\u53ef\u548c Pytest \u65e0\u7f1d \u96c6\u6210\uff0c\u8f7b\u677e\u4f7f\u7528\u3002
\u4e0b\u9762\u5217\u4e3e\u51e0\u4e2a\u5e38\u7528\u7684\u63d2\u4ef6
"},{"location":"guidelines/advanced/test/#1241-pytest-asyncio","title":"1.2.4.1 pytest-asyncio","text":"pytest-asyncio \u53ef\u4ee5\u8f7b\u677e\u6d4b\u8bd5 asyncio
\u65b9\u6cd5
@pytest.mark.asyncio\nasync def test_some_asyncio_code():\nres = await library.do_something()\nassert b'expected result' == res\n
"},{"location":"guidelines/advanced/test/#1242-pytest-mock","title":"1.2.4.2 pytest-mock","text":"pytest-mock \u53ef\u4ee5\u50cf\u4f7f\u7528 fixture
\u4e00\u6837\u4f7f\u7528 mock
\uff0c\u800c\u4e0d\u5fc5\u5bfc\u5165 unittest.mock
import os\nclass UnixFS:\n@staticmethod\ndef rm(filename):\nos.remove(filename)\ndef test_unix_fs(mocker):\nmocker.patch('os.remove')\nUnixFS.rm('file')\nos.remove.assert_called_once_with('file')\n
"},{"location":"guidelines/advanced/test/#1243-pytest-cov","title":"1.2.4.3 pytest-cov","text":"pytest-cov \u8ba9 coverage \u548c Pytest \u96c6\u6210\uff0c \u65b9\u4fbf\u4f7f\u7528\u3002
"},{"location":"guidelines/advanced/test/#13","title":"1.3 \u6846\u67b6\u81ea\u5e26\u6d4b\u8bd5","text":"\u672c\u8282\u5185\u5bb9\u4e3b\u8981\u7b80\u5355\u63cf\u8ff0\u4e00\u4e9b\u6846\u67b6\u81ea\u5e26\u7684\u6d4b\u8bd5\u7684\u4f7f\u7528\u3002 Pytest \u4e5f\u90fd\u80fd\u517c\u5bb9\u8fd9\u4e9b\u6d4b\u8bd5\u3002\u6240\u4ee5\u5982\u679c\u4f7f\u7528\u6846\u67b6\u63a8\u8350\u7684\u5199\u6cd5\u6765\u5199\u6d4b\u8bd5\uff0c\u5728\u4f7f\u7528 Pytest \u8fd0\u884c \u4e5f\u662f\u6ca1\u6709\u95ee\u9898\u7684\u3002
"},{"location":"guidelines/advanced/test/#131-django","title":"1.3.1 Django","text":"Django \u7684\u5355\u5143\u6d4b\u8bd5\u4e5f\u662f\u57fa\u4e8e unittest
\u6765\u505a\u7684\uff0c \u53ea\u4e0d\u8fc7\u589e\u52a0\u4e86\u4e00\u4e9b\u6846\u67b6\u4e0a\u7684\u5185\u5bb9\uff0c\u4fbf\u4e8e\u5728\u6d4b\u8bd5\u65f6\uff0c\u9644\u5e26\u6846\u67b6\u529f\u80fd\u3002
\u6d4b\u8bd5\u7528\u4f8b\uff1a
from django.test import TestCase\nfrom myapp.models import Animal\nclass AnimalTestCase(TestCase):\ndef setUp(self):\nAnimal.objects.create(name=\"lion\", sound=\"roar\")\nAnimal.objects.create(name=\"cat\", sound=\"meow\")\ndef test_animals_can_speak(self):\n\"\"\"Animals that can speak are correctly identified\"\"\"\nlion = Animal.objects.get(name=\"lion\")\ncat = Animal.objects.get(name=\"cat\")\nself.assertEqual(lion.speak(), 'The lion says \"roar\"')\nself.assertEqual(cat.speak(), 'The cat says \"meow\"')\n
\u8fd0\u884c\u6d4b\u8bd5 ./manage.py test
\u3002
\u5728\u8fd0\u884c\u65f6\uff0c\u5185\u90e8\u903b\u8f91\u4f9d\u7136\u662f\u901a\u8fc7 unittest
\u6765\u81ea\u52a8\u67e5\u627e\u6d4b\u8bd5\u7c7b\u3002
"},{"location":"guidelines/advanced/test/#132-fastapi","title":"1.3.2 Fastapi","text":"Fastapi \u4ec5\u4ec5\u63d0\u4f9b\u4e86\u5e26\u6709\u6846\u67b6\u529f\u80fd\u7684 TestClient
\u3002\u521d\u59cb\u5316\u7684\u5b9e\u4f8b \u65b9\u4fbf\u6d4b\u8bd5 API \u63a5\u53e3\uff0c\u800c\u4e0d\u662f\u771f\u6b63\u542f\u52a8\u4e00\u4e2a Web \u670d\u52a1\u3002
from fastapi import FastAPI\nfrom fastapi.testclient import TestClient\napp = FastAPI()\n@app.get(\"/\")\nasync def read_main():\nreturn {\"msg\": \"Hello World\"}\nclient = TestClient(app)\ndef test_read_main():\nresponse = client.get(\"/\")\nassert response.status_code == 200\nassert response.json() == {\"msg\": \"Hello World\"}\n
\u8fd0\u884c pytest
\u3002
"},{"location":"guidelines/advanced/type_hint/","title":"\u7c7b\u578b\u63d0\u793a","text":"Python \u4f5c\u4e3a\u4e00\u4e2a\u52a8\u6001\u7c7b\u578b\u8bed\u8a00\uff0c\u5728\u7f16\u7801\u8fc7\u7a0b\u4e2d\u51fa\u73b0\u7684\u4e00\u4e9b\u5c0f\u95ee\u9898\uff0c\u76f4\u5230\u8fd0\u884c\u65f6\u624d\u88ab\u53d1\u73b0\u3002\u76f8\u6bd4\u4e8e\u9759\u6001\u8bed\u8a00\uff0c \u50cf Java \u3001 C/C++ \u7b49\uff0c\u5728\u7f16\u8bd1\u671f\u95f4\u5c31\u80fd\u53d1\u73b0\u5e76\u6539\u8fdb\u4ee3\u7801\u95ee\u9898\u3002 \u6240\u4ee5\u4e3a\u4e86\u5728\u8fd0\u884c\u65f6\u4e4b\u524d\u5c3d\u53ef\u80fd\u907f\u514d\u51fa\u95ee\u9898\uff0c \u5728 2014 \u5e74 Guido van Rossum \u7b49\u4eba\u4e3a Python \u63d0\u51fa\u4e86 \u7c7b\u578b\u63d0\u793a\u7406\u8bba \u3002 \u5728 2015 \u5e74\u7684 Pycon \u505a\u4e86\u8be5\u4e3b\u9898\u7684\u6f14\u8bb2\u3002 \u76f4\u5230\u73b0\u5728\uff0c\u5173\u4e8e\u9759\u6001\u7c7b\u578b\u76f8\u5173\u7684 PEP \u6709\uff1a
- PEP 484 -- Type Hints
- PEP 526 -- Syntax for Variable Annotations
- PEP 544 -- Protocols: Structural subtyping (static duck typing)
- PEP 586 -- Literal Types
- PEP 589 -- TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys
- PEP 591 -- Adding a final qualifier to typing
\u5230\u73b0\u5728\u7684 python 3.9 \u7248\u672c\uff0c\u7c7b\u578b\u63d0\u793a\u7684\u652f\u6301\u5df2\u7ecf\u5f88\u4e30\u5bcc\u4e86\u3002\u540c\u65f6\u4e0e\u7c7b\u578b\u63d0\u793a\u76f8\u5173\u7684\u68c0\u6d4b\u5de5\u5177\uff0c\u5de5\u5177\u5728 IDE \u4e0a\u7684\u96c6\u6210\u529f\u80fd\u4e5f\u5f88\u5b8c\u5584\uff0c \u5728\u5f00\u53d1\u4f53\u9a8c\u4e0a\u6709\u4e86\u5f88\u5927\u7684\u63d0\u5347\u3002\u540c\u65f6\u7c7b\u578b\u68c0\u67e5\u4e5f\u6210\u4e3a\u4e86 CI \u7684\u91cd\u8981\u73af\u8282\uff0c\u6709\u52a9\u4e8e\u66f4\u65e9\u66f4\u53ca\u65f6\u7684\u89c4\u907f Bug \u7684\u51fa\u73b0\u3002
"},{"location":"guidelines/advanced/type_hint/#1","title":"1. \u521d\u8bc6\u7c7b\u578b\u63d0\u793a","text":"\u7c7b\u578b\u63d0\u793a\u53ef\u4ee5\u5728\u7c7b\u3001\u65b9\u6cd5\u6216\u53d8\u91cf\u4e0a\u6807\u6ce8\u76f8\u5e94\u7684\u7c7b\u578b\uff0c\u5728\u8c03\u7528\u7684\u65f6\u5019\u901a\u8fc7\u9759\u6001\u7c7b\u578b\u68c0\u67e5\u5de5\u5177\u68c0\u6d4b\u8c03\u7528\u662f\u5426\u5b58\u5728\u95ee\u9898\u3002
\u5982\u4e0b\u9762\u7684\u4f8b\u5b50\uff1a
def greeting(name: str) -> str:\nreturn f'Hello {name}'\n
\u5728\u5b9a\u4e49\u65b9\u6cd5 greeting
\u7684\u65f6\u5019\uff0c\u58f0\u660e\u53c2\u6570 name
\u662f str
\u7c7b\u578b\uff0c\u8fd4\u56de\u503c\u662f str
\u7c7b\u578b\u3002\u5f53\u8c03\u7528 greeting
\u51fd\u6570\u65f6\uff0c\u5982\u679c\u4f20\u9012\u4e00\u4e2a int
\u7c7b\u578b \u7684\u503c\uff0c \u8fd0\u884c\u7c7b\u578b\u68c0\u67e5\u4f1a\u5931\u8d25\uff0c\u540c\u65f6\u53d1\u51fa\u8b66\u544a\u63d0\u793a\u3002\u5982\u679c IDE \u5df2\u7ecf\u652f\u6301\u7c7b\u578b\u68c0\u67e5\uff0c\u5219\u5728\u8c03\u7528\u7684\u65f6\u5019\uff0c\u4f1a\u63d0\u793a\u8be5\u65b9\u6cd5\u7684\u53c2\u6570\u7c7b\u578b\uff0c \u5982\u679c\u4f20\u9012\u9519\u8bef\u7c7b\u578b\u7684\u53c2\u6570 IDE \u4f1a\u53ca\u65f6\u53d1\u51fa\u8b66\u544a\uff0c\u63d0\u793a\u6211\u4eec\u4fee\u590d\u3002
import logging\nfrom pathlib import Path # Config root logger\nlogging.basicConfig(\nlevel=logging.DEBUG,\nformat='%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n)\ndef count(source_file: str, dest_file: str) -> None:\n\"\"\"\n Count source\n :param source_file:\n :param dest_file:\n :return:\n \"\"\"\ntotal = read_from_file(Path(source_file))\nwrite_to_file(Path(dest_file), total)\ndef read_from_file(source_file: Path) -> int:\n\"\"\"\n Read file\n :param source_file:\n :return:\n \"\"\"\ntotal_words = 0\n# Read source_file\nlogging.debug('Read file: %s', source_file)\nwith open(source_file, 'r') as source_obj:\nfor line in source_obj.readlines():\ntotal_words += len(line.split(' '))\nreturn total_words\ndef write_to_file(dest_file: Path, total_words: int) -> None:\n\"\"\"\n Write result to file\n :param dest_file:\n :param total_words:\n :return:\n \"\"\"\nlogging.debug('Count %s words, write to %d', dest_file, total_words)\nwith open(dest_file, 'w') as dest_obj:\ndest_obj.write(f'Total count: {total_words}')\n
\u4e0a\u8ff0\u4ee3\u7801\u4e2d\uff0c\u6240\u6709\u65b9\u6cd5\u7684\u53c2\u6570\u548c\u8fd4\u56de\u503c\u90fd\u8fdb\u884c\u4e86\u7c7b\u578b\u6807\u6ce8\u3002
"},{"location":"guidelines/advanced/type_hint/#2","title":"2. \u4f7f\u7528\u7c7b\u578b\u63d0\u793a","text":""},{"location":"guidelines/advanced/type_hint/#21","title":"2.1 \u4e00\u822c\u7c7b\u578b\u63d0\u793a","text":"\u5728\u8fdb\u884c\u7c7b\u578b\u6807\u6ce8\u7684\u8fc7\u7a0b\u4e2d\uff0c\u4e00\u822c\u76f4\u63a5\u901a\u8fc7\u6807\u6ce8\u53d8\u91cf\u672c\u8eab\u7684\u7c7b\u578b\u5c31\u53ef\u4ee5\u4e86\u3002
\u4f8b\u5982\uff1a
\"\"\"Example\"\"\"\nimport logging\nlogging.basicConfig(level=logging.DEBUG)\nclass User:\n\"\"\"User\"\"\"\ndef __init__(self, name: str):\nself._name = name\n@property\ndef name(self) -> str:\n\"\"\"User's name\"\"\"\nreturn self._name\ndef __repr__(self):\n\"\"\"repr\"\"\"\nreturn f'<User(name=\"{self.name}\")>'\ndef save(user: User):\n\"\"\"Mock to save a user\"\"\"\nlogging.info('Save object: %s', user)\nif __name__ == '__main__':\nsave(User('Jim'))\n
\u5982\u4e0a\u8ff0\u4f8b\u5b50\u4e2d\uff0c save
\u65b9\u6cd5\u4f20\u5165\u4e00\u4e2a User
\u7c7b\u578b\u7684\u53c2\u6570\uff0c\u76f4\u63a5\u4f7f\u7528\u8be5\u7c7b\u6807\u6ce8\u5c31\u53ef\u4ee5\u4e86\u3002
\u9488\u5bf9\u4e00\u822c\u6570\u636e\u7c7b\u578b\uff0c\u5982 int
\u3001 str
\u3001 float
\u3001 bytes
\u7b49\uff0c\u53ef\u4ee5\u76f4\u63a5\u6807\u6ce8\u3002
"},{"location":"guidelines/advanced/type_hint/#22","title":"2.2 \u6cdb\u578b\u5177\u8c61\u5bb9\u5668","text":"\"\"\"Example\"\"\"\nfrom typing import Dict, List\ndef count_words(records: List[str]) -> Dict[str, int]:\n\"\"\"Count word of all lines.\"\"\"\nresult: Dict[str, int] = {}\nfor record in records:\nfor word in record.split(' '):\ncount = result.get(word, 0)\nresult.update({word: count + 1})\nreturn result\n
count_words
\u65b9\u6cd5\u63a5\u6536\u4e00\u4e2a\u5185\u542b str
\u7684 list
\u53c2\u6570\uff0c\u540c\u65f6\u8fd4\u56de dict
\u3002
\u8fd9\u4e9b typing.Dict
\u3001 typing.List
\u3001 typing.Set
\u7b49\u90fd\u662f\u5bf9\u5e94\u57fa\u672c\u6570\u636e\u7ed3\u6784\u7684\u6cdb\u578b\u7248\u672c\u3002
\u6ce8\u610f\uff1a \u6839\u636e\u6587\u6863 \u6a21\u5757\u5185\u5bb9 \u4e00\u8282\u63cf\u8ff0\uff0c \u5728 Python 3.9 \u5df2\u7ecf\u5bf9\u4e00\u4e9b\u57fa\u672c\u6570\u636e \u63a5\u53e3\u505a\u4e86\u6cdb\u578b\u9002\u914d\uff0c\u8fd9\u548c\u73b0\u6709 typing \u4e0b\u7684\u6cdb\u578b\u7c7b\u578b\u91cd\u590d\uff0c \u6240\u4ee5\u4f1a\u5f03\u7528\u8fd9\u4e9b\u6cdb\u578b\u5bb9\u5668\u7c7b\u578b\uff0c\u5177\u4f53\u8bf7\u53c2\u8003\u4f53\u5305\u542b\u54ea\u4e9b\u8bf7\u53c2\u8003 PEP 585 \u3002 \u5982\u679c\u9700\u8981\u63d0\u524d\u4f7f\u7528\u65b0\u7279\u6027\uff0c\u5728 Python 3.7 \u5f00\u59cb\uff0c\u53ef\u4ee5\u5bfc\u5165 from __future__ import annotations
\u6765\u4f7f\u7528\u65b0\u7684\u6cdb\u578b\u7c7b\u578b\u3002 \u5b98\u65b9\u4f1a\u5728 Python 3.9 \u53d1\u5e03\u4e94\u5e74\u540e\u7684\u6536\u4e2a Python \u53d1\u884c\u7248\uff0c\u53732025\u5e7410\u67085\u65e5\u4e4b\u540e\u7684\u6536\u4e2a\u53d1\u884c\u7248\u4f1a\u79fb\u9664 PEP 585 \u4e2d\u5f03\u7528\u7684\u6cdb\u578b\u5bb9\u5668\u7c7b\u578b\u3002
"},{"location":"guidelines/advanced/type_hint/#23","title":"2.3 \u7279\u6b8a\u7c7b\u578b","text":"\"\"\"Example\"\"\"\nimport asyncio\nfrom typing import Callable, Any, Type, Tuple, Dict, Optional\nfrom functools import partial\nclass BaseTask:\n\"\"\"base Task\"\"\"\ndef run(self) -> bool:\n\"\"\"Run task\"\"\"\nraise NotImplementedError\ndef stop(self) -> None:\n\"\"\"Stop task\"\"\"\nraise NotImplementedError\nclass FileTask(BaseTask):\n\"\"\"File task\"\"\"\ndef run(self) -> bool:\npass\ndef stop(self) -> None:\npass\nclass NetworkTask(BaseTask):\n\"\"\"Network task\"\"\"\ndef run(self) -> bool:\npass\ndef stop(self) -> None:\npass\nKwargsType = Dict[str, Any]\nArgsType = Tuple[Any]\nasync def run_in_executor(\nfunc: Callable[..., Any],\nargs: Optional[ArgsType] = (),\nkwargs: Optional[KwargsType] = None\n) -> Any:\n\"\"\"Wrap a func in a threading executor \"\"\"\nif kwargs:\nfunc = partial(func, **kwargs)\nloop = asyncio.get_running_loop()\nreturn await loop.run_in_executor(None, func, *args)\ndef task_runner(task_kls: Type[BaseTask]) -> None:\n\"\"\"Task runner\"\"\"\ntask = task_kls()\nasyncio.run(run_in_executor(task.run))\n
\u4ece\u4e0a\u9762\u7684\u4f8b\u5b50\u53ef\u4ee5\u770b\u5230\uff0c\u4f7f\u7528\u4e86\u4e00\u4e9b\u65b0\u7684\u7c7b\u578b\u6807\u6ce8\u65b9\u5f0f\u3002
\u5728 run_in_executor
\u65b9\u6cd5\u4e4b\u524d\uff0c\u5b9a\u4e49\u4e86\u4e24\u4e2a\u7c7b\u578b\uff0c\u5e76\u8d4b\u4e88\u5176\u522b\u540d\uff0c\u65b9\u4fbf\u540e\u9762\u4f7f\u7528\u3002
\u5728 run_in_executor
\u65b9\u6cd5\u4e2d\u4f7f\u7528\u4e86 typing.Callable
\u3001 typing.Optional
\u3001 typing.Any
\u7279\u6b8a\u7c7b\u578b\u3002
\u5728 task_runner
\u4e2d\u4f7f\u7528 typing.Type
\u7c7b\u578b\uff0c\u8868\u660e task_kls
\u53c2\u6570\u662f\u4e00\u4e2a BaseTask
\u7c7b\u81ea\u8eab\uff0c \u800c\u4e0d\u662f\u5b83\u7684\u5bf9\u8c61\uff0c\u51c6\u786e\u8bf4\u662f\u5b83\u7684\u7c7b\u5bf9\u8c61\u3002
\u5982 a = int
\u548c b = type(a)
\u4e2d\uff0c a
\u548c b
\u6240\u6807\u6ce8\u7684\u7c7b\u578b\u662f\u4e00\u6837\u7684\uff0c\u90fd\u662f int
\u7c7b\u578b\u3002
"},{"location":"guidelines/advanced/type_hint/#3","title":"3. \u9ad8\u9636\u4f7f\u7528","text":""},{"location":"guidelines/advanced/type_hint/#31-callable","title":"3.1 \u53ef\u8c03\u5bf9\u8c61(Callable)","text":"\u4e0a\u4e00\u7ae0\u5df2\u7ecf\u63d0\u5230\u4e86\u53ef\u8c03\u5bf9\u8c61( Callable
) \u7684\u4f7f\u7528\uff0c\u8fd9\u91cc\u9700\u8981\u5728\u8be6\u7ec6\u8bf4\u660e\u4e00\u8d77\u5b83\u7684\u7528\u6cd5\u3002
from typing import Callable, Tuple\ndef task_a(name: str) -> str:\nreturn name\ndef task_sum(a: int, b: int) -> int:\nreturn a + b\ndef task_a_executor(func: Callable[[str], str], args: Tuple[str]) -> str:\nreturn func(*args)\ndef task_sum_executor(func: Callable[[int, int], int], args: Tuple[int]) -> int:\nreturn func(*args)\n
\u9488\u5bf9\u53ef\u8c03\u5bf9\u8c61\u4e2d\u9700\u8981\u4f20\u9012\u53c2\u6570\u7684\u7c7b\u578b\uff0c\u53ef\u4ee5\u5728 Callable
\u4e2d\u6807\u6ce8\u3002
\u4ece\u4e0a\u9762\u793a\u4f8b\uff0c\u5305\u62ec Callable
\u7684\u4f7f\u7528\u65b9\u6cd5\u4e2d\u53ef\u4ee5\u770b\u5230\uff0c\u5b83\u90fd\u662f\u5728\u6807\u6ce8\u5217\u8868\u53c2\u6570( args
) \uff0c\u4f46\u5982\u679c\u9700\u8981\u6807\u6ce8\u5b57\u5178\u53c2\u6570 \u5374\u65e0\u6cd5\u6807\u6ce8\u3002 \u4f8b\u5982\u4e00\u4e2a\u65b9\u6cd5 def foo(a: Optional[int] = None, *, b: Optional[int] = None) -> None: ...
\uff0c \u5b83\u5728\u65b9\u6cd5\u5b9a\u4e49\u9636\u6bb5\u5df2\u7ecf\u58f0\u660e\u4e86\u63a5\u6536 b
\u53c2\u6570\u65f6\uff0c \u5fc5\u987b\u4e3a\u5b57\u5178\u7c7b\u578b\uff0c\u4e5f\u5c31\u662f\u8bf4\u5f53\u4f60\u4e0d\u4f20\u9012 a
\u53c2\u6570\uff0c\u4f46\u53c8\u9700\u8981\u4f20\u9012 b
\u53c2\u6570\u7684\u65f6\u5019\uff0c\u5fc5\u987b\u8fd9\u4e48\u8c03\u7528 foo(b=3)
\uff0c\u5426\u5219\u4f20\u9012\u7684\u503c\uff0c\u53ea\u4f1a\u8d4b\u503c\u5230 a
\u4e0a\u9762\u3002 \u800c\u8fd9\u79cd\u7c7b\u578b\u7684\u8c03\u7528\u5bf9\u8c61\u5374\u65e0\u6cd5\u4f7f\u7528\u6b63\u5e38\u64cd\u4f5c\u7684 \u6807\u6ce8\u4e3a Callable[[int, \"b\": int], int]
\u3002
\u5bf9\u4e8e\u8fd9\u79cd\u60c5\u51b5\uff0c\u867d\u7136\u5b98\u65b9\u6587\u6863\u4e2d\u6ca1\u6709\u5bf9\u6b64\u8bf4\u660e\uff0c\u4f46\u53ef\u4ee5\u901a\u8fc7\u7ed3\u6784\u5b50\u7c7b\u578b\u5b9a\u4e49\u8c03\u7528\u5bf9\u8c61\u7684\u7c7b\u578b\u3002
\u4e86\u89e3 \u540d\u4e49\u5b50\u7c7b\u578b vs \u7ed3\u6784\u5b50\u7c7b\u578b
\u6240\u4ee5\u53ef\u4ee5\u8fd9\u4e48\u5b9a\u4e49\uff1a
from typing import Optional, Protocol\ndef foo(\na: Optional[int],\n*,\nb: Optional[int]\n) -> None: ...\nclass FooCallableType(Protocol):\ndef __call__(\nself,\na: Optional[int] = None,\n*,\nb: Optional[int] = None\n) -> None: ...\ndef foo_executor(func: FooCallableType) -> None: ...\n
\u53c2\u8003\uff1a python typing signature (typing.Callable) for function with kwargs
"},{"location":"guidelines/advanced/type_hint/#32","title":"3.2 \u5f02\u6b65\u7f16\u7a0b","text":"import asyncio\nfrom typing import Tuple, Any, Awaitable, Union, Callable, AsyncGenerator\nfrom asyncio import iscoroutinefunction\nasync def func(length: int) -> AsyncGenerator:\nfor i in range(length):\nyield i\nasync def run_in_executor(\nfunc: Union[Callable[..., Any], Awaitable[..., Any]],\nargs: Tuple[...]\n) -> Any:\nif iscoroutinefunction(func):\nreturn await func(*args)\nelse:\nloop = asyncio.get_running_loop()\nreturn await loop.run_in_executor(None, func, *args, )\n
\u9488\u5bf9\u5f02\u6b65\u7f16\u7a0b\u7684\u7684\u6240\u6709\u7c7b\u578b\uff0c\u90fd\u5df2\u7ecf\u5728 typing
\u4e0b\u5b9a\u4e49\u4e86\uff0c\u53ef\u4ee5\u5f88\u65b9\u4fbf\u7684\u53bb\u4f7f\u7528\u3002
"},{"location":"guidelines/project_management/code_lint/","title":"\u4ee3\u7801\u68c0\u6d4b","text":"\u4ee3\u7801\u68c0\u6d4b\u662f\u4f7f\u7528\u4e00\u4e9b\u5de5\u5177\u68c0\u67e5\u4ee3\u7801\u77e5\u5426\u7b26\u5408 Python \u76f8\u5173\u89c4\u8303\u3002
\u5f53\u524d\u4e3b\u6d41\u7684\u4ee3\u7801\u68c0\u6d4b\u89c4\u8303\u5305\u62ec
- black
- flake8
- pylint
- yapf
"},{"location":"guidelines/project_management/code_lint/#_2","title":"\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177","text":""},{"location":"guidelines/project_management/code_lint/#black","title":"black","text":"black \u662f PSF \u7ec4\u7ec7\u4e0b\u7684\u4e00\u4e2a\u4ee3\u7801\u683c\u5f0f\u5316\u5de5\u5177\u3002 \u5176\u7279\u70b9\u662f\u5f3a\u5236\u683c\u5f0f\u5316\u4ee3\u7801\uff0c\u4f7f\u4ee3\u7801\u4fdd\u6301\u4e00\u81f4\u6027\u3002\u4f46\u7f3a\u70b9\u662f\u4f1a\u81ea\u52a8\u8c03\u6574\u4ee3\u7801\u683c\u5f0f\u3002
\u7279\u70b9\uff1a
- \u7b26\u5408 PEP 8 \u6807\u51c6
- \u652f\u6301\u81ea\u5b9a\u4e49\u89c4\u5219
- \u81ea\u52a8\u683c\u5f0f\u5316\u4ee3\u7801
- IDE \u63d2\u4ef6
- psf \u793e\u533a\u7ef4\u62a4
"},{"location":"guidelines/project_management/code_lint/#flake8","title":"flake8","text":"flake8 \u662f pycqa \u7ec4\u7ec7\u4e0b\u7684\u4e00\u4e2a\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177\u3002\u5b83\u9075\u5faa PEP 8 \u89c4\u8303\uff0c \u6307\u793a\u51fa\u4e0d\u7b26\u5408\u89c4\u8303\u7684\u4ee3\u7801\u3002
\u7279\u70b9\uff1a
- \u7b26\u5408 PEP 8 \u89c4\u8303
- \u96c6\u5408\u4f7f\u7528 pycodestyle \uff0c pyflakes \uff0c mccabe \u7b49\u7b2c\u4e09\u65b9\u63d2\u4ef6\u3002
- \u652f\u6301\u81ea\u5b9a\u4e49\u89c4\u5219
- \u63d0\u793a\u4e0d\u7b26\u5408\u89c4\u8303\u7684\u5185\u5bb9
- IDE \u63d2\u4ef6
- git \u6216 Mercurial \u6269\u5c55
- pycoa \u793e\u533a\u7ef4\u62a4
"},{"location":"guidelines/project_management/code_lint/#pylint","title":"pylint","text":"pylint \u662f pycqa \u7ec4\u7ec7\u4e0b\u7ef4\u62a4\u7684\u5de5\u5177\u3002\u5b83\u4e0d\u4ec5\u4ec5\u662f\u4e00\u6b3e\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177\uff0c\u8fd8\u53ef\u4ee5\u53d1\u73b0\u53d8\u6210\u9519\u8bef\uff0c\u4ee3\u7801\u5f02\u5e38\uff0c\u5e76\u63d0\u4f9b\u7b80\u5355\u7684\u91cd\u6784\u5efa\u8bae\u3002
\u7279\u70b9\uff1a
- \u7b26\u5408 PEP 8 \u89c4\u8303
- \u652f\u6301\u81ea\u5b9a\u4e49\u89c4\u5219
- \u9519\u8bef\u68c0\u6d4b
- \u91cd\u6784\u5efa\u8bae
- IDE \u63d2\u4ef6
- pycoa \u793e\u533a\u7ef4\u62a4
"},{"location":"guidelines/project_management/code_lint/#yapf","title":"yapf","text":"yapf \u662f Google \u7ef4\u62a4\u7684\u4e00\u4e2a\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177\u3002\u5b83\u548c\u4e0a\u8ff0\u5de5\u5177\u4e0d\u540c\uff0c \u4f7f\u7528\u57fa\u4e8e clang-format \u7684\u7b97\u6cd5\u5c06\u4ee3\u7801\u91cd\u65b0\u683c\u5f0f\u5316\u4e3a\u590d\u5408\u98ce\u683c\u6307\u5357\u7684\u6700\u4f73\u683c\u5f0f\u3002\u7c7b\u4f3c\u4e8e Golang \u7684 gofmt
\u5de5\u5177\u3002 \u6240\u4ee5\u5b83\u548c black \u5de5\u5177\u6709\u70b9\u7c7b\u4f3c\u3002
\u7279\u70b9\uff1a
- \u7b26\u5408 PEP 8 \u89c4\u8303
- \u652f\u6301\u81ea\u5b9a\u4e49\u89c4\u5219
- \u81ea\u52a8\u683c\u5f0f\u5316\u4ee3\u7801
- IDE \u63d2\u4ef6
- google \u793e\u533a\u7ef4\u62a4
"},{"location":"guidelines/project_management/code_lint/#_3","title":"\u4f7f\u7528\u5b9e\u8df5","text":"\u867d\u7136\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177\u6709\u5f88\u591a\uff0c\u4f46\u662f\u5b83\u4eec\u7684\u521d\u8877\u90fd\u662f\u4e3a\u4e86\u8ba9 Python \u4ee3\u7801\u7b26\u5408\u4e00\u81f4\u7684\u98ce\u683c\u548c\u89c4\u8303\u3002\u53ea\u4e0d\u8fc7\u662f\u6709\u7684\u5de5\u5177\u66f4\u6fc0\u8fdb\u800c\u5df2\u3002\u5177\u6709\u826f\u597d\u7f16\u7801\u4e60\u60ef\u7684\u5f00\u53d1\u4eba\u5458\uff0c\u5199\u51fa\u7684\u4ee3\u7801\uff0c \u65e0\u8bba\u4f7f\u7528\u54ea\u79cd\u5de5\u5177\uff0c\u90fd\u80fd\u8f7b\u677e\u901a\u8fc7\u3002\u6240\u4ee5\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177\u7684\u6700\u7ec8\u76ee\u7684\u662f\u544a\u77e5\u5f00\u53d1\u4eba\u5458\u5c3d\u53ef\u80fd\u9075\u5b88\u4e00\u81f4\u7684\u98ce\u683c\u6765\u7f16\u5199\u4ee3\u7801\u3002
\u8003\u8651\u5230\u4ee3\u7801\u68c0\u6d4b\u5de5\u5177\u7684\u6307\u5bfc\u6027\uff0c\u548c\u529f\u80fd\u6027\uff0c\u63a8\u8350\u4f7f\u7528 pylint \u4f5c\u4e3a\u9996\u9009\u68c0\u6d4b\u5de5\u5177\u3002\u5728\u5b9e\u8df5\u4e2d\u53d1\u73b0\u7531\u4e8e\u67d0\u4e9b\u5e93\u548c pylint \u7684\u517c\u5bb9\u6027\u95ee\u9898\uff0c\u5f53 \u4f7f\u7528 pylint \u6709\u95ee\u9898\u65f6\uff0c\u53ef\u4ee5\u4f7f\u7528 flake8 \u4f5c\u4e3a\u66ff\u4ee3\u7684\u68c0\u6d4b\u5de5\u5177\u3002
"},{"location":"guidelines/project_management/code_lint/#_4","title":"\u4f7f\u7528","text":"\u5728\u5b9e\u9645\u4f7f\u7528\u8fc7\u7a0b\u4e2d\uff0c\u53ef\u4ee5\u5c06\u4ee3\u7801\u68c0\u6d4b\u903b\u8f91\u653e\u5728\u81ea\u52a8\u5316\u5de5\u5177\u4e2d\u8fd0\u884c\u3002\u5c06\u903b\u8f91\u653e\u5728 tox
\u4e2d\uff0c\u53ef\u4ee5\u5728\u672c\u5730\u5f00\u53d1\u65f6\u65b9\u4fbf\u4f7f\u7528\u3002\u5728 CI \u9636\u6bb5\u53ea\u9700\u8981\u8c03\u7528 tox \u5c31\u53ef\u4ee5\u4e86\u3002
"},{"location":"guidelines/project_management/code_lint/#tox","title":"tox","text":"# tox (https://tox.readthedocs.io/) is a tool for running tests\n# in multiple virtualenvs. This configuration file will run the\n# test suite on all supported python versions. To use it, \"pip install tox\"\n# and then run \"tox\" from this directory.\n[tox]\nisolated_build = True\nenvlist =\npy{37,38,39,310}\nisort\nlint\n[testenv]\ndeps =\npipenv\nusedevelop = true\ncommands =\npipenv sync -d\npytest --cov=src\n[testenv:isort]\ndeps =\nisort\ncommands =\nisort . --check-only --diff\n[testenv:lint]\ndeps =\npipenv\nchangedir = {toxinidir}\ncommands =\npipenv sync -d\npylint src tests\n
"},{"location":"guidelines/project_management/code_lint/#github-action","title":"github action","text":"# This workflow will install Python dependencies, run tests and lint with a single version of Python\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions\nname: main\non: [push, pull_request]\njobs:\ntest:\nruns-on: ${{ matrix.os }}\nstrategy:\nfail-fast: false\nmatrix:\nos: [ubuntu-20.04]\npython: [\"3.7\", \"3.8\", \"3.9\", \"3.10\"]\nsteps:\n- uses: actions/checkout@v2\n- name: Set up Python ${{ matrix.python }} on ${{ matrix.os }}\nuses: actions/setup-python@v2\nwith:\npython-version: ${{ matrix.python }}\n- name: Install dependencies\nrun: |\npython -m pip install --upgrade pip\npip install tox\n- name: Test with tox\nrun: |\ntox -e py\nlinting:\nruns-on: ubuntu-latest\nsteps:\n- uses: actions/checkout@v2\n- uses: actions/setup-python@v2\n- run: pip install tox\n- run: |\ntox -e isort\ntox -e lint\n
"},{"location":"guidelines/project_management/code_lint/#gitlab-ci","title":"gitlab-ci","text":"default:\nimage: python:3.9\nbefore_script:\n- pip install -U pip\n.base_test:\nstage: test\nscript:\n- pip install -U tox\n- tox -e py\nstages:\n- test\n- build\n- upload\n# Due to gitlab ci not support matrix build. So use YAML anchors:\n# https://forum.gitlab.com/t/matrix-builds-in-ci/9629\ntest:py37:\nimage: python:3.7\nextends:\n- .base_test\ntest:py38:\nimage: python:3.8\nextends:\n- .base_test\ntest:py39:\nimage: python:3.9\nextends:\n- .base_test\ntest:py310:\nimage: python:3.10\nextends:\n- .base_test\ntest:lint:\nstage: test\nscript:\n- pip install -U tox\n- tox -e isort\n- tox -e lint\n
"},{"location":"guidelines/project_management/distribution/","title":"\u6784\u5efa\u4e0e\u53d1\u5e03","text":"\u4f5c\u4e3a\u9879\u76ee\u7684\u6700\u540e\u4e00\u73af\uff0c\u5206\u53d1\u81f3\u5173\u91cd\u8981\u3002\u6709\u826f\u597d\u7684\u5206\u53d1\u6d41\u7a0b\uff0c\u4fbf\u4e8e\u4f7f\u7528\u3002\u57fa\u4e8e Python \u81ea\u5e26\u7684\u5206\u53d1\u673a\u5236\u663e\u7136\u662f \u66f4\u597d\u7684\u9009\u62e9\u3002
\u672c\u6587\u5c06\u4ee5\u4e00\u4e2a\u6570\u636e\u5bfc\u51fa\u7684\u9879\u76ee\u8bb2\u8ff0\u3002
"},{"location":"guidelines/project_management/distribution/#1","title":"1. \u9879\u76ee\u51c6\u5907","text":"\u56e0\u4e3a\u672c\u6587\u7684\u91cd\u70b9\u662f\u5bf9\u6253\u5305\u5206\u53d1\uff0c\u6240\u4ee5\u9879\u76ee\u7684\u529f\u80fd\u5f00\u53d1\u5c31\u4e0d\u4f5c\u4e3a\u91cd\u70b9\u3002
\u9879\u76ee\u6e90\u4ee3\u7801\u53ef\u4ee5\u5728 pythonic-project-samples \u4e2d\u83b7\u53d6\u3002
\u9879\u76ee\u91c7\u7528 src
\u76ee\u5f55\u7ed3\u6784\uff0c\u9879\u76ee\u63cf\u8ff0\u4fe1\u606f\u90fd\u5728 pyproject.toml
\u4e2d\u5b9a\u4e49\u3002
"},{"location":"guidelines/project_management/distribution/#2","title":"2. \u9879\u76ee\u6253\u5305","text":""},{"location":"guidelines/project_management/distribution/#21","title":"2.1 \u6253\u5305\u5de5\u5177","text":"\u7531\u4e8e\u5386\u53f2\u539f\u56e0\uff0c Python \u7684\u6253\u5305\u8d70\u4e86\u5f88\u957f\u4e00\u6bb5\u8def\u4e86\uff0c\u4f46\u548c\u5176\u4ed6\u8bed\u8a00\u7684\u6253\u5305\u5de5\u5177\u76f8\u6bd4\uff0c\u672a\u6765\u8fd8\u662f\u6709\u5f88\u957f\u4e00\u6bb5\u8def\u8981\u8d70\u3002
\u5728 PEP-517 \u4e2d\uff0c\u63d0\u5230\u4e86\u5f53\u524d Python \u6784\u5efa\u7cfb\u7edf\u7684\u4e0d\u8db3\u548c\u76f8\u5e94\u7684\u89e3\u51b3\u65b9\u6848\u3002 \u5176\u4e3b\u8981\u5c31\u662f\u89e3\u51b3\u8ba9 Python \u652f\u6301\u66f4\u52a0\u7075\u6d3b\u7684\u6784\u5efa\u7cfb\u7edf\u3002 PEP-518 \u5219\u63d0\u51fa\u4e3a\u9879\u76ee\u6307\u5b9a\u4e00\u4e2a\u6700\u5c0f\u7684\u6784\u5efa\u7cfb\u7edf\u3002
"},{"location":"guidelines/project_management/distribution/#211-setuptools","title":"2.1.1 setuptools","text":"Setuptools \u662f\u5f53\u524d\u4f7f\u7528\u6700\u4e3a\u5e7f\u6cdb\u7684\u6784\u5efa\u5de5\u5177\uff0c\u73b0\u5728\u7edd\u5927\u591a\u6570\u5de5\u5177 \u90fd\u5728\u4f7f\u7528 setuptools
\u6784\u5efa\u9879\u76ee\u3002\u5b83\u662f disutils
\u7684\u589e\u5f3a\u7248\u3002
Setuptools \u53ef\u4ee5\u8bf4\u662f\u73b0\u5728\u6700\u6210\u719f\u7684\u6784\u5efa\u5de5\u5177\u4e86\uff0c\u652f\u6301\u5e38\u7528\u7279\u6027\u5982\u4e0b\uff1a
- \u652f\u6301\u6253\u5305\u8d44\u6e90\u6587\u4ef6
- \u652f\u6301\u6253\u5305\u6570\u636e\u6587\u4ef6
- \u652f\u6301 CPython \u7f16\u8bd1\u5668
- \u652f\u6301 zip \u538b\u7f29\u9009\u9879
- \u652f\u6301\u5305\u547d\u540d\u7a7a\u95f4
- \u652f\u6301\u5b89\u88c5\u4f9d\u8d56
- \u652f\u6301\u53ef\u9009\u5b89\u88c5\u4f9d\u8d56
- \u652f\u6301\u6307\u5b9a Python \u7248\u672c
- \u652f\u6301\u6ce8\u518c Setuptools \u5b50\u547d\u4ee4
- \u652f\u6301\u5165\u53e3\u70b9\uff08Entry Points\uff09
\u7531\u4e8e Setuptools \u662f\u6700\u6210\u719f\u7684\u6784\u5efa\u5de5\u5177\uff0c\u6240\u4ee5\u4e0e\u5176\u5b83\u6784\u5efa\u5de5\u5177\u5bf9\u6bd4\u6765\u770b\uff0c\u5b83\u7684\u7f3a\u70b9\u53ef\u80fd\u5c31\u662f\u73b0\u5728\u7684\u914d\u7f6e\u4ecd\u9700\u8981\u5b9a\u4e49\u5728 setup.cfg
\u6216\u8005 setup.py
\u6587\u4ef6\u4e2d\uff0c\u800c\u4e0d\u662f\u5b9a\u4e49\u5728 pyproject.toml
\u6587\u4ef6\u4e2d\u3002
\u7f3a\u70b9\uff1a
- \u4e0d\u652f\u6301\u5728
pyproject.toml
\u4e2d\u5b9a\u4e49\u914d\u7f6e - \u4e0d\u652f\u6301\u53d1\u5e03\uff0c\u9700\u8981\u914d\u5408
twine
\u3002
"},{"location":"guidelines/project_management/distribution/#2111","title":"2.1.1.1 \u793a\u4f8b\u914d\u7f6e","text":"pyproject.toml :
[build-system]\nrequires = [\"setuptools\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n
\u589e\u52a0 setup.py
\u6216\u8005 setup.cfg
\u4e24\u79cd\u6709\u5176\u4e00\u5373\u53ef\u3002\u7136\u540e\u5728\u6587\u4ef6\u4e2d\u5b9a\u4e49\u914d\u7f6e\u3002
\u63a8\u8350\u4f7f\u7528 setup.cfg
\u3002
setup.cfgsetup.py [metadata]\nname = mypackage\nversion = 0.0.1\n[options]\npackages = mypackage\ninstall_requires =\nrequests\nimportlib; python_version == \"3.7\"\n
from setuptools import setup\nsetup(\nname='mypackage',\nversion='0.0.1',\npackages=['mypackage'],\ninstall_requires=[\n'requests',\n'importlib; python_version == \"2.6\"',\n],\n)\n
\u6b64\u65f6\u4f60\u7684\u9879\u76ee\u7ed3\u6784\u5e94\u5728\u662f\u8fd9\u6837\u7684\uff1a
~/mypackage/\n pyproject.toml\n setup.cfg # or setup.py\n mypackage/__init__.py\n
"},{"location":"guidelines/project_management/distribution/#2112","title":"2.1.1.2 \u901a\u7528\u65b9\u5f0f\u6784\u5efa","text":"\u5b89\u88c5 PEP-517 \u89c4\u8303\u7684\u5305\u751f\u6210\u5668 build \u548c setuptools\uff0cpip install build setuptools
\u3002
\u7136\u540e\u5f00\u59cb\u6784\u5efa python -m build wheel
\u3002
"},{"location":"guidelines/project_management/distribution/#2113-setuptools","title":"2.1.1.3 Setuptools \u6784\u5efa","text":"\u6b64\u65b9\u6cd5\u662f\u5728\u4e0d\u5b89\u88c5 build \u7684\u60c5\u51b5\u4e0b\u4f7f\u7528\u7684\u3002
\u5982\u679c\u4f60\u53ea\u662f\u7528\u4e86 setup.cfg
\u914d\u7f6e\u7684\u60c5\u51b5\u4e0b\uff0c\u4f60\u8fd8\u9700\u8981\u589e\u52a0\u4e00\u4e2a setup.py
\u6587\u4ef6\uff0c\u5185\u5bb9\u5982\u4e0b\uff1a
setup.py \uff1a
from setuptools import setup\nsetup()\n
\u5982\u679c\u4f60\u4ec5\u4f7f\u7528\u4e86 setup.py
\u914d\u7f6e Setuptools \u7684\u8bdd\uff0c\u53ef\u4ee5\u4e0d\u9700\u8981 setup.cfg
\u6587\u4ef6\u3002
\u5b89\u88c5\u4f9d\u8d56 pip install setuptools
\uff0c\u7136\u540e\u8fdb\u884c\u6784\u5efa python setup.py bdist_wheel
\u3002
"},{"location":"guidelines/project_management/distribution/#2114-twine","title":"2.1.1.4 twine \u53d1\u5e03","text":"\u7531\u4e8e setuptools \u4e0d\u652f\u6301\u53d1\u5e03\u529f\u80fd\uff0c\u6240\u4ee5\u9700\u8981\u501f\u52a9\u5176\u4ed6\u5de5\u5177\u5c06\u5305\u53d1\u5e03\u4e2d\u592e\u4ed3\u5e93\u3002
Twine \u662f Pypa \u56e2\u961f\u7ef4\u62a4\u7684\u4e00\u4e2a\u5c06 Python \u5305\u53d1\u5e03\u5230 Pypi \u7684\u5de5\u5177\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a pip install twine
\u3002
\u4f7f\u7528 Setuptools \u6784\u5efa\u9879\u76ee\uff0c\u6784\u5efa\u7ed3\u679c\u9ed8\u8ba4\u662f\u653e\u5728\u9879\u76ee\u6839\u76ee\u5f55\u7684 ./dist
\u4e0b\u9762 \u3002
\u53d1\u5e03\u9879\u76ee\u5230 Pypi \uff1a
twine upload dist/*\n
"},{"location":"guidelines/project_management/distribution/#212-flit","title":"2.1.2 flit","text":"Flit \u662f\u4e00\u4e2a\u8f7b\u91cf\u7b80\u5355\u7684 Python \u6784\u5efa\u5de5\u5177\uff0c\u5b83\u7684\u51fa\u73b0\u4e5f\u53ef\u4ee5\u8bf4\u662f\u5212\u65f6\u4ee3\u7684\uff0c\u56e0\u4e3a\u5b83\u7684\u51fa\u73b0\u4fc3\u8fdb\u4e86\u65b0\u6807\u51c6\u7684\u53d1\u73b0\uff0c \u5982 PEP-517 \u548c PEP-518 \u3002
Flit
\u5177\u6709\u5982\u4e0b\u7279\u70b9\uff1a
- \u7b80\u5355\u8f7b\u91cf
- \u652f\u6301\u53d1\u5e03
- \u652f\u6301\u6253\u5305\u6570\u636e\u6587\u4ef6
- \u652f\u6301\u5b50\u5305
- \u652f\u6301\u590d\u5236\u6784\u5efa
- \u652f\u6301\u5b89\u88c5\u4f9d\u8d56
- \u652f\u6301\u53ef\u9009\u5b89\u88c5\u4f9d\u8d56
- \u652f\u6301\u6307\u5b9a Python \u7248\u672c
- \u652f\u6301\u6ce8\u518c Flit \u5b50\u547d\u4ee4
- \u652f\u6301\u5165\u53e3\u70b9\uff08Entry Points\uff09
- \u652f\u6301
pyproject.toml
\u6587\u4ef6\u5b9a\u4e49\u914d\u7f6e
\u7f3a\u70b9\uff1a
- \u4e0d\u652f\u6301 CPython \u7f16\u8bd1
- \u4e0d\u652f\u6301 zip \u538b\u7f29\u9009\u9879
"},{"location":"guidelines/project_management/distribution/#2121","title":"2.1.2.1 \u793a\u4f8b\u914d\u7f6e","text":"pyproject.toml \uff1a
[build-system]\nrequires = [\"flit_core >=2,<4\"]\nbuild-backend = \"flit_core.buildapi\"\n[tool.flit.metadata]\nmodule = \"foobar\"\nauthor = \"Sir Robin\"\nauthor-email = \"robin@camelot.uk\"\nhome-page = \"https://github.com/sirrobin/foobar\"\n
"},{"location":"guidelines/project_management/distribution/#2122","title":"2.1.2.2 \u901a\u7528\u65b9\u5f0f\u6784\u5efa","text":"\u5b89\u88c5 PEP-517 \u89c4\u8303\u7684\u5305\u751f\u6210\u5668 build \u548c flit \uff0cpip install build flit
\u3002
\u7136\u540e\u5f00\u59cb\u6784\u5efa python -m build wheel
\u3002
"},{"location":"guidelines/project_management/distribution/#2123-flit","title":"2.1.2.3 flit \u6784\u5efa","text":"\u6b64\u65b9\u6cd5\u662f\u5728\u4e0d\u5b89\u88c5 build \u7684\u60c5\u51b5\u4e0b\u4f7f\u7528\u7684\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a pip install flit
\uff0c\u7136\u540e\u8fdb\u884c\u6784\u5efa flit build --format wheel
\u3002
"},{"location":"guidelines/project_management/distribution/#2124-flit","title":"2.1.2.4 flit \u53d1\u5e03","text":"flit
\u7684\u53d1\u5e03\u547d\u4ee4\u4f1a\u81ea\u884c\u5148\u6784\u5efa wheel
\u548c sdist
\u5305\uff0c\u7136\u540e\u4e0a\u4f20\u5230 Pypi \u6216\u8005\u5176\u4ed6\u4ed3\u5e93\u3002
flit build --format wheel\n
"},{"location":"guidelines/project_management/distribution/#213-poetry","title":"2.1.3 poetry","text":"python-poetry \u662f\u4e00\u4e2a\u66f4\u9ad8\u7ea7\u7684\u5de5\u5177\u3002\u5b83\u662f\u4e00\u4e2a\u6784\u5efa\u5de5\u5177\u7684\u540c\u65f6\u4e5f\u662f\u4e00\u4e2a\u4f9d\u8d56\u7ba1\u7406\u5de5\u5177\u3002 \u5728\u6784\u5efa\u4e0a\uff0c\u540c\u6837\u9075\u5faa\u4e86 PEP-517 \u89c4\u8303\uff0c\u5728\u4f9d\u8d56\u7ba1\u7406\u4e0a\uff0c\u6709\u70b9\u7c7b\u4f3c\u4e8e Pipenv \u3002
python-poetry \u5177\u6709\u5982\u4e0b\u7279\u70b9\uff1a
- \u652f\u6301\u73af\u5883\u7ba1\u7406
- \u652f\u6301\u53d1\u5e03
- \u652f\u6301 shell \u63d2\u4ef6\uff0c\u5982
bash
\u3001 Fish
\u3001 Zsh
- \u652f\u6301\u6253\u5305\u6570\u636e\u6587\u4ef6
- \u652f\u6301\u6ce8\u518c poetry \u5b50\u547d\u4ee4
- \u652f\u6301 Setuptools \u7684\u5165\u53e3\u70b9\uff08Entry Points\uff09
- \u652f\u6301\u5305\u547d\u540d\u7a7a\u95f4
- \u652f\u6301\u5b89\u88c5\u4f9d\u8d56
- \u652f\u6301\u53ef\u9009\u5b89\u88c5\u4f9d\u8d56
- \u652f\u6301\u5728
pyproject.toml
\u4e2d\u5b9a\u4e49\u914d\u7f6e
\u7f3a\u70b9\uff1a
- \u4e0d\u652f\u6301 CPython \u7f16\u8bd1
- \u4e0d\u652f\u6301 zip \u538b\u7f29\u9009\u9879
"},{"location":"guidelines/project_management/distribution/#2131","title":"2.1.3.1 \u793a\u4f8b\u914d\u7f6e","text":"[build-system]\nrequires = [\"poetry_core>=1.0.0\"]\nbuild-backend = \"poetry.core.masonry.api\"\n[tool.poetry]\nname = \"poetry-demo\"\nversion = \"0.1.0\"\ndescription = \"\"\nauthors = [\"S\u00e9bastien Eustace <sebastien@eustace.io>\"]\n[tool.poetry.dependencies]\npython = \"^3.10\"\n[tool.poetry.dev-dependencies]\npytest = \"^3.4\"\n
"},{"location":"guidelines/project_management/distribution/#22-poetry","title":"2.2 \u6253\u5305\u6784\u5efa\uff08poetry\uff09","text":"\u73b0\u9636\u6bb5\u9009\u7528 poetry
\u4f5c\u4e3a\u6784\u5efa\u5de5\u5177\u3002
\u4e3a\u9879\u76ee\u6307\u5b9a\u6240\u9700\u8981\u4f7f\u7528\u7684\u6784\u5efa\u5de5\u5177\u3002\u521b\u5efa pyproject.toml
\u6587\u4ef6\uff0c\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
[tool.poetry]\nname = \"file2mongo\"\nversion = \"0.1.0\"\ndescription = \"File data to MongoDB\"\nreadme = \"README.md\"\nauthors = [\"demo <demo@example.com>\"]\nlicense = \"MIT\"\nclassifiers = [\n\"Operating System :: OS Independent\",\n\"Programming Language :: Python :: 3.10\",\n]\n[tool.poetry.dependencies]\npython = \"^3.10\"\ndynaconf = \"^3.1.9\"\nclick = \"^8.1.3\"\npymongo = \"^4.3.3\"\n[tool.poetry.dev-dependencies]\npylint = \"^2.14.5\"\nisort = \"^5.10.1\"\npytest = \"^7.1.2\"\nmkdocs = \"^1.3.1\"\nmkdocs-material = \"^8.4.1\"\n[tool.poetry.plugins.\"scripts\"]\nfile2mongo = \"file2mongo.cmdline:main\"\n[build-system]\nrequires = [\"poetry-core>=1.0.0\"]\nbuild-backend = \"poetry.core.masonry.api\"\n
"},{"location":"guidelines/project_management/distribution/#221","title":"2.2.1 \u9879\u76ee\u57fa\u672c\u4fe1\u606f","text":"tool.poetry \u662f\u9879\u76ee\u57fa\u672c\u63cf\u8ff0\u4fe1\u606f\uff0c\u6709\u9879\u76ee\u540d\u79f0\uff0c\u7248\u672c\u53f7\uff0c\u4f5c\u8005\u76f8\u5173\u4fe1\u606f\u7b49\u3002
\u4e3a\u4e86\u8ba9\u522b\u4eba\u66f4\u7cbe\u51c6\u7684\u83b7\u53d6\u4f60\u7684\u5305\u7684\u4fe1\u606f\uff0c\u5e94\u8be5\u5c3d\u91cf\u5305\u542b\u5982\u4e0b\uff1a
name
\uff1a \u9879\u76ee\u540d\u79f0\u3002\u5fc5\u9700\u5b57\u6bb5 version
\uff1a \u7248\u672c\u53f7\u3002\u5fc5\u9700\u5b57\u6bb5 description
: \u9879\u76ee\u7b80\u77ed\u63cf\u8ff0\u3002\u5fc5\u9700\u5b57\u6bb5 license
\uff1a \u8bb8\u53ef\u8bc1\u3002\u53ef\u9009\u5b57\u6bb5\uff0c\u5efa\u8bae\u6dfb\u52a0 author
\uff1a \u9879\u76ee\u4f5c\u8005\u3002\u5fc5\u9700\u5b57\u6bb5 maintainers
: \u7ef4\u62a4\u8005\u3002\u8fd9\u662f\u4e00\u4e2a\u7ef4\u62a4\u8005\u5217\u8868\uff0c\u5e94\u8be5\u4e0e\u4f5c\u8005\u533a\u5206\u5f00\u6765\u3002 \u53ef\u80fd\u5305\u542b\u4e00\u4e2a\u7535\u5b50\u90ae\u4ef6\uff0c\u683c\u5f0f\u4e3a name <email>
\u3002\u53ef\u9009\u5b57\u6bb5 readme
: \u9879\u76ee\u7684 README \u6587\u4ef6\u6216\u76f8\u5bf9\u5e94\u7684\u8def\u5f84\u6216\u8def\u5f84\u5217\u8868\u3002\u53ef\u9009\u5b57\u6bb5 homepage
: \u9879\u76ee\u7f51\u7ad9\u7684 URL\u3002\u53ef\u9009\u5b57\u6bb5 keywords
\uff1a \u9879\u76ee\u5173\u952e\u5b57\uff0c\u6709\u52a9\u4e8e\u6a21\u7cca\u641c\u7d22\u5339\u914d\u3002\u53ef\u9009\u5b57\u6bb5
"},{"location":"guidelines/project_management/distribution/#222-options","title":"2.2.2 options","text":"\u6b64\u8282\u70b9\u5185\u5bb9\u867d\u7136\u4e3a\u53ef\u9009\uff0c\u4f46\u4e3a\u4e86\u9879\u76ee\u7684\u5b8c\u6574\u6027\uff0c\u6709\u4e9b\u5185\u5bb9\u8fd8\u662f\u9700\u8981\u7684\u3002
tool.poetry.dependencies
: \u9879\u76ee\u4f7f\u7528\u8fc7\u7a0b\u4e2d\u4f9d\u8d56\u7684\u5305 tool.poetry.scripts
: \u5b89\u88c5\u5305\u65f6\u5c06\u5b89\u88c5\u7684\u811a\u672c\u6216\u53ef\u6267\u884c\u6587\u4ef6 tool.poetry.extras
:\u53ef\u9009\u4f9d\u8d56\u9879\uff0c\u589e\u5f3a\u5305\uff0c\u4f46\u4e0d\u662f\u5fc5\u9700\u7684\uff0c\u53ef\u9009\u4f9d\u8d56\u9879\u7684\u96c6\u7fa4\u3002 tool.poetry.plugins
: \u63d2\u4ef6\uff0c\u53ef\u901a\u8fc7 importlib.metadata
\u5bfc\u5165 tool.poetry.urls
: \u81ea\u5b9a\u4e49 url\uff0c\u53d1\u5e03 pypi \u540e\u5c55\u793a build-system
: \u6784\u5efa\u7cfb\u7edf\u5f15\u7528\u90e8\u5206
"},{"location":"guidelines/project_management/distribution/#2221-entrypoints","title":"2.2.2.1 \u5165\u53e3\u70b9 EntryPoints","text":"Entry-points \u53ef\u4ee5\u5f88\u65b9\u4fbf\u7684\u6ce8\u518c\u547d\u4ee4\u884c\u811a\u672c\uff0c\u6216\u8005\u63d0\u4f9b\u4e00\u79cd\u63d2\u4ef6\u52a0\u8f7d\u673a\u5236\u3002
\u6ce8\u518c\u547d\u4ee4\u884c \uff1a
\u4f8b\u5982\uff0c\u8981\u521b\u5efa\u540d\u4e3a foo
\u7684\u63a7\u5236\u53f0\u811a\u672c\uff0cpyproject.toml
\u6587\u4ef6\u6dfb\u52a0\u5982\u4e0b\u793a\u4f8b\uff1a
[tool.poetry.scripts]\nfoo = \"my_package.some_module:main_func\"\n
"},{"location":"guidelines/project_management/distribution/#223","title":"2.2.3 \u6784\u5efa","text":"\u5f53\u914d\u7f6e\u5b8c\u6210\u540e\uff0c\u5c31\u53ef\u4ee5\u5f00\u59cb\u6784\u5efa\u4e86\u3002
\u8fd0\u884c\u547d\u4ee4\uff1a
poetry build\n
\u8fd0\u884c\u5b8c\u6210\u540e\uff0c\u4f1a\u5728\u9879\u76ee\u6839\u76ee\u5f55\u7684 ./dist
\u4e2d\u751f\u6210\u4e24\u4e2a\u5206\u53d1\u6587\u4ef6\u3002\u4e00\u4e2a\u662f .tar.gz
\u7ed3\u5c3e\u7684\u6e90\u7801\u538b\u7f29\u5305\uff0c\u4e00\u4e2a\u662f .whl
\u7ed3\u5c3e\u7684\u4e8c\u8fdb\u5236\u5305\u3002
"},{"location":"guidelines/project_management/distribution/#3","title":"3 \u5206\u53d1","text":"\u6253\u5305\u540e\u7684\u6587\u4ef6\u53ef\u4ee5\u901a\u8fc7\u5206\u53d1\u624b\u6bb5\u7ed9\u5176\u4ed6\u4eba\u4f7f\u7528\u3002
"},{"location":"guidelines/project_management/distribution/#31","title":"3.1 \u624b\u52a8\u5206\u53d1","text":"\u624b\u52a8\u5206\u53d1\uff0c\u5373\u81ea\u5df1\u7ba1\u7406\u8fd9\u4e9b\u8f6f\u4ef6\u5305\uff0c\u5982\u901a\u8fc7\u590d\u5236\u3001 ftp
\u6216\u8005\u7f51\u7edc\u53d1\u9001\u7b49\u65b9\u5f0f\u3002 \u4f7f\u7528\u65f6\uff0c\u4e0b\u8f7d\u6240\u9700\u8981\u7684\u7248\u672c\u5206\u53d1\u5305\uff0c\u7136\u540e\u4f7f\u7528 Pip \u5b89\u88c5 pip install foo.whl
\u5373\u53ef\u3002
"},{"location":"guidelines/project_management/distribution/#32","title":"3.2 \u4f7f\u7528\u4ed3\u5e93\u5206\u53d1","text":"Python \u6240\u7528\u516c\u5f00\u5305\u90fd\u5b58\u653e\u5728 Pypi \uff0c\u5f53\u6211\u4eec\u4f7f\u7528 pip install requests
\u7684\u65f6\u5019\uff0c\u9ed8\u8ba4\u4f1a\u4ece Pypi \u4e2d\u67e5\u627e\u6700\u65b0\u7248\u672c\u7684\u5206\u53d1\u5305\uff0c\u627e\u5230\u4e86\u5c31\u5148\u4e0b\u8f7d\u5230\u672c\u5730\uff0c\u7136\u540e\u5b89\u88c5\u5230\u73af\u5883\u4e2d\u3002\u9664\u4e86\u5b98\u65b9\u4ed3\u5e93\uff0c\u8fd8\u652f\u6301\u79c1\u6709\u4ed3\u5e93\u3002
\u8981\u53d1\u5e03\u5230 Pypi \uff0c\u9996\u5148\u9700\u8981\u6ce8\u518c\u8d26\u53f7\uff0c\u5982\u679c\u662f\u8981\u6d4b\u8bd5\uff0c\u5219\u53ef\u4ee5\u4f7f\u7528\u6d4b\u8bd5\u4ed3\u5e93Test-Pypi \u3002\u5bf9\u4e8e\u79c1\u6709\u4ed3\u5e93\uff0c\u53ef\u4ee5\u53c2\u8003\u5177\u4f53\u6587\u6863poetry-publish\uff0c\u4f46\u4f7f\u7528\u65b9\u6cd5\u57fa\u672c\u4e00\u81f4\uff0c \u53ea\u9700\u8981\u66ff\u6362\u4e00\u4e0b\u4ed3\u5e93\u5730\u5740\u3002
\u4e0a\u4f20\u5230 Test-Pypi \uff1a
poetry publish -r https://test.pypi.org/ -u username -p password\n
\u586b\u5199\u7528\u6237\u540d\u548c\u5bc6\u7801\u5373\u53ef\u4e0a\u4f20\u3002
\u4e0a\u4f20\u5230 Pypi \uff1a
poetry publish -u username -p password\n
"},{"location":"guidelines/project_management/document/","title":"\u6587\u6863\u7ba1\u7406","text":"\u9879\u76ee\u6587\u6863\u7528\u6765\u8bf4\u660e\u548c\u8bb0\u5f55\u9879\u76ee\u7684\u4fe1\u606f\uff0c\u6709\u52a9\u4e8e\u5f00\u53d1\u4eba\u5458\u3001\u7ba1\u7406\u4eba\u5458\u3001\u4f7f\u7528\u8005\u7684\u4ea4\u6d41\u548c\u6c9f\u901a\u3002\u5728 Python \u9879\u76ee\u4e2d \u4e00\u822c\u901a\u8fc7 Mkdocs \u548c sphinx \u6765 \u6784\u5efa\u9879\u76ee\u6587\u6863\u3002\u4e24\u8005\u90fd\u652f\u6301 markdown \u6807\u8bb0\u7684\u6587\u4ef6\uff0c\u4f46\u540e\u8005\u4e5f\u652f\u6301 reStructuredText \u6807\u8bb0\u6587\u4ef6\u3002
"},{"location":"guidelines/project_management/document/#mkdocs","title":"mkdocs","text":"Mkdocs \u662f\u4e00\u4e2a\u5feb\u901f\u3001\u7b80\u5355\u7684\u9759\u6001\u7ad9\u70b9\u751f\u6210\u5de5\u5177\u3002\u53ef\u4ee5\u901a\u8fc7\u6307\u5b9a\u76ee\u5f55\u4e2d\u7684 markdown \u6807\u8bb0\u6587\u4ef6\uff0c\u6765\u751f\u6210\u9759\u6001\u7f51\u9875\u3002 \u4f7f\u7528 YAML \u683c\u5f0f\u914d\u7f6e\u6587\u4ef6\u3002
\u7279\u70b9\uff1a
- YAML \u5355\u6587\u4ef6\u914d\u7f6e
- \u751f\u6210\u9759\u6001\u7ad9\u70b9
- \u652f\u6301 markdown
- \u652f\u6301\u81ea\u5b9a\u4e49\u4e3b\u9898
- \u652f\u6301 markdown \u6269\u5c55\u6807\u8bb0
- \u652f\u6301\u63d2\u4ef6
"},{"location":"guidelines/project_management/document/#sphinx","title":"sphinx","text":"sphinx \u662f\u4f7f\u7528 reStructuredText \u6807\u8bb0\u7f16\u5199\u6587\u6863\uff0c\u5e76 \u751f\u6210\u9759\u6001\u7ad9\u70b9\u7684\u5de5\u5177\u3002
\u7279\u70b9\uff1a
- \u5355\u4e2a Python \u6587\u4ef6\u914d\u7f6e
- \u751f\u6210 HTML \u3001 ePub \u7b49\u591a\u79cd\u683c\u5f0f
- \u652f\u6301 markdown \u548c reStructuredText
- \u652f\u6301\u81ea\u5b9a\u4e49\u4e3b\u9898
- \u652f\u6301\u6269\u5c55
"},{"location":"guidelines/project_management/document/#_2","title":"\u5b9e\u8df5","text":"\u5728\u5f00\u53d1\u5b9e\u8df5\u4e2d\uff0c\u63a8\u8350\u4f7f\u7528 Mkdocs \uff0c\u56e0\u4e3a\u5b83\u7b80\u5355\u4e0a\u624b\uff0c\u5e76\u4e14\u6709\u8bb8\u591a\u4f18\u79c0\u7684\u7b2c\u4e09\u65b9\u4e3b\u9898\u3002
"},{"location":"guidelines/project_management/document/#_3","title":"\u5b9e\u8df5\u6848\u4f8b","text":"\u9996\u5148\u521b\u5efa\u4e00\u4e2a example-doc
\u7684\u76ee\u5f55\uff0c\u7136\u540e\u521d\u59cb\u5316\u9879\u76ee\u865a\u62df\u73af\u5883\uff0c\u5b89\u88c5\u73af\u5883\u4f9d\u8d56\uff1a
\u276f mkdir example-doc\n\u276f cd example-doc\n\u276f poetry init\nPackage name [example-doc]: \nVersion [0.1.0]: \nDescription []: \nAuthor [doc <doc@example.com>, n to skip]: \nLicense []: \nCompatible Python versions [^3.10]: \n\nWould you like to define your main dependencies interactively? (yes/no) [yes]\nYou can specify a package in the following forms:\n - A single name (requests): this will search for matches on PyPI\n - A name and a constraint (requests@^2.23.0)\n - A git url (git+https://github.com/python-poetry/poetry.git)\n - A git url with a revision (git+https://github.com/python-poetry/poetry.git#develop)\n - A file path (../my-package/my-package.whl)\n - A directory (../my-package/)\n - A url (https://example.com/packages/my-package-0.1.0.tar.gz)\n\nPackage to add or search for (leave blank to skip):\n\nWould you like to define your development dependencies interactively? (yes/no) [yes]\nPackage to add or search for (leave blank to skip):\n\nGenerated file\n\n[tool.poetry]\nname = \"example-doc\"\nversion = \"0.1.0\"\ndescription = \"\"\nauthors = [\"doc <doc@example.com>\"]\nreadme = \"README.md\"\npackages = [{include = \"example_doc\"}]\n\n[tool.poetry.dependencies]\npython = \"^3.10\"\n\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n\nDo you confirm generation? (yes/no) [yes]\n\n\u276f poetry shell\nCreating virtualenv example-doc-DN2_2NFH-py3.10 in C:\\Users\\qiang.xie\\AppData\\Local\\pypoetry\\Cache\\virtualenvs\nSpawning shell within C:\\Users\\qiang.xie\\AppData\\Local\\pypoetry\\Cache\\virtualenvs\\example-doc-DN2_2NFH-py3.10\n\u276f poetry add mkdocs\nUsing version ^1.4.2 for mkdocs\n\nUpdating dependencies\nResolving dependencies...\n\nWriting lock file\n\nPackage operations: 14 installs, 0 updates, 0 removals\n\n \u2022 Installing six (1.16.0)\n \u2022 Installing colorama (0.4.6)\n \u2022 Installing markupsafe (2.1.1)\n \u2022 Installing python-dateutil (2.8.2)\n \u2022 Installing pyyaml (6.0)\n \u2022 Installing click (8.1.3)\n \u2022 Installing ghp-import (2.1.0)\n \u2022 Installing jinja2 (3.1.2)\n \u2022 Installing packaging (22.0)\n \u2022 Installing pyyaml-env-tag (0.1)\n \u2022 Installing watchdog (2.2.0)\n \u2022 Installing mergedeep (1.3.4)\n \u2022 Installing markdown (3.3.7)\n \u2022 Installing mkdocs (1.4.2)\n
\u521d\u59cb\u5316\u6587\u6863\u914d\u7f6e\uff1a
\u276f mkdocs new .\nINFO - Writing config file: ./mkdocs.yml\nINFO - Writing initial docs: ./docs/index.md\n\u276f ls\ndocs mkdocs.yml pyproject.toml poetry.lock\n
\u7136\u540e\u542f\u52a8 mkdocs \u7684\u672c\u5730\u670d\u52a1\u5668\uff1a
\u276f mkdocs serve\nINFO - Building documentation...\nINFO - Cleaning site directory\nINFO - Documentation built in 0.05 seconds\nINFO - [11:00:22] Serving on http://127.0.0.1:8000/\n
\u7136\u540e\u6d4f\u89c8\u5668\u6253\u5f00 [http://127.0.0.1:8000] \u8bbf\u95ee\u751f\u6210\u7684\u6587\u6863\u7ad9\u70b9\u3002
\u7ad9\u70b9\u4f7f\u7528\u9ed8\u8ba4\u4e3b\u9898\uff0c\u98ce\u683c\u6709\u70b9\u590d\u53e4\u3002\u53ef\u4ee5\u4f7f\u7528 mkdocs-material \u8ba9\u7ad9\u70b9\u66f4\u597d\u770b\uff1a
\u5b89\u88c5 mkdocs-material
\uff1a
poetry add mkdocs-material\n
\u4fee\u6539\u914d\u7f6e\u6587\u4ef6 mkdocs.yml
\uff0c\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
theme:\nname: material\n
\u91cd\u65b0\u542f\u52a8 mkdocs serve
\uff0c\u5373\u53ef\u770b\u5230\u6ce8\u610f\u5df2\u7ecf\u6539\u53d8\u3002
\u5bf9\u4e8e Mkdocs \u7684\u66f4\u591a\u4f7f\u7528\u7ec6\u8282\u53ef\u4ee5\u53c2\u8003\u6587\u6863\uff1a
- Mkdocs \u5feb\u901f\u5f00\u59cb
- Mkdocs \u914d\u7f6e
- mkdocs-material \u4e3b\u9898
"},{"location":"guidelines/project_management/project_structure/","title":"\u9879\u76ee\u7ed3\u6784","text":"\u4ece\u54ea\u4e9b\u5730\u65b9\u63cf\u8ff0\uff1a
- \u5206\u522b\u63cf\u8ff0\u4e24\u79cd\u76ee\u5f55\u7ed3\u6784
- \u4e24\u79cd\u76ee\u5f55\u7ed3\u6784\u7684\u6bd4\u8f83\u4e0e\u533a\u522b
- \u5f53\u524d\u91c7\u7528\u7684\u7ed3\u6784
\u7531\u4e8e Python \u7b80\u5355\u6613\u7528\uff0c\u5f88\u591a\u5f00\u59cb\u4f7f\u7528 Python \u7684\u4eba\u90fd\u662f\u4ece\u4e00\u4e2a\u811a\u672c\u6587\u4ef6\u5f00\u59cb\uff0c\u9010\u6b65\u5f62\u6210\u591a\u4e2a Python \u6587\u4ef6\u7ec4\u6210\u7684\u7a0b\u5e8f\u3002\u4e5f\u6b63\u56e0\u4e3a\u5982\u6b64\u5927\u90e8\u5206\u4eba\u5e76\u6ca1\u4ee5\u4e00\u4e2a\u9879\u76ee\u6216\u5de5\u7a0b\u7684\u6982\u5ff5\u53bb\u770b\u5f85\u81ea\u5df1\u7684\u7a0b\u5e8f\u3002\u800c\u73b0\u5728\u793e\u533a\u4e2d\u7684\u6d41\u884c\u9879\u76ee\u4e5f\u5b58\u5728\u4e24\u79cd\u4e0d\u540c\u7684\u76ee\u5f55\u7ed3\u6784\u3002
"},{"location":"guidelines/project_management/project_structure/#1","title":"1 \u7b80\u5355\u7ed3\u6784","text":"Python \u9879\u76ee\u6253\u5305 \u6587\u7ae0\u4e2d\u4ee5\u4e00\u4e2a\u7b80\u5355\u9879\u76ee\u7ed3\u6784\u6f14\u793a\u4e86\u5982\u4f55\u6253\u5305\u4e00\u4e2a Python \u9879\u76ee
packaging_tutorial\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 example_pkg\n\u2502 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 pyproject.toml\n\u2514\u2500\u2500 tests\n
\u9879\u76ee\u7ed3\u6784\u4ee5\u6839\u76ee\u5f55\u5f00\u59cb\uff0c\u4f5c\u4e3a\u9879\u76ee\u7684\u73af\u5883\u3002\u56e0\u4e3a\uff0c\u4e3a\u4e86\u5728\u5f00\u53d1\u4e2d\u6b63\u5e38\u5bfc\u5165 example_pkg
\u4e2d\u6240\u6709\u7684\u4e1c\u897f\uff0c\u5c31\u9700\u8981\u5c06\u9879\u76ee\u6839\u76ee\u5f55\u6dfb\u52a0\u5230 sys.path
\u4e2d\u3002\u8fd9\u4e5f\u5c31\u8ba9\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\u7684\u6240\u6709\u5305\u90fd\u53d8\u6210\u4e86\u53ef\u5bfc\u5165\u3002\u5f53\u6709\u591a\u4e2a\u540c\u7ea7\u5305\u65f6\uff0c\u5b83\u4eec\u90fd\u662f\u6241\u5e73\u7684\u6563\u843d\u5728\u9879\u76ee\u6839\u76ee\u5f55\u3002\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\u53ef\u80fd\u8fd8\u5b58\u5728\u5176\u4ed6\u975e\u5305\u76ee\u5f55\uff0c\u5982 data
\u3001 docs
\u7b49\u3002\u5982\u679c\u9700\u8981\u672c\u5730\u5f15\u7528\u7b2c\u4e09\u65b9\u5e93\uff0c\u4e5f\u9700\u8981\u653e\u5230\u6839\u76ee\u5f55\uff0c\u4f46\u7b2c\u4e09\u65b9\u5305\u5e76\u4e0d\u662f\u9879\u76ee\u7684\u5b50\u5305\uff0c\u800c\u662f\u5b83\u7684\u4e00\u4e2a\u5f15\u7528\u3002\u8fd9\u6837\u505a\u4f1a\u9020\u6210\u804c\u8d23\u6df7\u4e71\u3002
\u6bd4\u5982\u8fd9\u6837\u7684\u4e00\u4e2a\u9879\u76ee\uff1a
tutorial\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 data\n| \u2514\u2500\u2500 user.json\n\u251c\u2500\u2500 docs\n\u2502 \u2514\u2500\u2500 history.md\n\u251c\u2500\u2500 user\n\u2502 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 views\n\u2502 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 requests # \u8fd9\u662f\u9700\u8981\u672c\u5730\u6253\u5305\u7684\u7b2c\u4e09\u65b9\u5305\n\u2502 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 pyproject.toml\n\u2514\u2500\u2500 tests\n
\u5f53\u591a\u4e2a\u76ee\u5f55\u6241\u5e73\u7684\u5206\u5e03\u5728\u9879\u76ee\u6839\u76ee\u5f55\u65f6\uff0c\u5b83\u4eec\u626e\u6f14\u8005\u4e0d\u540c\u7684\u529f\u80fd\uff0c\u5728\u5f00\u53d1\u4e0a\uff0c\u4f1a\u5e26\u4e86\u4e00\u5b9a\u7684\u6df7\u4e71\u3002\u800c\u4e14\u5728\u6253\u5305\u548c\u6d4b\u8bd5\u4e0a\u4e5f\u4f1a\u5e26\u6765\u4e00\u4e9b\u4e0d\u4fbf\u3002
\u5728\u6253\u5305\u4e0a\uff0c\u9700\u8981\u63d0\u4f9b\u66f4\u591a\u7684\u914d\u7f6e\u6392\u9664\u4e0d\u5fc5\u8981\u7684\u76ee\u5f55\uff0c\u5982 docs
\u6216\u8005\u5176\u4ed6\u4e0d\u9700\u8981\u6253\u5305\u4ec5\u9879\u76ee\u4e2d\u7684\u4e1c\u897f\u3002
\u5f53\u4f7f\u7528\u53ef\u7f16\u8f91\u5b89\u88c5\uff08 pip install -e .
\uff09 \u65f6\uff0c\u4f1a\u5c06\u9879\u76ee\u6839\u76ee\u5f55\u4e2d\u7684\u6240\u6709\u4e1c\u897f\u5b89\u88c5\u5230\u73af\u5883\u4e2d\uff0c\u5305\u62ec\u4e00\u4e9b\u4e0d\u9700\u8981\u7684\u3002
\u4f7f\u7528\u81ea\u52a8\u5316\u6d4b\u8bd5 tox
\u5de5\u5177\u65e0\u6cd5\u68c0\u6d4b\u5b89\u88c5\u4e4b\u540e\u7684\u95ee\u9898\uff0c\u56e0\u4e3a\u8fd9\u79cd\u76ee\u5f55\u73af\u5883\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\u73af\u5883\u4e2d\u7684\u5305\uff08\u9879\u76ee\u6839\u76ee\u5f55\u88ab\u6dfb\u52a0\u5230 sys.path
\u4e2d\u4e86\uff09\u3002
"},{"location":"guidelines/project_management/project_structure/#2-src","title":"2 src \u7ed3\u6784","text":"Pypa \u7ef4\u62a4\u7684\u793a\u4f8b\u9879\u76ee \u4e2d\u91c7\u7528\u4e86\u4e00\u79cd\u66f4\u63a8\u8350\u7684\u7ed3\u6784 src
\u7ed3\u6784\u3002
sampleproject\n\u251c\u2500\u2500 data\n\u251c\u2500\u2500 src\n| \u2514\u2500\u2500 sample\n| \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 pyproject.toml\n\u2514\u2500\u2500 tests\n
\u516d\u5e74\u524d\u7684\u8fd9\u7bc7\u6587\u7ae0 Packaging a python library \u5c31\u8be6\u7ec6\u9610\u8ff0\u4e86\u4f7f\u7528 src
\u7ed3\u6784\u6bd4\u7b80\u5355\u7ed3\u6784\u7684\u8bf8\u591a\u4f18\u70b9\u3002\u800c\u73b0\u5728\u4e5f\u9010\u6e10\u88ab\u793e\u533a\u4f5c\u4e3a\u4e00\u4e2a\u6807\u51c6\u9075\u5faa\u3002\u867d\u7136\u793e\u533a\u4e2d\u6709\u5927\u91cf\u8001\u7684\u9879\u76ee\u4f9d\u7136\u91c7\u7528\u7b80\u5355\u5e03\u5c40\uff0c\u4f46\u65b0\u9879\u76ee\u63a8\u8350\u4f7f\u7528 src
\u7ed3\u6784\u3002
\u5982\u4e0b\u9762\u8fd9\u4e2a\u793a\u4f8b\u9879\u76ee\u7ed3\u6784\uff1a
sampleproject\n\u251c\u2500\u2500 data\n\u2502 \u2514\u2500\u2500 user.json\n\u251c\u2500\u2500 docs\n\u2502 \u2514\u2500\u2500 history.md\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 src\n\u2502 \u251c\u2500\u2500 requests\n\u2502 \u2502 \u2514\u2500\u2500 __init__.py\n\u2502 \u2514\u2500\u2500 sample\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 user\n\u2502 \u2502 \u2514\u2500\u2500 __init__.py\n\u2502 \u2514\u2500\u2500 views\n\u2502 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 tests\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 user\n\u2502 \u2502 \u2514\u2500\u2500 __init__.py\n\u2502 \u2514\u2500\u2500 views\n\u2502 \u2514\u2500\u2500 __init__.py\n\u2514\u2500\u2500 tox.ini\n
\u9879\u76ee\u7684\u5305\u7ed3\u6784\u5f88\u6e05\u6670\uff0c\u5728\u73af\u5883\u4e2d\u53ea\u9700\u8981\u5f15\u5165 src
\u76ee\u5f55\uff0c\u5c31\u53ef\u4ee5\u8f7b\u677e\u5bfc\u5165\u9879\u76ee\u6e90\u4ee3\u7801\u3002\u901a\u8fc7 pip install -e .
\u53ef\u7f16\u8f91\u5b89\u88c5\uff0c\u4e5f\u53ea\u4f1a\u5b89\u88c5 src
\u4e2d\u7684\u5305\u3002\u7ba1\u7406\u8d77\u6765\u66f4\u52a0\u6e05\u6670\u3002
"},{"location":"guidelines/project_management/project_structure/#3","title":"3 \u5b9e\u8df5","text":"\u4e0b\u9762\u4ee5\u4e00\u4e2a\u7b80\u5355\u771f\u5b9e\u7684\u9879\u76ee\u6765\u6f14\u793a\u4f7f\u7528 src
\u7ec4\u7ec7\u9879\u76ee
"},{"location":"guidelines/project_management/project_structure/#31","title":"3.1 \u521b\u5efa\u9879\u76ee","text":"\u521b\u5efa\u9879\u76ee:
mkdir sampleproject\ncd sampleproject\n
\u521d\u59cb\u5316\u7248\u672c\u7ba1\u7406\uff1a
git init\n# \u5982\u679c\u6ca1\u6709\u5168\u5c40\u7528\u6237\u540d\u548c\u90ae\u7bb1\uff0c\u9700\u8981\u5148\u914d\u7f6e\ngit config user.email example@example.com\ngit config user.name example\n
\u521b\u5efa\u9879\u76ee\u81ea\u8ff0\u6587\u4ef6\uff1a
touch README.md\n
"},{"location":"guidelines/project_management/project_structure/#32","title":"3.2 \u7f16\u5199\u9879\u76ee\u6e90\u4ee3\u7801","text":"\u521b\u5efa\u9879\u76ee\u5305\uff1a
mkdir src/sample_project\ntouch src/sample_project/__init__.py\n
\u521d\u59cb\u5316\u7248\u672c\u53f7\uff1a
src/sample_project/__init__.py
__version__ = '0.1.0'\n
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add click\n
\u521b\u5efa\u547d\u4ee4\u5165\u53e3\u6587\u4ef6\uff1a
src/sample_project/cmdline.py
import click\n@click.command()\ndef main():\nclick.echo('Hello world!')\nif __name__ == \"__main__\":\nmain()\n
"},{"location":"guidelines/project_management/project_structure/#33","title":"3.3 \u7f16\u5199\u6d4b\u8bd5","text":"\u521b\u5efa\u6d4b\u8bd5\u76ee\u5f55\uff1a
mkdir -p tests/sample_project\ntouch tests/sample_project/__init__.py\n
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add -D pytest\n
\u521b\u5efa\u6d4b\u8bd5\u6587\u4ef6\uff1a
tests/sample_project/test_cmdline.py
from click.testing import CliRunner\nfrom sample_project import cmdline\ndef test_main():\nrunner = CliRunner()\nresult = runner.invoke(cmdline.main)\nassert 'Hello world!' in result.output\n
\u8fd0\u884c\u6d4b\u8bd5\uff1a
pip install -e . # \u4ee5\u53ef\u7f16\u8f91\u5b89\u88c5\u65b9\u5f0f\u5230\u73af\u5883\u4e2d\npytest\n
\u6d4b\u8bd5\u8fd0\u884c\u6210\u529f\uff0c\u8bf4\u660e\u529f\u80fd\u6b63\u786e
"},{"location":"guidelines/project_management/project_structure/#34","title":"3.4 \u521d\u59cb\u5316\u6253\u5305\u914d\u7f6e","text":"\u7f16\u5199\u6253\u5305\u914d\u7f6e\uff1a
pyproject.toml
[tool.poetry]\nname = \"sample_project\"\nversion = \"0.1.0\"\ndescription = \"Sample Project\"\nreadme = \"README.md\"\nauthors = [\"example <example@example.com>\"]\nlicense = \"MIT\"\nclassifiers = [\n\"Operating System :: OS Independent\",\n\"Programming Language :: Python :: 3.10\",\n]\n[tool.poetry.dependencies]\npython = \"^3.10\"\nclick = \"^8.1.3\"\n[tool.poetry.dev-dependencies]\npytest = \"^7.1.2\"\n[tool.poetry.plugins.\"scripts\"]\nsample_project = \"sample_project.cmdline:main\"\n[build-system]\nrequires = [\"poetry-core>=1.0.0\"]\nbuild-backend = \"poetry.core.masonry.api\"\n
\u6253\u5305\uff1a
poetry build\n
"},{"location":"guidelines/project_management/project_structure/#35","title":"3.5 \u603b\u7ed3","text":"\u81f3\u6b64\uff0c\u4e00\u4e2a\u9879\u76ee\u5f00\u53d1\u5b8c\u6210\uff0c\u5b8c\u6574\u9879\u76ee\u7ed3\u6784\u5982\u4e0b\uff1a
\u251c\u2500\u2500 dist\n\u2502 \u251c\u2500\u2500 sample_project-0.1.0.tar.gz \n| \u2514\u2500\u2500 sample_project-0.1.0-py3-none-any.whl\n\u251c\u2500\u2500 poetry.lock\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 src\n\u2502 \u2514\u2500\u2500 sample_project\n\u2502 \u251c\u2500\u2500 cmdline.py\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2514\u2500\u2500 tests\n \u251c\u2500\u2500 __init__.py\n \u2514\u2500\u2500 sample_project\n \u251c\u2500\u2500 __init__.py\n \u2514\u2500\u2500 test_cmdline.py\n
"},{"location":"guidelines/tutorial/develop/","title":"\u529f\u80fd\u5f00\u53d1","text":"\u5728\u672c\u7ae0\u8282\u5185\u5bb9\uff0c\u4f60\u5c06\u5b66\u4e60\u5230\u5982\u4e0b\u5185\u5bb9\uff1a
- \u8bbe\u8ba1 ETL \u4e09\u4e2a\u9636\u6bb5\u7684\u63a5\u53e3\u5e76\u505a\u9ed8\u8ba4\u5b9e\u73b0
- \u4f7f\u7528\u63d2\u4ef6\u5316\u673a\u5236\u6ce8\u518c\u548c\u53d1\u73b0\u63a5\u53e3\u5b9e\u73b0
- \u901a\u8fc7\u914d\u7f6e\u9009\u62e9\u4f7f\u7528\u7684\u5b9e\u73b0\u5185\u5bb9
- \u66f4\u65b0\u547d\u4ee4\u884c
\u6839\u636e\u524d\u9762\u7684\u7cfb\u7edf\u8bbe\u8ba1\uff0c ETL \u9879\u76ee\u603b\u5171\u6709\u4e09\u4e2a\u6838\u5fc3\u6a21\u5757\uff0c\u5206\u522b\u662f extractor
\u3001 transformer
\u548c loader
\u3002\u4e3a\u4e86 \u80fd\u8fd0\u884c\u903b\u8f91\uff0c\u8fd8\u9700\u8981\u4e00\u4e2a manage
\u6a21\u5757\u7528\u6765\u7f16\u6392\u4e09\u4e2a\u6a21\u5757\u7684\u903b\u8f91\u3002\u7136\u540e\u4f1a\u5728\u547d\u4ee4\u884c\u4e2d\u6ce8\u518c\u4e00\u4e2a\u5165\u53e3\u65b9\u6cd5\uff0c\u8c03\u7528 mange
\u7684\u903b\u8f91\u3002
"},{"location":"guidelines/tutorial/develop/#extractor","title":"extractor","text":"extractor
\u7684\u4f5c\u7528\u662f\u4ece\u6e90\u76ee\u6807\u63d0\u53d6\u6570\u636e\uff0c\u76ee\u6807\u53ef\u4ee5\u662f\u6587\u4ef6\u3001\u6570\u636e\u5e93\u3001\u6d88\u606f\u961f\u5217\u7b49\u3002\u8fd9\u5178\u578b\u662f\u4e00\u4e2a\u591a\u5b9e\u73b0\u7684\u60c5\u51b5\uff0c\u540c\u65f6\u4e5f \u4e3a\u4e86\u7edf\u4e00\u5176\u4ed6\u5f00\u53d1\u4eba\u5458\u7f16\u5199\u81ea\u5df1\u7684 extractor
\uff0c\u5c31\u9700\u8981\u5bf9 extractor
\u505a\u51fa\u4e00\u4e2a\u62bd\u8c61\u8bbe\u8ba1\u3002\u6211\u4eec\u4f7f\u7528 BaseExtractor
\u7c7b \u505a\u4e00\u4e2a\u62bd\u8c61\u57fa\u7c7b\u3002
"},{"location":"guidelines/tutorial/develop/#extractor_1","title":"extractor \u57fa\u7c7b","text":"\u521b\u5efa extractor
\u5305\uff0c\u5e76\u5728\u91cc\u9762\u65b0\u5efa\u4e00\u4e2a base.py
\u6587\u4ef6\uff0c\u6587\u4ef6\u5185\u5bb9\u5982\u4e0b\uff1a
\u6ce8\u610f\uff1aPython \u7684\u5305\u662f\u4e00\u4e2a\u6587\u4ef6\u5939\uff0c\u91cc\u9762\u5fc5\u987b\u5305\u542b\u4e00\u4e2a __init__.py
\u6587\u4ef6\u3002\u53ea\u6709\u4e00\u4e2a\u7a7a\u6587\u4ef6\u5939\uff0c\u4e0d\u662f\u5408\u6cd5\u7684 Python \u5305\u3002
src/example_etl/extractor/base.py
\"\"\"Base extractor.\"\"\"\nfrom typing import Iterable\nclass BaseExtractor:\n\"\"\"Base extractor\"\"\"\ndef __init__(self, settings):\nself.settings = settings\nself.setup()\ndef setup(self):\n\"\"\"Setup something when init extractor\"\"\"\ndef extract(self) -> Iterable[str]:\n\"\"\"Extract data.\"\"\"\nraise NotImplementedError()\ndef close(self):\n\"\"\"Close something.\"\"\"\ndef __enter__(self):\nreturn self\ndef __exit__(self, exc_type, exc_val, exc_tb):\nself.close()\n
BaseExtractor
\u6709\u4e00\u4e2a\u62bd\u8c61\u65b9\u6cd5 extract
\uff0c\u9700\u8981\u5b9e\u73b0\u65f6\uff0c\u7ee7\u627f\u8be5\u7c7b\uff0c\u5e76\u5b9e\u73b0\u8fd9\u4e2a\u65b9\u6cd5\u5373\u53ef\u3002 BaseExtractor
\u540c\u65f6\u9ed8\u8ba4\u5b9e\u73b0\u4e86 __enter__
\u548c __exit__
\u4e24\u4e2a\u65b9\u6cd5\uff0c\u76ee\u7684\u662f\u8ba9\u5b9e\u73b0\u7c7b\u53ef\u4ee5\u901a\u8fc7 with
\u5173\u952e\u5b57\u8c03\u7528\uff0c\u5e76\u81ea\u52a8\u7ba1\u7406 close
\u65b9\u6cd5\u3002\u8fd9\u5bf9\u4e8e\u6570\u636e\u5e93 \u8fde\u63a5\u7684\u5b9e\u73b0\u5f88\u6709\u5e2e\u52a9\u3002
BaseExtractor
\u63a5\u6536\u4e00\u4e2a settings
\u5bf9\u8c61\uff0c\u8fd9\u4e2a\u5bf9\u8c61\u5176\u5b9e\u5c31\u662f example_etl.config.settings
\u5bf9\u8c61\uff0c\u8fd9\u91cc\u901a\u8fc7\u8c03\u7528\u8005\u4f20\u9012\u3002
extract
\u7684\u8fd4\u56de\u503c\u662f\u4e00\u4e2a\u53ef\u8fed\u4ee3\u7684\u5bf9\u8c61\uff0c\u8fed\u4ee3\u5185\u5bb9\u4e3a str
\u3002
"},{"location":"guidelines/tutorial/develop/#extractor-file","title":"extractor \u7684 file \u5b9e\u73b0","text":"\u57fa\u4e8e BaseExtractor
\u505a\u4e00\u4e2a\u6587\u4ef6\u63d0\u53d6\u5176\u5b9e\u73b0\u3002
\u5728 extractor
\u5305\u4e2d\u521b\u5efa\u6587\u4ef6 file.py
\uff0c\u5e76\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
src/example_etl/extractor/file.py
\"\"\"\nFile extractor\nextract data from file.\n\"\"\"\nimport logging\nfrom typing import Iterable\nfrom example_etl.constants import DEFAULT_ENCODING\nfrom example_etl.extractor.base import BaseExtractor\nlogger = logging.getLogger(__name__)\nclass FileExtractor(BaseExtractor):\n\"\"\"File extractor\"\"\"\ndef extract(self) -> Iterable[str]:\n\"\"\"Open and read file\"\"\"\nextractor_path = self.settings.FILE_EXTRACTOR_PATH\nlogger.info('Extract data from %s', extractor_path)\nwith open(extractor_path, 'r', encoding=DEFAULT_ENCODING) as file:\nfor i in file:\nyield i\n
\u5728\u5b9e\u73b0\u7684 extract
\u65b9\u6cd5\u4e2d\uff0c\u4ece FileExtractor.settings
\u5bf9\u8c61\u4e2d\u83b7\u53d6\u4e86\u4e00\u4e2a FILE_EXTRACTOR_PATH
\u53d8\u91cf\uff0c\u8fd9\u4e2a\u53d8\u91cf\u662f\u4ece \u914d\u7f6e\u6587\u4ef6\u4e2d\u83b7\u53d6\u7684\u3002\u56e0\u6b64\u9700\u8981\u5728\u914d\u7f6e\u6587\u4ef6 src/example_etl/config/settings.yml
\u4e2d\u589e\u52a0 file_extractor_path: /tmp/foo.txt
\u7684\u503c:
verbose: false\ndebug: false\nloglevel: warning\nlogpath: /tmp/example_etl\nfile_extractor_path: /tmp/foo.txt\n
extract
\u65b9\u6cd5\u4e2d\u76f4\u63a5\u53ef\u4ee5\u901a\u8fc7\u8fd4\u56de\u8fed\u4ee3\u5bf9\u8c61\u7684\u65b9\u5f0f\u81ea\u52a8\u7ba1\u7406\u6587\u4ef6\u8bfb\u5bf9\u8c61\u3002
\u6ce8\u610f\u4e00\u70b9\u7684\u662f\uff0c\u6253\u5f00\u6587\u4ef6\u65f6\u4f7f\u7528\u4e86\u9ed8\u8ba4\u5b57\u7b26\u96c6\u7684\u5e38\u91cf\u503c DEFAULT_ENCODING
\u3002\u6240\u4ee5\u8fd8\u8981\u521b\u5efa src/example_etl/constants.py
\uff0c \u5e76\u52a0\u5165\u5982\u4e0b\u5185\u5bb9\uff1a
\"\"\"Constants\"\"\"\nDEFAULT_ENCODING = 'utf-8'\n
file.py
\u6587\u4ef6\u4e2d\u8fd8\u521b\u5efa\u4e86\u4e00\u4e2a\u5168\u5c40 logger
\u5bf9\u8c61\uff0c\u5bf9\u8c61\u540d\u79f0\u4f7f\u7528\u4e86 __name__
\u83b7\u53d6\u8be5\u5305\u7684\u540d\u79f0\u3002\u5728\u6253\u5370\u65e5\u5fd7\u65f6\uff0c\u663e\u793a\u7684\u5305\u540d \u4e3a example_etl.extractor.file
\u3002\u5728 extract
\u65b9\u6cd5\u4e2d\u6253\u5370\u4e00\u6761\u6267\u884c\u8bb0\u5f55\u3002
"},{"location":"guidelines/tutorial/develop/#transformer","title":"transformer","text":"transformer
\u6a21\u5757\u7684\u529f\u80fd\u662f\u8f6c\u6362\u8bfb\u53d6\u5230\u7684\u903b\u8f91\u3002\u5728\u8fd9\u4e2a\u8fc7\u7a0b\u4e2d\uff0c\u901a\u8fc7\u63a5\u6536 extractor
\u8bfb\u53d6\u5230\u7684\u6587\u672c\uff0c\u5904\u7406\u540e\u4f20\u9012\u7ed9 loader
\u3002
\u6b64\u8fc7\u7a0b\u53ef\u4ee5\u6267\u884c\u53bb\u9664\u7a7a\u683c\u3001\u5220\u51cf\u5b57\u7b26\u7b49\u64cd\u4f5c\u3002
\u4e3a\u4e86\u65b9\u4fbf\u5b9e\u73b0\uff0c\u521b\u5efa\u4e00\u4e2a\u57fa\u7c7b BaseTransformer
\u3002
"},{"location":"guidelines/tutorial/develop/#transformer_1","title":"transformer \u57fa\u7c7b","text":"\u9996\u5148\u521b\u5efa transformer
\u5305\uff0c\u7136\u540e\u65b0\u5efa BaseTransformer.py
\u6587\u4ef6\uff1a
src/example_etl/transformer/base.py
\"\"\"Base transformer\"\"\"\nclass BaseTransformer:\n\"\"\"Base transformer\"\"\"\ndef __init__(self, settings):\nself.settings = settings\ndef transform(self, data: str) -> str:\n\"\"\"Transform data\"\"\"\nraise NotImplementedError()\n
BaseTransformer
\u540c\u6837\u63a5\u6536\u4e00\u4e2a settings
\u5bf9\u8c61\u3002\u5176\u62bd\u8c61\u65b9\u6cd5 transform
\u63a5\u6536\u4e00\u4e2a\u5b57\u7b26\u4e32\u7c7b\u578b\u7684 data
\u5e76\u8fd4\u56de str
\u7c7b\u578b \u7684\u6570\u636e\u3002
"},{"location":"guidelines/tutorial/develop/#tansformer","title":"tansformer \u53bb\u7a7a\u683c\u5b9e\u73b0","text":"BaseTransformer
\u5b9e\u73b0\u4e00\u4e2a\u53ef\u4ee5\u5220\u9664\u6587\u672c\u524d\u540e\u7a7a\u683c\u7684\u5b9e\u73b0 StripTransformer
\uff1a
\u521b\u5efa strip.py
src/example_etl/transformer/strip.py
\"\"\"Transform data and remove blank of data star and end.\"\"\"\nimport logging\nfrom example_etl.transformer.base import BaseTransformer\nlogger = logging.getLogger(__name__)\nclass StripTransformer(BaseTransformer):\n\"\"\"\n Transform data and remove blank of data star and end.\n \"\"\"\ndef transform(self, data: str) -> str:\n\"\"\"Remove blank of data star and end.\"\"\"\nlogger.debug('Strip data: \"%s\"', data)\nreturn data.strip()\n
StripTransformer
\u5b9e\u73b0\u662f\u901a\u8fc7\u5b57\u7b26\u4e32\u65b9\u6cd5 strip
\u5220\u9664\u63a5\u6536\u5230\u5b57\u7b26\u4e32\u6570\u636e\u524d\u540e\u7a7a\u683c\uff0c\u5e76\u8fd4\u56de\u7ed3\u679c\u3002
strip.py
\u6587\u4ef6\u4e2d\u540c\u6837\u521d\u59cb\u5316\u4e00\u4e2a logger
\u5bf9\u8c61\uff0c\u5728 transform
\u4e2d\u6253\u5370\u4e00\u6761\u8bb0\u5f55\u3002\u9700\u8981\u6ce8\u610f\u7684\u662f\uff0c\u8fd9\u91cc\u4f7f\u7528\u4e86 debug
\u65b9\u6cd5\uff0c\u6253\u5370\u7684\u65e5\u5fd7\u4e3a DEBUG
\u7ea7\u522b\u3002\u5f53\u65e5\u5fd7\u7ea7\u522b\u8bbe\u7f6e\u5728 INFO
\u65f6\uff0c\u8fd9\u91cc\u7684\u6267\u884c\u8bb0\u5f55\u662f\u4e0d\u4f1a\u6253\u5370\u7684\u3002\u5bf9\u4e8e \u5173\u6ce8\u4f4e\u7684\u8bb0\u5f55\uff0c\u53ef\u4ee5\u4f7f\u7528 DEBUG
\u3002
"},{"location":"guidelines/tutorial/develop/#loader","title":"loader","text":"loader
\u6a21\u5757\u7528\u6765\u5c06 transformer
\u8f6c\u6362\u7684\u6570\u636e\u52a0\u8f7d\u5230\u76ee\u6807\u4f4d\u7f6e\u3002\u76ee\u6807\u53ef\u4ee5\u662f\u6587\u4ef6\u3001\u6570\u636e\u5e93\u3001\u6d88\u606f\u961f\u5217\u7b49\u3002
\u540c\u6837\u7684\uff0c\u5bf9 loader
\u505a\u51fa\u62bd\u8c61\u7c7b BaseLoader
\u3002
"},{"location":"guidelines/tutorial/develop/#loader_1","title":"loader \u57fa\u7c7b","text":"\u5728 loader
\u5305\u4e2d\u521b\u5efa base.py
\u6587\u4ef6\uff0c\u6587\u4ef6\u5185\u5bb9\u5982\u4e0b\uff1a
src/example_etl/loader/base.py
\"\"\"Base loader\"\"\"\nclass BaseLoader:\n\"\"\"Base loader\"\"\"\ndef __init__(self, settings):\nself.settings = settings\nself.setup()\ndef setup(self):\n\"\"\"Setup something when init loader.\"\"\"\ndef load(self, data: str):\n\"\"\"Write data to loader\"\"\"\nraise NotImplementedError()\ndef close(self):\n\"\"\"Close something\"\"\"\ndef __exit__(self, exc_type, exc_val, exc_tb):\nself.close()\ndef __enter__(self):\nreturn self\n
\u5728 BaseLoader
\u4e2d\u6709\u4e00\u4e2a load
\u7684\u62bd\u8c61\u65b9\u6cd5\uff0c\u7528\u6765\u7ed9\u7ee7\u627f\u7c7b\u5b9e\u73b0\u3002\u9ed8\u8ba4\u7684 setup
\u65b9\u6cd5\u53ef\u4ee5\u5728\u521d\u59cb\u5316 \u65f6\u505a\u4e00\u4e9b\u903b\u8f91\uff0c\u6bd4\u5982\u6253\u5f00\u6587\u4ef6\u3001\u521b\u5efa\u6570\u636e\u5e93\u8fde\u63a5\u7b49\u3002 close
\u7528\u6765\u5173\u95ed\u8fd9\u4e9b\u903b\u8f91\u3002 __exit__
\u548c __enter__
\u53ef\u4ee5 \u8ba9 BaseLoader
\u901a\u8fc7 with
\u5173\u952e\u5b57\u4f7f\u7528\u3002
\u9700\u8981\u6ce8\u610f\u7684\u662f\uff0c BaseLoader
\u7684 load
\u65b9\u6cd5\u4e2d\u4e0d\u80fd\u6709\u521b\u5efa\u8fde\u63a5\u5bf9\u8c61\u7684\u903b\u8f91\uff0c\u56e0\u4e3a load
\u4f1a\u51fa\u73b0\u5728\u5faa\u73af\u4e2d\u7684\u3002
"},{"location":"guidelines/tutorial/develop/#loader-file","title":"loader \u7684 file \u5b9e\u73b0","text":"\u9ed8\u8ba4\u5b9e\u73b0\u4e00\u4e2a\u5c06\u6570\u636e\u5199\u5165\u6587\u4ef6\u7684\u5b9e\u73b0\u7c7b FileLoader
\u3002
\u5728 loader
\u5305\u4e2d\u521b\u5efa file.py
\u6587\u4ef6\uff0c\u6587\u4ef6\u5185\u5bb9\u5982\u4e0b\uff1a
src/example_etl/loader/file.py
\"\"\"\nFile loader\nWrite data to loader file.\n\"\"\"\nimport logging\nfrom example_etl.constants import DEFAULT_ENCODING\nfrom example_etl.loader.base import BaseLoader\nlogger = logging.getLogger(__name__)\nclass FileLoader(BaseLoader):\n\"\"\"\n File loader\n \"\"\"\nfile = None\ndef setup(self):\n\"\"\"Open a file when init loader.\"\"\"\nloader_path = self.settings.FILE_LOADER_PATH\nlogger.info('Write data to %s', loader_path)\nself.file = open(loader_path, 'w', encoding=DEFAULT_ENCODING) # pylint: disable=consider-using-with\ndef load(self, data: str):\n\"\"\"Write data to a file.\"\"\"\nself.file.write(data)\nself.file.flush()\ndef close(self):\n\"\"\"Close file object when task done.\"\"\"\nself.file.close()\n
\u8be5\u7c7b\u5728 setup
\u65b9\u6cd5\u4e2d\u6253\u5f00\u6587\u4ef6\u5bf9\u8c61\uff0c\u5e76\u5728 close
\u65b9\u6cd5\u4e2d\u5173\u95ed\u6587\u4ef6\u3002 load
\u65b9\u6cd5\u4f1a\u5199\u5165\u6570\u636e\uff0c\u5e76\u7acb\u5373 \u5c06\u5185\u5bb9\u5237\u65b0\u5230\u6587\u4ef6\u4e2d\u3002
\u521d\u59cb\u5316 FileLoader
\u65f6\u9700\u8981\u901a\u8fc7\u914d\u7f6e\u8bfb\u53d6\u6587\u4ef6\uff0c\u5e76\u5199\u5165\u3002\u6240\u4ee5\u9700\u8981\u5728\u914d\u7f6e\u6587\u4ef6 src/example_etl/config/settings.yml
\u4e2d\u589e\u52a0\u914d\u7f6e file_loader_path: /tmp/bar.txt
:
verbose: false\ndebug: false\nloglevel: warning\nlogpath: /tmp/example_etl\nfile_extractor_path: /tmp/foo.txt\nfile_loader_path: /tmp/bar.txt\n
"},{"location":"guidelines/tutorial/develop/#_2","title":"\u63d2\u4ef6\u6ce8\u518c","text":"\u4e09\u4e2a\u57fa\u7840\u6a21\u5757\u4f7f\u7528\u63d2\u4ef6\u673a\u5236\u81ea\u52a8\u53d1\u73b0\uff0c\u5e76\u901a\u8fc7\u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u9700\u8981\u4f7f\u7528\u7684\u5177\u4f53\u5b9e\u73b0\u3002\u5728\u540e\u7eed\u4f7f\u7528\u4e2d\uff0c\u57fa\u4e8e\u62bd\u8c61\u57fa\u7c7b \u5f00\u53d1\u7684\u5176\u4ed6\u5b9e\u73b0\u4e5f\u662f\u901a\u8fc7\u8fd9\u79cd\u6765\u505a\u3002
\u5b89\u88c5\u63d2\u4ef6\u6846\u67b6 stevedore \uff1a
poetry add stevedore\n
"},{"location":"guidelines/tutorial/develop/#_3","title":"\u6ce8\u518c\u63d2\u4ef6","text":"\u5c06\u4e0a\u8ff0\u5b9e\u73b0\u7684\u4e09\u4e2a\u7c7b\u6ce8\u518c\u5230\u547d\u540d\u7a7a\u95f4\u4e2d\u3002
\u7f16\u8f91 pyproject.toml
\u6587\u4ef6\uff0c\u589e\u52a0\u5982\u4e0b\u5185\u5bb9\uff1a
[tool.poetry]\nname = \"example_etl\"\nversion = \"0.1.0\"\ndescription = \"This is my first etl project.\"\nreadme = \"README.md\"\nauthors = [\"test <test@example.com>\"]\nlicense = \"MIT\"\nclassifiers = [\n\"Operating System :: OS Independent\",\n\"Programming Language :: Python :: 3.10\",\n]\n[tool.poetry.dependencies]\npython = \"^3.10\"\ndynaconf = \"^3.1.12\"\nclick = \"^8.1.3\"\n[tool.poetry.group.dev.dependencies]\npylint = \"^2.17.4\"\nisort = \"^5.12.0\"\npytest = \"^7.3.1\"\ntox = \"^4.5.2\"\nmkdocs = \"^1.4.3\"\nmkdocs-material = \"^8.5.11\"\npytest-pylint = \"^0.19.0\"\npre-commit = \"^3.3.2\"\n[tool.poetry.plugins.\"example_etl.extractor\"]\nfile = \"example_etl.extractor.file:FileExtractor\"\n[tool.poetry.plugins.\"example_etl.loader\"]\nfile = \"example_etl.loader.file:FileLoader\"\n[tool.poetry.plugins.\"example_etl.transformer\"]\nstrip = \"example_etl.transformer.strip:StripTransformer\"\n[tool.poetry.scripts]\nexample_etl = \"example_etl.cmdline:main\"\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n[tool.pytest.ini_options]\ntestpaths = \"tests\"\npython_files = \"tests.py test_*.py *_tests.py\"\n[tool.pylint.design]\nmax-line-length = 120\n
\u8fd9\u4e48\u505a\u7684\u76ee\u7684\u662f\u5c06 FileExtractor
\u3001 FileLoader
\u3001 StripTransformer
\u5206\u522b\u6ce8\u518c\u5230 entry_points
\u4e2d\uff0c \u7136\u540e\u5728\u7a0b\u5e8f\u4e2d\u4f7f\u7528 import.metadata
\u6839\u636e\u540d\u79f0\u7a7a\u95f4\u67e5\u627e\u3002\u800c stevedore
\u5219\u662f\u5c01\u88c5\u4e86\u67e5\u627e\u7684\u590d\u6742\u903b\u8f91\uff0c\u8ba9\u4f7f\u7528 \u66f4\u7b80\u5355\u3002
\u5c06\u9879\u76ee\u4ee5\u53ef\u7f16\u8f91\u6a21\u5f0f\u5b89\u88c5\u5230\u5f53\u524d\u73af\u5883\uff1a
poetry install\n
"},{"location":"guidelines/tutorial/develop/#_4","title":"\u7ba1\u7406\u6a21\u5757","text":"manage
\u6a21\u5757\u662f\u7528\u6765\u7f16\u6392\u524d\u9762\u4e09\u4e2a\u6a21\u5757\u7684\u903b\u8f91\u3002
\u521b\u5efa src/example_etl/manage.py
\uff0c\u6587\u4ef6\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Manage\"\"\"\nimport logging\nfrom typing import Type\nfrom stevedore import ExtensionManager\nfrom example_etl.config import settings\nfrom example_etl.exceptions import PluginNotFoundError\nfrom example_etl.extractor.base import BaseExtractor\nfrom example_etl.loader.base import BaseLoader\nfrom example_etl.transformer.base import BaseTransformer\nlogger = logging.getLogger(__name__)\nclass Manage:\n\"\"\"Manager\"\"\"\ndef __init__(self):\nself.extractor_kls: Type[BaseExtractor] = get_extension(\n'example_etl.extractor',\nsettings.EXTRACTOR_NAME,\n)\nself.loader_kls: Type[BaseLoader] = get_extension(\n'example_etl.loader',\nsettings.LOADER_NAME,\n)\nself.transformer_kls: Type[BaseTransformer] = get_extension(\n'example_etl.transformer',\nsettings.TRANSFORMER_NAME,\n)\nself.transformer: BaseTransformer = self.transformer_kls(settings)\ndef run(self):\n\"\"\"Run manage\"\"\"\nwith self.extractor_kls(settings) as extractor:\nwith self.loader_kls(settings) as loader:\nself.transform(extractor, loader)\nlogger.info('Exit example_etl.')\ndef transform(self, extractor: BaseExtractor, loader: BaseLoader):\n\"\"\"Transform data from extractor to loader.\"\"\"\nlogger.info('Start transformer data ......')\nfor i in extractor.extract():\ndata = self.transformer.transform(i)\nloader.load(data)\nlogger.info('Data processed.')\ndef get_extension(namespace: str, name: str):\n\"\"\"Get extension by name from namespace.\"\"\"\nextension_manager = ExtensionManager(namespace=namespace, invoke_on_load=False)\nfor ext in extension_manager.extensions:\nif ext.name == name:\nlogger.info('Load plugin: %s in namespace \"%s\"', ext.plugin, namespace)\nreturn ext.plugin\nraise PluginNotFoundError(namespace=namespace, name=name)\n
\u5728 manage
\u4e2d\u5c01\u88c5\u4e86\u4e00\u4e2a\u901a\u8fc7\u540d\u79f0\u7a7a\u95f4\u548c\u540d\u79f0\u4e24\u4e2a\u53c2\u6570\u67e5\u627e\u63d2\u4ef6\u7684\u65b9\u6cd5 get_extension
\u3002\u5f53\u627e\u4e0d\u5230\u5bf9\u5e94 \u7684\u63d2\u4ef6\u65f6\uff0c\u4f1a\u629b\u51fa PluginNotFoundError
\u5f02\u5e38\u3002
\u5728 Manage
\u7c7b\u7684 __init__
\u65b9\u6cd5\u4e2d\uff0c\u5206\u522b\u4ece\u4e09\u4e2a\u540d\u79f0\u7a7a\u95f4\u67e5\u627e\u5b9e\u73b0\u7c7b\uff0c\u67e5\u627e\u7684\u540d\u5b57\u5219\u662f\u901a\u8fc7\u914d\u7f6e\u6587\u4ef6\u7684 \u53d8\u91cf\u83b7\u53d6\u7684\uff0c\u8fd9\u6837\u5c31\u53ef\u4ee5\u901a\u8fc7\u914d\u7f6e\u7075\u6d3b\u5730\u8c03\u6574\u9700\u8981\u4f7f\u7528\u7684\u5177\u4f53\u5b9e\u73b0\u4e86\u3002
run
\u65b9\u6cd5\u4e2d\u4f7f\u7528 with
\u5173\u952e\u5b57\u5206\u522b\u521d\u59cb\u5316 extractor
\u548c loader
\uff0c\u5728\u903b\u8f91\u7ed3\u675f\u65f6\uff0c\u53ef\u4ee5\u81ea\u52a8\u7ba1\u7406 \u5728 close
\u4e2d\u5173\u95ed\u7684\u5bf9\u8c61\u3002
transform
\u65b9\u6cd5\u4e2d\u8c03\u7528 extractor.extract
\u65b9\u6cd5\u904d\u5386\u8bfb\u53d6\u7684\u6570\u636e\uff0c\u5e76\u5728\u8f6c\u6362\u540e\u5c06\u6570\u636e\u901a\u8fc7 loader.load
\u5199\u5165 \u76ee\u6807\u4f4d\u7f6e\u3002
\u5728\u4f7f\u7528 Manage
\u7684\u65f6\u5019\uff0c\u9700\u8981\u4ece\u914d\u7f6e\u4e2d\u8bfb\u53d6\u4e09\u4e2a\u5177\u4f53\u5b9e\u73b0\uff0c\u6240\u4ee5\u9700\u8981\u5728\u914d\u7f6e\u6587\u4ef6 src/example_etl/config/settings.yml
\u4e2d\u589e\u52a0\u5982\u4e0b\u53d8\u91cf\uff1a
verbose: false\ndebug: false\nloglevel: warning\nlogpath: /tmp/example_etl\nfile_extractor_path: /tmp/foo.txt\nfile_loader_path: /tmp/bar.txt\nextractor_name: file\nloader_name: file\ntransformer_name: strip\n
"},{"location":"guidelines/tutorial/develop/#_5","title":"\u5f02\u5e38\u5904\u7406","text":"\u5728\u4f7f\u7528\u5f02\u5e38\u7684\u65f6\u5019\uff0c\u5efa\u8bae\u521b\u5efa\u4e00\u4e2a\u9879\u76ee\u7ea7\u522b\u7684\u5f02\u5e38\u7d2f\uff0c\u7528\u6765\u5b9a\u4e49\u5f53\u524d\u9879\u76ee\u7684\u9876\u7ea7\u5f02\u5e38\u3002\u9879\u76ee\u5185\u90e8\u7684\u5176\u4ed6\u5f02\u5e38\u90fd \u9700\u8981\u57fa\u4e8e\u9879\u76ee\u9876\u7ea7\u5f02\u5e38\u5b9e\u73b0\u3002\u8fd9\u4e48\u505a\u7684\u4e00\u4e2a\u597d\u5904\u662f\u5f53\u4f60\u7684\u9879\u76ee\u88ab\u522b\u4eba\u5f15\u7528\u65f6\uff0c\u8c03\u7528\u65b9\u53ef\u4ee5\u901a\u8fc7\u6355\u83b7\u9879\u76ee\u9876\u7ea7 \u5f02\u5e38\uff0c\u6765\u7edf\u4e00\u5904\u7406\u9879\u76ee\u7684\u6240\u6709\u5f02\u5e38\u3002
\u521b\u5efa\u4e00\u4e2a src/example_etl/exceptions.py
\u6587\u4ef6\uff0c\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Exception\"\"\"\nclass EtlError(Exception):\n\"\"\"Etl error\"\"\"\nclass PluginNotFoundError(EtlError):\n\"\"\"PluginNotFoundError\"\"\"\ndef __init__(self, namespace: str, name: str):\nsuper().__init__()\nself._namespace = namespace\nself._name = name\ndef __repr__(self):\nreturn f'Can not found \"{self._name}\" plugin in {self._namespace}'\ndef __str__(self):\nreturn self.__repr__()\n
\u5728 exceptions.py
\u6587\u4ef6\u4e2d\u9996\u5148\u521b\u5efa\u4e86\u4e00\u4e2a\u5168\u5c40\u5f02\u5e38\u7c7b EtlError
\uff0c PluginNotFoundError
\u5f02\u5e38\u7ee7\u627f\u5b83\u3002 \u5f53\u9700\u8981\u6355\u83b7\u6240\u4ee5\u9879\u76ee\u5f02\u5e38\u65f6\uff0c\u53ef\u4ee5\u901a\u8fc7 EtlError
\u6355\u83b7\u3002
"},{"location":"guidelines/tutorial/develop/#_6","title":"\u589e\u52a0\u547d\u4ee4\u884c\u8c03\u7528","text":"\u7f16\u8f91 src/example_etl/cmdline.py
\u6587\u4ef6\uff0c\u4fee\u6539 rum
\u65b9\u6cd5\uff0c\u4fee\u6539\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Command line\"\"\"\nimport click\nfrom click import Context\nfrom example_etl import __version__\nfrom example_etl.config import settings\nfrom example_etl.log import init_log\nfrom example_etl.manage import Manage\n@click.group(invoke_without_command=True)\n@click.pass_context\n@click.option(\n'-V',\n'--version',\nis_flag=True,\nhelp='Show version and exit.'\n) # If it's true, it will override `settings.VERBOSE`\n@click.option('-v', '--verbose', is_flag=True, help='Show more info.')\n@click.option(\n'--debug',\nis_flag=True,\nhelp='Enable debug.'\n) # If it's true, it will override `settings.DEBUG`\ndef main(ctx: Context, version: str, verbose: bool, debug: bool):\n\"\"\"Main commands\"\"\"\nif version:\nclick.echo(__version__)\nelif ctx.invoked_subcommand is None:\nclick.echo(ctx.get_help())\nelse:\nif verbose:\nsettings.set('VERBOSE', True)\nif debug:\nsettings.set('DEBUG', True)\n@main.command()\ndef run():\n\"\"\"Run command\"\"\"\ninit_log()\nmanage = Manage()\nmanage.run()\n
\u5728\u4f7f\u7528\u547d\u4ee4 example_etl
\u8c03\u7528\u65f6\uff0c\u53ef\u4ee5\u901a\u8fc7\u4f20\u9012 run
\u6307\u4ee4\u8fd0\u884c\u3002
"},{"location":"guidelines/tutorial/develop/#_7","title":"\u68c0\u67e5\u4ee3\u7801","text":"\u7f16\u7801\u5b8c\u6210\u540e\uff0c\u5efa\u8bae\u901a\u8fc7 isort
\u68c0\u67e5\u5bfc\u5305\u98ce\u683c\uff0c\u4f7f\u7528 pylint
\u68c0\u67e5\u4ee3\u7801\u7684\u8bed\u6cd5\u548c\u7f16\u7801\u98ce\u683c\u3002
\u8fd0\u884c isort
\uff1a
$ isort .\nSkipped 1 files\n
\u8fd0\u884c pylint
\uff1a
$ pylint src tests\n************* Module example_etl.transformer.strip\nsrc/example_etl/transformer/strip.py:9:0: R0903: Too few public methods (1/2) (too-few-public-methods)\n************* Module example_etl.transformer.base\nsrc/example_etl/transformer/base.py:4:0: R0903: Too few public methods (1/2) (too-few-public-methods)\n-------------------------------------------------------------------\nYour code has been rated at 9.89/10 (previous run: 10.00/10, -0.11)\n
\u53ef\u4ee5\u770b\u5230\u6839\u636e pylint
\u7684\u9ed8\u8ba4\u8bed\u6cd5\u89c4\u8303\uff0c\u6211\u4eec\u6709\u4e24\u4e2a\u65b9\u6cd5\u4e0d\u7b26\u5408\u3002\u4f46\u6839\u636e\u5b9e\u9645\u60c5\u51b5\u6211\u4eec\u7684\u5b9e\u73b0\u662f\u6ca1\u6709\u95ee\u9898\u7684\uff0c\u6240\u4ee5\u6211\u4eec\u9700\u8981\u8c03\u6574 pylint
\u7684\u89c4\u5219\u3002
\u7f16\u8f91 src/example_etl/transformer/base.py
\uff0c\u8c03\u6574\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Base transformer\"\"\"\n# pylint: disable=too-few-public-methods\nclass BaseTransformer:\n\"\"\"Base transformer\"\"\"\ndef __init__(self, settings):\nself.settings = settings\ndef transform(self, data: str) -> str:\n\"\"\"Transform data\"\"\"\nraise NotImplementedError()\n
\u7f16\u8f91 src/example_etl/transformer/strip.py
\uff0c\u8c03\u6574\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Transform data and remove blank of data star and end.\"\"\"\nimport logging\nfrom example_etl.transformer.base import BaseTransformer\nlogger = logging.getLogger(__name__)\n# pylint: disable=too-few-public-methods\nclass StripTransformer(BaseTransformer):\n\"\"\"\n Transform data and remove blank of data star and end.\n \"\"\"\ndef transform(self, data: str) -> str:\n\"\"\"Remove blank of data star and end.\"\"\"\nlogger.debug('Strip data: \"%s\"', data)\nreturn data.strip()\n
\u4e0a\u9762\u4e24\u5904\u8c03\u6574\uff0c\u662f\u4f7f\u7528\u4e86 pylint \u7684\u89c4\u5219\u7981\u7528\u529f\u80fd\uff0c\u5728\u8fd9\u4e24\u4e2a\u6a21\u5757\u4e0a\uff0c\u6291\u5236 pylint \u7684 too-few-public-methods
\u89c4\u5219\u3002
\u6b64\u65f6\u518d\u6b21\u8fd0\u884c pylint
\u68c0\u67e5\u4ee3\u7801\uff1a
$ pylint src tests\n\n-------------------------------------------------------------------\nYour code has been rated at 10.00/10 (previous run: 9.89/10, +0.11)\n
\u53ef\u4ee5\u770b\u5230\u4ee3\u7801\u90fd\u6b63\u5e38\u4e86\u3002\u8fd9\u662f\u7b26\u5408\u6211\u4eec\u7684\u9884\u671f\u7684\u3002
"},{"location":"guidelines/tutorial/develop/#_8","title":"\u63d0\u4ea4\u4ee3\u7801","text":"\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u901a\u8fc7\u62bd\u8c61 ETL \u903b\u8f91\u4ee3\u7801\uff0c\u5e76\u6839\u636e\u5177\u4f53\u4e1a\u52a1\u505a\u4e00\u4e2a\u5b9e\u73b0\uff0c\u7136\u540e\u5c06\u5b9e\u73b0\u6ce8\u518c\u5230\u73af\u5883\u4e2d\uff0c\u5e76\u6839\u636e\u914d\u7f6e\u8c03\u7528\u5177\u4f53\u5b9e\u73b0\u3002
\u6b64\u65f6\u9879\u76ee\u7ed3\u6784\u5982\u4e0b\uff1a
example_etl\n\u251c\u2500\u2500 .editorconfig\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 .pre-commit-config.yaml\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 all.log\n\u251c\u2500\u2500 docs\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 development.md\n\u251c\u2500\u2500 poetry.lock\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 src\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 example_etl\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 cmdline.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 config\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 settings.yml\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 constants.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 exceptions.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 extractor\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 base.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 file.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 loader\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 base.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 file.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 log.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 manage.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 transformer\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 base.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 strip.py\n\u251c\u2500\u2500 tests\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 conftest.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 settings.yml\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 test_cmdline.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 test_log.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 test_version.py\n\u2514\u2500\u2500 tox.ini\n
\u63d0\u53ca\u672c\u6b21\u529f\u80fd\uff1a
git add .\ngit commit -m \"feat: add etl logic.\"\n
"},{"location":"guidelines/tutorial/init_project/","title":"\u521d\u59cb\u5316\u9879\u76ee","text":"\u5728\u672c\u7ae0\u8282\uff0c\u4f60\u8bb2\u5b66\u4e60\u5230\u4e00\u4e0b\u5185\u5bb9\uff1a
- \u4f7f\u7528 cookiecutter \u521d\u59cb\u5316\u4e00\u4e2a\u9879\u76ee\u7ed3\u6784
- \u67e5\u770b\u521d\u59cb\u5316\u9879\u76ee\u91cc\u9762\u7684\u4e3b\u8981\u6587\u4ef6\u5185\u5bb9
- \u5bf9\u9879\u76ee\u8fdb\u884c git \u521d\u59cb\u5316\u548c\u5185\u5bb9\u63d0\u4ea4
- \u4f7f\u7528 poetry \u521d\u59cb\u5316\u9879\u76ee\u7684 python \u73af\u5883
- \u4f7f\u7528 tox \u81ea\u52a8\u5316\u6d4b\u8bd5\u9879\u76ee\uff0c\u68c0\u67e5\u521d\u59cb\u5316\u7684\u9879\u76ee\u6709\u6ca1\u6709\u95ee\u9898
\u521d\u59cb\u5316\u9879\u76ee\u65f6\uff0c\u4f7f\u7528 cookiecutter \u52a0\u8f7d \u9879\u76ee\u6a21\u677f \u521b\u5efa\u3002 \u901a\u8fc7\u4ea4\u4e92\u64cd\u4f5c\uff0c\u53ef\u4ee5\u9009\u62e9\u4f7f\u7528\u7684\u529f\u80fd\u3002
"},{"location":"guidelines/tutorial/init_project/#_2","title":"\u521b\u5efa\u9879\u76ee\u9aa8\u67b6","text":"\u5728\u7ec8\u7aef\u8fd0\u884c\u547d\u4ee4\uff1a
cookiecutter https://github.com/pyloong/cookiecutter-pythonic-project\n
\u7136\u540e\u6839\u636e\u4ea4\u4e92\u63d0\u793a\uff0c\u9009\u62e9\u9700\u8981\u7684\u5185\u5bb9\u3002\u6700\u7ec8\u8f93\u5165\u5982\u4e0b\uff1a
\u276f cookiecutter https://github.com/pyloong/cookiecutter-pythonic-project\nproject_name [My Project]: example-etl\nproject_slug [example_etl]: \nproject_description [My Awesome Project!]: This is my first etl project.\nauthor_name [Author]: test\nauthor_email [test@example.com]: test@example.com\nversion [0.1.0]: \nSelect python_version:\n1 - 3.10\n2 - 3.11\nChoose from 1, 2 [1]: \nuse_src_layout [y]: \nuse_poetry [y]: \nuse_docker [n]: \nSelect ci_tools:\n1 - none\n2 - Gitlab\n3 - Github\nChoose from 1, 2, 3 [1]: \ninit_skeleton [n]: y\n
\u7136\u540e\u4f7f\u7528 vscode \u6253\u5f00\u9879\u76ee\uff1a
code example_etl\n
\u5efa\u8bae\u5728\u9879\u76ee\u5f00\u59cb\u7684\u65f6\u5019\u5c31\u521d\u59cb\u5316 git \u4ed3\u5e93\uff0c\u5e76\u5728\u540e\u7eed\u53ca\u65f6\u63d0\u4ea4\u529f\u80fd\u4fee\u6539\u3002
git init\ngit config user.name test\ngit config user.email test@example.com\ngit commit -m \"feat: init project.\"\n
"},{"location":"guidelines/tutorial/init_project/#_3","title":"\u9879\u76ee\u5185\u5bb9","text":"\u5728 IDE \u4e2d\u67e5\u770b\u9879\u76ee\uff0c\u53ef\u4ee5\u770b\u5230\u76ee\u5f55\u7ed3\u6784\u5982\u4e0b\uff1a
\u276f tree example_etl\nexample_etl\n\u251c\u2500\u2500 .editorconfig\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 .pre-commit-config.yaml\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 docs\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 development.md\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 src\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 example_etl\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 cmdline.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 config\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 settings.yml\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 log.py\n\u251c\u2500\u2500 tests\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 conftest.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 settings.yml\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 test_cmdline.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 test_log.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 test_version.py\n\u2514\u2500\u2500 tox.ini\n\n6 directories, 19 files\n
"},{"location":"guidelines/tutorial/init_project/#pyprojecttoml","title":"pyproject.toml","text":"pyproject.toml
\u662f\u9879\u76ee\u7684\u6253\u5305\u914d\u7f6e\u6587\u4ef6\u3002\u6587\u4ef6\u524d\u9762 tool.poetry
\u4e2d\u8bbe\u7f6e\u4e86\u9879\u76ee\u7684\u57fa\u672c\u4fe1\u606f\u3002 tool.poetry.dependencies
\u4e2d\u914d\u7f6e\u4e86\u6b64\u9879\u76ee\u7684\u4f9d\u8d56\u5e93\u3002
\u6587\u4ef6\u5185\u5bb9\u5982\u4e0b\uff1a
[tool.poetry]\nname = \"example_etl\"\nversion = \"0.1.0\"\ndescription = \"This is my first etl project.\"\nreadme = \"README.md\"\nauthors = [\"test <test@example.com>\"]\nlicense = \"MIT\"\nclassifiers = [\n\"Operating System :: OS Independent\",\n\"Programming Language :: Python :: 3.10\",\n]\n[tool.poetry.dependencies]\npython = \"^3.10\"\ndynaconf = \"^3.1.12\"\nclick = \"^8.1.3\"\n[tool.poetry.group.dev.dependencies]\npylint = \"^2.17.4\"\nisort = \"^5.12.0\"\npytest = \"^7.3.1\"\ntox = \"^4.5.2\"\nmkdocs = \"^1.4.3\"\nmkdocs-material = \"^8.5.11\"\npytest-pylint = \"^0.19.0\"\npre-commit = \"^3.3.2\"\n[tool.poetry.scripts]\nexample_etl = \"example_etl.cmdline:main\"\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n[tool.pytest.ini_options]\ntestpaths = \"tests\"\npython_files = \"tests.py test_*.py *_tests.py\"\n[tool.pylint.design]\nmax-line-length = 120\n
"},{"location":"guidelines/tutorial/init_project/#srcexample_etlcmdlinepy","title":"src/example_etl/cmdline.py","text":"src/example_etl/cmdline.py
\u662f\u4f7f\u7528 click
\u7f16\u5199\u7684\u4e00\u4e2a\u547d\u4ee4\u884c\u5165\u53e3\u6587\u4ef6\uff0c\u901a\u8fc7\u4e00\u4e9b\u81ea\u5b9a\u4e49\u547d\u4ee4\u548c\u53c2\u6570\u6765\u63a7\u5236\u7a0b\u5e8f\u7684\u903b\u8f91\u3002
\"\"\"Command line\"\"\"\nimport click\nfrom click import Context\nfrom example_etl import __version__\nfrom example_etl.config import settings\nfrom example_etl.log import init_log\n@click.group(invoke_without_command=True)\n@click.pass_context\n@click.option(\n'-V',\n'--version',\nis_flag=True,\nhelp='Show version and exit.'\n) # If it's true, it will override `settings.VERBOSE`\n@click.option('-v', '--verbose', is_flag=True, help='Show more info.')\n@click.option(\n'--debug',\nis_flag=True,\nhelp='Enable debug.'\n) # If it's true, it will override `settings.DEBUG`\ndef main(ctx: Context, version: str, verbose: bool, debug: bool):\n\"\"\"Main commands\"\"\"\nif version:\nclick.echo(__version__)\nelif ctx.invoked_subcommand is None:\nclick.echo(ctx.get_help())\nelse:\nif verbose:\nsettings.set('VERBOSE', True)\nif debug:\nsettings.set('DEBUG', True)\n@main.command()\ndef run():\n\"\"\"Run command\"\"\"\ninit_log()\nclick.echo('run......')\n
"},{"location":"guidelines/tutorial/init_project/#srcexample_etllogpy","title":"src/example_etl/log.py","text":"src/example_etl/log.py
\u662f\u9884\u5b9a\u4e49\u65e5\u5fd7\u914d\u7f6e\u6587\u4ef6\uff0c\u5f53\u9879\u76ee\u542f\u52a8\u65f6\uff0c\u4f1a\u81ea\u52a8\u521d\u59cb\u5316\u9ed8\u8ba4\u7684\u65e5\u5fd7\u914d\u7f6e\u3002
\"\"\"Log\"\"\"\nimport logging\nimport os\nfrom logging.config import dictConfig\nfrom example_etl.config import settings\nos.makedirs(settings.LOGPATH, exist_ok=True)\ndef verbose_formatter(verbose: int) -> str:\n\"\"\"formatter factory\"\"\"\nif verbose is True:\nreturn 'verbose'\nreturn 'simple'\ndef update_log_level(debug: bool, level: str) -> str:\n\"\"\"update log level\"\"\"\nif debug is True:\nlevel_num = logging.DEBUG\nelse:\nlevel_num = logging.getLevelName(level)\nsettings.set('LOGLEVEL', logging.getLevelName(level_num))\nreturn settings.LOGLEVEL\ndef init_log() -> None:\n\"\"\"Init log config.\"\"\"\nlog_level = update_log_level(settings.DEBUG, str(settings.LOGLEVEL).upper())\nlog_config = {\n\"version\": 1,\n\"disable_existing_loggers\": False,\n\"formatters\": {\n'verbose': {\n'format': '%(asctime)s %(levelname)s %(name)s %(process)d %(thread)d %(message)s',\n},\n'simple': {\n'format': '%(asctime)s %(levelname)s %(name)s %(message)s',\n},\n},\n\"handlers\": {\n\"console\": {\n\"formatter\": verbose_formatter(settings.VERBOSE),\n'level': 'DEBUG',\n\"class\": \"logging.StreamHandler\",\n},\n'file': {\n'class': 'logging.handlers.RotatingFileHandler',\n'level': 'DEBUG',\n'formatter': verbose_formatter(settings.VERBOSE),\n'filename': os.path.join(settings.LOGPATH, 'all.log'),\n'maxBytes': 1024 * 1024 * 1024 * 200, # 200M\n'backupCount': '5',\n'encoding': 'utf-8'\n},\n},\n\"loggers\": {\n'': {'level': log_level, 'handlers': ['console']},\n}\n}\ndictConfig(log_config)\n
"},{"location":"guidelines/tutorial/init_project/#srcexample_etlconfig__init__py","title":"src/example_etl/config/__init__.py","text":"src/example_etl/config/__init__.py
\u662f\u4f7f\u7528 dynaconf
\u521d\u59cb\u5316\u7684\u914d\u7f6e\u4e2d\u5fc3\uff0c\u9879\u76ee\u6240\u6709\u7684\u914d\u7f6e\u90fd\u662f \u4ece settings
\u5bf9\u8c61\u4e2d\u83b7\u53d6\uff0c\u5b83\u4f1a\u8bfb\u53d6\u9879\u76ee\u7ea7\u522b\u7684\u9ed8\u8ba4\u914d\u7f6e\u6587\u4ef6\uff0c\u4e5f\u4f1a\u8bfb\u53d6\u81ea\u5b9a\u4e49\u914d\u7f6e\u6587\u4ef6\u3002
\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u52a0\u8f7d\u7684\u914d\u7f6e\u6587\u4ef6\u5982\u4e0b\uff1a
src/example_etl/config/settings.yml
\uff1a\u9879\u76ee\u9ed8\u8ba4\u914d\u7f6e\u6587\u4ef6 src/example_etl/config/settings.local.yml
\uff1a\u8fd9\u4e2a\u5728\u9879\u76ee\u4e2d\u662f\u4e0d\u4f1a git \u8ffd\u8e2a\u7684\uff0c\u5c5e\u4e8e\u672c\u5730\u81ea\u5b9a\u4e49\u914d\u7f6e <sys.prefix>/etc/example_etl/settings.yml
\uff1a\u64cd\u4f5c\u7cfb\u7edf\u5916\u90e8\u914d\u7f6e\u6587\u4ef6\u3002\u9ed8\u8ba4\u8fd9\u4e2a\u914d\u7f6e\u6587\u4ef6\u548c\u9879\u76ee\u9ed8\u8ba4\u914d\u7f6e\u6587\u4ef6\u7684\u5185\u5bb9\u4e00\u81f4\u3002 - \u4f7f\u7528
EXAMPLE_ETL_<name>=<value>
\u73af\u5883\u53d8\u91cf\u4f20\u9012
\u4f18\u5148\u7ea7\u4ece\u4ece\u4e0a\u5230\u4e0b\u4f9d\u6b21\u589e\u5927\uff0c\u4f18\u5148\u7ea7\u9ad8\u7684\u4f1a\u8986\u76d6\u4f18\u5148\u7ea7\u4f4e\u7684\u914d\u7f6e\u3002
\"\"\"\nConfiguration center.\nUse https://www.dynaconf.com/\n\"\"\"\"\"\nimport os\nimport sys\nfrom pathlib import Path\nfrom dynaconf import Dynaconf\n_base_dir = Path(__file__).parent.parent\n_settings_files = [\n# All config file will merge.\nPath(__file__).parent / 'settings.yml', # Load default config.\n]\n# User configuration. It will be created automatically by the pip installer .\n_external_files = [\nPath(sys.prefix, 'etc', 'example_etl', 'settings.yml')\n]\nsettings = Dynaconf(\n# Set env `EXAMPLE_ETL_FOO='bar'`\uff0cuse `settings.FOO` .\nenvvar_prefix='EXAMPLE_ETL',\nsettings_files=_settings_files, # load user configuration.\n# environments=True, # Enable multi-level configuration\uff0ceg: default, development, production\nload_dotenv=True, # Enable load .env\n# env_switcher='EXAMPLE_ETL_ENV',\nlowercase_read=False, # If true, can't use `settings.foo`, but can only use `settings.FOO`\nincludes=_external_files, # Customs settings.\nbase_dir=_base_dir, # `settings.BASE_DIR`\n)\n
"},{"location":"guidelines/tutorial/init_project/#_4","title":"\u521d\u59cb\u5316\u73af\u5883","text":"\u9879\u76ee\u4f7f\u7528 poetry
\u7ba1\u7406\u865a\u62df\u73af\u5883\uff0c\u8fd0\u884c\u547d\u4ee4\u81ea\u52a8\u521b\u5efa\u865a\u62df\u73af\u5883\uff0c\u540c\u65f6\u5b89\u88c5\u5f00\u53d1\u73af\u5883\u4f9d\u8d56
"},{"location":"guidelines/tutorial/init_project/#_5","title":"\u5b89\u88c5\u9879\u76ee\u9ed8\u8ba4\u4f9d\u8d56","text":"\u5728\u9879\u76ee\u76ee\u5f55\u6267\u884c poetry install -v
\u4ee5\u53ef\u89c6\u5316\u8fc7\u7a0b\u5b89\u88c5\u4f9d\u8d56\uff1a
$ poetry install -v\nCreating virtualenv example-etl-B-7RVLBy-py3.10 in /Users/kevin/Library/Caches/pypoetry/virtualenvs\nUsing virtualenv: /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10\nUpdating dependencies\nResolving dependencies... (7.5s)\nFinding the necessary packages for the current system\n\nPackage operations: 52 installs, 1 update, 0 removals\n\n\u2022 Installing six (1.16.0)\n\u2022 Installing lazy-object-proxy (1.9.0)\n\u2022 Installing markupsafe (2.1.2)\n\u2022 Installing python-dateutil (2.8.2)\n\u2022 Installing pyyaml (6.0)\n\u2022 Installing typing-extensions (4.6.2)\n\u2022 Installing wrapt (1.15.0)\n\u2022 Installing astroid (2.15.5)\n\u2022 Installing certifi (2023.5.7)\n\u2022 Installing charset-normalizer (3.1.0)\n\u2022 Installing click (8.1.3)\n\u2022 Installing dill (0.3.6)\n\u2022 Installing filelock (3.12.0)\n\u2022 Installing exceptiongroup (1.1.1)\n\u2022 Installing ghp-import (2.1.0)\n\u2022 Installing idna (3.4)\n\u2022 Installing distlib (0.3.6)\n\u2022 Installing iniconfig (2.0.0)\n\u2022 Installing isort (5.12.0)\n\u2022 Installing jinja2 (3.1.2)\n\u2022 Installing markdown (3.3.7)\n\u2022 Installing mccabe (0.7.0)\n\u2022 Installing mergedeep (1.3.4)\n\u2022 Installing packaging (23.1)\n\u2022 Installing platformdirs (3.5.1)\n\u2022 Installing pyyaml-env-tag (0.1)\n\u2022 Installing pluggy (1.0.0)\n\u2022 Updating setuptools (67.7.2 -> 67.8.0)\n\u2022 Installing tomli (2.0.1)\n\u2022 Installing urllib3 (2.0.2)\n\u2022 Installing tomlkit (0.11.8)\n\u2022 Installing watchdog (3.0.0)\n\u2022 Installing cachetools (5.3.1)\n\u2022 Installing cfgv (3.3.1)\n\u2022 Installing chardet (5.1.0)\n\u2022 Installing colorama (0.4.6)\n\u2022 Installing identify (2.5.24)\n\u2022 Installing mkdocs (1.4.3)\n\u2022 Installing mkdocs-material-extensions (1.1.1)\n\u2022 Installing nodeenv (1.8.0)\n\u2022 Installing pygments (2.15.1)\n\u2022 Installing pylint (2.17.4)\n\u2022 Installing pymdown-extensions (10.0.1)\n\u2022 Installing pyproject-api (1.5.1)\n\u2022 Installing pytest (7.3.1)\n\u2022 Installing requests (2.31.0)\n\u2022 Installing toml (0.10.2)\n\u2022 Installing virtualenv (20.23.0)\n\u2022 Installing dynaconf (3.1.12)\n\u2022 Installing mkdocs-material (8.5.11)\n\u2022 Installing pre-commit (3.3.2)\n\u2022 Installing pytest-pylint (0.19.0)\n\u2022 Installing tox (4.5.2)\nWriting lock file\n\nInstalling the current project: example_etl (0.1.0)\n
\u7136\u540e\u6267\u884c poetry shell
\u8fdb\u5165\u5230\u865a\u62df\u73af\u5883\u3002
\u5728\u4f7f\u7528 vscode \u7684\u65f6\u5019\uff0c\u53ef\u4ee5\u8fd0\u884c Ctrl + Shift + p
\u6253\u5f00\u6307\u4ee4\uff0c\u8f93\u5165 > Python: Select Interpreter
\u9009\u62e9\u521a\u521a\u521b\u5efa\u7684\u865a\u62df\u73af\u5883\u3002 \u5982\u679c\u770b\u4e0d\u5230\uff0c\u53ea\u9700\u8981\u70b9\u51fb\u65c1\u8fb9\u7684\u5237\u65b0\u6309\u94ae\u5373\u53ef\u3002\u7136\u540e\u91cd\u65b0\u6253\u5f00\u4e00\u4e2a\u65b0\u7684\u7ec8\u7aef\uff0c\u4f1a\u81ea\u52a8\u8fdb\u5165\u865a\u62df\u73af\u5883\u3002
"},{"location":"guidelines/tutorial/init_project/#_6","title":"\u8fd0\u884c\u6d4b\u8bd5","text":"\u4e3a\u4e86\u4fdd\u8bc1\u521d\u59cb\u5316\u9879\u76ee\u662f\u6b63\u5e38\u7684\uff0c\u5728\u8fdb\u884c\u540e\u7eed\u6b65\u9aa4\u4e4b\u524d\uff0c\u8fd0\u884c\u81ea\u52a8\u5316\u6d4b\u8bd5\u903b\u8f91\uff1a
tox\n
\u53ef\u4ee5\u770b\u5230\u6700\u540e\u8f93\u51fa\u5982\u4e0b\uff1a
$ tox\npy310: install_deps> python -I -m pip install poetry\n.pkg: install_requires> python -I -m pip install poetry-core\n.pkg: _optional_hooks> python /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pyproject_api/_backend.py True poetry.core.masonry.api\n.pkg: get_requires_for_build_sdist> python /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pyproject_api/_backend.py True poetry.core.masonry.api\n.pkg: prepare_metadata_for_build_wheel> python /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pyproject_api/_backend.py True poetry.core.masonry.api\n.pkg: build_sdist> python /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pyproject_api/_backend.py True poetry.core.masonry.api\npy310: install_package_deps> python -I -m pip install 'click<9.0.0,>=8.1.3' 'dynaconf<4.0.0,>=3.1.12'\npy310: install_package> python -I -m pip install --force-reinstall --no-deps /private/tmp/example_etl/.tox/.tmp/package/1/example_etl-0.1.0.tar.gz\npy310: commands[0]> poetry install -v\nUsing virtualenv: /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10\nInstalling dependencies from lock file\n\nFinding the necessary packages for the current system\n\nPackage operations: 0 installs, 0 updates, 0 removals, 53 skipped\n\n\u2022 Installing astroid (2.15.5): Skipped for the following reason: Already installed\n \u2022 Installing identify (2.5.24): Skipped for the following reason: Already installed\n \u2022 Installing chardet (5.1.0): Skipped for the following reason: Already installed\n \u2022 Installing charset-normalizer (3.1.0): Skipped for the following reason: Already installed\n \u2022 Installing click (8.1.3): Skipped for the following reason: Already installed\n \u2022 Installing colorama (0.4.6): Skipped for the following reason: Already installed\n \u2022 Installing distlib (0.3.6): Skipped for the following reason: Already installed\n \u2022 Installing isort (5.12.0): Skipped for the following reason: Already installed\n \u2022 Installing jinja2 (3.1.2): Skipped for the following reason: Already installed\n \u2022 Installing idna (3.4): Skipped for the following reason: Already installed\n \u2022 Installing certifi (2023.5.7): Skipped for the following reason: Already installed\n \u2022 Installing iniconfig (2.0.0): Skipped for the following reason: Already installed\n \u2022 Installing lazy-object-proxy (1.9.0): Skipped for the following reason: Already installed\n \u2022 Installing mkdocs-material (8.5.11): Skipped for the following reason: Already installed\n \u2022 Installing dynaconf (3.1.12): Skipped for the following reason: Already installed\n \u2022 Installing mkdocs-material-extensions (1.1.1): Skipped for the following reason: Already installed\n \u2022 Installing markdown (3.3.7): Skipped for the following reason: Already installed\n \u2022 Installing packaging (23.1): Skipped for the following reason: Already installed\n \u2022 Installing mergedeep (1.3.4): Skipped for the following reason: Already installed\n \u2022 Installing mkdocs (1.4.3): Skipped for the following reason: Already installed\n \u2022 Installing pre-commit (3.3.2): Skipped for the following reason: Already installed\n \u2022 Installing nodeenv (1.8.0): Skipped for the following reason: Already installed\n \u2022 Installing filelock (3.12.0): Skipped for the following reason: Already installed\n \u2022 Installing mccabe (0.7.0): Skipped for the following reason: Already installed\n \u2022 Installing markupsafe (2.1.2): Skipped for the following reason: Already installed\n \u2022 Installing pytest (7.3.1): Skipped for the following reason: Already installed\n \u2022 Installing pytest-pylint (0.19.0): Skipped for the following reason: Already installed\n \u2022 Installing ghp-import (2.1.0): Skipped for the following reason: Already installed\n \u2022 Installing pylint (2.17.4): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml-env-tag (0.1): Skipped for the following reason: Already installed\n \u2022 Installing requests (2.31.0): Skipped for the following reason: Already installed\n \u2022 Installing dill (0.3.6): Skipped for the following reason: Already installed\n \u2022 Installing platformdirs (3.5.1): Skipped for the following reason: Already installed\n \u2022 Installing exceptiongroup (1.1.1): Skipped for the following reason: Already installed\n \u2022 Installing python-dateutil (2.8.2): Skipped for the following reason: Already installed\n \u2022 Installing pygments (2.15.1): Skipped for the following reason: Already installed\n \u2022 Installing pyproject-api (1.5.1): Skipped for the following reason: Already installed\n \u2022 Installing cfgv (3.3.1): Skipped for the following reason: Already installed\n \u2022 Installing six (1.16.0): Skipped for the following reason: Already installed\n \u2022 Installing virtualenv (20.23.0): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml (6.0): Skipped for the following reason: Already installed\n \u2022 Installing cachetools (5.3.1): Skipped for the following reason: Already installed\n \u2022 Installing tomlkit (0.11.8): Skipped for the following reason: Already installed\n \u2022 Installing tox (4.5.2): Skipped for the following reason: Already installed\n \u2022 Installing urllib3 (2.0.2): Skipped for the following reason: Already installed\n \u2022 Installing setuptools (67.8.0): Skipped for the following reason: Already installed\n \u2022 Installing toml (0.10.2): Skipped for the following reason: Already installed\n \u2022 Installing wrapt (1.15.0): Skipped for the following reason: Already installed\n \u2022 Installing typing-extensions (4.6.2): Skipped for the following reason: Already installed\n \u2022 Installing pymdown-extensions (10.0.1): Skipped for the following reason: Already installed\n \u2022 Installing watchdog (3.0.0): Skipped for the following reason: Already installed\n \u2022 Installing tomli (2.0.1): Skipped for the following reason: Already installed\n \u2022 Installing pluggy (1.0.0): Skipped for the following reason: Already installed\n\nInstalling the current project: example_etl (0.1.0)\npy310: commands[1]> poetry run pytest tests\n================================================================================================================================= test session starts =================================================================================================================================\nplatform darwin -- Python 3.10.11, pytest-7.3.1, pluggy-1.0.0\ncachedir: .tox/py310/.pytest_cache\nrootdir: /private/tmp/example_etl\nconfigfile: pyproject.toml\nplugins: pylint-0.19.0\ncollected 10 items tests/test_cmdline.py ..... [ 50%]\ntests/test_exceptions.py . [ 50%]\ntests/test_log.py ..... [ 91%]\ntests/test_version.py . [100%]\n================================================================================================================================== warnings summary ===================================================================================================================================\n../../../Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pkg_resources/__init__.py:121\n /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pkg_resources/__init__.py:121: DeprecationWarning: pkg_resources is deprecated as an API\n warnings.warn(\"pkg_resources is deprecated as an API\", DeprecationWarning)\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n============================================================================================================================ 10 passed, 1 warning in 0.01s ============================================================================================================================\npy310: OK \u2714 in 14.05 seconds\nisort: install_deps> python -I -m pip install isort\nisort: install_package_deps> python -I -m pip install 'click<9.0.0,>=8.1.3' 'dynaconf<4.0.0,>=3.1.12'\nisort: install_package> python -I -m pip install --force-reinstall --no-deps /private/tmp/example_etl/.tox/.tmp/package/2/example_etl-0.1.0.tar.gz\nisort: commands[0]> isort . --check-only --diff\nSkipped 1 files\nisort: OK \u2714 in 3.05 seconds\npylint: install_deps> python -I -m pip install poetry\npylint: install_package_deps> python -I -m pip install 'click<9.0.0,>=8.1.3' 'dynaconf<4.0.0,>=3.1.12'\npylint: install_package> python -I -m pip install --force-reinstall --no-deps /private/tmp/example_etl/.tox/.tmp/package/3/example_etl-0.1.0.tar.gz\npylint: commands[0]> poetry install -v\nUsing virtualenv: /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10\nInstalling dependencies from lock file\n\nFinding the necessary packages for the current system\n\nPackage operations: 0 installs, 0 updates, 0 removals, 53 skipped\n\n\u2022 Installing astroid (2.15.5): Skipped for the following reason: Already installed\n \u2022 Installing certifi (2023.5.7): Skipped for the following reason: Already installed\n \u2022 Installing cfgv (3.3.1): Skipped for the following reason: Already installed\n \u2022 Installing cachetools (5.3.1): Skipped for the following reason: Already installed\n \u2022 Installing dill (0.3.6): Skipped for the following reason: Already installed\n \u2022 Installing click (8.1.3): Skipped for the following reason: Already installed\n \u2022 Installing colorama (0.4.6): Skipped for the following reason: Already installed\n \u2022 Installing chardet (5.1.0): Skipped for the following reason: Already installed\n \u2022 Installing distlib (0.3.6): Skipped for the following reason: Already installed\n \u2022 Installing markdown (3.3.7): Skipped for the following reason: Already installed\n \u2022 Installing filelock (3.12.0): Skipped for the following reason: Already installed\n \u2022 Installing identify (2.5.24): Skipped for the following reason: Already installed\n \u2022 Installing idna (3.4): Skipped for the following reason: Already installed\n \u2022 Installing isort (5.12.0): Skipped for the following reason: Already installed\n \u2022 Installing charset-normalizer (3.1.0): Skipped for the following reason: Already installed\n \u2022 Installing dynaconf (3.1.12): Skipped for the following reason: Already installed\n \u2022 Installing markupsafe (2.1.2): Skipped for the following reason: Already installed\n \u2022 Installing ghp-import (2.1.0): Skipped for the following reason: Already installed\n \u2022 Installing mergedeep (1.3.4): Skipped for the following reason: Already installed\n \u2022 Installing iniconfig (2.0.0): Skipped for the following reason: Already installed\n \u2022 Installing mkdocs-material (8.5.11): Skipped for the following reason: Already installed\n \u2022 Installing nodeenv (1.8.0): Skipped for the following reason: Already installed\n \u2022 Installing exceptiongroup (1.1.1): Skipped for the following reason: Already installed\n \u2022 Installing pyproject-api (1.5.1): Skipped for the following reason: Already installed\n \u2022 Installing pluggy (1.0.0): Skipped for the following reason: Already installed\n \u2022 Installing python-dateutil (2.8.2): Skipped for the following reason: Already installed\n \u2022 Installing jinja2 (3.1.2): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml-env-tag (0.1): Pending...\n \u2022 Installing pymdown-extensions (10.0.1): Skipped for the following reason: Already installed\n \u2022 Installing packaging (23.1): Skipped for the following reason: Already installed\n \u2022 Installing mccabe (0.7.0): Skipped for the following reason: Already installed\n \u2022 Installing pytest-pylint (0.19.0): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml (6.0): Skipped for the following reason: Already installed\n \u2022 Installing pygments (2.15.1): Skipped for the following reason: Already installed\n \u2022 Installing pymdown-extensions (10.0.1): Skipped for the following reason: Already installed\n \u2022 Installing packaging (23.1): Skipped for the following reason: Already installed\n \u2022 Installing mccabe (0.7.0): Skipped for the following reason: Already installed\n \u2022 Installing pytest-pylint (0.19.0): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml (6.0): Skipped for the following reason: Already installed\n \u2022 Installing pygments (2.15.1): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml-env-tag (0.1): Skipped for the following reason: Already installed\n \u2022 Installing pymdown-extensions (10.0.1): Skipped for the following reason: Already installed\n \u2022 Installing packaging (23.1): Skipped for the following reason: Already installed\n \u2022 Installing mccabe (0.7.0): Skipped for the following reason: Already installed\n \u2022 Installing pytest-pylint (0.19.0): Skipped for the following reason: Already installed\n \u2022 Installing pyyaml (6.0): Skipped for the following reason: Already installed\n \u2022 Installing pygments (2.15.1): Skipped for the following reason: Already installed\n \u2022 Installing pylint (2.17.4): Skipped for the following reason: Already installed\n \u2022 Installing tox (4.5.2): Skipped for the following reason: Already installed\n \u2022 Installing setuptools (67.8.0): Skipped for the following reason: Already installed\n \u2022 Installing platformdirs (3.5.1): Skipped for the following reason: Already installed\n \u2022 Installing toml (0.10.2): Skipped for the following reason: Already installed\n \u2022 Installing watchdog (3.0.0): Skipped for the following reason: Already installed\n \u2022 Installing tomlkit (0.11.8): Skipped for the following reason: Already installed\n \u2022 Installing mkdocs-material-extensions (1.1.1): Skipped for the following reason: Already installed\n \u2022 Installing typing-extensions (4.6.2): Skipped for the following reason: Already installed\n \u2022 Installing urllib3 (2.0.2): Skipped for the following reason: Already installed\n \u2022 Installing lazy-object-proxy (1.9.0): Skipped for the following reason: Already installed\n \u2022 Installing six (1.16.0): Skipped for the following reason: Already installed\n \u2022 Installing tomli (2.0.1): Skipped for the following reason: Already installed\n \u2022 Installing mkdocs (1.4.3): Skipped for the following reason: Already installed\n \u2022 Installing requests (2.31.0): Skipped for the following reason: Already installed\n \u2022 Installing pytest (7.3.1): Skipped for the following reason: Already installed\n \u2022 Installing virtualenv (20.23.0): Skipped for the following reason: Already installed\n \u2022 Installing wrapt (1.15.0): Skipped for the following reason: Already installed\n \u2022 Installing pre-commit (3.3.2): Skipped for the following reason: Already installed\n\nInstalling the current project: example_etl (0.1.0)\npylint: commands[1]> poetry run pylint tests src\n\n--------------------------------------------------------------------\nYour code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)\n.pkg: _exit> python /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pyproject_api/_backend.py True poetry.core.masonry.api\n py310: OK (14.05=setup[9.47]+cmd[3.84,0.75] seconds)\nisort: OK (3.05=setup[2.67]+cmd[0.38] seconds)\npylint: OK (12.92=setup[7.86]+cmd[2.91,2.15] seconds)\ncongratulations :) (30.10 seconds)\n
\u81f3\u6b64\uff0c\u9879\u76ee\u73af\u5883\u521d\u59cb\u5316\u5b8c\u6210\u3002\u4e00\u5207\u6b63\u5e38\u3002
"},{"location":"guidelines/tutorial/init_project/#_7","title":"\u63d0\u4ea4\u4ee3\u7801","text":"\u5728\u5f00\u53d1\u65f6\uff0c\u9700\u8981\u517b\u6210\u53ca\u65f6\u63d0\u4ea4\u4ee3\u7801\u7684\u597d\u4e60\u60ef\u3002
git add .\ngit commit -m \"feat: init project env.\"\n
"},{"location":"guidelines/tutorial/publish/","title":"\u6253\u5305\u53d1\u5e03","text":"\u672c\u7ae0\u8282\uff0c\u4f60\u53ef\u4ee5\u5b66\u5230\uff1a
- \u4f7f\u7528 poetry \u6784\u5efa\u9879\u76ee
- \u4f7f\u7528 poetry \u5c06\u9879\u76ee\u53d1\u5e03\u5230 pypi
- \u5b89\u88c5\u5e76\u4f7f\u7528\u4f60\u53d1\u5e03\u5230 pypi \u7684\u9879\u76ee
\u9879\u76ee\u5f00\u53d1\u6d4b\u8bd5\u5b8c\u6210\u540e\uff0c\u53ef\u4ee5\u5c06\u9879\u76ee\u53d1\u5e03\u5230 Pypi \u4ed3\u5e93\u4e2d\uff0c\u7136\u540e\u5728\u5176\u4ed6\u5730\u65b9\u901a\u8fc7 Pip \u547d\u4ee4\u5373\u53ef \u5b89\u88c5\u4f7f\u7528\u3002\u5bf9\u4e8e\u4e00\u4e9b\u5de5\u5177\u5305\u6bd4\u8f83\u65b9\u4fbf\u3002
"},{"location":"guidelines/tutorial/publish/#_2","title":"\u6253\u5305","text":"\u6839\u636e PEP 517 \u89c4\u8303\uff0c\u65b0\u7684\u6253\u5305\u673a\u5236\u53ef\u901a\u8fc7 poetry
\u5de5\u5177\u6765\u64cd\u4f5c\u3002
\u6267\u884c\u6784\u5efa\u547d\u4ee4\uff1a
$ poetry build\nBuilding example_etl (0.1.0)\n- Building sdist\n - Built example_etl-0.1.0.tar.gz\n - Building wheel\n - Built example_etl-0.1.0-py3-none-any.whl\n
"},{"location":"guidelines/tutorial/publish/#_3","title":"\u53d1\u5e03","text":"\u672c\u9879\u76ee\u4e3a\u6d4b\u8bd5\u9879\u76ee\uff0c\u4ee5\u4e0b\u64cd\u4f5c\u53ef\u4ee5\u5c06\u9879\u76ee\u53d1\u5e03\u5230 https://test.pypi.org/ \u3002
\u9996\u5148\u5728 https://test.pypi.org/ \u6ce8\u518c\u8d26\u53f7\u3002
\u7136\u540e\u914d\u7f6e poetry \u53d1\u5e03\u7684\u4ed3\u5e93\uff1a
poetry config repositories.testpypi https://test.pypi.org/legacy/\n
\u7136\u540e\u586b\u5199\u6839\u636e\u4f60\u7684\u7528\u6237\u540d\u5bc6\u7801\u4fee\u6539\u5982\u4e0b\u547d\u4ee4\u540e\uff0c\u5c06\u9879\u76ee\u53d1\u5e03\u5230 testpypi \u4e0a\u3002
poetry publish --repository=testpypi --username=USERNAME --password=PASSWORD\n
\u6ce8\u610f\uff1a\u4e0d\u5efa\u8bae\u5c06\u6d4b\u8bd5\u9879\u76ee\u53d1\u5e03\u5230 https://pypi.org/ \u4e0a\u3002\u5728 https://pypi.org/ \u4e0a\u7684\u9879\u76ee\u540d\u79f0\u662f\u5168\u5c40\u552f\u4e00\u7684\uff0c \u6240\u4ee5\u5982\u679c\u4f60\u8003\u8651\u5230\u5c06\u9879\u76ee\u53d1\u5e03\u5230 https://pypi.org/ \u4e0a\u524d\uff0c\u5e94\u5b9a\u4e00\u4e2a\u4e0d\u5b58\u5728\u540d\u79f0\u3002
"},{"location":"guidelines/tutorial/publish/#_4","title":"\u5b89\u88c5\u6d4b\u8bd5","text":"\u9879\u76ee\u6b63\u5e38\u53d1\u5e03\u540e\uff0c\u53ef\u4ee5\u901a\u8fc7 pip \u5b89\u88c5\u5230\u672c\u5730\u4f7f\u7528\uff1a
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple example-etl\n
\u7531\u4e8e\u6211\u4eec\u4f7f\u7528\u7684\u662f\u6d4b\u8bd5\u4ed3\u5e93\uff0c\u6240\u4ee5\u9700\u8981\u6307\u5b9a --index-url https://test.pypi.org/simple/
\u53c2\u6570\uff0c\u7528\u6765\u5b89\u88c5\u53d1\u5e03\u5230 https://test.pypi.org/ \u7684\u5305 \u540c\u65f6\u4f7f\u7528 --extra-index-url https://pypi.org/simple
\u53c2\u6570\uff0c\u6765\u5b89\u88c5 example-etl
\u4f9d\u8d56\u7684\u5305\u3002
\u8f93\u51fa\u7ed3\u679c\u5982\u4e0b\uff1a
\u276f pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple example-etl\nLooking in indexes: https://test.pypi.org/simple/, https://pypi.org/simple\nCollecting example-etl\n Downloading https://test-files.pythonhosted.org/packages/f3/51/8cea9e34ae2f0e48abb6a0aa58cdf29d4d2900bdd97e45b8d4ee24b357f0/example_etl-0.1.0-py3-none-any.whl (9.5 kB)\nCollecting stevedore<6.0.0,>=5.1.0\n Downloading stevedore-5.1.0-py3-none-any.whl (49 kB)\n \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 49.6/49.6 kB 195.3 kB/s eta 0:00:00\nCollecting click<9.0.0,>=8.1.3\n Downloading click-8.1.3-py3-none-any.whl (96 kB)\n \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 96.6/96.6 kB 389.6 kB/s eta 0:00:00\nCollecting dynaconf<4.0.0,>=3.1.12\n Downloading dynaconf-3.1.12-py2.py3-none-any.whl (211 kB)\n \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 211.8/211.8 kB 878.8 kB/s eta 0:00:00\nCollecting pbr!=2.1.0,>=2.0.0\n Downloading pbr-5.11.1-py2.py3-none-any.whl (112 kB)\n \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 112.7/112.7 kB 1.7 MB/s eta 0:00:00\nInstalling collected packages: pbr, dynaconf, click, stevedore, example-etl\nSuccessfully installed click-8.1.3 dynaconf-3.1.12 example-etl-0.1.0 pbr-5.11.1 stevedore-5.1.0\n Downloading example_etl-0.0.1.dev0-py3-none-any.whl (14 kB)\nCollecting dynaconf==3.1.7\n Downloading dynaconf-3.1.7-py2.py3-none-any.whl (200 kB)\n |\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 200 kB 850 kB/s \nCollecting stevedore==3.5.0\n Downloading stevedore-3.5.0-py3-none-any.whl (49 kB)\n |\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 49 kB 747 kB/s \nCollecting click==8.0.3\n Downloading click-8.0.3-py3-none-any.whl (97 kB)\n |\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 97 kB 554 kB/s \nCollecting pbr!=2.1.0,>=2.0.0\n Downloading pbr-5.8.0-py2.py3-none-any.whl (112 kB)\n |\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 112 kB 1.9 MB/s \nInstalling collected packages: pbr, stevedore, dynaconf, click, example-etl\nSuccessfully installed click-8.0.3 dynaconf-3.1.7 example-etl-0.0.1.dev0 pbr-5.8.0 stevedore-3.5.0\n
"},{"location":"guidelines/tutorial/test/","title":"\u6d4b\u8bd5","text":"\u5728\u672c\u7ae0\u8282\uff0c\u4f60\u5c06\u5b66\u5230\u5982\u4e0b\u5185\u5bb9\uff1a
- \u4f7f\u7528 pytest \u7f16\u5199\u5355\u5143\u6d4b\u8bd5
- \u4f7f\u7528 pytest-mock \u6a21\u62df\u5355\u5143\u6d4b\u8bd5\u4e2d\u7684\u4f9d\u8d56\u903b\u8f91
- \u4f7f\u7528 tox \u81ea\u52a8\u5316\u6d4b\u8bd5\u6d41\u7a0b
\u6d4b\u8bd5\u662f\u4fdd\u969c\u5b89\u5168\u4e0a\u7ebf\u4e00\u4e2a\u91cd\u8981\u7684\u6b65\u9aa4\uff0c\u7f16\u5199\u826f\u597d\u7684\u6d4b\u8bd5\uff0c\u53ef\u4ee5\u5728\u53d1\u5e03\u4e4b\u524d\u5c3d\u53ef\u80fd\u907f\u514d BUG \u51fa\u73b0\u3002 \u5728\u4fee\u6539\u529f\u80fd\u540e\uff0c\u4e5f\u53ef\u4ee5\u901a\u8fc7\u56de\u5f52\u6d4b\u8bd5\uff0c\u68c0\u67e5\u73b0\u6709\u529f\u80fd\u7684\u7a33\u5b9a\u6027\u3002
\u7f16\u5199\u5355\u5143\u6d4b\u8bd5\u8fc7\u7a0b\uff0c\u548c\u5f00\u53d1\u987a\u5e8f\u4e00\u6837\uff0c\u5148\u6d4b\u8bd5\u4e09\u4e2a\u6a21\u5757\uff0c\u518d\u6d4b\u8bd5 manage
\u6a21\u5757\uff0c\u6700\u540e\u6d4b\u8bd5\u8c03\u7528\u903b\u8f91\u3002
\u6d4b\u8bd5\u65f6\uff0c\u4f7f\u7528\u7684\u662f pytest
\u5de5\u5177\uff0c\u800c\u4e0d\u662f\u4f7f\u7528 unittest
\u3002
"},{"location":"guidelines/tutorial/test/#_2","title":"\u8c03\u6574\u6d4b\u8bd5\u4ee3\u7801","text":"\"\"\"Test cmdline\"\"\"\nfrom __future__ import annotations # PEP 585\nimport pytest\nfrom click.testing import CliRunner\nfrom example_etl import __version__\nfrom example_etl.cmdline import main\n@pytest.mark.parametrize(\n['invoke_args', 'exit_code', 'output_keyword'],\n[\n([], 0, 'help'),\n(['--help'], 0, 'help'),\n(['--version'], 0, __version__),\n(['-V'], 0, __version__),\n]\n)\ndef test_main(\nclicker: CliRunner,\ninvoke_args: list[str],\nexit_code: int,\noutput_keyword: str,\n):\n\"\"\"Test main cmdline\"\"\"\nresult = clicker.invoke(main, invoke_args)\nassert result.exit_code == exit_code\nassert output_keyword in result.output\n
"},{"location":"guidelines/tutorial/test/#extractor","title":"\u6d4b\u8bd5 extractor","text":"\u5728 tests
\u5305\u4e2d\u65b0\u5efa test_extractor.py
\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Test extractor\"\"\"\nimport pytest\nfrom example_etl.extractor.base import BaseExtractor\nfrom example_etl.extractor.file import FileExtractor\ndef test_base_source(mocker):\n\"\"\"Test base extractor\"\"\"\nclose_mock = mocker.patch.object(BaseExtractor, 'close')\nwith pytest.raises(NotImplementedError):\nwith BaseExtractor(mocker.MagicMock()) as base:\nbase.extract()\nassert close_mock.called_once()\ndef test_file_source(mocker, foo_file):\n\"\"\"Test file extractor\"\"\"\nextractor = FileExtractor(mocker.MagicMock())\nextractor.settings.FILE_EXTRACTOR_PATH = foo_file\ndata = list(extractor.extract())\nassert data == ['foo']\n
\u6d4b\u8bd5\u4ee3\u7801\u4e2d\uff0c\u5206\u522b\u6d4b\u8bd5\u4e86 BaseExtractor
\u548c FileExtractor
\u7684\u903b\u8f91\u3002
\u5728\u6d4b\u8bd5\u903b\u8f91\u4e2d\u4f7f\u7528\u4e86 mocker
\u529f\u80fd\uff0c\u53ef\u4ee5\u5728\u6d4b\u8bd5\u5355\u5143\u903b\u8f91\u65f6\uff0c\u5c06\u5176\u4f9d\u8d56\u7684\u4e1c\u897f mock \u6389\u3002\u5728 pytest
\u6d4b\u8bd5\u6846\u67b6\u4e2d\uff0c \u4f7f\u7528 pytest-mock
\u6269\u5c55\u53ef\u4ee5\u5f88\u65b9\u4fbf\u7684\u4f7f\u7528 mocker
\u5bf9\u8c61\u3002
\u5b89\u88c5 pytest-mock
\uff1a
poetry add --group dev pytest-mock\n
\u8fd9\u91cc\u4f7f\u7528\u4e86 poetry add -D
\uff0c\u610f\u601d\u662f\u5c06 pytest-mock
\u5b89\u88c5\u5230\u5f00\u53d1\u73af\u5883\u4f9d\u8d56\u4e2d\u3002 \u5f53\u5728\u4e00\u4e2a\u65b0\u73af\u5883 poetry install
\u5b89\u88c5\u65f6\uff0c\u5b89\u88c5\u6240\u6709\u975e\u53ef\u9009\u7ec4\u7684\u4f9d\u8d56\u9879\u3002
\u6d4b\u8bd5\u4ee3\u7801\u4e2d\u540c\u65f6\u4f7f\u7528\u4e86 foo_file
\u7684 fixture \uff0c\u5b83\u5b9a\u4e49\u5728 conftest.py
\u4e2d\uff0c\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Test config\"\"\"\nimport tempfile\nimport pytest\nfrom click.testing import CliRunner\n@pytest.fixture()\ndef clicker():\n\"\"\"clicker fixture\"\"\"\nyield CliRunner()\n@pytest.fixture()\ndef foo_file():\n\"\"\"foo file\"\"\"\nwith tempfile.NamedTemporaryFile(mode='w') as file:\nfile.write('foo')\nfile.flush()\nyield file.name\n
\u7136\u540e\u5728\u547d\u4ee4\u884c\u4e2d\u8fd0\u884c pytest
\uff0c\u6d4b\u8bd5\u521a\u521a\u7f16\u5199\u7684\u6d4b\u8bd5\u4ee3\u7801\u3002\u53ef\u4ee5\u770b\u5230\u5982\u4e0b\u8f93\u51fa\uff1a
\u276f pytest\n================================================================================================================================= test session starts =================================================================================================================================\nplatform darwin -- Python 3.10.11, pytest-7.3.1, pluggy-1.0.0\nrootdir: /private/tmp/example_etl\nconfigfile: pyproject.toml\ntestpaths: tests\nplugins: pylint-0.19.0, mock-3.10.0\ncollected 12 items \n\ntests/test_cmdline.py ..... [ 35%]\ntests/test_exceptions.py . [ 42%]\ntests/test_extractor.py .. [ 57%]\ntests/test_log.py ..... [ 92%]\ntests/test_version.py . [100%]\n\n================================================================================================================================== warnings summary ===================================================================================================================================\n../../../Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pkg_resources/__init__.py:121\n /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pkg_resources/__init__.py:121: DeprecationWarning: pkg_resources is deprecated as an API\n warnings.warn(\"pkg_resources is deprecated as an API\", DeprecationWarning)\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n============================================================================================================================ 12 passed, 1 warning in 0.04s ============================================================================================================================\n
\u6d4b\u8bd5\u6210\u529f\u3002
\u8bf4\u660e\uff1a \u4e0a\u9762\u6d4b\u8bd5\u7ed3\u679c\u4e2d\u6709 pkg_resources
\u7684\u8b66\u544a\uff0c\u8fd9\u662f\u7531\u4e8e\u5f53\u524d\u7248\u672c\u7684 dynaocnf
\u4e2d\u7684\u4e00\u4e2a\u903b\u8f91\u5728 python3.10 \u4e0b\u88ab\u63d0\u793a\u51fa API \u5f03\u7528\u7684\u8b66\u544a\u9020\u6210\u7684\u3002\u8fd9\u4e2a\u95ee\u9898\u5728 dynaconf
\u7684\u4e0b\u4e00\u4e2a\u7248\u672c\u4e2d\u5df2\u7ecf\u4fee\u590d\u4e86\u3002\u5f53 dynaconf
\u7684\u4e0b\u4e00\u4e2a\u7248\u672c\u53d1\u5e03\u540e\uff0c\u53ef\u4ee5\u5c06 dynaconf
\u7684\u7248\u672c\u5347\u7ea7\u5230\u4e0b\u4e00\u4e2a\u7248\u672c\uff0c\u8fd9\u4e2a\u8b66\u544a\u5c31\u4f1a\u6d88\u5931\u3002\u5f53\u524d dynaconf
\u7248\u672c\u4e3a 3.1.12
\u3002
"},{"location":"guidelines/tutorial/test/#transformer","title":"\u6d4b\u8bd5 transformer","text":"\u5728 tests
\u5305\u4e2d\u521b\u5efa test_transformer.py
\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Test transformer\"\"\"\nimport pytest\nfrom example_etl.transformer.base import BaseTransformer\nfrom example_etl.transformer.strip import StripTransformer\ndef test_base_process(mocker):\n\"\"\"Test base transformer\"\"\"\nprocess = BaseTransformer(mocker.MagicMock())\nwith pytest.raises(NotImplementedError):\nprocess.transform('foo')\n@pytest.mark.parametrize(\n'data, expect_value',\n[\n('xx ', 'xx'),\n(' xx ', 'xx'),\n('xx', 'xx'),\n]\n)\ndef test_strip_process(mocker, data, expect_value):\n\"\"\"Test strip transformer\"\"\"\nprocessor = StripTransformer(mocker.MagicMock())\nres = processor.transform(data)\nassert res == expect_value\n
\u5728\u6d4b\u8bd5 test_strip_process
\u65f6\uff0c\u4f7f\u7528\u4e86 pytest \u7684\u53c2\u6570\u5316\u6d4b\u8bd5\u3002\u53ef\u4ee5\u5728\u4e00\u4e2a\u6d4b\u8bd5\u903b\u8f91\u4e2d\uff0c\u6d4b\u8bd5\u4e0d\u540c\u7684\u8f93\u5165\u8f93\u51fa\u503c\u3002
\u518d\u6b21\u8fd0\u884c pytest
\u547d\u4ee4\uff0c\u68c0\u6d4b\u6d4b\u8bd5\u662f\u5426\u6b63\u786e\u3002
"},{"location":"guidelines/tutorial/test/#loader","title":"\u6d4b\u8bd5 loader","text":"\u5728 tests
\u5305\u4e2d\u521b\u5efa test_loader.py
\uff0c\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Test loader\"\"\"\nimport tempfile\nfrom pathlib import Path\nimport pytest\nfrom example_etl.loader.base import BaseLoader\nfrom example_etl.loader.file import FileLoader\ndef test_base_dest(mocker):\n\"\"\"Test base loader\"\"\"\nclose_mock = mocker.patch.object(BaseLoader, 'close')\nwith BaseLoader(mocker.MagicMock()) as base:\nwith pytest.raises(NotImplementedError):\nbase.load('foo')\nassert close_mock.called_once()\ndef test_file_dest(mocker):\n\"\"\"Test file loader\"\"\"\nwith tempfile.NamedTemporaryFile() as file:\nsettings_mock = mocker.MagicMock()\nsettings_mock.FILE_LOADER_PATH = file.name\nwith FileLoader(settings_mock) as loader:\nloader.load('foo')\nfile = Path(file.name)\nstat = file.stat()\nassert stat.st_size == 3\n
\u5728\u6d4b\u8bd5 test_file_dest
\u65f6\uff0c\u4f7f\u7528\u4e86\u4e00\u4e2a\u4e34\u65f6\u6587\u4ef6\u4f5c\u4e3a\u76ee\u6807\u5199\u5165\uff0c\u4f7f\u7528\u4e86 with
\u5173\u952e\u5b57\u6253\u5f00\u6587\u4ef6\uff0c \u5728\u6d4b\u8bd5\u5b8c\u6210\u540e\uff0c\u4f1a\u81ea\u52a8\u5220\u9664\u4e34\u65f6\u6587\u4ef6\u3002
\u518d\u6b21\u8fd0\u884c pytest
\u68c0\u67e5\u6d4b\u8bd5\u7ed3\u679c\u3002
"},{"location":"guidelines/tutorial/test/#manage","title":"\u6d4b\u8bd5 manage","text":"manage
\u7684\u903b\u8f91\u540c\u6837\u9700\u8981\u6d4b\u8bd5\uff0c\u5728 tests
\u5305\u4e2d\u521b\u5efa test_manage.py
\u6587\u4ef6\uff0c\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Test manage\"\"\"\nimport pytest\nfrom example_etl.exceptions import PluginNotFoundError\nfrom example_etl.extractor.file import FileExtractor\nfrom example_etl.manage import Manage, get_extension\ndef test_get_extension():\n\"\"\"Test get extension\"\"\"\nplugin = get_extension('example_etl.extractor', 'file')\nassert plugin is FileExtractor\ndef test_get_extension_error():\n\"\"\"Test get extension error\"\"\"\nwith pytest.raises(PluginNotFoundError):\nget_extension('example_etl.extractor', 'xxx')\ndef test_manage_run(mocker):\n\"\"\"Test manage run\"\"\"\nmocker.patch('example_etl.manage.get_extension')\nprocess_mock = mocker.patch.object(Manage, 'transform')\nmanage = Manage()\nmanage.run()\nassert process_mock.called_once()\ndef test_manage_transform(mocker):\n\"\"\"Test manage transform\"\"\"\nmagic_mock = mocker.MagicMock()\nmanage = Manage()\nmanage.transformer = magic_mock\nmagic_mock.extract.return_value = [1, 2]\nmanage.transform(magic_mock, magic_mock)\nassert magic_mock.extract.called_once()\nassert magic_mock.load.call_count == 2\nassert magic_mock.transform.call_count == 2\n
\u5728\u6d4b\u8bd5\u65f6\uff0c\u9700\u8981\u4fdd\u8bc1\u914d\u7f6e\u6587\u4ef6\u4e2d\u5b58\u5728\u4e4b\u524d\u5728\u4ee3\u7801\u4e2d\u4f7f\u7528\u7684\u53d8\u91cf\u3002
\u518d\u6b21\u8fd0\u884c pytest
\u68c0\u67e5\u6d4b\u8bd5\u7ed3\u679c\u3002
"},{"location":"guidelines/tutorial/test/#_3","title":"\u68c0\u67e5\u6d4b\u8bd5\u8986\u76d6\u7387","text":"\u6d4b\u8bd5\u8986\u76d6\u7387\u6307\u793a\u7f16\u5199\u7684\u5355\u5143\u6d4b\u8bd5\uff0c\u8986\u76d6\u4e86\u591a\u5c11\u6e90\u4ee3\u7801\u3002\u80fd\u591f\u901a\u8fc7\u6d4b\u8bd5\u8986\u76d6\u7387\u67e5\u770b\u8fd8\u6709\u54ea\u4e9b\u5185\u5bb9\u6ca1\u6709\u88ab\u6d4b\u8bd5\u5230\u3002
\u589e\u52a0 pytest-cov
\u4f9d\u8d56\uff1a
poetry add --group dev pytest-cov\n
\u8fd0\u884c pytest --cov
\u67e5\u770b\u6d4b\u8bd5\u8986\u76d6\u7387\u3002
\u276f pytest --cov\n================================================================================================================================= test session starts =================================================================================================================================\nplatform darwin -- Python 3.10.11, pytest-7.3.1, pluggy-1.0.0\nrootdir: /private/tmp/example_etl\nconfigfile: pyproject.toml\ntestpaths: tests\nplugins: pylint-0.19.0, mock-3.10.0, cov-4.1.0\ncollected 23 items \n\ntests/test_cmdline.py ..... [ 21%]\ntests/test_extractor.py .. [ 30%]\ntests/test_loader.py .. [ 39%]\ntests/test_log.py ..... [ 60%]\ntests/test_manage.py .... [ 78%]\ntests/test_transformer.py .... [ 95%]\ntests/test_version.py . [100%]\n\n================================================================================================================================== warnings summary ===================================================================================================================================\n../../../Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pkg_resources/__init__.py:121\n /Users/kevin/Library/Caches/pypoetry/virtualenvs/example-etl-B-7RVLBy-py3.10/lib/python3.10/site-packages/pkg_resources/__init__.py:121: DeprecationWarning: pkg_resources is deprecated as an API\n warnings.warn(\"pkg_resources is deprecated as an API\", DeprecationWarning)\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n\n--------- coverage: platform darwin, python 3.10.11-final-0 ----------\nName Stmts Miss Cover\n-------------------------------------------------------------\nsrc/example_etl/__init__.py 1 0 100%\nsrc/example_etl/cmdline.py 26 0 100%\nsrc/example_etl/config/__init__.py 8 0 100%\nsrc/example_etl/constants.py 1 0 100%\nsrc/example_etl/exceptions.py 10 2 80%\nsrc/example_etl/extractor/__init__.py 0 0 100%\nsrc/example_etl/extractor/base.py 13 0 100%\nsrc/example_etl/extractor/file.py 12 0 100%\nsrc/example_etl/loader/__init__.py 0 0 100%\nsrc/example_etl/loader/base.py 12 0 100%\nsrc/example_etl/loader/file.py 15 0 100%\nsrc/example_etl/log.py 19 0 100%\nsrc/example_etl/manage.py 33 0 100%\nsrc/example_etl/transformer/__init__.py 0 0 100%\nsrc/example_etl/transformer/base.py 5 0 100%\nsrc/example_etl/transformer/strip.py 7 0 100%\ntests/__init__.py 7 0 100%\ntests/conftest.py 12 0 100%\ntests/test_cmdline.py 10 0 100%\ntests/test_extractor.py 14 0 100%\ntests/test_loader.py 20 0 100%\ntests/test_log.py 9 0 100%\ntests/test_manage.py 25 0 100%\ntests/test_transformer.py 12 0 100%\ntests/test_version.py 3 0 100%\n-------------------------------------------------------------\nTOTAL 274 2 99%\n\n============================================================================================================================ 23 passed, 1 warning in 0.08s ============================================================================================================================\n
\u901a\u8fc7\u8986\u76d6\u7387\u53ef\u4ee5\u770b\u5230 src/example_etl/exceptions.py
\u7684\u903b\u8f91\u8fd8\u6709\u6ca1\u6d4b\u8bd5\u7684\u3002
"},{"location":"guidelines/tutorial/test/#_4","title":"\u5b8c\u5584\u5176\u4ed6\u6d4b\u8bd5","text":"\u5728 tests
\u6a21\u5757\u4e2d\u521b\u5efa test_exceptions.py
\u6587\u4ef6\uff0c\u5185\u5bb9\u5982\u4e0b\uff1a
\"\"\"Test exception\"\"\"\nfrom example_etl.exceptions import PluginNotFoundError\ndef test_plugin_not_found_error():\n\"\"\"test plugin not found error\"\"\"\nerror = PluginNotFoundError('foo', 'bar')\nassert str(error) == 'Can not found \"bar\" plugin in foo'\n
\u518d\u6b21\u8fd0\u884c pytest --cov
\u53ef\u4ee5\u770b\u5230\u8986\u76d6\u7387 100% \u3002
"},{"location":"guidelines/tutorial/test/#_5","title":"\u4ee3\u7801\u98ce\u683c\u68c0\u6d4b","text":"\u4e3a\u4e86\u8ba9\u5f00\u53d1\u98ce\u683c\u8fbe\u5230\u7edf\u4e00\uff0c\u4f7f\u7528\u4ee3\u7801\u683c\u5f0f\u5316\u5de5\u5177\u68c0\u6d4b\u3002
\u4f7f\u7528 isort
\u5c06\u5bfc\u5305\u90e8\u5206\u683c\u5f0f\u5316\u4e3a\u7edf\u4e00\u683c\u5f0f\uff0c\u4f7f\u7528 pylint
\u68c0\u6d4b\u4ee3\u7801\u662f\u5426\u7b26\u5408 PEP8 \u89c4\u8303\uff0c\u540c\u65f6\u8fd8\u80fd\u68c0\u6d4b \u4e00\u4e9b\u4e0d\u6807\u51c6\u7684\u7684\u8bed\u6cd5\uff0c\u5e76\u7ed9\u51fa\u4fee\u6539\u5efa\u8bae\u3002
\u6267\u884c isort . --check-only --diff
\u68c0\u6d4b\u4ee3\u7801\u98ce\u683c\uff0c\u5e76\u4ec5\u8f93\u51fa\u4e0d\u7b26\u5408\u89c4\u8303\u7684\u5bfc\u5305\uff0c\u6267\u884c isort
\u4f1a\u81ea\u52a8\u683c\u5f0f \u5316\u4ee3\u7801\u3002
\u8fd0\u884c pylint src tests
\u68c0\u67e5 src
\u76ee\u5f55\u548c tests
\u76ee\u5f55\u4e0b\u7684 Python \u4ee3\u7801\u3002\u4f1a\u8f93\u51fa\u4e0d\u7b26\u5408\u89c4\u8303\u7684\u5185\u5bb9\uff0c\u7136\u540e \u6839\u636e\u5efa\u8bae\u4fee\u6539\u5373\u53ef\u3002
"},{"location":"guidelines/tutorial/test/#_6","title":"\u81ea\u52a8\u5316\u6d4b\u8bd5","text":"\u9879\u76ee\u9ed8\u8ba4\u5e26\u6709 tox
\u81ea\u52a8\u5316\u914d\u7f6e\u3002\u5f53\u5f00\u53d1\u5b8c\u6210\u540e\uff0c\u76f4\u63a5\u8fd0\u884c tox
\uff0c\u4f1a\u81ea\u52a8\u5728\u6a21\u62df\u73af\u5883\u4e2d\u6d4b\u8bd5\u4ee3\u7801\u3002\u6d4b\u8bd5\u65f6\uff0c\u4f1a \u521b\u5efa\u72ec\u7acb\u7684\u865a\u62df\u73af\u5883\uff0c\u7136\u540e\u5c06\u9879\u76ee\u6253\u5305\u5b89\u88c5\u5230\u73af\u5883\u4e2d\uff0c\u518d\u8fdb\u884c\u6d4b\u8bd5\u3002
tox
\u4f1a\u81ea\u52a8\u6267\u884c pytest
\u6d4b\u8bd5\uff0c \u5bfc\u5305\u68c0\u6d4b\uff0c\u4ee3\u7801\u98ce\u683c\u68c0\u6d4b\u3002
"},{"location":"guidelines/tutorial/tutorial/","title":"\u521d\u7ea7\u6559\u7a0b","text":"\u521d\u7ea7\u6559\u7a0b\u662f\u4e00\u4e2a ETL \u793a\u4f8b\u9879\u76ee\u3002\u5b83\u548c\u4e4b\u524d\u7684\u5feb\u901f\u4e0a\u624b\u4e0d\u540c\u7684\u662f\u5305\u542b\u4e86\u66f4\u591a Python \u5de5\u7a0b\u5316\u7684\u5185\u5bb9\u3002 \u4e3b\u8981\u4e00\u4e0b\u51e0\u4e2a\u70b9\uff1a
- \u4f7f\u7528\u9879\u76ee\u6a21\u677f\u521d\u59cb\u5316\u9879\u76ee
- \u4f7f\u7528\u914d\u7f6e\u7cfb\u7edf\u52a0\u8f7d\u9879\u76ee\u914d\u7f6e\uff0c\u5e76\u4e14\u53ef\u4ee5\u8bfb\u53d6\u5916\u90e8\u914d\u7f6e\u6587\u4ef6\uff0c\u4f7f\u7528 YAML \u683c\u5f0f\u6587\u4ef6
- \u4f7f\u7528\u63d2\u4ef6\u5316\u673a\u5236\u5f00\u53d1\u81ea\u5b9a\u4e49\u903b\u8f91\uff0c\u5e76\u80fd\u81ea\u52a8\u53d1\u73b0
- \u5b8c\u6574\u7684\u6253\u5305\u53d1\u5e03\u6d41\u7a0b\uff0c\u5e76\u5c06\u9879\u76ee\u53d1\u5e03\u5230 pypi
- \u7f16\u5199\u9879\u76ee\u8bf4\u660e\u6587\u6863\uff0c\u5e76\u81ea\u52a8\u6784\u5efa\u9759\u6001\u7ad9\u70b9
- \u6784\u5efa Docker \u955c\u50cf\uff0c\u9879\u76ee\u53ef\u4ee5\u5bb9\u5668\u5316\u8fd0\u884c
- \u5b8c\u6574\u7684\u5355\u5143\u6d4b\u8bd5\uff0c\u8986\u76d6\u7387 100%
"},{"location":"guidelines/tutorial/tutorial/#_2","title":"\u9879\u76ee\u8bbe\u8ba1","text":"ETL \u793a\u4f8b\u9879\u76ee\u7684\u8bbe\u8ba1\u6709\u4e09\u90e8\u5206\u7ec4\u6210\uff0c\u5206\u522b\u4e3a extractor
\u3001loader
\u548c transformer
\u3002 extractor
\u8d1f\u8d23\u4ece\u6e90\u76ee\u6807\u63d0\u53d6 \u6570\u636e\uff0c transform
\u8d1f\u8d23\u5904\u7406\u4e2d\u95f4\u7684\u8f6c\u6362\u903b\u8f91\uff0c loader
\u8d1f\u8d23\u5c06\u5904\u7406\u540e\u7684\u7ed3\u679c\u52a0\u8f7d\u5230\u76ee\u6807\u4f4d\u7f6e\u3002
\u5728\u4e09\u4e2a\u6a21\u5757\u4e2d\uff0c\u90fd\u6709\u5bf9\u5e94\u7684\u62bd\u8c61\u57fa\u7c7b\u3002\u5982\u679c\u9700\u8981\u81ea\u5b9a\u4e49\uff0c\u53ea\u9700\u5b9e\u73b0\u5bf9\u5e94\u7684\u903b\u8f91\uff0c\u5e76\u5c06\u5b9e\u73b0\u7684\u7c7b\u6ce8\u518c\u5230\u547d\u540d\u7a7a\u95f4\uff0c\u7136\u540e\u901a\u8fc7\u914d\u7f6e \u6587\u4ef6\u52a0\u8f7d\u5b9e\u73b0\u7684\u540d\u79f0\uff0c\u5373\u53ef\u6b63\u5e38\u4f7f\u7528\u3002
"},{"location":"guidelines/tutorial/tutorial/#_3","title":"\u4f7f\u7528\u8bf4\u660e","text":"\u56e0\u4e3a\u8be5\u9879\u76ee\u5df2\u7ecf\u53d1\u5e03\u5230 Pypi \u4e2d\uff0c\u6240\u4ee5\u53ef\u4ee5\u76f4\u63a5\u901a\u8fc7 pip \u547d\u4ee4\u5b89\u88c5\u4f7f\u7528\u3002
\u5efa\u8bae\u5728\u865a\u62df\u73af\u5883\u4e2d\u5b89\u88c5\uff01
pip install example-etl\n
\u7136\u540e\u4f7f\u7528 example_etl
\u547d\u4ee4
example_etl --help\n
\u9ed8\u8ba4\u53ea\u5b9e\u73b0\u4ece\u6587\u672c\u6309\u884c\u63d0\u53d6\uff0c\u7136\u540e\u5220\u9664\u6587\u672c\u524d\u540e\u7a7a\u683c\uff0c\u518d\u5c06\u6587\u672c\u5199\u5165\u76ee\u6807\u6587\u4ef6\u7684\u4e09\u4e2a\u5b9e\u73b0\u3002
"},{"location":"introduction/ides/","title":"Python \u5f00\u53d1\u5de5\u5177","text":"\u80fd\u591f\u505a Python \u5f00\u53d1\u7684\u5de5\u5177\u6709\u5f88\u591a\uff0c\u751a\u81f3\u6765\u8bf4\uff0c\u5982\u679c\u4f60\u4e60\u60ef\u6bd4\u8f83\u597d\uff0c\u90fd\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528 VIM \u6216\u8005 Windows \u4e0b\u7684\u8bb0\u4e8b\u672c \u6765\u7f16\u5199 Python \u4ee3\u7801\u3002\u4f46\u662f\u4e3a\u4e86\u9762\u5411\u4f01\u4e1a\u548c\u5de5\u7a0b\u5316\u5f00\u53d1\uff0c\u63a8\u8350\u4f7f\u7528\u96c6\u6210\u4e86\u8bf8\u591a\u7279\u6027\u7684\u5f00\u53d1\u5de5\u5177\uff0c\u6765\u6539\u5584\u5f00\u53d1\u4f53\u9a8c\u3002
\u5f53\u524d\u4e3b\u6d41\u7684\u5f00\u53d1\u5de5\u5177\u6709\uff1a
- Visual Studio Code
- Pycharm
- Eclipse
\u4e0a\u8ff0\u8f6f\u4ef6\u9664\u4e86 Pycharm \u4e13\u4e1a\u7248\u662f\u6536\u8d39\u7684\uff0c\u5176\u4f59\u90fd\u662f\u514d\u8d39\u3002\u5982\u679c\u9700\u8981\u4f7f\u7528 Pycharm\uff0c \u5efa\u8bae\u4f7f\u7528\u793e\u533a\u7248\uff0c\u6216\u8005\u8d2d\u4e70\u6b63\u7248\u3002
"},{"location":"introduction/ides/#visual-studio-code","title":"Visual Studio Code","text":"VScode \u662f\u5fae\u8f6f\u5f00\u53d1\u7684\u4e00\u6b3e\u6587\u672c\u7f16\u8f91\u5668\uff0c\u901a\u8fc7\u81ea\u5e26\u7684\u63d2\u4ef6\u7cfb\u7edf\uff0c\u53ef\u4ee5\u5c06\u6587\u672c\u7f16\u8f91\u5668\u6253\u9020\u6210\u4e00\u4e2a\u96c6\u6210\u5f00\u53d1\u5de5\u5177\u3002
\u4ece\u5b98\u7f51\u4e0b\u8f7d\u5e76\u5b89\u88c5\u3002
"},{"location":"introduction/ides/#_1","title":"\u914d\u7f6e","text":"\u4ece\u63d2\u4ef6\u4e2d\u5fc3\u5b89\u88c5\u4e2d\u6587\u63d2\u4ef6\uff1a
\u4ece\u63d2\u4ef6\u4e2d\u5fc3\u5b89\u88c5 Python Extension Pack
\u63d2\u4ef6\uff1a
"},{"location":"introduction/ides/#_2","title":"\u4f7f\u7528","text":"\u521b\u5efa\u4e00\u4e2a\u4e34\u65f6\u76ee\u5f55\uff0c\u7136\u540e\u9009\u62e9\u4f7f\u7528 vscode \u6253\u5f00\u3002Windows \u53ef\u4ee5\u901a\u8fc7\u53f3\u51fb\uff0c\u9009\u62e9 \u901a\u8fc7 Code \u6253\u5f00
\uff0c Linux \u53ef\u4ee5\u5728\u7ec8\u7aef\u4f7f\u7528 code demo
\u547d\u4ee4\u6253\u5f00\u3002
\u7136\u540e\u4f7f\u7528\u5feb\u6377\u952e Ctrl + `
\u7ec4\u5408\u952e\u6253\u5f00\u7ec8\u7aef\uff0c\u6267\u884c poetry init
\u6839\u636e\u63d0\u793a\u64cd\u4f5c\uff0c\u521d\u59cb\u5316 pyproject.toml
\u7684\u914d\u7f6e\u6587\u4ef6\uff0c\u6267\u884c poetry shell
\u8fdb\u884c\u865a\u62df\u73af\u5883\u521b\u5efa\uff0c\u6267\u884c poetry install
\u8fdb\u884c\u4f9d\u8d56\u5b89\u88c5\uff1a
\u7136\u540e\u4f7f\u7528 Ctrl + Shift + p
\u6253\u5f00 vscode \u7684\u6307\u4ee4\u7a97\u53e3\uff0c\u5728\u7a97\u53e3\u4e2d\u8f93\u5165 >python: select Interpreter
\u6765\u9009\u62e9\u9879\u76ee\u9700\u8981\u4f7f\u7528\u7684 Python \u89e3\u91ca\u5668\uff0c \u7136\u540e\u9009\u62e9\u4e0a\u9762\u4e00\u6b65\u521b\u5efa\u7684 Python \u89e3\u91ca\u5668\uff1a
\u7136\u540e\u53ef\u4ee5\u770b\u5230\u7a97\u53e3\u7684\u5de6\u4e0b\u89d2\u5df2\u7ecf\u51fa\u73b0\u4e86\u4e0a\u4e00\u6b65\u9009\u62e9 Python \u89e3\u91ca\u5668\u3002
\u521b\u5efa\u65b0\u6587\u4ef6 demo.py
\uff0c\u5e76\u8f93\u5165\u5982\u4e0b\u4ee3\u7801\uff1a
import sys\nprint(sys.version)\n
\u7136\u540e\u53f3\u51fb\u8be5\u6587\u4ef6\uff0c\u9009\u62e9 \u5728\u7ec8\u7aef\u4e2d\u8fd0\u884c Python \u6587\u4ef6
\uff1a
\u53ef\u4ee5\u770b\u5230\u8f93\u5165\uff1a
\u66f4\u591a\u5173\u4e8e\u5728 vscode \u4e2d\u4f7f\u7528 Python \u7684\u5185\u5bb9\uff0c\u8bf7\u53c2\u8003 Getting Started with Python in VS Code \u3002
"},{"location":"introduction/ides/#_3","title":"\u95ee\u9898\u6392\u67e5","text":""},{"location":"introduction/ides/#vscode","title":"vscode \u7ec8\u7aef\u65e0\u6cd5\u81ea\u52a8\u542f\u7528 \u865a\u62df\u73af\u5883","text":""},{"location":"introduction/ides/#_4","title":"\u95ee\u9898\u539f\u56e0","text":"\u7531\u4e8e poetry \u4e3a\u7ec8\u7aef(\u9ed8\u8ba4\u662f Powershell) \u542f\u52a8\u865a\u62df\u73af\u5883\u65f6\uff0c\u4f7f\u7528\u7684 ps1 \u811a\u672c\u6587\u4ef6\uff0c\u800c Powershell \u9ed8\u8ba4\u7684\u6267\u884c\u7b56\u7565 \u662f\u7981\u7528\u811a\u672c\u6587\u4ef6\u6267\u884c\u7684\u3002\u6240\u4ee5\u5f53 vscode \u914d\u7f6e\u4e86\u865a\u62df\u73af\u5883\u540e\uff0c\u542f\u52a8\u7ec8\u7aef\uff0c\u4f1a\u65e0\u6cd5\u6267\u884c\u811a\u672c\u6587\u4ef6\u3002
"},{"location":"introduction/ides/#_5","title":"\u89e3\u51b3\u65b9\u6cd5","text":"\u53c2\u8003 about_Execution_Policies \u7684\u8bf4\u660e\uff0c\u5e76\u66f4\u6539\u5f53\u524d\u7528\u6237\u7684 Powershell \u6267\u884c\u7b56\u7565\uff1a
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser\n
\u7136\u540e\u5173\u95ed\u7ec8\u7aef\u7a97\u53e3\u540e\u518d\u6b21\u6253\u5f00\u5373\u53ef\u3002
"},{"location":"introduction/install/","title":"Python \u73af\u5883\u5b89\u88c5","text":"\u672c\u6587\u4ee5\u622a\u56fe\u8bb0\u5f55\u7684\u5f62\u5f0f\u5c55\u793a\u5982\u4f55\u5728\u4e3b\u6d41\u64cd\u4f5c\u7cfb\u7edf\u4e0a\u5b89\u88c5 Python \u73af\u5883\u3002
\u5728 Python \u73af\u5883\u9009\u62e9\u4e0a\uff0c\u63a8\u8350\u4f7f\u7528\u8f83\u65b0\u7684 Python \u7248\u672c\u3002\u6839\u636e\u5b98\u65b9\u53d1\u5e03\u6d88\u606f \uff0c \u81ea 2020\u5e74 1 \u6708 1 \u65e5\u8d77\uff0c Python 2 \u5c06\u505c\u6b62\u7ef4\u62a4\uff0c\u5305\u62ec\u4efb\u4f55\u65b0\u7684\u9519\u8bef\u62a5\u544a\u3001\u4fee\u590d\u548c\u66f4\u6539\u3002 \u6240\u4ee5\u5f3a\u70c8\u5efa\u8bae\u4f60\u5728\u7248\u672c\u9009\u62e9\u4e0a\u4f7f\u7528 Python 3.7 \u4e4b\u540e\u7684\u7248\u672c\u3002\u8003\u8651\u5230 Python 3 \u5404\u4e2a\u7248\u672c\u7684\u65b0\u7279\u6027\u548c\u517c\u5bb9\u6027\uff0c \u5efa\u8bae\u9009\u62e9 Python 3.9 \u6216 Python 3.10 \u3002
\u622a\u6b62\u5230\u5f53\u524d\u65f6\u95f4\uff082021-12-03\uff09\uff0c Python \u5404\u4e2a\u7248\u672c\u7684\u72b6\u6001\u5982\u4e0b\uff1a
Branch Schedule Status First release End-of-life main PEP 664 features 2022-10-03 2027-10 3.10 PEP 619 bugfix 2021-10-04 2026-10 3.9 PEP 596 bugfix 2020-10-05 2025-10 3.8 PEP 569 security 2019-10-14 2024-10 3.7 PEP 537 security 2018-06-27 2023-06-27 3.6 PEP 494 security 2016-12-23 2021-12-23 \u672c\u6587\u5b89\u88c5\u7684\u7248\u672c\u4f7f\u7528\u6700\u65b0\u7684\u7a33\u5b9a\u7248 python 3.10
\uff0c\u4f1a\u5728\u5982\u4e0b\u64cd\u4f5c\u7cfb\u7edf\u4e0a\u5b89\u88c5\uff1a
- Windows 11 \uff1a \u5b89\u88c5\u5305\u5b89\u88c5
- Ubuntu Desktop 21 \uff1a \u6e90\u4ee3\u7801\u7f16\u8bd1\u5b89\u88c5
\u4ece Python \u4e0b\u8f7d\u9875\u9762 \u627e\u5230 Python 3.10 \u7684\u4e0b\u8f7d\u9875\u9762 \u7136\u540e\u4e0b\u8f7d\u5bf9\u5e94\u7684\u6587\u4ef6\u5373\u53ef\u3002
"},{"location":"introduction/install/#1","title":"1 \u5b89\u88c5","text":"\u5173\u4e8e Python \u7684\u5b89\u88c5\u4f7f\u7528\u7684\u66f4\u591a\u7ec6\u8282\uff0c\u53ef\u4ee5\u53c2\u8003 Python\u5b89\u88c5\u548c\u4f7f\u7528 \u3002
"},{"location":"introduction/install/#11-windows-11","title":"1.1 Windows 11","text":""},{"location":"introduction/install/#111-python","title":"1.1.1 \u5b89\u88c5 Python \u73af\u5883","text":"\u4e0b\u8f7d Windows installer(64-bit) \u5230\u672c\u5730\uff0c\u7136\u540e\u53cc\u51fb\u8fd0\u884c\u5b89\u88c5\u6587\u4ef6\u3002
\u6ce8\u610f\uff1a\u5b89\u88c5\u65f6\uff0c\u9700\u8981\u8d26\u6237\u63a7\u5236\u6743\u9650\u3002
\u5982\u679c\u60f3\u8981\u5c06 Python \u5b89\u88c5\u5230\u9ed8\u8ba4\u76ee\u5f55\uff0c\u76f4\u63a5\u70b9\u51fb Install Now
\u5373\u53ef\u3002
\u70b9\u51fb Customize Installation
:
\u6b64\u65f6\u53ef\u4ee5\u9009\u62e9\u53ef\u9009\u7684\u7279\u6027\uff0c\u4e0d\u8fc7\u5982\u679c\u4f60\u4e0d\u77e5\u9053\u5b83\u4eec\u662f\u505a\u4ec0\u4e48\u7684\uff0c\u6216\u8005\u4e0d\u6e05\u695a\u4f60\u662f\u5426\u9700\u8981\u5b83\u4eec\uff0c\u90a3\u4e48\u4fdd\u6301\u9ed8\u8ba4\u5373\u53ef\u3002 \u7136\u540e\u70b9\u51fb Next
\uff1a
\u7136\u540e\u8fdb\u884c\u5982\u4e0b\u64cd\u4f5c\uff1a
- \u52fe\u9009
Install for all users
\u5c06 Python \u5b89\u88c5\u4e3a\u6240\u6709\u7528\u6237\u53ef\u7528 - \u52fe\u9009
Add Python to environment variables
\u5c06\u4f1a\u81ea\u52a8\u521b\u5efa Python \u7684\u73af\u5883\u53d8\u91cf\u3002\u6b64\u9009\u9879\u4f1a\u5728 Windows \u73af\u5883 PATH
\u4e2d\u65b0\u589e\u4e24\u4e2a\u53d8\u91cf C:\\devtools\\Python310\\Scripts\\
\u548c C:\\devtools\\Python310\\
\u3002\u76ee\u5f55\u4e3a Python \u7684\u5b89\u88c5\u76ee\u5f55\u3002 - \u5982\u679c\u6709\u9700\u8981\uff0c\u4fee\u6539
Customize install location
\u4e0b\u7684\u5b89\u88c5\u8def\u5f84\u3002
\u7136\u540e\u70b9\u51fb Install
\uff0c\u5c06 Python \u5b89\u88c5\u5230\u6307\u5b9a\u7684\u76ee\u5f55\u3002\u6b64\u8fc7\u7a0b\u9700\u8981\u8d26\u6237\u6388\u6743\u3002
\u7b49\u5f85\u5b89\u88c5\u5b8c\u6210\u540e\uff0c\u70b9\u51fb Close
\u3002\u5f53\u7136\u5efa\u8bae\u70b9\u51fb Disable path length limit
\uff0c\u6765\u7981\u7528 Windows \u4e0b\u7684 260 \u5b57\u8282\u6587\u4ef6 \u8def\u5f84\u7684\u9650\u5236\u3002
\u81f3\u6b64\u5b89\u88c5\u5b8c\u6210\u3002
\u66f4\u591a\u5173\u4e8e Windows \u7cfb\u7edf\u7684\u5176\u4ed6\u7ec6\u8282\uff0c\u8bf7\u53c2\u8003 \u5728Windows\u4e0a\u4f7f\u7528 Python \u3002
"},{"location":"introduction/install/#112-python","title":"1.1.2 \u6d4b\u8bd5 Python \u73af\u5883","text":"\u6253\u5f00 Windows \u7684 CMD \uff0c\u7136\u540e\u8f93\u5165 python --version
\u5373\u53ef\u83b7\u5f97 Python \u7248\u672c\uff1a
"},{"location":"introduction/install/#12-ubuntu-desktop-21","title":"1.2 Ubuntu Desktop 21","text":"\u5bf9\u4e8e\u7f16\u8bd1\u5b89\u88c5\uff0c\u9002\u7528\u4e8e\u5927\u90e8\u5206 Linux \u7cfb\u7edf\uff0c\u9664\u4e86 Python \u5b89\u88c5\u8fc7\u7a0b\u4e2d\u7684\u4f9d\u8d56\u5305\u5728\u7279\u5b9a\u64cd\u4f5c\u7cfb\u7edf\u4e2d\u6709\u533a\u522b\u5916\uff0c\u5176\u4ed6\u64cd\u4f5c\u90fd\u662f\u4e00\u81f4\u7684\u3002
"},{"location":"introduction/install/#121","title":"1.2.1 \u5b89\u88c5\u4f9d\u8d56","text":"sudo apt-get install build-essential gdb lcov pkg-config \\\nlibbz2-dev libffi-dev libgdbm-dev libgdbm-compat-dev liblzma-dev \\\nlibncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev \\\nlzma lzma-dev tk-dev uuid-dev zlib1g-dev\n
"},{"location":"introduction/install/#122-python","title":"1.2.2 \u5b89\u88c5 Python \u73af\u5883","text":"\u4e0b\u8f7d XZ compressed source tarball \u6e90\u7801\u5305\uff0c\u7136\u540e\u89e3\u538b\u5230 /tmp
\uff0c \u7136\u540e\u89e3\u538b\uff1a
cd /tmp/\nwget https://www.python.org/ftp/python/3.10.0/Python-3.10.0.tar.xz\ntar -Jxf Python-3.10.0.tar.xz\ncd Python-3.10.0/\n
\u4f7f\u7528 ./configure \u8fdb\u884c\u9884\u7f16\u8bd1\u3002\u5728\u9884\u7f16\u8bd1\u8fc7\u7a0b\u4e2d\uff0c\u53ef\u4ee5\u6307\u5b9a\u8981\u7f16\u8bd1\u5230\u6e90\u4ee3\u7801\u4e2d\u7684\u5185\u5bb9\u3002\u4f7f\u7528 ./configure --help \u53ef\u4ee5\u67e5\u770b\u652f\u6301\u54ea\u4e9b\u9009\u9879\u3002
\u4e00\u822c\u4f1a\u8fdb\u884c\u5982\u4e0b\u64cd\u4f5c\uff1a
./configure --enable-optimizations\n
\u5982\u679c\u9700\u8981\u5b89\u88c5\u5230\u5176\u4ed6\u4f4d\u7f6e\uff0c\u53ef\u4ee5\u4f7f\u7528 --prefix=/usr/bin \u6307\u5b9a\u3002\u9ed8\u8ba4\u662f\u5b89\u88c5\u5230 /usr/local/bin \u3002
\u5f53\u51fa\u73b0\u5982\u4e0b\u8f93\u51fa\uff0c\u8bf4\u660e\u9884\u7f16\u8bd1\u5b8c\u6210\uff1a
creating Modules/Setup.local\ncreating Makefile\n
\u7f16\u8bd1 \u4f7f\u7528 make \u547d\u4ee4\u7f16\u8bd1\u6784\u5efa
make -s -j2\n
-j \u53ef\u4ee5\u6307\u5b9a\u5e76\u53d1\u6784\u5efa\u4efb\u52a1\u6570\u3002\u5982\u679c\u591a\u6838 CPU \u53ef\u4ee5\u6307\u5b9a\u6838\u5fc3\u6570\u3002
\u5b89\u88c5
sudo make altinstall\n
\u4f7f\u7528 altinstall \u53ef\u4ee5\u907f\u514d\u8986\u76d6\u7cfb\u7edf\u73b0\u6709\u9ed8\u8ba4\u547d\u4ee4\u3002\u5373\u4e0d\u4f1a\u8986\u76d6 python \u547d\u4ee4\u3002
"},{"location":"introduction/install/#123-python","title":"1.2.3 \u6d4b\u8bd5 Python \u73af\u5883","text":"\u6253\u5f00\u7ec8\u7aef\uff0c\u8fd0\u884c python3.10 --version
\u4f1a\u8f93\u51fa Python \u7684\u7248\u672c\u3002
\u81f3\u6b64 Python \u73af\u5883\u5b89\u88c5\u5b8c\u6210\u3002
\u66f4\u591a\u5173\u4e8e Unix \u7cfb\u7edf\u7684\u5176\u4ed6\u7ec6\u8282\uff0c\u8bf7\u53c2\u8003 \u5728\u7c7bUnix\u73af\u5883\u4e0b\u4f7f\u7528Python \u3002
"},{"location":"introduction/install/#2","title":"2 \u4ed3\u5e93\u52a0\u901f","text":"\u9274\u4e8e\u56fd\u5185\u7f51\u7edc\u7684\u95ee\u9898\uff0c\u4e3a\u4e86\u5feb\u901f\u5b89\u88c5 Python \u4f9d\u8d56\u5305\uff0c\u6700\u597d\u4f7f\u7528\u56fd\u5185\u955c\u50cf\u4ed3\u5e93\u52a0\u901f Pypi \u7684\u5305\u3002
Pypi \u56fd\u5185\u955c\u50cf\u6709\u5f88\u591a\uff0c\u73b0\u5728\u63a8\u8350\u5982\u4e0b\u51e0\u4e2a\uff1a
- \u6e05\u534e mirror
- \u963f\u91cc\u4e91 mirror
- 163 mirror
\u4e0b\u9762\u4f7f\u7528\u963f\u91cc\u4e91\u955c\u50cf\u914d\u7f6e\uff0c\u5982\u679c\u9700\u8981\u4f7f\u7528\u5176\u4ed6\u955c\u50cf\u4ed3\u5e93\uff0c\u6539\u52a8 index-url
\u540e\u9762\u7684\u5730\u5740\u5373\u53ef\uff1a
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/\n
"},{"location":"introduction/install/#3","title":"3 \u591a\u73af\u5883\u5171\u5b58","text":"\u591a\u73af\u5883\u5171\u5b58\u662f\u4e3a\u4e86\u5728\u540c\u4e00\u4e2a\u64cd\u4f5c\u7cfb\u7edf\u4e2d\uff0c\u540c\u65f6\u4f7f\u7528\u4e0d\u540c\u7248\u672c\u7684 Python \u73af\u5883\uff0c\u6216\u8005\u7f16\u5199\u7684\u7a0b\u5e8f\u9700\u8981\u5728 \u4e0d\u540c\u7248\u672c\u4e0b\u8fd0\u884c\u6d4b\u8bd5\u3002
"},{"location":"introduction/install/#31-windows","title":"3.1 Windows","text":"\u7ecf\u6d4b\u8bd5\uff0c\u7531\u4e8e DLL \u7684\u95ee\u9898\uff0c\u65e0\u6cd5\u901a\u8fc7 Windows \u7684 mklink
\u547d\u4ee4\u8f6f\u8fde\u63a5\u4e00\u4e2a\u65b0\u7684 python.exe
\u53ef\u6267\u884c\u7a0b\u5e8f\u7684\u522b\u540d\u3002
"},{"location":"introduction/install/#32-linux","title":"3.2 Linux","text":"Linux \u672c\u8eab\u7684\u4f18\u52bf\uff0c\u53ef\u4ee5\u4f7f\u7528\u8f6f\u8fde\u63a5\u751f\u6210\u4e0d\u540c\u7684\u53ef\u6267\u884c\u6587\u4ef6\u540d\u3002\u5728\u5b89\u88c5\u597d Python 3.10 \u7248\u672c\u540e\uff0c\u9ed8\u8ba4\u4f1a\u5728\u751f\u6210 /usr/local/bin/python3.10
\u53ef\u6267\u884c\u6587\u4ef6\u3002\u5982\u679c\u9700\u8981\u5c06\u9ed8\u8ba4\u7684 Python \u547d\u4ee4\u66ff\u6362\u4e3a python3.10
\u5219\u53ef\u4ee5\u5220\u9664\u539f\u6709\u7684 python
\u547d\u4ee4\uff0c\u7136\u540e\u91cd\u65b0\u8f6f\u8fde\u63a5\u3002
# \u5907\u4efd\u5f53\u524d\u9ed8\u8ba4\u7684 python3 \u547d\u4ee4\u5230 /tmp\nmv /usr/bin/python3 /tmp\n# \u91cd\u65b0\u8fde\u63a5 python3 \u547d\u4ee4\nln -s /usr/local/bin/python3.10 /usr/bin/python3\n\n# \u5907\u4efd\u5f53\u524d\u9ed8\u8ba4 pip3 \u547d\u4ee4\nmv /usr/bin/pip3 /tmp\n# \u91cd\u65b0\u8fde\u63a5 pip3 \u547d\u4ee4\nln -s /usr/local/bin/pip3.10 /usr/bin/pip3\n
"},{"location":"introduction/install/#4","title":"4 \u95ee\u9898\u6392\u67e5","text":""},{"location":"introduction/install/#41-linux","title":"4.1 Linux \u5b89\u88c5\u51fa\u73b0\u95ee\u9898","text":"\u5982\u679c\u7f16\u8bd1\u8fc7\u7a0b\u4e2d\u51fa\u73b0\u95ee\u9898\uff0c\u8bf7\u68c0\u67e5\u4f9d\u8d56\u662f\u5426\u5b89\u88c5\u5b8c\u6210\u3002
Debian / Ubuntu \u7cfb\u5217\u64cd\u4f5c\u7cfb\u7edf\u4f9d\u8d56\u5982\u4e0b\uff1a
sudo apt-get install build-essential gdb lcov pkg-config \\\nlibbz2-dev libffi-dev libgdbm-dev libgdbm-compat-dev liblzma-dev \\\nlibncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev \\\nlzma lzma-dev tk-dev uuid-dev zlib1g-dev\n
\u5bf9\u4e8e RHEL \u7cfb\u5217\u64cd\u4f5c\u7cfb\u7edf\uff0c\u4f9d\u8d56\u5b89\u88c5\u5982\u4e0b\uff1a
sudo dnf install dnf-plugins-core # install this to use 'dnf builddep'\nsudo dnf builddep python3\n
"},{"location":"introduction/install/#42-python","title":"4.2 \u5378\u8f7d Python","text":"\u6ce8\u610f\uff1a\u5982\u679c\u662f Linux \u64cd\u4f5c\u7cfb\u7edf\uff0c\u4f60\u5e94\u8be5\u81f3\u5c11\u4fdd\u7559\u7cfb\u7edf\u7684\u9ed8\u8ba4 Python \u73af\u5883\uff0c\u6216\u8005\u4e00\u4e2a\u5176\u4ed6\u7248\u672c\u7684 PYthon \u73af\u5883\uff0c\u5426\u5219 \u64cd\u4f5c\u7cfb\u7edf\u53ef\u80fd\u65e0\u6cd5\u6b63\u5e38\u4f7f\u7528\u3002
\u8981\u5378\u8f7d\u5bf9\u5e94\u7248\u672c\u7684 Python \u73af\u5883\uff0c\u53ea\u9700\u8981\u5c06\u7cfb\u7edf\u6839\u76ee\u5f55\u76f8\u5173\u76ee\u5f55\u67e5\u627e\u5230\uff0c\u7136\u540e\u5220\u9664\u5373\u53ef\u3002
\u5bf9\u4e8e\u7f16\u8bd1\u5b89\u88c5\u7684 Python \u73af\u5883\uff0c\u4f1a\u5c06 Python \u5b89\u88c5\u5230\u5982\u4e0b\u51e0\u4e2a\u76ee\u5f55\uff1a
/usr/lib/python3.10
/usr/local/lib/libpython3.10.a
/usr/local/lib/python3.10
/usr/local/include/python3.10
/usr/local/bin/python3.10-config
/usr/local/bin/python3.10
/usr/local/share/man/man1/python3.10.1
\u8fd0\u884c\u547d\u4ee4\u5220\u9664\uff1a
# \u521b\u5efa\u5907\u4efd\u76ee\u5f55\uff0c\u4ee5\u4fbf\u51fa\u73b0\u95ee\u9898\uff0c\u53ef\u4ee5\u6267\u884c\u6062\u590d\n# \u6ce8\u610f\u4e0d\u8981\u5728 /tmp \u4e0b\u521b\u5efa\uff0c\u5982\u679c\u91cd\u542f\u7cfb\u7edf /tmp \u4e0b\u7684\u6587\u4ef6\u4f1a\u5220\u9664\u3002\n# \u653e\u5728\u5bb6\u76ee\u5f55\uff0c\u53ef\u4ee5\u901a\u8fc7\u5e94\u6025\u6a21\u5f0f\u627e\u5230\u76f8\u5e94\u6587\u4ef6\u3002\n# \u7b49\u786e\u4fdd\u64cd\u4f5c\u7cfb\u7edf\u6ca1\u6709\u4efb\u4f55\u5f02\u5e38\u95ee\u9898\u7684\u65f6\u5019\uff0c\u518d\u5220\u9664\nmkdir ~/removed_python310\nmv -f \\\n/usr/lib/python3.10 \\\n/usr/local/lib/libpython3.10.a \\\n/usr/local/lib/python3.10 \\\n/usr/local/include/python3.10 \\\n/usr/local/bin/python3.10-config \\\n/usr/local/bin/python3.10 \\\n/usr/local/share/man/man1/python3.10.1 \\\n~/removed_python310\n
\u5982\u679c\u4f60\u66fe\u4f7f\u7528\u8fc7 pip3.10
\u5b89\u88c5\u4f9d\u8d56\uff0c\u8bf7\u68c0\u67e5\u7528\u6237\u76ee\u5f55\u4e0b\u662f\u5426\u5b58\u5728\u76f8\u5173\u4f9d\u8d56\u76ee\u5f55\uff1a
/home/god/.local/lib/python3.10
"},{"location":"introduction/virtualenv/","title":"\u865a\u62df\u73af\u5883","text":"\u6587\u7ae0\u8981\u70b9\uff1a
- \u4ecb\u7ecd Python \u7684\u865a\u62df\u73af\u5883
- \u4ecb\u7ecd\u5e76\u4f7f\u7528 Python \u4e2d\u5e38\u89c1\u7684\u865a\u62df\u73af\u5883
- \u603b\u7ed3\u5f00\u53d1\u4e2d\u7684\u865a\u62df\u73af\u5883\u7684\u4f7f\u7528\u5b9e\u8df5
"},{"location":"introduction/virtualenv/#1","title":"1. \u6982\u8ff0","text":"Python \u5e94\u7528\u7a0b\u5e8f\u901a\u5e38\u4f1a\u4f7f\u7528\u4e0d\u5728\u6807\u51c6\u5e93\u5185\u7684\u8f6f\u4ef6\u5305\u548c\u6a21\u5757\u3002\u5e94\u7528\u7a0b\u5e8f\u6709\u65f6\u9700\u8981\u7279\u5b9a\u7248\u672c\u7684\u5e93\uff0c\u56e0\u4e3a\u5e94\u7528\u7a0b\u5e8f\u53ef\u80fd\u9700\u8981\u4fee\u590d\u7279\u5b9a\u7684\u9519\u8bef\uff0c\u6216\u8005\u53ef\u4ee5\u4f7f\u7528\u5e93\u7684\u8fc7\u65f6\u7248\u672c\u7684\u63a5\u53e3\u7f16\u5199\u5e94\u7528\u7a0b\u5e8f\u3002
\u8fd9\u610f\u5473\u7740\u4e00\u4e2a Python \u73af\u5883\u53ef\u80fd\u65e0\u6cd5\u6ee1\u8db3\u6bcf\u4e2a\u5e94\u7528\u7a0b\u5e8f\u7684\u8981\u6c42\u3002\u5982\u679c\u5e94\u7528\u7a0b\u5e8f A \u9700\u8981\u7279\u5b9a\u6a21\u5757\u7684 1.0 \u7248\u672c\uff0c\u4f46\u5e94\u7528\u7a0b\u5e8f B \u9700\u8981 2.0 \u7248\u672c\uff0c\u5219\u9700\u6c42\u5b58\u5728\u51b2\u7a81\uff0c\u5b89\u88c5\u7248\u672c 1.0 \u6216 2.0 \u5c06\u5bfc\u81f4\u67d0\u4e00\u4e2a\u5e94\u7528\u7a0b\u5e8f\u65e0\u6cd5\u8fd0\u884c\u3002
\u8fd9\u4e2a\u95ee\u9898\u7684\u89e3\u51b3\u65b9\u6848\u662f\u521b\u5efa\u4e00\u4e2a virtual environment \uff0c\u4e00\u4e2a\u76ee\u5f55\u6811\uff0c\u5176\u4e2d\u5b89\u88c5\u6709\u7279\u5b9aPython\u7248\u672c\uff0c\u4ee5\u53ca\u8bb8\u591a\u5176\u4ed6\u5305\u3002
\u7136\u540e\uff0c\u4e0d\u540c\u7684\u5e94\u7528\u5c06\u53ef\u4ee5\u4f7f\u7528\u4e0d\u540c\u7684\u865a\u62df\u73af\u5883\u3002 \u8981\u89e3\u51b3\u5148\u524d\u9762\u4f8b\u5b50\u4e2d\u7684\u51b2\u7a81\uff0c\u5e94\u7528\u7a0b\u5e8f A \u53ef\u4ee5\u62e5\u6709\u81ea\u5df1\u7684\u5b89\u88c5\u4e86 1.0 \u7248\u672c\u7684\u865a\u62df\u73af\u5883\uff0c\u800c\u5e94\u7528\u7a0b\u5e8f B \u5219\u62e5\u6709\u5b89\u88c5\u4e86 2.0 \u7248\u672c\u7684\u53e6\u4e00\u4e2a\u865a\u62df\u73af\u5883\u3002 \u5982\u679c\u5e94\u7528\u7a0b\u5e8f B \u8981\u6c42\u5c06\u67d0\u4e2a\u5e93\u5347\u7ea7\u5230 3.0 \u7248\u672c\uff0c\u4e5f\u4e0d\u4f1a\u5f71\u54cd\u5e94\u7528\u7a0b\u5e8f A \u7684\u73af\u5883\u3002
"},{"location":"introduction/virtualenv/#2","title":"2. \u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177","text":"\u73b0\u5728 Python \u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\u8d8a\u6765\u8d8a\u5f3a\u5927\u3002\u5e38\u89c1\u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\u5982\u4e0b\uff1a
venv
\uff1a Python \u6807\u51c6\u5e93\u4e2d\u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177 conda
\uff1a Anaconda \u4e0b\u7684\u7ba1\u7406\u5de5\u5177 Virtualenv
\uff1a \u7b2c\u4e09\u65b9\u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\uff0c\u73b0\u5728\u5728 Pypa \u4e2d\u7ef4\u62a4\u3002 Pipenv
\uff1a \u7b2c\u4e09\u65b9\u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\uff0c\u73b0\u5728\u5728 Pypa \u4e2d\u7ef4\u62a4\u3002 poetry
\uff1a \u7b2c\u4e09\u65b9\u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\u3002
"},{"location":"introduction/virtualenv/#21-venv","title":"2.1 venv","text":"venv
\u662f Python \u6807\u51c6\u5e93\u4e2d\u7684\u4e00\u4e2a\u6a21\u5757\u3002\u5982\u679c\u7cfb\u7edf\u4e2d\u6709\u591a\u4e2a\u7248\u672c\u7684 Python \u73af\u5883\uff0c\u53ef\u4ee5\u521b\u5efa\u6307\u5b9a\u7248\u672c\u7684\u865a\u62df\u73af\u5883\u3002
\u521b\u5efa\u865a\u62df\u73af\u5883\uff1a
\u5728\u5f53\u524d\u76ee\u5f55\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a demo
\u7684\u865a\u62df\u73af\u5883\u76ee\u5f55\uff1a
python3 -m venv demo\n
\u5982\u679c demo
\u4e0d\u5b58\u5728\uff0c\u5c31\u4f1a\u521b\u5efa\u8be5\u76ee\u5f55\uff0c\u540c\u65f6\u5728\u91cc\u9762\u521b\u5efa Python \u89e3\u91ca\u5668\uff0c\u6807\u51c6\u5e93\u548c\u5404\u79cd\u652f\u6301\u6587\u4ef6\u7684\u526f\u672c\u76ee\u5f55\u3002
\u901a\u5e38\u521b\u5efa\u4ee5\u70b9\u5f00\u5934\u7684 .venv
\u76ee\u5f55\u3002\u65e2\u53ef\u4ee5\u505a\u5230\u9690\u85cf\u76ee\u5f55\u7684\u6548\u679c\uff0c\u4e5f\u53ef\u4ee5\u548c\u5e38\u89c1\u7684 .env
\u73af\u5883\u53d8\u91cf\u5b9a\u4e49\u6587\u4ef6\u533a\u5206\u3002
\u4f7f\u7528\u865a\u62df\u73af\u5883\uff1a
\u4e0b\u9762\u6fc0\u6d3b\u73af\u5883\u53d8\u91cf
Windows:
demo\\Scripts\\activate.bat\n
Unix \u6216 MacOs \u4e0a\uff1a
source demo/bin/active\n
\u6fc0\u6d3b\u540e\u5c31\u53ef\u4ee5\u5728\u7ec8\u7aef\u4e2d\u4f7f\u7528\u521b\u5efa\u7684\u865a\u62df\u73af\u5883\u4e86\u3002
$ source demo/bin/activate\n(demo) $ python\nPython 3.7.3 (default, Oct 28 2020, 14:33:53)\n[GCC 8.3.0] on linux\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n>>> import sys\n>>> sys.version\n'3.7.3 (default, Oct 28 2020, 14:33:53) \\n[GCC 8.3.0]'\n>>> sys.path\n['', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/tmp/test/demo/lib/python3.7/site-packages']\n
\u9000\u51fa\u865a\u62df\u73af\u5883\uff1a
deactive\n
"},{"location":"introduction/virtualenv/#22-conda","title":"2.2 Conda","text":"Conda \u662f\u5728 Windows\uff0c macOS \u548c Linux \u4e0a\u8fd0\u884c\u7684\u5f00\u6e90\u8f6f\u4ef6\u5305\u7ba1\u7406\u7cfb\u7edf\u548c\u73af\u5883\u7ba1\u7406\u7cfb\u7edf\u3002 Conda \u5feb\u901f\u5b89\u88c5\uff0c\u8fd0\u884c\u548c\u66f4\u65b0\u8f6f\u4ef6\u5305\u53ca\u5176\u4f9d\u8d56\u9879\u3002Conda \u53ef\u4ee5\u8f7b\u677e\u5730\u5728\u672c\u5730\u8ba1\u7b97\u673a\u4e0a\u7684\u73af\u5883\u4e2d\u521b\u5efa\uff0c\u4fdd\u5b58\uff0c\u52a0\u8f7d\u548c\u5207\u6362\u3002\u5b83\u662f\u4e3a Python \u7a0b\u5e8f\u521b\u5efa\u7684\uff0c\u4f46\u53ef\u4ee5\u6253\u5305\u548c\u5206\u53d1\u9002\u7528\u4e8e\u4efb\u4f55\u8bed\u8a00\u7684\u8f6f\u4ef6\u3002
Conda \u4f5c\u4e3a\u8f6f\u4ef6\u5305\u7ba1\u7406\u5668\u53ef\u4ee5\u5e2e\u52a9\u60a8\u67e5\u627e\u548c\u5b89\u88c5\u8f6f\u4ef6\u5305\u3002\u5982\u679c\u60a8\u9700\u8981\u4e00\u4e2a\u9700\u8981\u4f7f\u7528\u5176\u4ed6\u7248\u672c\u7684 Python \u7684\u8f6f\u4ef6\u5305\uff0c\u5219\u65e0\u9700\u5207\u6362\u5230\u5176\u4ed6\u73af\u5883\u7ba1\u7406\u5668\uff0c\u56e0\u4e3a Conda \u4e5f\u662f\u73af\u5883\u7ba1\u7406\u5668\u3002\u4ec5\u9700\u51e0\u4e2a\u547d\u4ee4\uff0c\u60a8\u5c31\u53ef\u4ee5\u8bbe\u7f6e\u4e00\u4e2a\u5b8c\u5168\u72ec\u7acb\u7684\u73af\u5883\u6765\u8fd0\u884c\u8be5\u4e0d\u540c\u7248\u672c\u7684Python\uff0c\u540c\u65f6\u7ee7\u7eed\u5728\u6b63\u5e38\u73af\u5883\u4e2d\u8fd0\u884c\u60a8\u901a\u5e38\u7684 Python \u7248\u672c\u3002
\u5728\u9ed8\u8ba4\u914d\u7f6e\u4e0b\uff0cConda \u53ef\u4ee5\u5b89\u88c5\u548c\u7ba1\u7406\u5728 repo.anaconda.com \u4e0a\uff0c\u7531 Anaconda\u00ae \u5ba1\u67e5\u548c\u7ef4\u62a4\u7684\u4e0a\u5343\u4e2a\u8f6f\u4ef6\u5305\u3002
Conda\u53ef\u4ee5\u4e0e Travis CI \u548c AppVeyor \u7b49\u6301\u7eed\u96c6\u6210\u7cfb\u7edf\u7ed3\u5408\u4f7f\u7528\uff0c\u4ee5\u63d0\u4f9b\u9891\u7e41\uff0c\u81ea\u52a8\u7684\u4ee3\u7801\u6d4b\u8bd5\u3002
\u6240\u6709\u7248\u672c\u7684 Anaconda \u548c Miniconda \u4e2d\u90fd\u5305\u542b conda \u8f6f\u4ef6\u5305\u548c\u73af\u5883\u7ba1\u7406\u5668\u3002
\u64cd\u4f5c\u524d\u63d0\uff1a
\u8bf7\u786e\u4fdd Python \u73af\u5883\u662f\u7531 Anaconda \u6216 Miniconda \u63d0\u4f9b\u7684\u3002
\u521b\u5efa\u865a\u62df\u73af\u5883\uff1a
\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a demo
\u76ee\u5f55\u7684\u865a\u62df\u73af\u5883
conda create --name demo\n
\u4f7f\u7528\u865a\u62df\u73af\u5883\uff1a
C:\\Users\\test>conda activate demo\n\n(demo) C:\\Users\\test>python\nPython 3.8.3 (default, Jul 2 2020, 17:30:36) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n>>> import sys\n>>> sys.path\n['', 'C:\\\\ProgramData\\\\Anaconda3\\\\python38.zip', 'C:\\\\ProgramData\\\\Anaconda3\\\\DLLs', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib', 'C:\\\\ProgramData\\\\Anaconda3', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\win32', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\win32\\\\lib', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\Pythonwin']\n>>> sys.version\n'3.8.3 (default, Jul 2 2020, 17:30:36) [MSC v.1916 64 bit (AMD64)]'\n
\u9000\u51fa\u865a\u62df\u73af\u5883\uff1a
deactivate\n
"},{"location":"introduction/virtualenv/#23-virtualenv","title":"2.3 Virtualenv","text":"Virtualenv \u662f\u4e00\u4e2a\u7b2c\u4e09\u65b9\u5e93\uff0c\u73b0\u5728\u7531 Pypa \u7ba1\u7406\u3002\u5176\u5177\u6709\u6bd4 venv
\u66f4\u5f3a\u5927\u7684\u529f\u80fd\uff0c\u4f46\u73b0\u5728 Virtualenv \u7684\u4e00\u4e9b\u529f\u80fd\u4e5f\u5728\u6162\u6162\u662f\u914d\u5230 venv
\u4e0a\u3002
\u5b89\u88c5\uff1a
pip install -U virtualenv\n
Virtualenv \u5728 Conda \u73af\u5883\u4e0b\u4f1a\u6709 Bug \u30021
\u521b\u5efa\u865a\u62df\u73af\u5883\uff1a
\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a venv
\u76ee\u5f55\u7684\u865a\u62df\u73af\u5883
virtualenv venv\n
\u4f7f\u7528\u865a\u62df\u73af\u5883\uff1a
source venv/bin/activate\n
\u597d\u7528\u7684\u5de5\u5177\uff1a
\u642d\u914d VirtualenvWrapper \u53ef\u4ee5\u66f4\u65b9\u4fbf\u7684\u4f7f\u7528\u548c\u7ba1\u7406\u865a\u62df\u73af\u5883\u3002
Linux \u5b89\u88c5\uff1a
pip install virtualenvwrapper\n# \u6267\u884c virtualvnewrapper \u521d\u59cb\u5316\u811a\u672c\u3002\u53ef\u4ee5\u5c06\u4e0b\u9762\u8fd9\u4e00\u884c\u52a0\u5165\u5230 `~/.bashrc` \u4e2d\uff0c\u65b9\u4fbf\u5f53\u524d\u7528\u6237\u4f7f\u7528\uff0c\u6216\u8005\u52a0\u5165\u5230 `/etc/profile` \u4e2d\u65b9\u4fbf\u6240\u6709\u7528\u6237\u4f7f\u7528\nsource /usr/local/bin/virtualenvwrapper.sh\n
\u521b\u5efa\u865a\u62df\u73af\u5883\uff1a
# \u6267\u884c\u547d\u4ee4\uff0c\u9ed8\u8ba4\u4f1a\u5728 `~/.virtualenvs` \u4e0b\u521b\u5efa\u5bf9\u5e94\u540d\u79f0\u7684\u865a\u62df\u73af\u5883\u76ee\u5f55\uff0c\u540c\u65f6\u521d\u59cb\u5316\u865a\u62df\u73af\u5883\u3002\n# \u6240\u6709\u865a\u62df\u73af\u5883\u90fd\u4f1a\u96c6\u4e2d\u5b58\u653e\u5728\u8fd9\u91cc\u3002\u907f\u514d\u4e86\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\u6709\u865a\u62df\u73af\u5883\u76ee\u5f55\u3002\nmkvirtualenv venv\n
\u4f7f\u7528\u865a\u62df\u73af\u5883\uff1a
workon venv\n
\u5220\u9664\u865a\u62df\u73af\u5883\uff1a
rmvirtualenv venv\n
Windows \u5b89\u88c5\uff1a
pip install virtualenvwrapper-win\n
\u8bbe\u7f6e\u73af\u5883\u53d8\u91cf WORKON_HOME=D:/virtualenvs
\u4f7f\u7528\u7684\u65b9\u6cd5\u548c\u4e0a\u9762\u4e00\u81f4\u3002
"},{"location":"introduction/virtualenv/#24-pipenv","title":"2.4 Pipenv","text":"Pipenv \u662f\u4e00\u4e2a\u66f4\u9ad8\u7ea7\u7684\u865a\u62df\u73af\u5883\u7ba1\u7406\u5de5\u5177\uff0c\u5176\u4f9d\u8d56 Virtualenv
\uff0c\u5e76\u5728\u4e4b\u4e0a\u505a\u4e86\u8bb8\u591a\u5176\u4ed6\u529f\u80fd\u3002\u6b63\u5982\u5176\u5b98\u7f51\u4e2d\u6240\u8bf4\uff0c\u5b83\u7684\u76ee\u7684\u662f\u8981\u628a\u6240\u6709\u6700\u597d\u7684\u5305\u7ba1\u7406\uff08 bundler
, composer
, npm
\uff0c yarn
\u7b49\uff09\u5f15\u5165\u5230 Python \u4e2d\u3002
Pipenv \u5177\u6709\u5982\u4e0b\u7279\u70b9
- \u96c6\u4e2d\u5b58\u50a8\u865a\u62df\u73af\u5883\uff0c\u5982\u679c\u4e0d\u5b58\u5728\u5219\u76f4\u63a5\u521b\u5efa\u3002\u53ef\u4ee5\u901a\u8fc7
WORKON_HOME
\u73af\u5883\u53d8\u91cf\u914d\u7f6e\u3002 - \u751f\u6210
Pipfile
\u548c Pipfile.lock
\u3002\u524d\u8005\u8bb0\u5f55\u4f9d\u8d56\u9879\u3001\u5b89\u88c5\u6e90\u3001\u8981\u4f7f\u7528\u7684 Python \u7248\u672c\uff0c\u540e\u8005\u8bb0\u5f55\u6240\u5b89\u88c5\u7684\u7684\u7248\u672c\u7684 Hash \u503c\u7b49\u4fe1\u606f\u3002 - \u81ea\u52a8\u5b89\u88c5\u5378\u8f7d\u4f9d\u8d56\uff0c\u81ea\u52a8\u6e05\u9664\u65e0\u7528\u7684\u4f9d\u8d56\u3002
- \u81ea\u52a8\u52a0\u8f7d
.env
\u6587\u4ef6\u3002 - \u80fd\u6839\u636e\u4f9d\u8d56\u6811\u7684\u5173\u7cfb\u68c0\u6d4b\u4f9d\u8d56\u51b2\u7a81\u3002
\u5b89\u88c5\uff1a
pip install pipenv\n
\u521b\u5efa\u865a\u62df\u73af\u5883\uff1a
\u5728\u9879\u76ee\u6839\u76ee\u5f55\u6267\u884c pipenv install
\uff1a
root@b2e8a92bace7:~/demo# pipenv install\nCreating a virtualenv for this project...\nPipfile: /root/demo/Pipfile\nUsing /usr/local/bin/python3 (3.7.7) to create virtualenv...\n\u2838 Creating virtual environment...created virtual environment CPython3.7.7.final.0-64 in 175ms\n creator CPython3Posix(dest=/root/.virtualenvs/demo-xfYnOzmm, clear=False, no_vcs_ignore=False, global=False) \n seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/root/.local/share/virtualenv) \n added seed packages: pip==20.2.4, setuptools==50.3.2, wheel==0.35.1 \n activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator \n\n\u2714 Successfully created virtual environment! \nVirtualenv location: /root/.virtualenvs/demo-xfYnOzmm\nCreating a Pipfile for this project...\nPipfile.lock not found, creating...\nLocking [dev-packages] dependencies...\nLocking [packages] dependencies...\nUpdated Pipfile.lock (a65489)!\nInstalling dependencies from Pipfile.lock (a65489)...\n \ud83d\udc0d \u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589\u2589 0/0 \u2014 00:00:00\nTo activate this project's virtualenv, run pipenv shell.\nAlternatively, run a command inside the virtualenv with pipenv run.\n
\u4f7f\u7528\u865a\u62df\u73af\u5883\uff1a
\u5355\u6b21\u4f7f\u7528
# \u67e5\u770b\u865a\u62df\u73af\u5883\u7684 Python \u7248\u672c\npipenv run python --version\n
\u8fdb\u5165\u865a\u62df\u73af\u5883
pipenv shell\n
\u5b89\u88c5\u4f9d\u8d56\uff1a
pipenv install tox\n
\u4f9d\u8d56\u5b89\u88c5\u5b8c\u6210\u540e\uff0c\u4f1a\u66f4\u65b0 Pipfile
\u6587\u4ef6\uff0c\u540c\u65f6\u66f4\u65b0 Pipfile.lock
\u6587\u4ef6\uff0c\u8bb0\u5f55\u5b89\u88c5\u7684\u7248\u672c\u548c\u5bf9\u5e94 HASH \u503c\u3002
"},{"location":"introduction/virtualenv/#25-poetry","title":"2.5 Poetry (\u63a8\u8350\u4f7f\u7528)","text":"Poetry \u662f\u540e\u671f\u4e4b\u79c0\uff0c\u5b83\u7684\u96c4\u5fc3\u4e0d\u4ec5\u4ec5\u662f\u505a Pipenv \u7684\u4e8b\uff0c\u5b83\u8fd8\u60f3\u628a Python \u7684\u6253\u5305\u7ba1\u7406\u4e00\u5e76\u505a\u4e86\uff0c\u5e76\u6d88\u9664 setup.py
\u6587\u4ef6\u3002\u5b83\u4f7f\u7528\u57fa\u4e8e PEP517 \u89c4\u8303\u7684 pyproject.toml
\u6587\u4ef6\u8bb0\u5f55\u4fe1\u606f\uff0c\u5e76\u6253\u5305\u3002 \u5177\u4f53\u5185\u5bb9\u53ef\u4ee5\u53c2\u8003 PEP 517 -- A build-system independent format for source trees \u3002 \u5f53\u524d\u57fa\u4e8e PEP517 \u7684\u6784\u5efa\u6a21\u5f0f\u5df2\u7ecf\u5b8c\u5168\u53ef\u7528\u3002\u5728 pip \u7684\u53d1\u884c\u8bb0\u5f55\u4e2d\uff0c\u6700\u65e9\u662f\u5728 18.1 (2018-10-05) \u5c31\u5f15\u5165\u4e86 PEP517 \u7684 0.2
\u7248\u672c\u3002
\u5728\u4f7f\u7528\u4e0a\uff0cPoetry \u7ed9\u4eba\u7684\u611f\u89c9\u66f4\u73b0\u4ee3\u5316\u3002
\u5b89\u88c5\uff1a
pip install poetry\n
\u4f7f\u7528\uff1a
# \u4f7f\u7528\u524d\u9700\u8981\u5148\u521d\u59cb\u5316\u9879\u76ee\u7684\u57fa\u672c\u4fe1\u606f\uff0c\u751f\u6210 `pyproject.toml` \u6587\u4ef6\npoetry init\n\n# \u5b89\u88c5\u4f9d\u8d56\npoetry add tox\n\n# \u8fdb\u5165\u865a\u62df\u73af\u5883\npoetry shell\n\n# \u6784\u5efa\u9879\u76ee\npoetry build\n\n# \u53d1\u5e03\u9879\u76ee\npoetry publish\n
"},{"location":"introduction/virtualenv/#3-poetry","title":"3. \u865a\u62df\u73af\u5883\u5b9e\u8df5(Poetry)","text":"\u4f17\u591a\u7684\u865a\u62df\u73af\u5883\uff0c\u548c\u5bf9\u5e94\u7684\u5de5\u5177\uff0c\u5728\u9009\u62e9\u65f6\u96be\u514d\u6709\u70b9\u56f0\u60d1\uff0c\u8981\u9009\u62e9\u4e00\u4e2a\u597d\u7528\u7684\u5de5\u5177\uff0c\u6700\u4f73\u9014\u5f84\u5c31\u662f\u81ea\u5df1\u90fd\u5c1d\u8bd5\u4e00\u904d\u3002
\u4e0a\u8ff0\u51e0\u4e2a\u4e3b\u6d41\u865a\u62df\u73af\u5883\u5de5\u5177\u9664\u4e86 venv
\u662f\u5185\u7f6e\u5e93\uff0c\u5176\u4ed6\u51e0\u4e2a\u90fd\u662f\u57fa\u4e8e Virtualenv
\u518d\u6b21\u5f00\u53d1\uff0c\u5e76\u63d0\u4f9b\u4e86\u5176\u4ed6\u529f\u80fd\u3002\u4f46\u662f Virtualenv
\u6ca1\u6709\u63d0\u4f9b\u4f9d\u8d56\u68c0\u6d4b\u7684\u529f\u80fd\uff0c\u800c\u4e14\u4f9d\u8d56\u5305\u7684\u7ba1\u7406\u8fd8\u662f\u9700\u8981\u4f7f\u7528 pip
\u547d\u4ee4\uff0c\u4f9d\u8d56\u9879\u9700\u8981\u901a\u8fc7 requirements.txt
\u3002
\u5f53\u4f60\u9700\u8981\u7ba1\u7406\u4e0d\u540c\u5f00\u53d1\u73af\u5883\u4e0b\u7684\u4f9d\u8d56\u65f6\uff0c\u5c31\u9700\u8981\u4e24\u4e2a\u6216\u66f4\u591a\u4e2a requirements.txt
\u3002\u4f8b\u5982 requirements-devlopment.txt
\uff0c requirements-production.txt
\u6216\u8fd9 requirements-test.txt
\u3002
Poetry
\u662f\u4e00\u4e2a\u66f4\u9177\u7684\u5de5\u5177\uff0c\u65e0\u8bba\u662f\u4ea4\u4e92\u5730\u8f93\u51fa\uff0c\u8fd8\u662f\u5b83\u57fa\u4e8e PEP517 \u7684\u7279\u6027\u3002\u5b83\u5141\u8bb8\u60a8\u58f0\u660e\u60a8\u7684\u9879\u76ee\u6240\u4f9d\u8d56\u7684\u5e93\uff0c\u5b83\u5c06\u4e3a\u60a8\u7ba1\u7406\uff08\u5b89\u88c5/\u66f4\u65b0\uff09\u5b83\u4eec\u3002Poetry \u63d0\u4f9b\u4e86\u4e00\u4e2a\u9501\u5b9a\u6587\u4ef6\u4ee5\u786e\u4fdd\u53ef\u91cd\u590d\u5b89\u88c5\uff0c\u5e76\u53ef\u4ee5\u6784\u5efa\u60a8\u7684\u9879\u76ee\u4ee5\u4f9b\u5206\u53d1\u3002 poetry \u901a\u8fc7\u914d\u7f6e\u6587\u4ef6 pyproject.toml \u6765\u5b8c\u6210\u4f9d\u8d56\u7ba1\u7406\u3001\u73af\u5883\u914d\u7f6e\u3001\u57fa\u672c\u4fe1\u606f\u914d\u7f6e\u7b49\u529f\u80fd\uff0c\u76f8\u5f53\u4e8e\u628a Python \u9879\u76ee\u4e2d\u7684 Pipfile\u3001setup.py\u3001setup.cfg\u3001requirements.txt\u3001MANIFEST.in \u878d\u5408\u5230\u4e00\u8d77\u3002
\u7efc\u5408\u6765\u770b Poetry
\u5c31\u663e\u5f97\u66f4\u52a0\u5408\u9002\uff0c\u652f\u6301\u591a\u79cd\u73af\u5883\u7ba1\u7406\uff0c\u63d0\u4f9b\u4f9d\u8d56\u5173\u7cfb\u6821\u9a8c\uff0c\u548c\u4f9d\u8d56\u7684 poetry.lock \u6587\u4ef6\uff0c\u4e5f\u6709\u81ea\u52a8\u7ba1\u7406\u4f9d\u8d56\u7684\u64cd\u4f5c\u3002\u540c\u65f6\u53ef\u4ee5\u7528\u4e8e Python \u5de5\u7a0b\u6253\u5305\u548c\u53d1\u5e03\u3002
\u4e0b\u9762\u4ee5\u4e00\u4e2a\u9879\u76ee\u7684\u751f\u547d\u5468\u671f\u63cf\u8ff0\u5982\u4f55\u66f4\u597d\u7684\u4f7f\u7528 Poetry
\u3002
"},{"location":"introduction/virtualenv/#31","title":"3.1 \u521d\u59cb\u5316\u9879\u76ee","text":"\u521d\u59cb\u5316\u9879\u76ee\uff0c\u4f7f\u7528 Poetry
\u5728\u9879\u76ee\u6839\u76ee\u5f55\u521b\u5efa\u5f53\u524d\u9879\u76ee\u7684\u865a\u62df\u73af\u5883\u3002
poetry init\n
\u8fdb\u5165\u5f53\u524d\u9879\u76ee\u7684\u865a\u62df\u73af\u5883\u3002
poetry shell\n
"},{"location":"introduction/virtualenv/#32","title":"3.2 \u5b89\u88c5\u9879\u76ee\u4f9d\u8d56","text":"\u5f53\u9700\u8981\u533a\u5206\u5f00\u53d1\u73af\u5883\u548c\u666e\u901a\u73af\u5883\u65f6\uff0c\u5c31\u53ef\u4ee5\u901a\u8fc7 add -D
\u9009\u9879\u5b89\u88c5\u5f00\u53d1\u73af\u5883\u4f9d\u8d56
poetry add -D pytest tox\n
\u4e00\u822c\u7684\u4f9d\u8d56\u76f4\u63a5\u5b89\u88c5\u5373\u53ef\u3002
poetry add django requests scrapy sqlalchemy\n
"},{"location":"introduction/virtualenv/#33","title":"3.3 \u6e05\u7406\u4f9d\u8d56","text":"\u5f53\u9700\u8981\u4ece\u73af\u5883\u4e2d\u6e05\u9664\u4e0d\u5728\u9700\u8981\u7684\u4f9d\u8d56\u65f6\uff0c\u53ef\u4ee5\u4f7f\u7528\u547d\u4ee4\u5378\u8f7d
poetry remove scrapy\n
\u6216\u8005\u76f4\u63a5\u4fee\u6539 pyproject.toml
\u6587\u4ef6\uff0c\u5220\u9664\u4e0d\u518d\u9700\u8981\u7684\u5185\u5bb9\uff0c\u7136\u540e\u901a\u8fc7 poetry lock
\u66f4\u65b0 poetry.lock
\u6587\u4ef6\u3002
"},{"location":"introduction/virtualenv/#34","title":"3.4 \u90e8\u7f72","text":"\u5728\u90e8\u7f72\u65f6\uff0c\u5f3a\u70c8\u63a8\u8350\u4f7f\u7528 poetry install
\u5b89\u88c5\u5728 pyproject.toml
\u6587\u4ef6\u4e2d\u4f9d\u8d56\u5305\u3002
"},{"location":"introduction/virtualenv/#35-requirementstxt","title":"3.5 \u751f\u6210 requirements.txt
","text":"\u4f7f\u7528 poetry show
\u53ef\u4ee5\u770b\u5230\u6240\u6709\u4f9d\u8d56\u5217\u8868\u3002
# \u67e5\u770b\u6240\u6709\u4f9d\u8d56\npoetry show\n# \u4ec5\u6240\u6709\u5f00\u53d1\u4f9d\u8d56\npoetry show --only dev
poetry export -f requirements.txt --output --without-hashes\n
-
\u5982\u679c\u4f60\u7684\u662f Conda \u73af\u5883\uff0c\u8bf7\u4f7f\u7528 20.0.34
\u4e4b\u524d\u7684 Virtualenv \u3002\u5177\u4f53\u8bf7\u53c2\u8003 virtualenv==20.0.34 not compatible with python on windows #12094 \u548c conda support - Windows 3.7+ #1986 \u3002\u5982\u679c\u8fd9\u4e2a\u95ee\u9898\u5df2\u7ecf\u4fee\u590d\uff0c\u8bf7\u5ffd\u7565\u3002\u00a0\u21a9
"},{"location":"practices/web/","title":"\u5feb\u901f\u4e0a\u624b","text":"\u8fd9\u662f\u4e00\u4e2a\u5feb\u901f\u4e0a\u624b\u7684\u793a\u4f8b\u9879\u76ee\uff0c\u65e8\u5728\u901a\u8fc7\u4e00\u4e2a\u5c3d\u53ef\u80fd\u5305\u542b\u4e3b\u8981\u77e5\u8bc6\u70b9\u7684\u7b80\u5355\u9879\u76ee\uff0c\u6765\u5411\u4f7f\u7528\u8005\u5c55\u793a\u4e00\u4e2a\u66f4 Python \u5316\u7684\u9879\u76ee\u5f00\u53d1\u6d41\u7a0b\u3002
\u793a\u4f8b\u9879\u76ee\u662f\u4e00\u4e2a\u4f7f\u7528\u5f02\u6b65\u5fae Web \u6846\u67b6 Fastapi \u5f00\u53d1\u7684\u535a\u5ba2\u7cfb\u7edf\u3002\u9879\u76ee\u4e1a\u52a1\u529f\u80fd\u6bd4\u8f83\u7b80\u5355\uff0c\u4f46\u5b8c\u6574\u4f53\u73b0\u4e86\u4e00\u4e2a\u9879\u76ee\u4ece\u73af\u5883\u642d\u5efa\uff0c\u5230\u5f00\u53d1\uff0c\u6700\u540e\u6d4b\u8bd5\u53d1\u5e03\u7684\u5b8c\u6574\u6d41\u7a0b\u3002
"},{"location":"practices/web/#1","title":"1. \u5f00\u53d1\u73af\u5883\u642d\u5efa","text":""},{"location":"practices/web/#11-python","title":"1.1 Python \u73af\u5883","text":"\u9274\u4e8e\u5b98\u65b9\u5df2\u7ecf\u505c\u6b62\u5bf9 Python 2 \u7684\u652f\u6301 1 \uff0c\u6211\u4eec\u4e0d\u63a8\u8350\u518d\u4f7f\u7528 Python 2 \u8fdb\u884c\u5f00\u53d1\u3002\u6839\u636e\u5f53\u524d Python \u7248\u672c\u4f7f\u7528\u60c5\u51b5\uff0c\u63a8\u8350\u4f7f\u7528 Python 3.7+ \u3002
\u5177\u4f53\u7684\u7248\u672c\u7684 Python \u73af\u5883\u53ef\u4ee5\u5728 \u5b98\u7f51 \u4e0b\u8f7d\u3002\u4e3a\u4e86\u4f7f\u7528\u4fbf\u5229\u6027\uff0c\u53ef\u4ee5\u9009\u62e9 Anaconda 2 \u3002
"},{"location":"practices/web/#12","title":"1.2 \u5f00\u53d1\u5de5\u5177","text":"\u63a8\u8350\u4f7f\u7528 Pycharm \u4f5c\u4e3a\u4e3b\u8981\u5f00\u53d1\u5de5\u5177\uff0c\u53ef\u4ee5\u9009\u62e9\u793e\u533a\u7248\u672c\u514d\u8d39\u4f7f\u7528\u3002
Visual Studio Code \u662f\u5fae\u8f6f\u5f00\u53d1\u7684\u4e00\u6b3e\u514d\u8d39\u8f7b\u91cf\u6587\u672c\u7f16\u8f91\u5668\uff0c\u901a\u8fc7\u5b89\u88c5\u63d2\u4ef6\u53ef\u4ee5\u81ea\u5b9a\u4e49\u6210\u4e00\u6b3e\u529f\u80fd\u5f3a\u5927\u7684 IDE \u3002\u5728\u5bf9 Python \u7684\u652f\u6301\u4e0a\uff0c\u5df2\u7ecf\u6709\u4e86\u8f83\u4e3a\u5b8c\u5584\u7684\u63d2\u4ef6\u4f53\u7cfb\uff0c\u6b64\u65b9\u6848\u4e5f\u53ef\u4ee5\u4f5c\u4e3a\u5907\u7528\u3002
"},{"location":"practices/web/#13","title":"1.3 \u865a\u62df\u73af\u5883\u5de5\u5177","text":"\u63a8\u8350\u4f7f\u7528 poetry\u3002poetry \u76f8\u6bd4\u4f7f\u7528 requirements.txt
\u7ba1\u7406\u4f9d\u8d56\u5217\u8868\uff0c\u66f4\u52a0\u5f3a\u5927\u3002\u5b83\u652f\u6301\u540c\u65f6\u7ba1\u7406\u5f00\u53d1\u751f\u4ea7\u73af\u5883\u4f9d\u8d56\uff0c\u81ea\u52a8\u67e5\u627e\u865a\u62df\u73af\u5883\uff0c\u751f\u6210\u4f9d\u8d56\u9501\u5b9a\u6587\u4ef6\u7b49\u5176\u4ed6\u7279\u6027\u3002
\u5728\u5b89\u88c5\u597d Python \u73af\u5883\u540e\uff0c\u5e94\u8be5\u5728\u5168\u5c40\u73af\u5883\u4e2d\u5b89\u88c5 poetry \u3002
"},{"location":"practices/web/#14-git","title":"1.4 Git \u4f7f\u7528","text":"\u63a8\u8350\u4f7f\u7528 Git \u5bf9\u9879\u76ee\u8fdb\u884c\u7248\u672c\u7ba1\u7406\u3002\u6240\u4ee5\u9700\u8981\u63d0\u524d\u5b89\u88c5 Git \uff0c\u5e76\u719f\u6089\u5e38\u7528 Git \u7684\u6982\u5ff5\u548c\u5e38\u7528 Git \u547d\u4ee4\u3002
"},{"location":"practices/web/#2","title":"2. \u9879\u76ee\u521d\u59cb\u5316","text":""},{"location":"practices/web/#21","title":"2.1 \u521d\u59cb\u5316\u9879\u76ee\u7ed3\u6784","text":"\u9879\u76ee\u7ed3\u6784\u91c7\u7528 src
\u76ee\u5f55\u7ed3\u6784\uff0c\u8be6\u89c1 pypa/sampleproject \u3002
\u521b\u5efa\u9879\u76ee\u76ee\u5f55\u7ed3\u6784\uff1a
.\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 src\n\u2502 \u2514\u2500\u2500 example_blog\n\u2502 \u2514\u2500\u2500 __init__.py\n\u2514\u2500\u2500 tests\n \u2514\u2500\u2500 __init__.py\n
\u521d\u59cb\u5316\u9879\u76ee\u865a\u62df\u73af\u5883\uff1a
poetry init\n
\u6839\u636e\u4ea4\u4e92\u5f0f\u63d0\u793a\uff0c\u8fdb\u884c\u76f8\u5e94\u5185\u5bb9\u9009\u53d6\u586b\u5199\uff0c\u5b89\u88c5\u5b8c\u6210\u540e\uff0c\u9879\u76ee\u76ee\u5f55\u4f1a\u81ea\u52a8\u751f\u6210 pyproject.toml
\u6587\u4ef6\u3002
"},{"location":"practices/web/#22","title":"2.2 \u521d\u59cb\u5316\u9879\u76ee\u57fa\u672c\u4fe1\u606f","text":"\u7f16\u8f91 pyproject.toml
\u6587\u4ef6\uff0c \u914d\u7f6e\u9879\u76ee\u63cf\u8ff0\u4fe1\u606f\uff1a
[tool.poetry]\nname = \"example_blog\"\nversion = \"0.1.0\"\ndescription = \"This is example blog system.\"\nauthors = [\"huagang517 <huagang517@126.com>\"]\nreadme = \"README.md\"\n[tool.poetry.dependencies]\npython = \"^3.10\"\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n
"},{"location":"practices/web/#23","title":"2.3 \u589e\u52a0\u9879\u76ee\u81ea\u8ff0\u6587\u4ef6","text":"\u7f16\u5199 README.md
\u6587\u4ef6
# \u4e00\u4e2a\u7b80\u5355\u535a\u5ba2\u7cfb\u7edf\u793a\u4f8b.\n\u6b64\u9879\u76ee\u662f\u4e00\u4e2a\u7b80\u5355\u7684\u535a\u5ba2\u7cfb\u7edf\uff0c\u63d0\u4f9b\u4e00\u4e9b\u7528\u6237\u7ba1\u7406\u548c\u535a\u5ba2\u6587\u7ae0\u7ba1\u7406\u3002\u76ee\u7684\u662f\u6f14\u793a\u5982\u4f55\u505a\u4e00\u4e2a\u66f4\u52a0 Pythonic \u7684\u9879\u76ee\u3002\n\n\u5982\u679c\u60a8\u6709\u4efb\u4f55\u610f\u89c1\u548c\u5efa\u8bae\uff0c\u6b22\u8fce\u5f00\u542f ISSUE \u53d1\u8d77\u8ba8\u8bba\u3002\u671f\u5f85\u4e0e\u60a8\u6253\u9020\u66f4\u52a0\u5b8c\u7f8e\u7684 Python \u793a\u4f8b\u3002\n\n## \u534f\u4f5c\u5f00\u53d1\n- Fork \u4ed3\u5e93\n- \u7f16\u5199\u4ee3\u7801\uff0c\u6d4b\u8bd5\uff0c\u63d0\u4ea4\n- \u53d1\u8d77 PR\n- \u5ba1\u6838\u901a\u8fc7\u540e\u5408\u5e76\uff0c\u534f\u4f5c\u5b8c\u6210\n
"},{"location":"practices/web/#24-gitignore","title":"2.4 \u589e\u52a0 .gitignore
","text":"# Created by .ignore support plugin (hsz.mobi)\n### Python template\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n# Usually these files are written by a python script from a template\n# before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n# For a library or package, you might want to ignore these files since the code is\n# intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n# However, in case of collaboration, if having platform-specific dependencies or dependencies\n# having no cross-platform support, pipenv may install dependencies that don't work, or not\n# install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n### Windows template\n# Windows thumbnail cache files\nThumbs.db\nThumbs.db:encryptable\nehthumbs.db\nehthumbs_vista.db\n\n# Dump file\n*.stackdump\n\n# Folder config file\n[Dd]esktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n### Linux template\n*~\n\n# temporary files which can be created if a process still has a handle open of a deleted file\n.fuse_hidden*\n\n# KDE directory preferences\n.directory\n\n# Linux trash folder which might appear on any partition or disk\n.Trash-*\n\n# .nfs files are created when an open file is removed but is still being accessed\n.nfs*\n\n### macOS template\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n.vscode\n.idea\n
"},{"location":"practices/web/#25","title":"2.5 \u5b89\u88c5\u5f00\u53d1\u5305","text":"poetry install
"},{"location":"practices/web/#26-git","title":"2.6 \u521d\u59cb Git \u63d0\u4ea4","text":"git init\ngit config user.name example\ngit config user.email example@example.com\ngit add .\ngit commit -m \"feat: First commit!\"\n
"},{"location":"practices/web/#3","title":"3. \u9879\u76ee\u529f\u80fd\u5f00\u53d1","text":""},{"location":"practices/web/#31","title":"3.1 \u521b\u5efa\u547d\u4ee4\u884c\u5165\u53e3","text":"\u547d\u4ee4\u884c\u5165\u53e3\u662f\u542f\u52a8\u9879\u76ee\u7684\u4e3b\u5165\u53e3\uff0c\u5e38\u89c1\u7684\u505a\u6cd5\u662f\u4f7f\u7528\u4e00\u4e2a __main__
\u51fd\u6570\uff0c\u8c03\u7528\u542f\u52a8\u4ee3\u7801\uff0c\u7136\u540e\u4f7f\u7528 python
\u547d\u4ee4\u542f\u52a8\u8be5\u6587\u4ef6\u3002\u4f46\u5bf9\u4e8e\u591a\u7ea7\u547d\u4ee4\u53c2\u6570\u7684\u60c5\u51b5\u5c31\u6bd4\u8f83\u9ebb\u70e6\uff0c\u63a8\u8350\u4f7f\u7528 click \u5de5\u5177\u7f16\u5199\u5165\u53e3\u903b\u8f91\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add click\n
\u67e5\u770b pyproject.toml
\uff0c\u5c06\u589e\u52a0\u5b89\u88c5\u4f9d\u8d56\uff1a
[tool.poetry.dependencies]\nclick = \"^8.1.3\"\n
\u521b\u5efa src/example_blog/cmdline.py
\u6587\u4ef6\uff1a
@click.group(invoke_without_command=True)\n@click.pass_context\n@click.option('-V', '--version', is_flag=True, help='Show version and exit.')\ndef main(ctx, version):\nif version:\nclick.echo(__version__)\nelif ctx.invoked_subcommand is None:\nclick.echo(ctx.get_help())\n
\u7f16\u8f91 pyproject.toml
\uff0c\u5c06\u547d\u4ee4\u884c\u5165\u53e3\u6ce8\u518c\u5230\u9879\u76ee\u63cf\u8ff0\u6587\u4ef6\u4e2d\uff1a
[tool.poetry.scripts]\nexample_blog = \"example_blog.cmdline:main\"\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Add cmdline.\"\n
"},{"location":"practices/web/#32","title":"3.2 \u5f15\u5165\u9879\u76ee\u914d\u7f6e\u7cfb\u7edf","text":"\u9879\u76ee\u7684\u914d\u7f6e\u7cfb\u7edf\u662f\u4e00\u4e2a\u9879\u76ee\u7684\u6838\u5fc3\u9a71\u52a8\uff0c\u4f7f\u7528\u914d\u7f6e\u7cfb\u7edf\u4fbf\u4e8e\u7ba1\u7406\u6563\u843d\u5728\u5404\u5904\u7684\u914d\u7f6e\u53c2\u6570\uff0c\u4e5f\u65b9\u4fbf\u5728\u542f\u52a8\u524d\u901a\u8fc7\u8c03\u6574\u914d\u7f6e\uff0c\u6539\u53d8\u7cfb\u7edf\u884c\u4e3a\u3002
Dynaconf \u662f\u4e00\u4e2a\u9ad8\u5ea6\u7075\u6d3b\u7684\u914d\u7f6e\u7ba1\u7406\u5de5\u5177\uff0c\u652f\u6301\u591a\u73af\u5883\u5206\u5c42\uff0c\u591a\u79cd\u914d\u7f6e\u5bfc\u5165\u7b49\u6709\u70b9\u3002\u5728\u9879\u76ee\u5f00\u53d1\u4e2d\uff0c\u63a8\u8350\u4f7f\u7528\u5982\u4e0b\u5b9e\u8df5\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add dynaconf\n
\u67e5\u770b pyproject.toml
\uff0c\u5c06\u589e\u52a0\u5b89\u88c5\u4f9d\u8d56\uff1a
[tool.poetry.dependencies]\nclick = \"^8.1.3\"\ndynaconf = \"^3.1.11\"\n
\u5efa\u7acb\u914d\u7f6e\u5305\uff0c\u548c\u914d\u7f6e\u6587\u4ef6\uff1a
mkdir src/example_blog/config\ntouch src/example_blog/config/__init__.py\ntouch src/example_blog/config/settings.yml\n
\u7f16\u8f91 src/example_blog/config/__init__.py
\uff0c \u521d\u59cb\u5316\u5168\u5c40\u914d\u7f6e\u5bf9\u8c61\uff1a
import os\nimport sys\nfrom pathlib import Path\nfrom dynaconf import Dynaconf\n_BASE_DIR = Path(__file__).parent.parent\nsettings_files = [\nPath(__file__).parent / 'settings.yml',\n] # \u6307\u5b9a\u7edd\u5bf9\u8def\u5f84\u52a0\u8f7d\u9ed8\u8ba4\u914d\u7f6e\nsettings = Dynaconf(\nenvvar_prefix=\"EXAMPLE_BLOG\", # \u73af\u5883\u53d8\u91cf\u524d\u7f00\u3002\u8bbe\u7f6e`EXAMPLE_BLOG_FOO='bar'`\uff0c\u4f7f\u7528`settings.FOO`\nsettings_files=settings_files,\nenvironments=False, # \u542f\u7528\u591a\u5c42\u6b21\u65e5\u5fd7\uff0c\u652f\u6301 dev, pro\nload_dotenv=True, # \u52a0\u8f7d .env\nenv_switcher=\"EXAMPLE_BLOG_ENV\", # \u7528\u4e8e\u5207\u6362\u6a21\u5f0f\u7684\u73af\u5883\u53d8\u91cf\u540d\u79f0 EXAMPLE_BLOG_ENV=production\nlowercase_read=False, # \u7981\u7528\u5c0f\u5199\u8bbf\u95ee\uff0c settings.name \u662f\u4e0d\u5141\u8bb8\u7684\nincludes=[os.path.join(sys.prefix, 'etc', 'example_blog', 'settings.yml')], # \u81ea\u5b9a\u4e49\u914d\u7f6e\u8986\u76d6\u9ed8\u8ba4\u914d\u7f6e\nbase_dir=_BASE_DIR, # \u7f16\u7801\u4f20\u5165\u914d\u7f6e\n)\n
\u7f16\u8f91 src/example_blog/config/settings.yml
\uff0c\u521d\u59cb\u5316\u914d\u7f6e\uff1a
LOG_LEVEL: INFO\n
\u7f16\u8f91 src/example_blog/config/settings.local.yml
\uff0c\u589e\u52a0\u672c\u5730\u5f00\u53d1\u914d\u7f6e\uff1a
LOG_LEVEL: DEBUG\n
\u6839\u636e Dynaconf \u89c4\u5219\uff0c settings.local.yml
\u7684\u914d\u7f6e\u4e3a\u672c\u5730\u914d\u7f6e\uff0c\u4e14\u4f18\u5148\u7ea7\u6bd4 settings.yml
\u4f4e\uff0c\u6240\u4ee5\u672c\u5730\u914d\u7f6e\u4f1a\u5728\u540e\u9762\u52a0\u8f7d\uff0c\u8986\u76d6\u4e4b\u524d\u7684\u914d\u7f6e\u3002
\u7f16\u8f91 .gitignore
\uff0c\u5c06\u6240\u6709\u672c\u5730\u914d\u7f6e\u6392\u9664\u7248\u672c\u63a7\u5236\u4e4b\u5916\u3002
**/settings.local.yml\n
\u63d0\u4ea4\u4ee3\u7801:
git add .\ngit commit -m \"feat: Add config.\"\n
"},{"location":"practices/web/#33","title":"3.3 \u5f15\u5165\u65e5\u5fd7","text":"\u521b\u5efa src/example_blog/log.py
\uff0c\u521d\u59cb\u5316 log \uff1a
from logging.config import dictConfig\nfrom example_blog.config import settings\ndef init_log():\nlog_config = {\n'version': 1,\n'disable_existing_loggers': False,\n'formatters': {\n'sample': {'format': '%(asctime)s %(levelname)s %(message)s'},\n'verbose': {'format': '%(asctime)s %(levelname)s %(name)s %(process)d %(thread)d %(message)s'},\n\"access\": {\n\"()\": \"uvicorn.logging.AccessFormatter\",\n\"fmt\": '%(asctime)s %(levelprefix)s %(client_addr)s - \"%(request_line)s\" %(status_code)s',\n},\n},\n'handlers': {\n\"console\": {\n\"formatter\": 'verbose',\n'level': 'DEBUG',\n\"class\": \"logging.StreamHandler\",\n},\n},\n'loggers': {\n'': {'level': settings.LOG_LEVEL, 'handlers': ['console']},\n},\n}\ndictConfig(log_config)\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Add log\"\n
"},{"location":"practices/web/#34","title":"3.4 \u6570\u636e\u8bbf\u95ee","text":"\u6570\u636e\u5c42\u662f\u5e94\u7528\u7684\u6700\u5e95\u5c42\uff0c\u548c\u6570\u636e\u5b58\u50a8\u6253\u4ea4\u9053\u3002\u4f7f\u7528 sqlalchemy \u4f5c\u5e95\u5c42\u6570\u636e\u6a21\u578b\u5efa\u6a21\u548c\u6570\u636e\u8bbf\u95ee\u64cd\u4f5c\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add sqlalchemy mysqlclient\n
\u67e5\u770b pyproject.toml
\uff0c\u5c06\u589e\u52a0\u5b89\u88c5\u4f9d\u8d56\uff1a
[tool.poetry.dependencies]\nclick = \"^8.1.3\"\ndynaconf = \"^3.1.11\"\nsqlalchemy = \"^1.4.44\"\nmysqlclient = \"^2.1.1\"\n
\u7f16\u5199 src/example_blog/config/settings.yml
\uff0c\u589e\u52a0\u6570\u636e\u5e93\u914d\u7f6e\u4fe1\u606f\uff1a
# ######################################################################################################\n# # https://docs.sqlalchemy.org/en/13/core/engines.html\nDATABASE:\nDRIVER: mysql\nNAME: example_blog\nHOST: 127.0.0.1\nPORT: 3306\nUSERNAME: root\nPASSWORD: root\nQUERY:\ncharset: utf8mb4\n
\u8b66\u544a
settings.yml
\u4e3a\u7cfb\u7edf\u9ed8\u8ba4\u914d\u7f6e\uff0c\u4f1a\u88ab git \u8ffd\u8e2a\u7ba1\u7406\uff0c\u4e0d\u8981\u586b\u5199\u771f\u6b63\u7684\u6570\u636e\u5e93\u8fde\u63a5\u4fe1\u606f\u3002\u771f\u5b9e\u914d\u7f6e\u4fe1\u606f\u53ef\u4ee5\u5199\u5728 settings.local.yml
\u6587\u4ef6\u4e2d\uff0c\u4f1a\u8986\u76d6\u9ed8\u8ba4\u914d\u7f6e\u3002
\u65b0\u5efa src/example_blog/db.py
\uff0c\u521b\u5efa sqlalchemy \u8bbf\u95ee\u5bf9\u8c61\uff1a
\"\"\"Database connections\"\"\"\nfrom sqlalchemy.engine import create_engine\nfrom sqlalchemy.engine.base import Engine\nfrom sqlalchemy.engine.url import URL\nfrom sqlalchemy.orm import scoped_session, sessionmaker\nfrom example_blog.config import settings\nurl = URL(\ndrivername=settings.DATABASE.DRIVER,\nusername=settings.DATABASE.get('USERNAME', None),\npassword=settings.DATABASE.get('PASSWORD', None),\nhost=settings.DATABASE.get('HOST', None),\nport=settings.DATABASE.get('PORT', None),\ndatabase=settings.DATABASE.get('NAME', None),\nquery=settings.DATABASE.get('QUERY', None),\n)\nengine: Engine = create_engine(url, echo=True)\nSessionFactory = sessionmaker(bind=engine, autocommit=False, autoflush=True)\nScopedSession = scoped_session(SessionFactory)\n
\u521b\u5efa src/example_blog/models.py
\uff0c\u521b\u5efa\u6570\u636e\u6a21\u578b\uff1a
\"\"\"Models\"\"\"\nfrom datetime import datetime\nfrom sqlalchemy import Column, DateTime, Integer, String, Text\nfrom sqlalchemy.ext.declarative import declarative_base, declared_attr\nclass CustomBase:\n\"\"\"https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/mixins.html\"\"\"\n@declared_attr\ndef __tablename__(cls):\nreturn cls.__name__.lower()\n__table_args__ = {\n'mysql_engine': 'InnoDB',\n'mysql_collate': 'utf8mb4_general_ci'\n}\nid = Column(Integer, primary_key=True, autoincrement=True)\nBaseModel = declarative_base(cls=CustomBase)\nclass Article(BaseModel):\n\"\"\"Article table\"\"\"\ntitle = Column(String(500))\nbody = Column(Text(), nullable=True)\ncreate_time = Column(DateTime, default=datetime.now, nullable=False)\nupdate_time = Column(DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)\n
\u4e3a\u4e86\u5728\u5e94\u7528\u4e2d\u66f4\u65b9\u4fbf\u7684\u4f7f\u7528\u6570\u636e\u6a21\u578b\u5bf9\u8c61\uff0c\u5f15\u5165 pydantic \u6765\u5b9a\u4e49\u4e00\u4e9b\u5bf9\u8c61\u6a21\u578b\u7684\u57fa\u672c\u4fe1\u606f\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add pydantic\n
\u67e5\u770b pyproject.toml
\uff0c\u5c06\u589e\u52a0\u5b89\u88c5\u4f9d\u8d56\uff1a
[tool.poetry.dependencies]\nclick = \"^8.1.3\"\ndynaconf = \"^3.1.11\"\nsqlalchemy = \"^1.4.44\"\nmysqlclient = \"^2.1.1\"\npydantic = \"^1.10.2\"\n
\u521b\u5efa src/example_blog/schemas.py
\uff0c\u521b\u5efa\u5bf9\u8c61\u6a21\u578b\uff1a
from datetime import datetime\nfrom typing import Optional, TypeVar\nfrom pydantic import BaseModel, constr\nfrom example_blog.models import BaseModel as DBModel\nModelType = TypeVar('ModelType', bound=DBModel)\nCreateSchema = TypeVar('CreateSchema', bound=BaseModel)\nUpdateSchema = TypeVar('UpdateSchema', bound=BaseModel)\nclass InDBMixin(BaseModel):\nid: int\nclass Config:\norm_mode = True\nclass BaseArticle(BaseModel):\ntitle: constr(max_length=500)\nbody: Optional[str] = None\nclass ArticleSchema(BaseArticle, InDBMixin):\ncreate_time: datetime\nupdate_time: datetime\nclass CreateArticleSchema(BaseArticle):\npass\nclass UpdateArticleSchema(BaseArticle):\ntitle: Optional[constr(max_length=500)] = None\n
\u521b\u5efa src/example_blog/dao.py
\uff0c\u521b\u5efa\u6570\u636e\u8bbf\u95ee\u5c42\uff1a
from typing import Generic, List\nfrom fastapi.encoders import jsonable_encoder\nfrom sqlalchemy.orm import Session\nfrom example_blog.models import Article\nfrom example_blog.schemas import CreateSchema, ModelType, UpdateSchema, CreateArticleSchema, UpdateArticleSchema\nclass BaseDAO(Generic[ModelType, CreateSchema, UpdateSchema]):\nmodel: ModelType\ndef get(self, session: Session, offset=0, limit=10) -> List[ModelType]:\nresult = session.query(self.model).offset(offset).limit(limit).all()\nreturn result\ndef get_by_id(self, session: Session, pk: int, ) -> ModelType:\nreturn session.query(self.model).get(pk)\ndef create(self, session: Session, obj_in: CreateSchema) -> ModelType:\n\"\"\"Create\"\"\"\nobj = self.model(**jsonable_encoder(obj_in))\nsession.add(obj)\nsession.commit()\nreturn obj\ndef patch(self, session: Session, pk: int, obj_in: UpdateSchema) -> ModelType:\n\"\"\"Patch\"\"\"\nobj = self.get_by_id(session, pk)\nupdate_data = obj_in.dict(exclude_unset=True)\nfor key, val in update_data.items():\nsetattr(obj, key, val)\nsession.add(obj)\nsession.commit()\nsession.refresh(obj)\nreturn obj\ndef delete(self, session: Session, pk: int) -> None:\n\"\"\"Delete\"\"\"\nobj = self.get_by_id(session, pk)\nsession.delete(obj)\nsession.commit()\ndef count(self, session: Session):\nreturn session.query(self.model).count()\nclass ArticleDAO(BaseDAO[Article, CreateArticleSchema, UpdateArticleSchema]):\nmodel = Article\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Add models and DAO\"\n
"},{"location":"practices/web/#35","title":"3.5 \u670d\u52a1\u5c42","text":"\u521b\u5efa src/example_blog/services.py
\uff0c\u521b\u5efa\u670d\u52a1\uff1a
\"\"\"Service\"\"\"\nfrom typing import Generic, List\nfrom sqlalchemy.orm import Session\nfrom example_blog.dao import ArticleDAO, BaseDAO\nfrom example_blog.models import Article\nfrom example_blog.schemas import CreateSchema, ModelType, UpdateSchema\nclass BaseService(Generic[ModelType, CreateSchema, UpdateSchema]):\ndao: BaseDAO\ndef get(self, session: Session, offset=0, limit=10) -> List[ModelType]:\n\"\"\"\"\"\"\nreturn self.dao.get(session, offset=offset, limit=limit)\ndef total(self, session: Session) -> int:\nreturn self.dao.count(session)\ndef get_by_id(self, session: Session, pk: int) -> ModelType:\n\"\"\"Get by id\"\"\"\nreturn self.dao.get_by_id(session, pk)\ndef create(self, session: Session, obj_in: CreateSchema) -> ModelType:\n\"\"\"Create a object\"\"\"\nreturn self.dao.create(session, obj_in)\ndef patch(self, session: Session, pk: int, obj_in: UpdateSchema) -> ModelType:\n\"\"\"Update\"\"\"\nreturn self.dao.patch(session, pk, obj_in)\ndef delete(self, session: Session, pk: int) -> None:\n\"\"\"Delete a object\"\"\"\nreturn self.dao.delete(session, pk)\nclass ArticleService(BaseService[Article, CreateSchema, UpdateSchema]):\ndao = ArticleDAO()\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Add services.\"\n
"},{"location":"practices/web/#36-fastapi","title":"3.6 \u5f15\u5165 Fastapi","text":"Fastapi \u662f\u4e00\u4e2a\u8f7b\u91cf\u7684 Web \u6846\u67b6\uff0c\u73b0\u5728\u5f15\u5165\uff0c\u4f7f\u5176\u4f5c\u4e3a API \u5c42
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add fastapi uvicorn\n
\u67e5\u770b pyproject.toml
\uff0c\u589e\u52a0\u5b89\u88c5\u4f9d\u8d56\uff1a
[tool.poetry.dependencies]\nclick = \"^8.1.3\"\ndynaconf = \"^3.1.11\"\nsqlalchemy = \"^1.4.44\"\nmysqlclient = \"^2.1.1\"\npydantic = \"^1.10.2\"\nfastapi = \"^0.88.0\"\nuvicorn = \"^0.20.0\"\n
\u521b\u5efa src/examp.e_blog/views.py
\uff0c\u521b\u5efa\u89c6\u56fe\uff1a
from fastapi import APIRouter, Depends\nfrom sqlalchemy.orm import Session\nfrom example_blog.dependencies import CommonQueryParams, get_db\nfrom example_blog.schemas import (ArticleSchema, CreateArticleSchema,\nUpdateArticleSchema)\nfrom example_blog.services import ArticleService\nrouter = APIRouter()\n_service = ArticleService()\n@router.get('/articles')\ndef get(\nsession: Session = Depends(get_db),\ncommons: CommonQueryParams = Depends()\n):\nreturn _service.get(session, offset=commons.offset, limit=commons.limit)\n@router.get('/articles/{pk}')\ndef get_by_id(\npk: int,\nsession: Session = Depends(get_db)\n):\nreturn _service.get_by_id(session, pk)\n@router.post('/articles', response_model=ArticleSchema)\ndef create(\nobj_in: CreateArticleSchema,\nsession: Session = Depends(get_db),\n):\nreturn _service.create(session, obj_in)\n@router.patch('/articles/{pk}', response_model=ArticleSchema)\ndef patch(\npk: int,\nobj_in: UpdateArticleSchema,\nsession: Session = Depends(get_db)\n):\nreturn _service.patch(session, pk, obj_in)\n@router.delete('/articles/{pk}')\ndef delete(\npk: int,\nsession: Session = Depends(get_db)\n):\nreturn _service.delete(session, pk)\n
\u521b\u5efa src/example_blog/middlewares.py
\uff0c\u521b\u5efa\u6570\u636e\u5e93\u4f1a\u8bdd\u4e2d\u95f4\u4ef6\uff1a
from typing import Callable\nfrom fastapi import FastAPI, Request, Response\nfrom example_blog.db import SessionFactory\nasync def db_session_middleware(request: Request, call_next: Callable) -> Response:\nresponse = Response('Internal server error', status_code=500)\ntry:\nrequest.state.db = SessionFactory()\nresponse = await call_next(request)\nfinally:\nrequest.state.db.close()\nreturn response\ndef init_middleware(app: FastAPI) -> None:\napp.middleware('http')(db_session_middleware)\n
\u521b\u5efa src/example_blog/dependencies.py
\uff0c\u521b\u5efa Fastapi \u7684\u4f9d\u8d56\u9879\uff1a
from fastapi import Request\nfrom sqlalchemy.orm import Session\ndef get_db(request: Request) -> Session:\nreturn request.state.db\nclass CommonQueryParams:\ndef __init__(self, offset: int = 1, limit: int = 10):\nself.offset = offset - 1\nif self.offset < 0:\nself.offset = 0\nself.limit = limit\nif self.limit < 0:\nself.limit = 10\n
\u521b\u5efa src/example_blog/routes.py
\uff0c\u521b\u5efa\u8def\u7531\uff1a
from fastapi import APIRouter, FastAPI\nfrom example_blog import views\ndef router_v1():\nrouter = APIRouter()\nrouter.include_router(views.router, tags=['Article'])\nreturn router\ndef init_routers(app: FastAPI):\napp.include_router(router_v1(), prefix='/api/v1', tags=['v1'])\n
\u521b\u5efa src/example_blog/server.py
\uff0c\u521b\u5efa\u670d\u52a1\u542f\u52a8\u903b\u8f91\uff1a
\"\"\"server\"\"\"\nimport uvicorn\nfrom fastapi import FastAPI\nfrom example_blog import middlewares, routes\nfrom example_blog.config import settings\nfrom example_blog.log import init_log\nclass Server:\ndef __init__(self):\ninit_log()\nself.app = FastAPI()\ndef init_app(self):\nmiddlewares.init_middleware(self.app)\nroutes.init_routers(self.app)\ndef run(self):\nself.init_app()\nuvicorn.run(\napp=self.app,\nhost=settings.HOST,\nport=settings.PORT,\n)\n
\u4fee\u6539 src/example_blog/config/settings.yml
\uff0c\u589e\u52a0\u670d\u52a1\u914d\u7f6e\uff1a
HOST: 127.0.0.1\nPORT: 8000\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Add api service.\"\n
"},{"location":"practices/web/#37","title":"3.7 \u7f16\u5199\u542f\u52a8\u547d\u4ee4","text":"\u7f16\u8f91 src/example_blog/cmdline.py
\uff0c\u589e\u52a0\u542f\u52a8 Server \u903b\u8f91\uff1a
@main.command()\n@click.option('-h', '--host', show_default=True, help=f'Host IP. Default: {settings.HOST}')\n@click.option('-p', '--port', show_default=True, type=int, help=f'Port. Default: {settings.PORT}')\n@click.option('--level', help='Log level')\ndef server(host, port, level):\n\"\"\"Start server.\"\"\"\nkwargs = {\n'LOGLEVEL': level,\n'HOST': host,\n'PORT': port,\n}\nfor name, value in kwargs.items():\nif value:\nsettings.set(name, value)\nServer().run()\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Add server cmdline.\"\n
"},{"location":"practices/web/#38-server","title":"3.8 \u542f\u52a8 Server","text":"\u5c06\u672c\u9879\u76ee\u4ee5\u53ef\u7f16\u8f91\u65b9\u5f0f\u5b89\u88c5\u5230\u5f53\u524d Python \u73af\u5883\uff1a
pip install -e .\n
\u547d\u4ee4\u884c\u8fd0\u884c\uff1a
example_blog server\n
\u53ef\u4ee5\u770b\u5230\u5982\u4e0b\u8f93\u51fa\uff1a
INFO: Started server process [21687]\n2020-12-28 18:11:56,341 INFO uvicorn.error 21687 139772921304768 Started server process [21687]\nINFO: Waiting for application startup.\n2020-12-28 18:11:56,341 INFO uvicorn.error 21687 139772921304768 Waiting for application startup.\nINFO: Application startup complete.\n2020-12-28 18:11:56,341 INFO uvicorn.error 21687 139772921304768 Application startup complete.\nINFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\n2020-12-28 18:11:56,341 INFO uvicorn.error 21687 139772921304768 Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\n
\u6d4f\u89c8\u5668\u6253\u5f00 http://127.0.0.1:8000/docs \u5373\u53ef\u67e5\u770b\u63a5\u53e3\u6587\u6863\u3002
\u63d0\u4ea4\u4ee3\u7801
"},{"location":"practices/web/#39","title":"3.9 \u5f15\u5165\u8fc1\u79fb\u5de5\u5177","text":"\u4e3a\u4e86\u4fbf\u4e8e\u6570\u636e\u6a21\u578b\u53d8\u66f4\uff0c\u5f15\u5165 alembic \u505a\u6570\u636e\u5e93\u8fc1\u79fb\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add alembic\n
\u67e5\u770b pyproject.toml
\uff0c\u5c06\u589e\u52a0\u5b89\u88c5\u4f9d\u8d56\uff1a
[tool.poetry.dependencies]\nclick = \"^8.1.3\"\ndynaconf = \"^3.1.11\"\nsqlalchemy = \"^1.4.44\"\nmysqlclient = \"^2.1.1\"\npydantic = \"^1.10.2\"\nfastapi = \"^0.88.0\"\nuvicorn = \"^0.20.0\"\nalembic = \"^1.8.1\"\n
\u521d\u59cb\u5316 alembic \uff1a
alembic init migration\nmv alembic.ini src/example_blog/migration\n
\u5c06 alembic \u7684\u76f8\u5173\u6587\u4ef6\u5168\u90e8\u653e\u5230 src/example_blog/migration
\u76ee\u5f55\u4e2d
\u4fee\u6539 src/example_blog/migration/alembic.ini
\uff1a
# A generic, single database configuration.\n[alembic]\n# path to migration scripts\n;script_location = src/example_blog/migration\nscript_location = .\n# template used to generate migration files\n# file_template = %%(rev)s_%%(slug)s\n# timezone to use when rendering the date\n# within the migration file as well as the filename.\n# string value is passed to dateutil.tz.gettz()\n# leave blank for localtime\n# timezone =\n# max length of characters to apply to the\n# \"slug\" field\n# truncate_slug_length = 40\n# set to 'true' to run the environment during\n# the 'revision' command, regardless of autogenerate\n# revision_environment = false\n# set to 'true' to allow .pyc and .pyo files without\n# a source .py file to be detected as revisions in the\n# versions/ directory\n# sourceless = false\n# version location specification; this defaults\n# to src/example_blog/migration/versions. When using multiple version\n# directories, initial revisions must be specified with --version-path\n# version_locations = %(here)s/bar %(here)s/bat src/example_blog/migration/versions\n# the output encoding used when revision files\n# are written from script.py.mako\n# output_encoding = utf-8\n;sqlalchemy.url = driver://user:pass@localhost/dbname\n[post_write_hooks]\n# post_write_hooks defines scripts or Python functions that are run\n# on newly generated revision scripts. See the documentation for further\n# detail and examples\n# format using \"black\" - use the console_scripts runner, against the \"black\" entrypoint\n# hooks=black\n# black.type=console_scripts\n# black.entrypoint=black\n# black.options=-l 79\n# Logging configuration\n[loggers]\nkeys = root,sqlalchemy,alembic\n[handlers]\nkeys = console\n[formatters]\nkeys = generic\n[logger_root]\nlevel = WARN\nhandlers = console\nqualname =\n[logger_sqlalchemy]\nlevel = WARN\nhandlers =\nqualname = sqlalchemy.engine\n[logger_alembic]\nlevel = INFO\nhandlers =\nqualname = alembic\n[handler_console]\nclass = StreamHandler\nargs = (sys.stderr,)\nlevel = NOTSET\nformatter = generic\n[formatter_generic]\nformat = %(levelname)-5.5s [%(name)s] %(message)s\ndatefmt = %H:%M:%S\n
\u4fee\u6539 src/example_blog/migration/env.py
\uff1a
from logging.config import fileConfig\nfrom alembic import context\nfrom sqlalchemy import engine_from_config, pool\nfrom example_blog import db\nfrom example_blog.models import BaseModel\n# this is the Alembic Config object, which provides\n# access to the values within the .ini file in use.\nconfig = context.config\n# Interpret the config file for Python logging.\n# This line sets up loggers basically.\nfileConfig(config.config_file_name)\n# add your model's MetaData object here\n# for 'autogenerate' support\n# from myapp import mymodel\n# target_metadata = mymodel.Base.metadata\n# target_metadata = None\ntarget_metadata = BaseModel.metadata\n# other values from the config, defined by the needs of env.py,\n# can be acquired:\n# my_important_option = config.get_main_option(\"my_important_option\")\n# ... etc.\ndef run_migrations_offline():\n\"\"\"Run migrations in 'offline' mode.\n This configures the context with just a URL\n and not an Engine, though an Engine is acceptable\n here as well. By skipping the Engine creation\n we don't even need a DBAPI to be available.\n Calls to context.execute() here emit the given string to the\n script output.\n \"\"\"\ncontext.configure(\nurl=db.url,\ntarget_metadata=target_metadata,\nliteral_binds=True,\ndialect_opts={\"paramstyle\": \"named\"},\n)\nwith context.begin_transaction():\ncontext.run_migrations()\ndef run_migrations_online():\n\"\"\"Run migrations in 'online' mode.\n In this scenario we need to create an Engine\n and associate a connection with the context.\n \"\"\"\nconfiguration = config.get_section(config.config_ini_section)\nconfiguration['sqlalchemy.url'] = str(db.url)\nconnectable = engine_from_config(\nconfiguration,\nprefix=\"sqlalchemy.\",\npoolclass=pool.NullPool,\n)\nwith connectable.connect() as connection:\ncontext.configure(\nconnection=connection, target_metadata=target_metadata\n)\nwith context.begin_transaction():\ncontext.run_migrations()\nif context.is_offline_mode():\nrun_migrations_offline()\nelse:\nrun_migrations_online()\n
\u7f16\u5199 src/example_blog/cmdline.py
\uff0c\u521b\u5efa\u8fc1\u79fb\u547d\u4ee4\uff1a
from pathlib import Path\nfrom alembic import config\nfrom click import Context\n@main.command()\n@click.pass_context\n@click.option('-h', '--help', is_flag=True)\n@click.argument('args', nargs=-1)\ndef migrate(ctx: Context, help, args):\n\"\"\"usage migrate -- arguments \"\"\"\nwith utils.chdir(Path(__file__).parent / 'migration'):\nargv = list(args)\nif help:\nargv.append('--help')\nconfig.main(prog=ctx.command_path, argv=argv)\n
\u521b\u5efa utils.py
\uff1a
\"\"\"Utils\"\"\"\nimport contextlib\nimport os\nfrom os import PathLike\nfrom typing import Union\n@contextlib.contextmanager\ndef chdir(path: Union[str, PathLike]):\ncwd = os.getcwd()\nos.chdir(path)\nyield\nos.chdir(cwd)\n
\u63d0\u793a
\u7531\u4e8e\u4f7f\u7528\u4e86 click \u5305\u88c5\u4e86 alembic \u547d\u4ee4\uff0c\u5728\u4f7f\u7528\u4e0a\u4f1a\u6709\u70b9\u4e0d\u540c\uff0c\u9ed8\u8ba4\u5e94\u8be5\u4f7f\u7528 migrate --
\u540e\u52a0 alembic \u7684\u5176\u4ed6\u53c2\u6570\uff0c\u5426\u5219\u591a\u53c2\u6570\u7684\u60c5\u51b5\u4e0b\u4f1a\u65e0\u6cd5\u8bc6\u522b\u3002
\u4e3a\u4e86\u5c06 src/example_blog/migration
\u6253\u5305\u5230\u9879\u76ee\u4e2d\uff0c\u9700\u8981\u5c06\u5176\u53d8\u6210 Python \u5305\u3002
\u521b\u5efa src/example_blog/migration/__init__.py
\u548c src/example_blog/migration/versions/__init__.py
\u521b\u5efa\u7a7a\u767d\u6570\u636e\u5e93\u8fc1\u79fb\u7248\u672c\uff1a
example_blog migrate -- revision -m \"init\"\n
\u6267\u884c\u8fc1\u79fb\uff1a
example_blog migrate -- upgrade head\n
\u521b\u5efa\u7b2c\u4e00\u4e2a\u6570\u636e\u5e93\u8fc1\u79fb\u7248\u672c\uff1a
example_blog migrate -- revision --autogenerate -m \"init_table\"\n
\u6267\u884c\u8fc1\u79fb\uff1a
example_blog migrate -- upgrade head\n
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"Add alembic migrate.\"\n
"},{"location":"practices/web/#4","title":"4. \u6d4b\u8bd5\u548c\u4f18\u5316\u4ee3\u7801","text":"\u6d4b\u8bd5\u662f\u8f6f\u4ef6\u5f00\u53d1\u4e2d\u91cd\u8981\u7684\u4e00\u73af\uff0c\u80fd\u591f\u5728\u53d1\u5e03\u4e4b\u524d\u68c0\u67e5\u51fa\u66f4\u591a\u53ef\u80fd\u51fa\u73b0\u7684\u5f02\u5e38\u60c5\u51b5\u3002
\u6d4b\u8bd5\u6846\u67b6\u9009\u7528\u6bd4\u8f83\u5e38\u7528\u7684 pytest \uff0c\u5b83\u5177\u6709\u5f3a\u5927\u7684\u529f\u80fd\u548c\u5f88\u597d\u7684\u517c\u5bb9\u6027\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add -D pytest\n
\u521b\u5efa tests/settings.yml
\uff0c\u521d\u59cb\u5316\u6d4b\u8bd5\u914d\u7f6e\uff1a
DEBUG: false\nLOG_LEVEL: INFO\n\nHOST: 127.0.0.1\nPORT: 8000\n\nDATABASE:\n DRIVER: mysql\n NAME: example_blog\n HOST: 127.0.0.1\n PORT: 3306\n USERNAME: root\n PASSWORD: root\n QUERY:\n charset: utf8mb4\n
\u7f16\u8f91 tests/__init__.py
\uff0c\u52a0\u8f7d\u6d4b\u8bd5\u914d\u7f6e\uff1a
import os\nfrom example_blog.config import settings\nsettings.load_file(os.path.join(os.path.dirname(__file__), 'settings.yml'))\nsettings.load_file(os.path.join(os.path.dirname(__file__), 'settings.local.yml'))\n
\u867d\u7136\u672c\u5730\u5f00\u53d1\u914d\u7f6e\u53ef\u4ee5\u4e34\u65f6\u8c03\u6574\uff0c\u4f46\u5bf9\u4e8e\u5f00\u53d1\u73af\u5883\u548c\u6d4b\u8bd5\u73af\u5883\u4f9d\u7136\u6709\u4e9b\u4e0d\u4e00\u6837\u3002\u4ece\u4e0a\u9762\u4ee3\u7801\u4e2d\u53ef\u4ee5\u770b\u5230\u52a0\u8f7d\u4e86\u4e24\u4e2a\u6d4b\u8bd5\u914d\u7f6e\uff0c\u548c Dynaconf \u89c4\u5219\u4e00\u6837\uff0c settings.local.yml
\u914d\u7f6e\u4e3a\u672c\u5730\u914d\u7f6e\uff0c\u4e0d\u4f1a\u88ab\u4ee3\u7801\u8ffd\u8e2a\uff0c\u53ea\u4e0d\u8fc7\u8fd9\u91cc\u662f\u624b\u52a8\u5b9e\u73b0\u7684\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"test: Init test.\"\n
"},{"location":"practices/web/#41","title":"4.1 \u6d4b\u8bd5\u6570\u636e\u8bbf\u95ee\u5c42","text":"\u7f16\u5199\u6d4b\u8bd5\u914d\u7f6e\uff1a
\u65b0\u5efa tests/conftest.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u914d\u7f6e\uff1a
\"\"\"Test config\"\"\"\nimport os\nfrom pathlib import Path\nimport pytest\nfrom alembic import command, config\nfrom sqlalchemy.orm import Session\nfrom example_blog import migration\nfrom example_blog.config import settings\nfrom example_blog.db import SessionFactory\nfrom example_blog.models import Article\n@pytest.fixture()\ndef migrate():\n\"\"\"Re-init database when run a test.\"\"\"\nos.chdir(Path(migration.__file__).parent)\nalembic_config = config.Config('./alembic.ini')\nalembic_config.set_main_option('script_location', os.getcwd())\nprint('\\n----- RUN ALEMBIC MIGRATION: -----\\n')\ncommand.downgrade(alembic_config, 'base')\ncommand.upgrade(alembic_config, 'head')\ntry:\nyield\nfinally:\ncommand.downgrade(alembic_config, 'base')\ndb_name = settings.DATABASE.get('NAME')\nif settings.DATABASE.DRIVER == 'sqlite' and os.path.isfile(db_name):\ntry:\nos.remove(db_name)\nexcept FileNotFoundError:\npass\n@pytest.fixture()\ndef session(migrate) -> Session:\n\"\"\"session fixture\"\"\"\n_s = SessionFactory()\nyield _s\n_s.close()\n@pytest.fixture()\ndef init_article(session):\n\"\"\"Init article\"\"\"\na_1 = Article(title='Hello world', body='Hello world, can you see me?')\na_2 = Article(title='Love baby', body='I love you everyday, and i want with you.')\na_3 = Article(title='Tomorrow', body='When the sun rises, this day is fine day, cheer up.')\nsession.add_all([a_1, a_2, a_3])\nsession.commit()\n
\u7f16\u5199\u6570\u636e\u8bbf\u95ee\u5c42\u7528\u4f8b\uff1a
import pytest\nfrom example_blog.dao import ArticleDAO\nfrom example_blog.models import Article\nfrom example_blog.schemas import CreateArticleSchema, UpdateArticleSchema\nclass TestArticle:\n@pytest.fixture()\ndef dao(self, init_article):\nyield ArticleDAO()\ndef test_get(self, dao, session):\nusers = dao.get(session)\nassert len(users) == 3\nusers = dao.get(session, limit=2)\nassert len(users) == 2\nusers = dao.get(session, offset=4)\nassert not users\ndef test_get_by_id(self, dao, session):\nuser = dao.get_by_id(session, 1)\nassert user.id == 1\ndef test_create(self, dao, session):\norigin_count = session.query(dao.model).count()\nobj_in = CreateArticleSchema(title='test')\ndao.create(session, obj_in)\ncount = session.query(dao.model).count()\nassert origin_count + 1 == count\ndef test_patch(self, dao, session):\nobj: Article = session.query(dao.model).first()\nbody = obj.body\nobj_in = UpdateArticleSchema(body='test')\nupdated_obj: Article = dao.patch(session, obj.id, obj_in)\nassert body != updated_obj.body\ndef test_delete(self, dao, session):\norigin_count = session.query(dao.model).count()\ndao.delete(session, 1)\ncount = session.query(dao.model).count()\nassert origin_count - 1 == count\ndef test_count(self, dao, session):\ncount = dao.count(session)\nassert count == 3\n
\u8fd0\u884c\u6d4b\u8bd5\uff1a
pytest tests/test_dao.py\n
\u5982\u679c\u8fd0\u884c\u6210\u529f\uff0c\u5219\u6d4b\u8bd5\u6b63\u786e\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"test: Add dao test.\"\n
"},{"location":"practices/web/#42","title":"4.2 \u6d4b\u8bd5\u670d\u52a1\u5c42","text":"\u521b\u5efa tests/test_services.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u7528\u4f8b\uff1a
import pytest\nfrom example_blog.schemas import CreateArticleSchema, UpdateArticleSchema\nfrom example_blog.services import ArticleService\nclass TestArticleService:\n@pytest.fixture()\ndef service(self, init_article):\nyield ArticleService()\ndef test_get(self, service, session):\nobjs = service.get(session)\nassert len(objs) == 3\nobjs = service.get(session, limit=2)\nassert len(objs) == 2\nobjs = service.get(session, offset=5)\nassert not objs\ndef test_total(self, service, session):\ntotal = service.total(session)\nassert total == 3\ndef test_by_id(self, service, session):\n__obj = session.query(service.dao.model).first()\nobj = service.get_by_id(session, __obj.id)\nassert obj.id == __obj.id\ndef test_create(self, service, session):\norigin_count = service.total(session)\nobj_in = CreateArticleSchema(title='test')\nservice.create(session, obj_in)\ncount = service.total(session)\nassert origin_count + 1 == count\ndef test_patch(self, service, session):\norigin_obj = session.query(service.dao.model).first()\nbody = origin_obj.body\nobj_in = UpdateArticleSchema(body='test')\nobj = service.patch(session, origin_obj.id, obj_in)\nassert body != obj.body\ndef test_delete(self, service, session):\norigin_count = service.total(session)\nobj = session.query(service.dao.model).first()\nservice.delete(session, obj.id)\ncount = service.total(session)\nassert origin_count - 1 == count\n
\u8fd0\u884c\u6d4b\u8bd5\uff1a
pytest tests/test_services.py\n
\u5982\u679c\u8fd0\u884c\u6210\u529f\uff0c\u5219\u6d4b\u8bd5\u6b63\u786e\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"test: Add service test.\"\n
"},{"location":"practices/web/#43","title":"4.3 \u6d4b\u8bd5\u89c6\u56fe\u5c42","text":"\u7f16\u8f91 tests/conftest.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u914d\u7f6e\uff1a
from fastapi.testclient import TestClient\nfrom example_blog import migration, server\n@pytest.fixture\ndef client():\n\"\"\"Fast api test client factory\"\"\"\n_s = server.Server()\n_s.init_app()\n_c = TestClient(app=_s.app)\nyield _c\n
\u7531\u4e8e Fastapi \u7684 TestClient
\u4f9d\u8d56 requests
\uff0c\u6240\u4ee5\u9700\u8981\u5148\u5b89\u88c5\uff1a
poetry add -D requests\n
\u521b\u5efa tests/test_views.py
\uff0c\u6d4b\u8bd5\u8bd5\u56fe\uff1a
import pytest\nfrom fastapi.encoders import jsonable_encoder\nfrom fastapi.responses import Response\nfrom example_blog.models import Article\nfrom example_blog.schemas import ModelType\ndef test_docs(client):\n\"\"\"Test view\"\"\"\nresponse = client.get('/docs')\nassert response.status_code == 200\nclass BaseTest:\nversion = 'v1'\nbase_url: str\nmodel: ModelType\n@pytest.fixture()\ndef init_data(self):\npass\ndef url(self, pk: int = None) -> str:\nurl_split = ['api', self.version, self.base_url]\nif pk:\nurl_split.append(str(pk))\nreturn '/'.join(url_split)\ndef assert_response_ok(self, response: Response):\nassert response.status_code == 200\ndef test_get(self, client, session, init_data):\ncount = session.query(self.model).count()\nresponse = client.get(self.url())\nself.assert_response_ok(response)\nassert count == len(response.json())\ndef test_get_by_id(self, client, session, init_data):\nobj = session.query(self.model).first()\nresponse = client.get(self.url(obj.id))\nself.assert_response_ok(response)\nassert jsonable_encoder(obj) == response.json()\ndef test_delete(self, client, session, init_data):\ncount = session.query(self.model).count()\nsession.close()\nresponse = client.delete(self.url(1))\nself.assert_response_ok(response)\nafter_count = session.query(self.model).count()\nassert after_count == 2\nassert count - 1 == after_count\nclass TestArticle(BaseTest):\nmodel = Article\nbase_url = 'articles'\n@pytest.fixture()\ndef init_data(self, init_article):\npass\ndef test_create(self, client, session, init_data):\nresponse = client.post(\nself.url(),\njson={'title': 'xxx'}\n)\nself.assert_response_ok(response)\nassert response.json().get('title') == 'xxx'\ndef test_patch(self, client, session, init_data):\nobj = session.query(Article).first()\nresponse = client.patch(self.url(obj.id), json={'body': 'xxx'})\nself.assert_response_ok(response)\nassert response.json().get('body') != obj.body\n
\u8fd0\u884c\u6d4b\u8bd5\uff1a
pytest tests/test_views.py\n
\u5982\u679c\u8fd0\u884c\u6210\u529f\uff0c\u5219\u6d4b\u8bd5\u6b63\u786e\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"test: Add view test.\"\n
"},{"location":"practices/web/#44","title":"4.4 \u6d4b\u8bd5\u547d\u4ee4\u884c","text":"\u7f16\u8f91 tests/conftest.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u914d\u7f6e\uff1a
from click.testing import CliRunner\n@pytest.fixture\ndef cli():\nrunner = CliRunner(echo_stdin=True, mix_stderr=False)\nyield runner\n
\u521b\u5efa tests/test_cmdline.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u7528\u4f8b\uff1a
import uvicorn\nfrom alembic import config\nimport example_blog\nfrom example_blog import cmdline\ndef test_main(cli):\nresult = cli.invoke(cmdline.main)\nassert result.exit_code == 0\nresult = cli.invoke(cmdline.main, '-V')\nassert result.exit_code == 0\nassert str(result.output).strip() == example_blog.__version__\ndef test_run(cli, mocker):\nmock_run = mocker.patch.object(uvicorn, 'run')\nresult = cli.invoke(cmdline.main, ['server', '-h', '127.0.0.1', '-p', '8080'])\nassert result.exit_code == 0\nmock_run.assert_called_once_with(app=mocker.ANY, host='127.0.0.1', port=8080)\ndef test_migrate(cli, mocker):\nmock_main = mocker.patch.object(config, 'main')\ncli.invoke(cmdline.main, ['migrate', '--help'])\nmock_main.assert_called_once()\n
\u56e0\u4e3a\u5355\u5143\u6d4b\u8bd5\u4e2d\u4f7f\u7528\u4e86 mock \uff0c\u6240\u4ee5\u5b89\u88c5\u914d\u5408 pytest \u4f7f\u7528\u7684 pytest-mock
poetry add -D pytest-mock\n
\u8fd0\u884c\u6d4b\u8bd5\uff1a
pytest tests/test_views.py\n
\u5982\u679c\u8fd0\u884c\u6210\u529f\uff0c\u5219\u6d4b\u8bd5\u6b63\u786e\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"test: Add cmdline test.\"\n
"},{"location":"practices/web/#45","title":"4.5 \u5176\u4ed6\u6d4b\u8bd5","text":"\u521b\u5efa tests/test_dependencies.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u7528\u4f8b\uff1a
import pytest\nfrom example_blog.dependencies import CommonQueryParams\n@pytest.mark.parametrize(\n['args', 'expect_value'],\n[\n((), (0, 10)),\n((0,), (0, 10)),\n((-10, -10), (0, 10)),\n((5, 100), (4, 100)),\n]\n)\ndef test_common_query_params(args, expect_value):\nparams = CommonQueryParams(*args)\nassert params.offset == expect_value[0]\nassert params.limit == expect_value[1]\n
\u521b\u5efa tests/test_utils.py
\uff0c\u521b\u5efa\u6d4b\u8bd5\u7528\u4f8b\uff1a
import os\nfrom example_blog.utils import chdir\ndef test_chdir():\npath = '/tmp'\ncwd = os.getcwd()\nwith chdir(path):\nassert path == os.getcwd()\nassert cwd == os.getcwd()\n
\u8fd0\u884c\u6d4b\u8bd5\uff1a
pytest\n
\u5982\u679c\u8fd0\u884c\u6210\u529f\uff0c\u5219\u6d4b\u8bd5\u6b63\u786e\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"test: Add other test.\"\n
\u81f3\u6b64\uff0c\u6240\u6709\u6d4b\u8bd5\u8fd0\u884c\u5b8c\u6bd5\uff0c\u9664\u4e86 src/example_blog/migration
\u4e4b\u5916\u7684\u5305\u7684\u6d4b\u8bd5\u5df2\u7ecf\u53ef\u4ee5\u5168\u90e8\u8986\u76d6\u3002
"},{"location":"practices/web/#46","title":"4.6 \u4f18\u5316\u4ee3\u7801","text":"\u4ee3\u7801\u98ce\u683c\u548c\u4ee3\u7801\u89c4\u8303\u662f\u4e00\u4e2a\u5f00\u53d1\u4eba\u5458\u5f00\u53d1\u4fee\u517b\u7684\u4f53\u73b0\uff0c\u597d\u7684\u4ee3\u7801\u80fd\u591f\u8ba9\u4eba\u773c\u524d\u4e00\u4eae\u3002\u4e3a\u4e86\u89c4\u8303\uff0c\u793e\u533a\u5f00\u53d1\u8bb8\u591a\u5de5\u5177\u7528\u4e8e\u68c0\u6d4b\u4ee3\u7801\u3002
"},{"location":"practices/web/#461","title":"4.6.1 \u4f18\u5316\u5bfc\u5165","text":"isort \u662f\u4e00\u4e2a\u81ea\u52a8\u683c\u5f0f\u5316\u5bfc\u5165\u7684\u5de5\u5177\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add -D isort\n
\u683c\u5f0f\u5316\u4ee3\u7801\uff1a
isort .\n
\u6b64\u65f6\u53ef\u4ee5\u4e0d\u7528\u5148\u6025\u7740\u63d0\u4ea4\uff0c\u5728\u540e\u9762\u5bf9\u4ee3\u7801\u98ce\u683c\u68c0\u6d4b\u7684\u65f6\u5019\u53ef\u80fd\u8fd8\u4f1a\u518d\u6b21\u683c\u5f0f\u5316\u4ee3\u7801\u3002
"},{"location":"practices/web/#462","title":"4.6.2 \u4f18\u5316\u4ee3\u7801\u98ce\u683c","text":"flake8 \u662f\u4e00\u4e2a\u9075\u5faa PEP8 \u89c4\u8303\u68c0\u6d4b\u4ee3\u7801\u7684\u5de5\u5177\u3002\u4f7f\u7528\u8be5\u5de5\u5177\uff0c\u53ef\u4ee5\u68c0\u6d4b\u51fa\u54ea\u4e9b\u4ee3\u7801\u4e0d\u7b26\u5408 PEP8 \u89c4\u8303\u3002
\u5b89\u88c5\u4f9d\u8d56\uff1a
poetry add -D flake8\n
\u68c0\u6d4b\u4ee3\u7801\uff1a
flake8\n
\u6839\u636e\u8f93\u51fa\u63d0\u793a\uff0c\u53c2\u7167 flake8 \u89c4\u5219 \u8fdb\u884c\u8c03\u6574\uff0c\u76f4\u81f3\u5b8c\u5168\u7b26\u5408\u4e3a\u6b62\u3002
\u63d0\u4ea4\u4ee3\u7801\uff1a
git add .\ngit commit -m \"feat: Lint code\"\n
"},{"location":"practices/web/#5","title":"5. \u6253\u5305\u53d1\u5e03","text":"\u5230\u8fd9\u4e00\u6b65\uff0c pyproject.toml
\u6587\u4ef6\u5e94\u8be5\u662f\u8fd9\u6837\u7684\uff1a
[tool.poetry]\nname = \"example_blog\"\nversion = \"0.1.0\"\ndescription = \"This is example blog system.\"\nauthors = [\"huagang <huagang517@126.com>\"]\nreadme = \"README.md\"\n[tool.poetry.dependencies]\npython = \"^3.10\"\nfastapi-sa = \"^0.0.1.dev0\"\nsqlalchemy = \"^1.4.44\"\nmysqlclient = \"^2.1.1\"\npydantic = \"^1.10.2\"\ndynaconf = \"^3.1.11\"\nfastapi = \"^0.88.0\"\nuvicorn = \"^0.20.0\"\nalembic = \"^1.8.1\"\n[tool.poetry.group.dev.dependencies]\npytest = \"^7.2.0\"\nisort = \"^5.10.1\"\nrequests = \"^2.28.1\"\npytest-mock = \"^3.10.0\"\nflake8 = \"^6.0.0\"\n[tool.poetry.scripts]\nexample_blog = \"example_blog.cmdline:main\"\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n
\u5728\u6574\u4e2a\u5f00\u53d1\u8fc7\u7a0b\u4e2d\uff0c\u662f\u9010\u6b65\u4e30\u5bcc\u6b64\u6587\u4ef6\u7684\u3002\u8fd9\u662f\u9879\u76ee\u7684\u63cf\u8ff0\u6587\u4ef6\uff0c\u63cf\u8ff0\u4e86\u6253\u5305\u7684\u914d\u7f6e\u4fe1\u606f\u3002
"},{"location":"practices/web/#51","title":"5.1 \u6253\u5305","text":"poetry build\n
\u5728 dist
\u76ee\u5f55\u4e2d\u53ef\u4ee5\u770b\u5230\u4e24\u4e2a\u6587\u4ef6\uff0c\u4e00\u4e2a\u662f .tar.gz
\u7684\u6e90\u7801\u6253\u5305\u6587\u4ef6\uff0c\u4e00\u4e2a\u662f .whl
\u7684\u4e8c\u8fdb\u5236\u6587\u4ef6\u3002
"},{"location":"practices/web/#52","title":"5.2 \u53d1\u5e03","text":"\u5c06\u5f00\u53d1\u597d\u7684\u9879\u76ee\u53d1\u5e03\u5230\u7d22\u5f15\u4ed3\u5e93\uff0c\u6216\u5185\u7f51\u7684\u79c1\u6709\u4ed3\u5e93\u3002
\u4f7f\u7528 poetry \u4e0a\u4f20\uff1a
poetry publish\n
-
https://www.python.org/doc/sunset-python-2/\u00a0\u21a9
-
\u73b0\u5728 Anaconda / Miniconda \u5728 Windows \u4e0a\u4f7f\u7528\u865a\u62df\u73af\u5883\u5de5\u5177 Virtualenv \u5b58\u5728\u4e00\u4e9b\u517c\u5bb9\u95ee\u9898\uff0c\u800c\u4e14 Pipenv \u662f\u4f9d\u8d56\u8fd9\u4e2a\u5de5\u5177\u7684\u3002\u8bf7\u53c2\u8003 conda support - Windows 3.7+ #1986 \u548c virtualenv==20.0.34 not compatible with python on windows #12094 \u21a9
"},{"location":"standard/language_rules/","title":"Python \u8bed\u8a00\u89c4\u8303","text":"\u672c\u6587\u6863\u4e3a Google Python Style Guide \u7b2c\u4e8c\u7ae0 Python Language Rules \u7684\u8bd1\u6587\u3002
\u6700\u540e\u66f4\u65b0\u65f6\u95f4\uff1a 2023-06-26
\u5982\u679c\u6709\u7ffb\u8bd1\u9519\u8bef\u6216\u8868\u8ff0\u4e0d\u51c6\u786e\u7684\u95ee\u9898\uff0c\u6b22\u8fce\u63d0\u4ea4 PR\uff0c\u611f\u8c22\u60a8\u7684\u53c2\u4e0e\u3002
"},{"location":"standard/language_rules/#11-lint","title":"1.1 Lint","text":"\u4f7f\u7528 pylintrc \u914d\u7f6e\uff0c\u5bf9\u4f60\u7684\u4ee3\u7801\u8fd0\u884c pylint
\u3002
"},{"location":"standard/language_rules/#111","title":"1.1.1 \u5b9a\u4e49","text":"Pylint
\u662f\u4e00\u4e2a\u5728 Python \u6e90\u4ee3\u7801\u4e2d\u67e5\u627e bug \u548c\u98ce\u683c\u95ee\u9898\u7684\u5de5\u5177\u3002\u5bf9\u4e8e C \u548c C++ \u8fd9\u6837\u7684\u4e0d\u90a3\u4e48\u52a8\u6001\u7684\u8bed\u8a00\uff0c\u8fd9\u4e9b\u95ee\u9898\u901a\u5e38\u7531\u7f16\u8bd1\u5668\u6765\u6355\u83b7\u3002\u7531\u4e8e Python \u7684\u52a8\u6001\u7279\u6027\uff0c\u6709\u4e9b\u8b66\u544a\u53ef\u80fd\u4e0d\u5bf9\u3002\u4e0d\u8fc7\u4f2a\u544a\u8b66\u5e94\u8be5\u5f88\u5c11\u3002
"},{"location":"standard/language_rules/#112","title":"1.1.2 \u4f18\u70b9","text":"\u53ef\u4ee5\u6355\u83b7\u5bb9\u6613\u5ffd\u89c6\u7684\u9519\u8bef\uff0c\u4f8b\u5982\u8f93\u5165\u9519\u8bef\uff0c\u4f7f\u7528\u672a\u8d4b\u503c\u7684\u53d8\u91cf\u7b49\u3002
"},{"location":"standard/language_rules/#113","title":"1.1.3 \u7f3a\u70b9","text":"pylint
\u4e0d\u5b8c\u7f8e\u3002\u8981\u5229\u7528\u5176\u4f18\u52bf\uff0c\u6211\u4eec\u6709\u65f6\u4faf\u9700\u8981\uff1a\u56f4\u7ed5\u7740\u5b83\u6765\u5199\u4ee3\u7801\u3001\u6291\u5236\u5176\u544a\u8b66\u3001\u6539\u8fdb\u5b83\u6216\u8005\u5ffd\u7565\u5b83\u3002
"},{"location":"standard/language_rules/#114","title":"1.1.4 \u7ed3\u8bba","text":"\u786e\u4fdd\u5bf9\u4f60\u7684\u4ee3\u7801\u8fd0\u884c pylint
\u3002
\u6291\u5236\u4e0d\u51c6\u786e\u7684\u8b66\u544a\uff0c\u4ee5\u4fbf\u80fd\u591f\u5c06\u5176\u4ed6\u8b66\u544a\u66b4\u9732\u51fa\u6765\u3002\u4f60\u53ef\u4ee5\u901a\u8fc7\u8bbe\u7f6e\u4e00\u4e2a\u884c\u6ce8\u91ca\u6765\u6291\u5236\u544a\u8b66\uff1a
def do_PUT(self): # WSGI name, so pylint: disable=invalid-name\n...\n
pylint
\u8b66\u544a\u662f\u4ee5\u4e00\u4e2a\u7b26\u53f7\u540d (\u5982 empty-docstring
) \u6765\u6807\u8bc6\u7684\uff0cGoogle \u7279\u5b9a\u7684\u8b66\u544a\u4ee5 g- \u5f00\u5934\u3002
\u5982\u679c\u4ece\u7b26\u53f7\u540d\u79f0\u4e2d\u770b\u4e0d\u51fa\u7981\u7528\u7684\u539f\u56e0\uff0c\u90a3\u4e48\u8bf7\u5bf9\u5176\u589e\u52a0\u4e00\u4e2a\u8be6\u7ec6\u89e3\u91ca\u3002
\u91c7\u7528\u8fd9\u79cd\u6291\u5236\u65b9\u5f0f\u7684\u597d\u5904\u662f\u6211\u4eec\u53ef\u4ee5\u8f7b\u677e\u67e5\u627e\u6291\u5236\u5e76\u56de\u987e\u5b83\u4eec\u3002
\u60a8\u53ef\u4ee5\u901a\u8fc7\u6267\u884c\u4ee5\u4e0b\u64cd\u4f5c\u6765\u83b7\u53d6 pylint
\u8b66\u544a\u5217\u8868\uff1a
pylint --list-msgs\n
\u83b7\u53d6\u5173\u4e8e\u7279\u5b9a\u6d88\u606f\u7684\u66f4\u591a\u4fe1\u606f\uff0c\u53ef\u4ee5\u6267\u884c\uff1a
pylint --help-msg=invalid-name\n
\u76f8\u6bd4\u8f83\u4e8e\u4e4b\u524d\u4f7f\u7528\u7684 pylint: disable-msg
\uff0c\u672c\u6587\u63a8\u8350\u4f7f\u7528 pylint: disable
\u3002
\u672a\u4f7f\u7528\u53c2\u6570\u7684\u8b66\u544a\u53ef\u4ee5\u901a\u8fc7\u5220\u9664\u51fd\u6570\u5f00\u5934\u7684\u53d8\u91cf\u6765\u6d88\u9664\u3002\u5e76\u5305\u542b\u4e00\u4e2a\u6ce8\u91ca\u89e3\u91ca\u4e3a\u4ec0\u4e48\u5220\u9664\u5b83\u3002\u4f7f\u7528 \u201cUnused.\u201d \u6ce8\u91ca\u5c31\u8db3\u591f\u4e86\u3002\u4f8b\u5982\uff1a
def viking_cafe_order(spam, beans, eggs=None):\ndel beans, eggs # Unused by vikings.\nreturn spam + spam + spam\n
\u8981\u6291\u5236\u8fd9\u79cd\u8b66\u544a\u7684\u5e38\u89c1\u5f62\u5f0f\u8fd8\u5305\u62ec\u4f7f\u7528 \u201c_\" \u4f5c\u4e3a\u672a\u4f7f\u7528\u53c2\u6570\u7684\u6807\u8bc6\u7b26\uff0c\u6216\u5728\u53c2\u6570\u540d\u524d\u52a0\u4e0a \u201cunused_\u201d\uff0c\u6216\u5c06\u5b83\u4eec\u8d4b\u503c\u7ed9 \u201c_\"\u3002
\u4e0a\u8ff0\u7684\u8fd9\u4e9b\u5f62\u5f0f\u90fd\u662f\u5141\u8bb8\u7684\uff0c\u4f46\u4e0d\u518d\u63a8\u8350\u3002\u8c03\u7528\u65b9\u6cd5\u65f6\u6309\u540d\u79f0\u4f20\u9012\u7684\u8fd9\u4e9b\u53c2\u6570\uff0c\u5b9e\u9645\u4e0a\u5e76\u4e0d\u4e00\u5b9a\u4f1a\u4f7f\u7528\u3002
"},{"location":"standard/language_rules/#12","title":"1.2 \u5bfc\u5165","text":"\u53ea\u5bf9\u5305\u548c\u6a21\u5757\u4f7f\u7528 import
\u8bed\u53e5\uff0c\u800c\u4e0d\u662f\u5355\u72ec\u7684\u7c7b\u6216\u51fd\u6570\u3002
"},{"location":"standard/language_rules/#121","title":"1.2.1 \u5b9a\u4e49","text":"\u6a21\u5757\u95f4\u5171\u4eab\u4ee3\u7801\u7684\u91cd\u7528\u673a\u5236\u3002
"},{"location":"standard/language_rules/#122","title":"1.2.2 \u4f18\u70b9","text":"\u547d\u540d\u7a7a\u95f4\u7ba1\u7406\u7ea6\u5b9a\u5341\u5206\u7b80\u5355\u3002\u6bcf\u4e2a\u6807\u8bc6\u7b26\u7684\u6765\u6e90\u90fd\u7528\u4e00\u79cd\u4e00\u81f4\u7684\u65b9\u5f0f\u6307\u793a\uff1ax.Obj
\u8868\u793a Obj
\u5bf9\u8c61\u5b9a\u4e49\u5728\u6a21\u5757 x
\u4e2d\u3002
"},{"location":"standard/language_rules/#123","title":"1.2.3 \u7f3a\u70b9","text":"\u6a21\u5757\u540d\u4ecd\u53ef\u80fd\u51b2\u7a81\u3002\u6709\u4e9b\u6a21\u5757\u540d\u592a\u957f, \u4e0d\u592a\u65b9\u4fbf\u3002
"},{"location":"standard/language_rules/#124","title":"1.2.4 \u7ed3\u8bba","text":" - \u4f7f\u7528
import x
\u6765\u5bfc\u5165\u5305\u548c\u6a21\u5757\u3002 - \u4f7f\u7528
from x import y
\uff0c\u5176\u4e2d x
\u662f\u5305\u524d\u7f00\uff0cy
\u662f\u4e0d\u5e26\u524d\u7f00\u7684\u6a21\u5757\u540d\u3002 -
\u5728\u4ee5\u4e0b\u4efb\u4e00\u60c5\u51b5\u4e0b\u4f7f\u7528 from x import y as z
\uff1a
- \u540d\u5b57\u90fd\u4e3a
y
\u7684\u6a21\u5757\u3002 y
\u4e0e\u5f53\u524d\u6a21\u5757\u4e2d\u9876\u7ea7\u540d\u79f0\u51b2\u7a81\u3002 y
\u4e0e\u4f5c\u4e3a\u516c\u5171 API \u4e00\u90e8\u5206\u7684\u516c\u5171\u53c2\u6570\u540d\u79f0\uff08\u4f8b\u5982\u529f\u80fd\uff09\u51b2\u7a81\u3002 y
\u662f\u4e00\u4e2a\u957f\u540d\u79f0\uff0c\u4f7f\u7528\u4e0d\u592a\u65b9\u4fbf\u3002 y
\u5728\u4ee3\u7801\u4e0a\u4e0b\u6587\u4e2d\u8fc7\u4e8e\u901a\u7528\uff08\u4f8b\u5982\uff1afrom storage.file_system import options as fs_options
\uff09
-
\u53ea\u6709\u5f53 z
\u662f\u6807\u51c6\u7f29\u5199\uff08\u4f8b\u5982\uff0cnumpy
\u4e3a np
\uff09\u65f6\uff0c\u624d\u4f7f\u7528 import y as z
\u3002
\u4f8b\u5982\uff0c\u6a21\u5757 sound.effects.echo
\u53ef\u4ee5\u7528\u5982\u4e0b\u65b9\u5f0f\u5bfc\u5165\uff1a
from sound.effects import echo\n...\necho.EchoFilter(input, output, delay=0.7, atten=4)\n
\u5bfc\u5165\u65f6\u4e0d\u8981\u4f7f\u7528\u76f8\u5bf9\u540d\u79f0\u3002\u5373\u4f7f\u6a21\u5757\u5728\u540c\u4e00\u4e2a\u5305\u4e2d\uff0c\u4e5f\u8981\u4f7f\u7528\u5b8c\u6574\u5305\u540d\u3002\u8fd9\u80fd\u5e2e\u52a9\u4f60\u907f\u514d\u65e0\u610f\u95f4\u5bfc\u5165\u4e00\u4e2a\u5305\u4e24\u6b21\u3002
\u4ee5\u4e0b\u89c4\u5219\u4e0d\u53d7\u7ea6\u675f\uff1a
-
\u7528\u4e8e\u652f\u6301\u9759\u6001\u5206\u6790\u548c\u7c7b\u578b\u68c0\u67e5\uff1a
- typing
- collections.abc
- typing_extensions
-
\u91cd\u5b9a\u5411\u6a21\u5757 Six.moves
"},{"location":"standard/language_rules/#13","title":"1.3 \u5305","text":"\u4f7f\u7528\u6a21\u5757\u7684\u5168\u8def\u5f84\u540d\u6765\u5bfc\u5165\u6bcf\u4e2a\u6a21\u5757\u3002
"},{"location":"standard/language_rules/#131","title":"1.3.1 \u4f18\u70b9","text":"\u907f\u514d\u6a21\u5757\u540d\u79f0\u51b2\u7a81\u6216\u56e0\u6a21\u5757\u641c\u7d22\u8def\u5f84\u4e0e\u4f5c\u8005\u9884\u671f\u4e0d\u7b26\u800c\u5bfc\u81f4\u7684\u9519\u8bef\u5bfc\u5165\u3002\u67e5\u627e\u5305\u66f4\u5bb9\u6613\u3002
"},{"location":"standard/language_rules/#132","title":"1.3.2 \u7f3a\u70b9","text":"\u56e0\u4e3a\u8981\u590d\u5236\u5305\u5c42\u6b21\u7ed3\u6784\uff0c\u6240\u4ee5\u5728\u90e8\u7f72\u4ee3\u7801\u65f6\u4f1a\u66f4\u52a0\u56f0\u96be\u3002\u4f46\u662f\u5bf9\u4e8e\u73b0\u4ee3\u7684\u90e8\u7f72\u673a\u5236\u6765\u8bf4\uff0c\u8fd9\u5e76\u4e0d\u662f\u771f\u6b63\u7684\u95ee\u9898\u3002
"},{"location":"standard/language_rules/#133","title":"1.3.3 \u7ed3\u8bba","text":"\u6240\u6709\u7684\u65b0\u4ee3\u7801\u90fd\u5e94\u8be5\u7528\u5b8c\u6574\u5305\u540d\u6765\u5bfc\u5165\u6bcf\u4e2a\u6a21\u5757\u3002
\u5e94\u8be5\u50cf\u4e0b\u9762\u8fd9\u6837\u5bfc\u5165\uff1a
\u63a8\u8350
# Reference absl.flags in code with the complete name (verbose).\nimport absl.flags\nfrom doctor.who import jodie\n_FOO = absl.flags.DEFINE_string(...)\n
\u63a8\u8350
# Reference flags in code with just the module name (common).\nfrom absl import flags\nfrom doctor.who import jodie\n_FOO = flags.DEFINE_string(...)\n
\u4e0d\u63a8\u8350 ( \u5047\u8bbe jodie.py
\u6587\u4ef6\u5728 doctor/who/
\u4e2d )
# Unclear what module the author wanted and what will be imported. The actual\n# import behavior depends on external factors controlling sys.path.\n# Which possible jodie module did the author intend to import?\nimport jodie\n
\u5c3d\u7ba1\u5728\u67d0\u4e9b\u73af\u5883\u4e2d\u4f1a\u53d1\u751f\u8fd9\u79cd\u60c5\u51b5\uff0c\u4f46\u4e0d\u5e94\u5047\u5b9a\u4e3b\u4e8c\u8fdb\u5236\u6587\u4ef6\u6240\u5728\u7684\u76ee\u5f55\u4f4d\u4e8e sys.path
\u4e2d\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u4ee3\u7801\u5e94\u5047\u5b9a import jodie
\u5f15\u7528\u4e86\u540d\u4e3a jodie
\u7684\u7b2c\u4e09\u65b9\u6216\u9876\u7ea7\u7a0b\u5e8f\u5305\uff0c\u800c\u4e0d\u662f\u672c\u5730\u7684 jodie.py
\u3002
"},{"location":"standard/language_rules/#14","title":"1.4 \u5f02\u5e38","text":"\u5141\u8bb8\u4f7f\u7528\u5f02\u5e38\uff0c\u4f46\u5fc5\u987b\u5c0f\u5fc3\u3002
"},{"location":"standard/language_rules/#141","title":"1.4.1 \u5b9a\u4e49","text":"\u5f02\u5e38\u662f\u4e00\u79cd\u8df3\u51fa\u4ee3\u7801\u5757\u7684\u6b63\u5e38\u63a7\u5236\u6d41\u6765\u5904\u7406\u9519\u8bef\u6216\u8005\u5176\u5b83\u5f02\u5e38\u6761\u4ef6\u7684\u65b9\u5f0f\u3002
"},{"location":"standard/language_rules/#141_1","title":"1.4.1 \u4f18\u70b9","text":"\u6b63\u5e38\u64cd\u4f5c\u4ee3\u7801\u7684\u63a7\u5236\u6d41\u4e0d\u4f1a\u548c\u9519\u8bef\u5904\u7406\u4ee3\u7801\u6df7\u5728\u4e00\u8d77\u3002\u5f53\u67d0\u79cd\u6761\u4ef6\u53d1\u751f\u65f6\uff0c\u5b83\u4e5f\u5141\u8bb8\u63a7\u5236\u6d41\u8df3\u8fc7\u591a\u4e2a\u6846\u67b6\u3002\u4f8b\u5982\uff0c\u4e00\u6b65\u8df3\u51fa N \u4e2a\u5d4c\u5957\u7684\u51fd\u6570\uff0c\u800c\u4e0d\u5fc5\u7ee7\u7eed\u6267\u884c\u9519\u8bef\u7684\u4ee3\u7801\u3002
"},{"location":"standard/language_rules/#142","title":"1.4.2 \u7f3a\u70b9","text":"\u53ef\u80fd\u4f1a\u5bfc\u81f4\u8ba9\u4eba\u56f0\u60d1\u7684\u63a7\u5236\u6d41\u3002\u8c03\u7528\u5e93\u65f6\u5bb9\u6613\u9519\u8fc7\u9519\u8bef\u60c5\u51b5\u3002
"},{"location":"standard/language_rules/#144","title":"1.4.4 \u7ed3\u8bba","text":"\u5f02\u5e38\u5fc5\u987b\u9075\u5b88\u7279\u5b9a\u6761\u4ef6\uff1a
-
\u5982\u679c\u6709\u5fc5\u8981\uff0c\u8bf7\u4f7f\u7528\u5185\u7f6e\u5f02\u5e38\u7c7b\u3002\u4f8b\u5982\uff0c\u629b\u51fa ValureError
\u6765\u6307\u793a\u7f16\u7a0b\u9519\u8bef\u3002\u6bd4\u5982\u8fdd\u53cd\u4e86\u524d\u7f6e\u6761\u4ef6\uff08\u9700\u8981\u4e00\u4e2a\u6b63\u6570\uff0c\u4f46\u4f20\u9012\u4e86\u4e00\u4e2a\u8d1f\u6570\uff09\u3002\u4e0d\u8981\u4f7f\u7528 assert
\u8bed\u53e5\u9a8c\u8bc1\u516c\u5171 API \u7684\u53c2\u6570\u503c\u3002assert
\u7528\u4e8e\u786e\u4fdd\u5185\u90e8\u6b63\u786e\u6027\uff0c\u4e0d\u5f97\u5f3a\u5236\u4f7f\u7528\uff0c\u4e5f\u4e0d\u8868\u793a\u53d1\u751f\u4e86\u67d0\u4e9b\u610f\u5916\u4e8b\u4ef6\u3002\u5982\u679c\u5728\u540e\u4e00\u79cd\u60c5\u51b5\u4e0b\u9700\u8981\u4f7f\u7528\u5f02\u5e38\uff0c\u8bf7\u4f7f\u7528 raise \u8bed\u53e5\u3002\u4f8b\u5982\uff1a
\u63a8\u8350
def connect_to_next_port(self, minimum):\n\"\"\"Connects to the next available port.\n Args:\n minimum: A port value greater or equal to 1024.\n Returns:\n The new minimum port.\n Raises:\n ConnectionError: If no available port is found.\n \"\"\"\nif minimum < 1024:\n# Note that this raising of ValueError is not mentioned in the doc\n# string's \"Raises:\" section because it is not appropriate to\n# guarantee this specific behavioral reaction to API misuse.\nraise ValueError(f'Min. port must be at least 1024, not {minimum}.')\nport = self._find_next_open_port(minimum)\nif not port:\nraise ConnectionError(\nf'Could not connect to service on port {minimum} or higher.')\nassert port >= minimum, (\nf'Unexpected port {port} when minimum was {minimum}.')\nreturn port\n
\u4e0d\u63a8\u8350
def connect_to_next_port(self, minimum):\n\"\"\"Connects to the next available port.\n Args:\n minimum: A port value greater or equal to 1024.\n Returns:\n The new minimum port.\n \"\"\"\nassert minimum >= 1024, 'Minimum port must be at least 1024.'\nport = self._find_next_open_port(minimum)\nassert port is not None\nreturn port\n
-
\u6a21\u5757\u6216\u5305\u5e94\u8be5\u5b9a\u4e49\u81ea\u5df1\u7684\u7279\u5b9a\u57df\u7684\u5f02\u5e38\u57fa\u7c7b\u3002\u8fd9\u4e2a\u57fa\u7c7b\u5e94\u8be5\u4ece\u5185\u5efa\u7684 Exception
\u7c7b\u7ee7\u627f\u3002\u5f02\u5e38\u540d\u79f0\u5e94\u8be5\u4ee5 Error
\u7ed3\u5c3e\uff0c\u800c\u4e14\u4e0d\u5e94\u8be5\u96be\u4ee5\u7406\u89e3\uff08foo.FooError
\uff09\u3002
-
\u6c38\u8fdc\u4e0d\u8981\u4f7f\u7528 expect:
\u8bed\u53e5\u6765\u6355\u83b7\u6240\u6709\u5f02\u5e38\uff0c\u4e5f\u4e0d\u8981\u6355\u83b7 Exception
\u6216\u8005 StandardError
\uff0c\u9664\u975e\uff1a
- \u91cd\u65b0\u89e6\u53d1\u8be5\u5f02\u5e38\uff0c\u6216
- \u5728\u7a0b\u5e8f\u4e2d\u521b\u5efa\u4e00\u4e2a\u9694\u79bb\u70b9\uff0c\u5176\u4e2d\u5f02\u5e38\u4e0d\u4f1a\u4f20\u64ad\uff0c\u800c\u662f\u88ab\u8bb0\u5f55\u548c\u6291\u5236\uff0c\u4f8b\u5982\u901a\u8fc7\u4fdd\u62a4\u7ebf\u7a0b\u7684\u6700\u5916\u5c42\u5757\u6765\u9632\u6b62\u7a0b\u5e8f\u5d29\u6e83\u3002
\u5728\u5f02\u5e38\u8fd9\u65b9\u9762, Python \u975e\u5e38\u5bbd\u5bb9\uff0c expect:
\u53ef\u4ee5\u6355\u83b7\u6240\u6709\u62fc\u5199\u9519\u8bef\u7684\u540d\u79f0\uff0c sys.exit()
\u8c03\u7528\uff0c Ctrl+C
\u4e2d\u65ad\uff0cunittest
\u5931\u8d25\u548c\u6240\u6709\u4f60\u4e0d\u60f3\u6355\u83b7\u7684\u5176\u4ed6\u5f02\u5e38\u3002
- \u5c3d\u91cf\u51cf\u5c11
try/except
\u5757\u4e2d\u7684\u4ee3\u7801\u91cf\u3002 try
\u5757\u7684\u4f53\u79ef\u8d8a\u5927\uff0c\u671f\u671b\u4e4b\u5916\u7684\u5f02\u5e38\u5c31\u8d8a\u5bb9\u6613\u88ab\u89e6\u53d1\u3002\u5728\u8fd9\u4e9b\u60c5\u51b5\u4e0b\uff0ctry/except
\u5757\u5c06\u9690\u85cf\u771f\u6b63\u7684\u9519\u8bef\u3002 - \u4f7f\u7528
finally
\u5b50\u53e5\u6765\u6267\u884c\u90a3\u4e9b\u65e0\u8bba try
\u5757\u4e2d\u6709\u6ca1\u6709\u5f02\u5e38\u90fd\u5e94\u8be5\u88ab\u6267\u884c\u7684\u4ee3\u7801\u3002\u8fd9\u5bf9\u4e8e\u6e05\u7406\u8d44\u6e90\u5e38\u5e38\u5f88\u6709\u7528\uff0c\u4f8b\u5982\u5173\u95ed\u6587\u4ef6\u3002
"},{"location":"standard/language_rules/#15","title":"1.5 \u5168\u5c40\u53d8\u91cf","text":"\u907f\u514d\u5168\u5c40\u53d8\u91cf\u3002
"},{"location":"standard/language_rules/#151","title":"1.5.1 \u5b9a\u4e49","text":"\u5b9a\u4e49\u5728\u6a21\u5757\u7ea7\u7684\u53d8\u91cf\u3002
"},{"location":"standard/language_rules/#152","title":"1.5.2 \u4f18\u70b9","text":"\u5076\u5c14\u6709\u7528\u3002
"},{"location":"standard/language_rules/#153","title":"1.5.3 \u7f3a\u70b9","text":" - \u7834\u574f\u5c01\u88c5\uff1a\u8fd9\u79cd\u8bbe\u8ba1\u53ef\u80fd\u4f1a\u8ba9\u6709\u6548\u76ee\u6807\u7684\u5b9e\u73b0\u53d8\u5f97\u56f0\u96be\u3002\u4f8b\u5982\u4f7f\u7528\u5168\u5c40\u72b6\u6001\u6765\u7ba1\u7406\u6570\u636e\u5e93\u8fde\u63a5\uff0c\u5219\u540c\u65f6\u8fde\u63a5\u4e24\u4e2a\u4e0d\u540c\u7684\u6570\u636e\u5e93\u53d8\u5f97\u56f0\u96be\uff08\u4f8b\u5982\u518d\u8fc1\u79fb\u671f\u95f4 \u8ba1\u7b97\u5dee\u5f02\uff09\u3002\u5168\u5c40\u6ce8\u518c\u8868\u4e5f\u5bb9\u6613\u51fa\u73b0\u7c7b\u4f3c\u7684\u95ee\u9898\u3002
- \u5bfc\u5165\u65f6\u53ef\u80fd\u6539\u53d8\u6a21\u5757\u884c\u4e3a\uff0c\u56e0\u4e3a\u5bfc\u5165\u6a21\u5757\u65f6\u4f1a\u5bf9\u6a21\u5757\u7ea7\u53d8\u91cf\u8d4b\u503c\u3002
"},{"location":"standard/language_rules/#154","title":"1.5.4 \u7ed3\u8bba","text":"\u907f\u514d\u4f7f\u7528\u5168\u5c40\u53d8\u91cf\u3002
\u5982\u679c\u9700\u8981\uff0c\u5168\u5c40\u53d8\u91cf\u5e94\u8be5\u4ec5\u5728\u6a21\u5757\u5185\u90e8\u53ef\u7528\uff0c\u5e76\u901a\u8fc7\u5728\u540d\u79f0\u524d\u52a0\u4e0a _
\u524d\u7f00\u4f7f\u5176\u6210\u4e3a\u6a21\u5757\u7684\u5185\u90e8\u53d8\u91cf\u3002\u5916\u90e8\u8bbf\u95ee\u5fc5\u987b\u901a\u8fc7\u6a21\u5757\u7ea7\u7684\u516c\u5171\u51fd\u6570\u6765\u8bbf\u95ee\u3002\u5177\u4f53\u8bf7\u53c2\u9605\u547d\u540d\u89c4\u8303\u3002\u8bf7\u5728\u6ce8\u91ca\u6216\u4e0e\u6ce8\u91ca\u76f8\u5173\u7684\u6587\u6863\u4e2d\u8bf4\u660e\u4f7f\u7528\u53ef\u53d8\u5168\u5c40\u72b6\u6001\u7684\u8bbe\u8ba1\u539f\u56e0\u3002
\u6a21\u5757\u7ea7\u5e38\u91cf\u662f\u5141\u8bb8\u548c\u9f13\u52b1\u4f7f\u7528\u7684\u3002\u4f8b\u5982\uff1a _MAX_HOLY_HANDGRENADE_COUNT = 3
(\u5bf9\u5185\u90e8\u4f7f\u7528\u5e38\u91cf)\u6216 SIR_LANCELOTS_FAVORITE_COLOR = \"blue\"
(\u5bf9\u516c\u5171 API \u5e38\u91cf)\u3002\u5e38\u91cf\u7684\u547d\u540d\u5fc5\u987b\u4f7f\u7528\u5168\u5927\u5199\u548c\u4e0b\u5212\u7ebf\u3002\u5177\u4f53\u8bf7\u53c2\u9605\u547d\u540d\u89c4\u8303\u3002
"},{"location":"standard/language_rules/#16","title":"1.6 \u5d4c\u5957/\u5c40\u90e8/\u5185\u90e8\u7c7b\u6216\u51fd\u6570","text":"\u5728\u9700\u8981\u5173\u95ed\u5c40\u90e8\u53d8\u91cf\u65f6\u9f13\u52b1\u4f7f\u7528\u5d4c\u5957\u672c\u5730\u5185\u90e8\u7c7b\u6216\u51fd\u6570\uff0c\u5d4c\u5957\u7c7b\u66f4\u597d\u3002
"},{"location":"standard/language_rules/#161","title":"1.6.1 \u5b9a\u4e49","text":"\u7c7b\u53ef\u4ee5\u5b9a\u4e49\u5728\u65b9\u6cd5\u3001\u51fd\u6570\u6216\u8005\u7c7b\u4e2d\u3002\u51fd\u6570\u53ef\u4ee5\u5b9a\u4e49\u5728\u65b9\u6cd5\u6216\u51fd\u6570\u4e2d\u3002\u5c01\u95ed\u533a\u95f4\u4e2d\u5b9a\u4e49\u7684\u53d8\u91cf\u5bf9\u5d4c\u5957\u51fd\u6570\u662f\u53ea\u8bfb\u7684\u3002
"},{"location":"standard/language_rules/#162","title":"1.6.2 \u4f18\u70b9","text":"\u5141\u8bb8\u5b9a\u4e49\u4ec5\u7528\u4e8e\u6709\u6548\u8303\u56f4\u7684\u5de5\u5177\u7c7b\u548c\u51fd\u6570\u3002\u975e\u5e38\u50cf ADT-y \u3002\u901a\u5e38\u7528\u4e8e\u5b9e\u73b0\u88c5\u9970\u5668\u3002
"},{"location":"standard/language_rules/#163","title":"1.6.3 \u7f3a\u70b9","text":" - \u4e0d\u80fd\u76f4\u63a5\u6d4b\u8bd5\u5d4c\u5957\u51fd\u6570\u548c\u7c7b\u3002
- \u5d4c\u5957\u4f1a\u4f7f\u5916\u90e8\u51fd\u6570\u66f4\u957f\u3002
- \u53ef\u8bfb\u6027\u66f4\u5dee\u3002
"},{"location":"standard/language_rules/#164","title":"1.6.4 \u7ed3\u8bba","text":"\u53ef\u4ee5\u4f7f\u7528\uff0c\u4f46\u6709\u4e00\u4e9b\u9650\u5236\u3002\u907f\u514d\u4f7f\u7528\u5d4c\u5957\u51fd\u6570\u6216\u7c7b\uff0c\u9664\u975e\u8981\u5173\u95ed\u5c40\u90e8\u503c\u3002\u4e0d\u8981\u4ec5\u4ec5\u4e3a\u4e86\u5bf9\u7528\u6237\u9690\u85cf\u6a21\u5757\u7684\u67d0\u4e2a\u51fd\u6570\u800c\u8fdb\u884c\u5d4c\u5957\u3002\u76f8\u53cd\uff0c\u5e94\u8be5\u5728\u6a21\u5757\u7ea7\u522b\u7684\u540d\u79f0\u4e0a\u52a0 _
\u524d\u7f00\uff0c\u8fd9\u6837\u65b9\u4fbf\u6d4b\u8bd5\u3002
"},{"location":"standard/language_rules/#17","title":"1.7 \u63a8\u5bfc\u5f0f\u548c\u751f\u6210\u8868\u8fbe\u5f0f","text":"\u53ef\u4ee5\u5728\u7b80\u5355\u60c5\u51b5\u4e0b\u4f7f\u7528\u3002
"},{"location":"standard/language_rules/#171","title":"1.7.1 \u5b9a\u4e49","text":"List
\u3001Dict
\u548c Set
\u63a8\u5bfc\u5f0f\u4e0e\u751f\u6210\u5668\u8868\u8fbe\u5f0f\u63d0\u4f9b\u4e86\u4e00\u79cd\u7b80\u6d01\u800c\u6709\u6548\u7684\u65b9\u6cd5\u6765\u521b\u5efa\u5217\u8868\u548c\u8fed\u4ee3\u5668\uff0c\u800c\u4e0d\u5fc5\u501f\u52a9\u4f20\u7edf\u7684\u5faa\u73af\u3001map()
\u3001filter()
\u6216\u8005 lambda
\u3002
"},{"location":"standard/language_rules/#172","title":"1.7.2 \u4f18\u70b9","text":"\u7b80\u5355\u7684\u63a8\u5bfc\u5f0f\u53ef\u4ee5\u6bd4\u5176\u4ed6\u7684\u5b57\u5178\u3001\u5217\u8868\u6216\u96c6\u5408\u521b\u5efa\u6280\u672f\u66f4\u52a0\u6e05\u6670\u7b80\u5355\u3002\u751f\u6210\u5668\u8868\u8fbe\u5f0f\u53ef\u4ee5\u5341\u5206\u9ad8\u6548\uff0c\u56e0\u4e3a\u5b83\u4eec\u907f\u514d\u4e86\u521b\u5efa\u6574\u4e2a\u5217\u8868\u3002
"},{"location":"standard/language_rules/#173","title":"1.7.3 \u7f3a\u70b9","text":"\u590d\u6742\u7684\u63a8\u5bfc\u5f0f\u6216\u8005\u751f\u6210\u5668\u8868\u8fbe\u5f0f\u53ef\u80fd\u96be\u4ee5\u9605\u8bfb\u3002
"},{"location":"standard/language_rules/#174","title":"1.7.4 \u7ed3\u8bba","text":"\u9002\u7528\u4e8e\u7b80\u5355\u60c5\u51b5\u3002\u6bcf\u4e2a\u90e8\u5206\u5e94\u8be5\u5355\u72ec\u7f6e\u4e8e\u4e00\u884c\uff1amapping
\u8868\u8fbe\u5f0f\uff0cfor
\u5b50\u53e5\uff0cfilter
\u8868\u8fbe\u5f0f\u3002\u7981\u6b62\u591a\u91cd for
\u8bed\u53e5\u6216\u8fc7\u6ee4\u5668\u8868\u8fbe\u5f0f\u3002\u590d\u6742\u60c5\u51b5\u4e0b\u8fd8\u662f\u4f7f\u7528\u5faa\u73af\u3002
\u63a8\u8350
result = [mapping_expr for value in iterable if filter_expr]\nresult = [{'key': value} for value in iterable\nif a_long_filter_expression(value)]\nresult = [complicated_transform(x)\nfor x in iterable if predicate(x)]\ndescriptive_name = [\ntransform({'key': key, 'value': value}, color='black')\nfor key, value in generate_iterable(some_input)\nif complicated_condition_is_met(key, value)\n]\nresult = []\nfor x in range(10):\nfor y in range(5):\nif x * y > 10:\nresult.append((x, y))\nreturn {x: complicated_transform(x)\nfor x in long_generator_function(parameter)\nif x is not None}\nsquares_generator = (x**2 for x in range(10))\nunique_names = {user.name for user in users if user is not None}\neat(jelly_bean for jelly_bean in jelly_beans\nif jelly_bean.color == 'black')\n
\u4e0d\u63a8\u8350
result = [complicated_transform(\nx, some_argument=x+1)\nfor x in iterable if predicate(x)]\nresult = [(x, y) for x in range(10) for y in range(5) if x * y > 10]\nreturn ((x, y, z)\nfor x in range(5)\nfor y in range(5)\nif x != y\nfor z in range(5)\nif y != z)\n
"},{"location":"standard/language_rules/#18","title":"1.8 \u9ed8\u8ba4\u8fed\u4ee3\u5668\u548c\u64cd\u4f5c\u7b26","text":"\u5982\u679c\u7c7b\u578b\u652f\u6301\uff0c\u5c31\u4f7f\u7528\u9ed8\u8ba4\u8fed\u4ee3\u5668\u548c\u64cd\u4f5c\u7b26\u3002\u6bd4\u5982\u5217\u8868\uff0c\u5b57\u5178\u53ca\u6587\u4ef6\u7b49\u3002
"},{"location":"standard/language_rules/#181","title":"1.8.1 \u5b9a\u4e49","text":"\u5bb9\u5668\u7c7b\u578b\uff0c\u50cf\u5b57\u5178\u548c\u5217\u8868\uff0c\u5b9a\u4e49\u4e86\u9ed8\u8ba4\u7684\u8fed\u4ee3\u5668\u548c\u5173\u7cfb\u6d4b\u8bd5\u64cd\u4f5c\u7b26\uff08 in
\u548c not in
\uff09
"},{"location":"standard/language_rules/#182","title":"1.8.2 \u4f18\u70b9","text":"\u9ed8\u8ba4\u64cd\u4f5c\u7b26\u548c\u8fed\u4ee3\u5668\u7b80\u5355\u9ad8\u6548\uff0c\u5b83\u4eec\u76f4\u63a5\u8868\u8fbe\u4e86\u64cd\u4f5c\uff0c\u6ca1\u6709\u989d\u5916\u7684\u65b9\u6cd5\u8c03\u7528\u3002\u4f7f\u7528\u9ed8\u8ba4\u64cd\u4f5c\u7b26\u7684\u51fd\u6570\u662f\u901a\u7528\u7684\u3002\u5b83\u53ef\u4ee5\u7528\u4e8e\u652f\u6301\u8be5\u64cd\u4f5c\u7684\u4efb\u4f55\u7c7b\u578b\u3002
"},{"location":"standard/language_rules/#183","title":"1.8.3 \u7f3a\u70b9","text":"\u60a8\u65e0\u6cd5\u901a\u8fc7\u8bfb\u53d6\u65b9\u6cd5\u540d\u79f0\u6765\u5224\u65ad\u5bf9\u8c61\u7684\u7c7b\u578b\uff08\u9664\u975e\u53d8\u91cf\u5177\u6709\u7c7b\u578b\u6ce8\u91ca\uff09\u3002\u8fd9\u4e5f\u662f\u4e00\u4e2a\u4f18\u70b9\u3002
"},{"location":"standard/language_rules/#184","title":"1.8.4 \u7ed3\u8bba","text":"\u5982\u679c\u7c7b\u578b\u652f\u6301\uff0c\u5c31\u4f7f\u7528\u9ed8\u8ba4\u8fed\u4ee3\u5668\u548c\u64cd\u4f5c\u7b26\uff0c\u4f8b\u5982\u5217\u8868\u3001\u5b57\u5178\u548c\u6587\u4ef6\u3002\u5185\u5efa\u7c7b\u578b\u4e5f\u5b9a\u4e49\u4e86\u8fed\u4ee3\u5668\u65b9\u6cd5\u3002\u4f18\u5148\u8003\u8651\u8fd9\u4e9b\u65b9\u6cd5\uff0c\u800c\u4e0d\u662f\u90a3\u4e9b\u8fd4\u56de\u5217\u8868\u7684\u65b9\u6cd5\u3002\u5f53\u7136\uff0c\u8fd9\u6837\u904d\u5386\u5bb9\u5668\u65f6\uff0c\u4f60\u5c06\u4e0d\u80fd\u4fee\u6539\u5bb9\u5668\u3002
\u63a8\u8350
for key in adict: ...\nif obj in alist: ...\nfor line in afile: ...\nfor k, v in adict.items(): ...\n
\u4e0d\u63a8\u8350
for key in adict.keys(): ...\nfor line in afile.readlines(): ...\n
"},{"location":"standard/language_rules/#19","title":"1.9 \u751f\u6210\u5668","text":"\u6309\u9700\u4f7f\u7528\u751f\u6210\u5668\u3002
"},{"location":"standard/language_rules/#191","title":"1.9.1 \u5b9a\u4e49","text":"\u6240\u8c13\u751f\u6210\u5668\u51fd\u6570\uff0c\u5c31\u662f\u6bcf\u5f53\u5b83\u6267\u884c\u4e00\u6b21\u751f\u6210 yield
\u8bed\u53e5\uff0c\u5b83\u5c31\u8fd4\u56de\u4e00\u4e2a\u8fed\u4ee3\u5668\uff0c\u8fd9\u4e2a\u8fed\u4ee3\u5668\u751f\u6210\u4e00\u4e2a\u503c\u3002 \u751f\u6210\u503c\u540e\uff0c\u751f\u6210\u5668\u51fd\u6570\u7684\u8fd0\u884c\u72b6\u6001\u5c06\u88ab\u6302\u8d77\uff0c\u76f4\u5230\u9700\u8981\u4e0b\u4e00\u6b21\u503c\u4e3a\u6b62\u3002
"},{"location":"standard/language_rules/#192","title":"1.9.2 \u4f18\u70b9","text":"\u7b80\u5316\u4ee3\u7801\uff0c\u56e0\u4e3a\u6bcf\u6b21\u8c03\u7528\u65f6\uff0c\u5c40\u90e8\u53d8\u91cf\u548c\u63a7\u5236\u6d41\u7684\u72b6\u6001\u90fd\u4f1a\u88ab\u4fdd\u5b58\u3002\u6bd4\u8d77\u4e00\u6b21\u521b\u5efa\u4e00\u7cfb\u5217\u503c\u7684\u51fd\u6570\uff0c\u751f\u6210\u5668\u4f7f\u7528\u7684\u5185\u5b58\u66f4\u5c11\u3002
"},{"location":"standard/language_rules/#193","title":"1.9.3 \u7f3a\u70b9","text":"\u751f\u6210\u5668\u4e2d\u7684\u5c40\u90e8\u53d8\u91cf\u4e0d\u4f1a\u88ab\u5783\u573e\u6536\u96c6\uff0c\u76f4\u5230\u751f\u6210\u5668\u8017\u5c3d\u6216\u672c\u8eab\u88ab\u5783\u573e\u6536\u96c6\u3002
"},{"location":"standard/language_rules/#194","title":"1.9.4 \u7ed3\u8bba","text":"\u9f13\u52b1\u4f7f\u7528\u3002\u6ce8\u610f\u5728\u751f\u6210\u5668\u51fd\u6570\u7684\u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u4f7f\u7528\u201cYields:
\u201d\u800c\u4e0d\u662f\u201cReturns:
\u201d\u3002
\u5982\u679c\u751f\u6210\u5668\u7ba1\u7406\u7740\u4e00\u4e2a\u6602\u8d35\u7684\u8d44\u6e90\uff0c\u8bf7\u786e\u4fdd\u5f3a\u5236\u8fdb\u884c\u6e05\u7406\u3002
\u4e00\u4e2a\u5f88\u597d\u7684\u6e05\u7406\u65b9\u5f0f\u662f\u4f7f\u7528\u4e0a\u4e0b\u6587\u7ba1\u7406\u5668 PEP-0533 \u6765\u5305\u88c5\u751f\u6210\u5668\u3002
"},{"location":"standard/language_rules/#110-lambda","title":"1.10 Lambda \u51fd\u6570","text":"\u9002\u7528\u4e8e\u5355\u884c\u51fd\u6570\u3002\u5e38\u7528\u4e8e\u4e3a map()
\u548c filter()
\u4e4b\u7c7b\u7684\u9ad8\u9636\u51fd\u6570\u5b9a\u4e49\u56de\u8c03\u51fd\u6570\u6216\u8005\u64cd\u4f5c\u7b26\u3002
"},{"location":"standard/language_rules/#1101","title":"1.10.1 \u5b9a\u4e49","text":"Lambdas
\u5728\u4e00\u4e2a\u8868\u8fbe\u5f0f\u4e2d\u5b9a\u4e49\u533f\u540d\u51fd\u6570\uff0c\u800c\u4e0d\u662f\u5728\u8bed\u53e5\u4e2d\u3002
"},{"location":"standard/language_rules/#1102","title":"1.10.2 \u4f18\u70b9","text":"\u65b9\u4fbf\u3002
"},{"location":"standard/language_rules/#1103","title":"1.10.3 \u7f3a\u70b9","text":"\u6bd4\u672c\u5730\u51fd\u6570\u66f4\u96be\u9605\u8bfb\u548c\u8c03\u8bd5\u3002\u6ca1\u6709\u51fd\u6570\u540d\u610f\u5473\u7740\u5806\u6808\u8ddf\u8e2a\u66f4\u96be\u7406\u89e3\u3002\u7531\u4e8e lambda
\u51fd\u6570\u901a\u5e38\u53ea\u5305\u542b\u4e00\u4e2a\u8868\u8fbe\u5f0f\uff0c\u56e0\u6b64\u5176\u8868\u8fbe\u80fd\u529b\u6709\u9650\u3002
"},{"location":"standard/language_rules/#1104","title":"1.10.4 \u7ed3\u8bba","text":"\u9002\u7528\u4e8e\u5355\u884c\u51fd\u6570\u3002\u5982\u679c\u4ee3\u7801\u8d85\u8fc760-80\u4e2a\u5b57\u7b26\uff0c\u6700\u597d\u8fd8\u662f\u5b9a\u4e49\u6210\u5e38\u89c4\uff08\u5d4c\u5957\uff09\u51fd\u6570\u3002
\u5bf9\u4e8e\u5e38\u89c1\u7684\u64cd\u4f5c\u7b26\uff0c\u4f8b\u5982\u4e58\u6cd5\u64cd\u4f5c\u7b26\uff0c\u4f7f\u7528 operator
\u6a21\u5757\u4e2d\u7684\u51fd\u6570\u4ee5\u4ee3\u66ff lambda
\u51fd\u6570\u3002\u4f8b\u5982\uff0c\u63a8\u8350\u4f7f\u7528 operator.mul
\u800c\u4e0d\u662f lambda x, y: x * y
\u3002
"},{"location":"standard/language_rules/#111_1","title":"1.11 \u6761\u4ef6\u8868\u8fbe\u5f0f","text":"\u9002\u7528\u4e8e\u5355\u884c\u51fd\u6570\u3002
"},{"location":"standard/language_rules/#1111","title":"1.11.1 \u5b9a\u4e49","text":"\u6761\u4ef6\u8868\u8fbe\u5f0f\u662f\u5bf9\u4e8e if
\u8bed\u53e5\u7684\u4e00\u79cd\u66f4\u4e3a\u7b80\u77ed\u7684\u53e5\u6cd5\u89c4\u5219\u3002\u4f8b\u5982 x = 1 if cond else 2
\u3002
"},{"location":"standard/language_rules/#1112","title":"1.11.2 \u4f18\u70b9","text":"\u6bd4 if
\u8bed\u53e5\u66f4\u52a0\u7b80\u77ed\u548c\u65b9\u4fbf\u3002
"},{"location":"standard/language_rules/#1112_1","title":"1.11.2 \u7f3a\u70b9","text":"\u6bd4 if
\u8bed\u53e5\u96be\u4e8e\u9605\u8bfb\u3002\u5982\u679c\u8868\u8fbe\u5f0f\u5f88\u957f\uff0c\u96be\u4e8e\u5b9a\u4f4d\u6761\u4ef6\u3002
"},{"location":"standard/language_rules/#1114","title":"1.11.4 \u7ed3\u8bba","text":"\u9002\u7528\u4e8e\u5355\u884c\u51fd\u6570\u3002\u6bcf\u4e2a\u90e8\u5206\u5fc5\u987b\u653e\u5728\u4e00\u884c\u4e0a\uff1a true-expression, if-expression, else-expression
\u3002\u5728\u5176\u4ed6\u60c5\u51b5\u4e0b\uff0c\u63a8\u8350\u4f7f\u7528\u5b8c\u6574\u7684 if
\u8bed\u53e5\u3002
\u63a8\u8350
one_line = 'yes' if predicate(value) else 'no'\nslightly_split = ('yes' if predicate(value) else 'no, nein, nyet')\nthe_longest_ternary_style_that_can_be_done = (\n'yes, true, affirmative, confirmed, correct'\nif predicate(value) else 'no, false, negative, nay')\n
\u4e0d\u63a8\u8350
bad_line_breaking = ('yes' if predicate(value) else 'no')\nportion_too_long = ('yes' if some_long_module.some_long_predicate_function(\nreally_long_variable_name) else 'no, false, negative, nay')\n
"},{"location":"standard/language_rules/#112_1","title":"1.12 \u9ed8\u8ba4\u53c2\u6570\u503c","text":"\u9002\u7528\u4e8e\u5927\u90e8\u5206\u60c5\u51b5\u3002
"},{"location":"standard/language_rules/#1121","title":"1.12.1 \u5b9a\u4e49","text":"\u4f60\u53ef\u4ee5\u5728\u51fd\u6570\u53c2\u6570\u5217\u8868\u7684\u6700\u540e\u6307\u5b9a\u53d8\u91cf\u7684\u503c\uff0c\u4f8b\u5982\uff0c def(a, b=0):
\u3002\u5982\u679c\u8c03\u7528 foo
\u65f6\u53ea\u5e26\u4e00\u4e2a\u53c2\u6570\uff0c\u5219 b
\u88ab\u8bbe\u4e3a 0
\uff0c\u5982\u679c\u5e26\u4e24\u4e2a\u53c2\u6570\uff0c\u5219 b
\u7684\u503c\u7b49\u4e8e\u7b2c\u4e8c\u4e2a\u53c2\u6570\u3002
"},{"location":"standard/language_rules/#1122","title":"1.12.2 \u4f18\u70b9","text":"\u4f60\u7ecf\u5e38\u4f1a\u78b0\u5230\u4e00\u4e9b\u4f7f\u7528\u5927\u91cf\u9ed8\u8ba4\u503c\u7684\u51fd\u6570\uff0c\u4f46\u5076\u5c14\uff08\u6bd4\u8f83\u5c11\u89c1\uff09\u4f60\u60f3\u8981\u8986\u76d6\u8fd9\u4e9b\u9ed8\u8ba4\u503c\u3002\u9ed8\u8ba4\u53c2\u6570\u503c\u63d0\u4f9b\u4e86\u4e00\u79cd\u7b80\u5355\u7684\u65b9\u6cd5\u6765\u5b8c\u6210\u8fd9\u4ef6\u4e8b\uff0c\u4f60\u4e0d\u9700\u8981\u4e3a\u8fd9\u4e9b\u7f55\u89c1\u7684\u4f8b\u5916\u5b9a\u4e49\u5927\u91cf\u51fd\u6570\u3002\u540c\u65f6\uff0c Python \u4e5f\u4e0d\u652f\u6301\u91cd\u8f7d\u65b9\u6cd5\u548c\u51fd\u6570\uff0c\u9ed8\u8ba4\u53c2\u6570\u662f\u4e00\u79cd\u201c\u6a21\u62df\u201d\u91cd\u8f7d\u884c\u4e3a\u7684\u7b80\u5355\u65b9\u5f0f\u3002
"},{"location":"standard/language_rules/#1123","title":"1.12.3 \u7f3a\u70b9","text":"\u9ed8\u8ba4\u53c2\u6570\u53ea\u5728\u6a21\u5757\u52a0\u8f7d\u65f6\u6c42\u503c\u4e00\u6b21\u3002\u5982\u679c\u53c2\u6570\u662f\u5217\u8868\u6216\u5b57\u5178\u4e4b\u7c7b\u7684\u53ef\u53d8\u7c7b\u578b\uff0c\u8fd9\u53ef\u80fd\u4f1a\u5bfc\u81f4\u95ee\u9898\u3002\u5982\u679c\u51fd\u6570\u4fee\u6539\u4e86\u5bf9\u8c61\uff08\u4f8b\u5982\uff0c\u5411\u5217\u8868\u8ffd\u52a0\u9879\uff09\uff0c\u9ed8\u8ba4\u503c\u5c31\u88ab\u4fee\u6539\u4e86\u3002
"},{"location":"standard/language_rules/#1124","title":"1.12.4 \u7ed3\u8bba","text":"\u9f13\u52b1\u4f7f\u7528\uff0c\u4e0d\u8981\u5728\u51fd\u6570\u6216\u65b9\u6cd5\u5b9a\u4e49\u4e2d\u4f7f\u7528\u53ef\u53d8\u5bf9\u8c61\u4f5c\u4e3a\u9ed8\u8ba4\u503c\u3002
\u63a8\u8350
def foo(a, b=None):\nif b is None:\nb = []\n
def foo(a, b: Optional[Sequence] = None):\nif b is None:\nb = []\n
def foo(a, b: Sequence = ()): # Empty tuple OK since tuples are immutable\n...\n
\u4e0d\u63a8\u8350
def foo(a, b=[]):\n...\n
def foo(a, b=time.time()): # The time the module was loaded???\n...\n
from absl import flags\n_FOO = flags.DEFINE_string(...)\ndef foo(a, b=_FOO.value): # sys.argv has not yet been parsed...\n...\n
def foo(a, b: Mapping = {}): # Could still get passed to unchecked code\n...\n
"},{"location":"standard/language_rules/#113-properties","title":"1.13 \u5c5e\u6027\uff08properties\uff09","text":"\u5c5e\u6027\uff08properties\uff09\u53ef\u4ee5\u7528\u4e8e\u63a7\u5236\u9700\u8981\u8fdb\u884c\u7b80\u5355\u8ba1\u7b97\u6216\u903b\u8f91\u7684\u5c5e\u6027\u7684\u83b7\u53d6\u6216\u8bbe\u7f6e\u3002\u5176\u5b9e\u73b0\u5fc5\u987b\u7b26\u5408\u5e38\u89c4\u5c5e\u6027\u8bbf\u95ee\u7684\u4e00\u822c\u671f\u671b\uff1a\u5b83\u4eec\u5e94\u8be5\u662f\u7b80\u5355\uff0c\u76f4\u63a5\uff0c\u4e14\u6613\u4e8e\u7406\u89e3\u3002
"},{"location":"standard/language_rules/#1131","title":"1.13.1 \u5b9a\u4e49","text":"\u4e00\u79cd\u7528\u4e8e\u5305\u88c5\u65b9\u6cd5\u8c03\u7528\u7684\u65b9\u5f0f\u3002\u5f53\u8fd0\u7b97\u91cf\u4e0d\u5927\uff0c\u5b83\u662f\u83b7\u53d6\u548c\u8bbe\u7f6e\u5c5e\u6027\u7684\u6807\u51c6\u65b9\u5f0f\u3002
"},{"location":"standard/language_rules/#1132","title":"1.13.2 \u4f18\u70b9","text":" - \u5141\u8bb8\u8fdb\u884c\u5c5e\u6027\u8bbf\u95ee\u548c\u8d4b\u503c\u7684 API\uff0c\u800c\u4e0d\u662f\u8c03\u7528
getter
\u548c setter
\u65b9\u6cd5\u3002 - \u53ef\u4f7f\u5f97\u5c5e\u6027\u4e3a\u53ea\u8bfb\u3002
- \u5141\u8bb8\u5ef6\u8fdf\u52a0\u8f7d\u3002
- \u63d0\u4f9b\u4e86\u4e00\u79cd\u65b9\u5f0f\uff0c\u5728\u7c7b\u7684\u5185\u90e8\u4e0e\u5916\u90e8\u72ec\u7acb\u6f14\u5316\u65f6\uff0c\u4ecd\u7136\u80fd\u591f\u4fdd\u6301\u7c7b\u7684\u516c\u5171\u63a5\u53e3\u4e0d\u53d8\u3002
"},{"location":"standard/language_rules/#1133","title":"1.13.3 \u7f3a\u70b9","text":" - \u4f1a\u9690\u85cf\u7c7b\u4f3c\u64cd\u4f5c\u7b26\u91cd\u8f7d\u7684\u526f\u4f5c\u7528\u3002
- \u5bf9\u4e8e\u5b50\u7c7b\u53ef\u80fd\u4f1a\u9020\u6210\u6df7\u6dc6\u3002
"},{"location":"standard/language_rules/#1134","title":"1.13.4 \u7ed3\u8bba","text":"\u4e0e\u8fd0\u7b97\u7b26\u91cd\u8f7d\u4e00\u6837\uff0c\u5728\u5fc5\u8981\u7684\u60c5\u51b5\u4e0b\u53ef\u4ee5\u4f7f\u7528\u5c5e\u6027\uff0c\u5e76\u4e14\u5e94\u7b26\u5408\u5178\u578b\u5c5e\u6027\u8bbf\u95ee\u7684\u9884\u671f\uff1b\u5426\u5219\uff0c\u8bf7\u9075\u5faa getters
\u548c setters
\u89c4\u5219\u8fdb\u884c\u64cd\u4f5c\u3002
\u4f8b\u5982\uff0c\u4f7f\u7528\u5c5e\u6027\u540c\u65f6\u83b7\u53d6\u548c\u8bbe\u7f6e\u5185\u90e8\u5c5e\u6027\u662f\u4e0d\u5141\u8bb8\u7684\uff1a\u56e0\u4e3a\u6ca1\u6709\u8fdb\u884c\u4efb\u4f55\u8ba1\u7b97\uff0c\u6240\u4ee5\u5c5e\u6027\u662f\u4e0d\u5fc5\u8981\u7684\uff08\u53ef\u4ee5\u5c06\u5c5e\u6027\u516c\u5f00\u800c\u4e0d\u7528\u4f7f\u7528\u5c5e\u6027\uff09\u3002\u76f8\u6bd4\u4e4b\u4e0b\uff0c\u4f7f\u7528\u5c5e\u6027\u6765\u63a7\u5236\u5c5e\u6027\u8bbf\u95ee\u6216\u8ba1\u7b97\u4e00\u4e2a\u5f88\u5bb9\u6613\u5f97\u51fa\u7684\u503c\u662f\u88ab\u5141\u8bb8\u7684\uff1a\u56e0\u4e3a\u903b\u8f91\u7b80\u5355\uff0c\u4e14\u6613\u4e8e\u7406\u89e3\u3002
\u5e94\u8be5\u4f7f\u7528 @property
\u88c5\u9970\u5668\u521b\u5efa\u5c5e\u6027\u3002\u624b\u52a8\u5b9e\u73b0\u5c5e\u6027\u63cf\u8ff0\u7b26\u88ab\u8ba4\u4e3a\u662f\u5a01\u529b\u8fc7\u5927\u7684\u7279\u6027\u3002
\u5c5e\u6027\u7684\u7ee7\u627f\u53ef\u80fd\u662f\u4e0d\u660e\u663e\u7684\u3002\u4e0d\u8981\u4f7f\u7528\u5c5e\u6027\u6765\u5b9e\u73b0\u5b50\u7c7b\u53ef\u80fd\u60f3\u8981\u91cd\u5199\u548c\u6269\u5c55\u7684\u8ba1\u7b97\u3002
"},{"location":"standard/language_rules/#114-true-false","title":"1.14 True
/ False
\u7684\u6c42\u503c","text":"\u5c3d\u53ef\u80fd\u4f7f\u7528\u9690\u5f0f False
\u3002
"},{"location":"standard/language_rules/#1141","title":"1.14.1 \u5b9a\u4e49","text":"Python \u5728\u5e03\u5c14\u4e0a\u4e0b\u6587\u4e2d\u4f1a\u5c06\u67d0\u4e9b\u503c\u6c42\u503c\u4e3a False
\u3002\u6309\u7b80\u5355\u7684\u76f4\u89c9\u6765\u8bb2\uff0c\u5c31\u662f\u6240\u6709\u7684\u7a7a\u503c\u90fd\u88ab\u8ba4\u4e3a\u662f False
\uff0c\u56e0\u6b64 0
, None
\uff0c[]
\uff0c{}
\uff0c''
\u90fd\u88ab\u8ba4\u4e3a\u662f False
\u3002
"},{"location":"standard/language_rules/#1142","title":"1.14.2 \u4f18\u70b9","text":"\u4f7f\u7528 Python \u5e03\u5c14\u503c\u7684\u6761\u4ef6\u8bed\u53e5\u66f4\u6613\u8bfb\u4e5f\u66f4\u4e0d\u6613\u72af\u9519\u3002\u5927\u90e8\u5206\u60c5\u51b5\u4e0b\uff0c\u4e5f\u66f4\u5feb\u3002
"},{"location":"standard/language_rules/#1143","title":"1.14.3 \u7f3a\u70b9","text":"\u5bf9\u4e8e C / C ++ \u5f00\u53d1\u4eba\u5458\u6765\u8bf4\uff0c\u53ef\u80fd\u770b\u8d77\u6765\u6709\u70b9\u602a\u3002
"},{"location":"standard/language_rules/#1144","title":"1.14.4 \u7ed3\u8bba","text":"\u5c3d\u53ef\u80fd\u4f7f\u7528\u9690\u5f0f\u7684 false
\uff0c\u4f8b\u5982\uff1a\u4f7f\u7528 if foo:
\u800c\u4e0d\u662f if foo !=[]:
\u3002\u4e0d\u8fc7\u8fd8\u662f\u6709\u4e00\u4e9b\u6ce8\u610f\u4e8b\u9879\u9700\u8981\u4f60\u94ed\u8bb0\u5728\u5fc3\uff1a
- \u603b\u662f\u4f7f\u7528
if foo is None:
\u6216 if foo is not None:
\u6765\u68c0\u67e5 None
\u503c\u3002\u4f8b\u5982\uff0c\u5f53\u4f60\u8981\u6d4b\u8bd5\u4e00\u4e2a\u9ed8\u8ba4\u503c\u662f None
\u7684\u53d8\u91cf\u6216\u53c2\u6570\u662f\u5426\u88ab\u8bbe\u4e3a\u5176\u5b83\u503c\u3002\u8fd9\u4e2a\u503c\u5728\u5e03\u5c14\u8bed\u4e49\u4e0b\u53ef\u80fd\u662f false
! - \u6c38\u8fdc\u4e0d\u8981\u7528
==
\u5c06\u4e00\u4e2a\u5e03\u5c14\u91cf\u4e0e False
\u76f8\u6bd4\u8f83\u3002\u4f7f\u7528 if not x:
\u4ee3\u66ff\u3002\u5982\u679c\u4f60\u9700\u8981\u533a\u5206 False
\u548c None
\uff0c\u4f60\u5e94\u8be5\u7528\u50cf if not x and x is not None:
\u8fd9\u6837\u7684\u8bed\u53e5\u3002 - \u5bf9\u4e8e\u5e8f\u5217\uff08\u5b57\u7b26\u4e32\u3001\u5217\u8868\u3001\u5143\u7ec4\uff09\uff0c \u8981\u6ce8\u610f\u7a7a\u5e8f\u5217\u662f
False
\u3002\u56e0\u6b64\uff1a if seq:
\u6216\u8005 if not seq:
\u6bd4 if len(seq):
\u6216 if not len(seq)
\u8981\u66f4\u597d\u3002 -
\u5904\u7406\u6574\u6570\u65f6\uff0c\u4f7f\u7528\u9690\u5f0f False
\u53ef\u80fd\u4f1a\u5f97\u4e0d\u507f\u5931\uff08\u5373\u4e0d\u5c0f\u5fc3\u5c06 None
\u5f53\u505a 0
\u6765\u5904\u7406\uff09\u3002\u4f60\u53ef\u4ee5\u5c06\u4e00\u4e2a\u5df2\u77e5\u662f\u6574\u578b\uff08\u4e14\u4e0d\u662f len()
\u7684\u8fd4\u56de\u7ed3\u679c\uff09\u7684\u503c\u4e0e 0
\u6bd4\u8f83\u3002
\u63a8\u8350
if not users:\nprint('no users')\nif i % 10 == 0:\nself.handle_multiple_of_ten()\ndef f(x=None):\nif x is None:\nx = []\n
\u4e0d\u63a8\u8350
if len(users) == 0:\nprint('no users')\nif not i % 10:\nself.handle_multiple_of_ten()\ndef f(x=None):\nx = x or []\n
-
\u6ce8\u610f\uff1a '0'
\uff08\u5373\uff1a 0
\u4f5c\u4e3a\u5b57\u7b26\u4e32\uff09\u7684\u8ba1\u7b97\u7ed3\u679c\u662f True
\u3002
- \u6ce8\u610f\uff1a Numpy \u6570\u7ec4\u53ef\u80fd\u4f1a\u5728\u9690\u5f0f\u5e03\u5c14\u4e0a\u4e0b\u6587\u4e2d\u5f15\u53d1\u5f02\u5e38\u3002\u6d4b\u8bd5\u4e00\u7ec4
np.array
\u4e3a\u7a7a\u9996\u9009 .size
\u5c5e\u6027 \uff08\u4f8b\u5982 if not users.size
\uff09\u3002
"},{"location":"standard/language_rules/#116-lexical-scoping","title":"1.16 \u8bcd\u6cd5\u4f5c\u7528\u57df\uff08Lexical Scoping\uff09","text":"\u63a8\u8350\u4f7f\u7528
"},{"location":"standard/language_rules/#1161","title":"1.16.1 \u5b9a\u4e49","text":"\u5d4c\u5957\u7684 Python \u51fd\u6570\u53ef\u4ee5\u5f15\u7528\u5916\u5c42\u51fd\u6570\u4e2d\u5b9a\u4e49\u7684\u53d8\u91cf\uff0c\u4f46\u662f\u4e0d\u80fd\u591f\u5bf9\u5b83\u4eec\u8d4b\u503c\u3002\u53d8\u91cf\u7ed1\u5b9a\u7684\u89e3\u6790\u662f\u4f7f\u7528\u8bcd\u6cd5\u4f5c\u7528\u57df\uff0c\u4e5f\u5c31\u662f\u57fa\u4e8e\u9759\u6001\u7684\u7a0b\u5e8f\u6587\u672c\u3002 \u5bf9\u4e00\u4e2a\u5757\u4e2d\u7684\u67d0\u4e2a\u540d\u79f0\u7684\u4efb\u4f55\u8d4b\u503c\u90fd\u4f1a\u5bfc\u81f4Python \u5c06\u5bf9\u8be5\u540d\u79f0\u7684\u5168\u90e8\u5f15\u7528\u5f53\u505a\u5c40\u90e8\u53d8\u91cf\uff0c\u751a\u81f3\u662f\u8d4b\u503c\u524d\u7684\u5904\u7406\u3002 \u5982\u679c\u78b0\u5230 global
\u58f0\u660e\uff0c\u8be5\u540d\u79f0\u5c31\u4f1a\u88ab\u89c6\u4f5c\u5168\u5c40\u53d8\u91cf\u3002
\u4e00\u4e2a\u4f7f\u7528\u8fd9\u4e2a\u7279\u6027\u7684\u4f8b\u5b50\uff1a
def get_adder(summand1):\n\"\"\"Returns a function that adds numbers to a given number.\"\"\"\ndef adder(summand2):\nreturn summand1 + summand2\nreturn adder\n
"},{"location":"standard/language_rules/#1162","title":"1.16.2 \u4f18\u70b9","text":"\u901a\u5e38\u53ef\u4ee5\u5e26\u6765\u66f4\u52a0\u6e05\u6670\uff0c\u4f18\u96c5\u7684\u4ee3\u7801\u3002\u5c24\u5176\u4f1a\u8ba9\u6709\u7ecf\u9a8c\u7684 Lisp \u548c Scheme \uff08\u8fd8\u6709 Haskell\uff0c ML \u7b49\uff09\u7a0b\u5e8f\u5458\u611f\u5230\u6b23\u6170\u3002
"},{"location":"standard/language_rules/#1163","title":"1.16.3 \u7f3a\u70b9","text":"\u53ef\u80fd\u5bfc\u81f4\u8ba9\u4eba\u8ff7\u60d1\u7684 bug\u3002\u4f8b\u5982\u4e0b\u9762\u8fd9\u4e2a\u4f9d\u636e PEP-0227 \u7684\u4f8b\u5b50\uff1a
i = 4\ndef foo(x):\ndef bar():\nprint(i, end='')\n# ...\n# A bunch of code here\n# ...\nfor i in x: # Ah, i *is* local to foo, so this is what bar sees\nprint(i, end='')\nbar()\n
\u56e0\u6b64 foo([1, 2, 3])
\u4f1a\u6253\u5370 1 2 3 3
\u800c\u4e0d\u662f 1 2 3 4
"},{"location":"standard/language_rules/#1164","title":"1.16.4 \u7ed3\u8bba","text":"\u9f13\u52b1\u4f7f\u7528\u3002
"},{"location":"standard/language_rules/#117","title":"1.17 \u51fd\u6570\u4e0e\u65b9\u6cd5\u88c5\u9970\u5668","text":"\u5f53\u6709\u660e\u663e\u4f18\u52bf\u65f6\uff0c\u5c31\u660e\u667a\u800c\u8c28\u614e\u7684\u4f7f\u7528\u88c5\u9970\u5668\u3002\u907f\u514d\u4f7f\u7528 staticmethod
\uff0c\u9650\u5236\u4f7f\u7528 classmethod
\u3002
"},{"location":"standard/language_rules/#1171","title":"1.17.1 \u5b9a\u4e49","text":"\u7528\u4e8e\u51fd\u6570\u53ca\u65b9\u6cd5\u7684\u88c5\u9970\u5668\uff08\u4e5f\u5c31\u662f @
\u6807\u8bb0\uff09\u3002 \u6700\u5e38\u89c1\u7684\u88c5\u9970\u5668\u662f @property
\uff0c\u7528\u4e8e\u5c06\u666e\u901a\u65b9\u6cd5\u8f6c\u6362\u4e3a\u52a8\u6001\u8fd0\u7b97\u7684\u5c5e\u6027\u3002\u4e0d\u8fc7\uff0c\u88c5\u9970\u5668\u8bed\u6cd5\u4e5f\u5141\u8bb8\u7528\u6237\u81ea\u5b9a\u4e49\u88c5\u9970\u5668\u3002 \u7279\u522b\u5730\uff0c\u5bf9\u4e8e\u67d0\u4e2a\u51fd\u6570 my_decorator
\uff0c\u4e0b\u9762\u7684\u4e24\u6bb5\u4ee3\u7801\u662f\u7b49\u6548\u7684\uff1a
class C:\n@my_decorator\ndef method(self):\n# method body ...\n
\u76f8\u5f53\u4e8e\uff1a
class C:\ndef method(self):\n# method body ...\nmethod = my_decorator(method)\n
"},{"location":"standard/language_rules/#1172","title":"1.17.2 \u4f18\u70b9","text":"\u4f18\u96c5\u7684\u5728\u51fd\u6570\u4e0a\u6307\u5b9a\u4e00\u4e9b\u8f6c\u6362\u3002\u8be5\u8f6c\u6362\u53ef\u80fd\u51cf\u5c11\u4e00\u4e9b\u91cd\u590d\u4ee3\u7801\uff0c\u4fdd\u6301\u5df2\u6709\u51fd\u6570\u4e0d\u53d8\uff08enforce invariants)\uff09\u7b49\u3002
"},{"location":"standard/language_rules/#1173","title":"1.17.3 \u7f3a\u70b9","text":"\u88c5\u9970\u5668\u53ef\u4ee5\u5728\u51fd\u6570\u7684\u53c2\u6570\u6216\u8fd4\u56de\u503c\u4e0a\u6267\u884c\u4efb\u4f55\u64cd\u4f5c\uff0c\u8fd9\u53ef\u80fd\u5bfc\u81f4\u8ba9\u4eba\u60ca\u5f02\u7684\u9690\u85cf\u884c\u4e3a\u3002\u6b64\u5916\uff0c\u88c5\u9970\u5668\u5728\u5bf9\u8c61\u5b9a\u4e49\u65f6\u6267\u884c\u3002 \u5bf9\u4e8e\u6a21\u5757\u7ea7\u522b\u7684\u5bf9\u8c61\uff08\u7c7b\u3001\u6a21\u5757\u51fd\u6570\u7b49\uff09\uff0c\u6b64\u8fc7\u7a0b\u53d1\u751f\u5728\u5bfc\u5165\u65f6\u3002\u4ece\u88c5\u9970\u5668\u4ee3\u7801\u7684\u5931\u8d25\u4e2d\u6062\u590d\u66f4\u52a0\u4e0d\u53ef\u80fd\u3002
"},{"location":"standard/language_rules/#1174","title":"1.17.4 \u7ed3\u8bba","text":" - \u5982\u679c\u597d\u5904\u5f88\u663e\u7136\uff0c\u5c31\u660e\u667a\u800c\u8c28\u614e\u7684\u4f7f\u7528\u88c5\u9970\u5668\u3002
- \u88c5\u9970\u5668\u5e94\u8be5\u9075\u5b88\u548c\u51fd\u6570\u4e00\u6837\u7684\u5bfc\u5165\u548c\u547d\u540d\u89c4\u5219\u3002
- \u88c5\u9970\u5668\u7684 Python \u6587\u6863\u5e94\u8be5\u6e05\u6670\u7684\u8bf4\u660e\u8be5\u51fd\u6570\u662f\u4e00\u4e2a\u88c5\u9970\u5668\u3002
- \u8bf7\u4e3a\u88c5\u9970\u5668\u7f16\u5199\u5355\u5143\u6d4b\u8bd5\u3002
\u907f\u514d\u88c5\u9970\u5668\u81ea\u8eab\u5bf9\u5916\u754c\u7684\u4f9d\u8d56\uff08\u5373\u4e0d\u8981\u4f9d\u8d56\u4e8e\u6587\u4ef6\uff0csocket
\uff0c\u6570\u636e\u5e93\u8fde\u63a5\u7b49\uff09\uff0c\u56e0\u4e3a\u88c5\u9970\u5668\u8fd0\u884c\u65f6\u8fd9\u4e9b\u8d44\u6e90\u53ef\u80fd\u4e0d\u53ef\u7528\uff08\u7531 pydoc
\u6216\u5176\u5b83\u5de5\u5177\u5bfc\u5165\uff09\u3002\u5e94\u8be5\u4fdd\u8bc1\u4e00\u4e2a\u7528\u6709\u6548\u53c2\u6570\u8c03\u7528\u7684\u88c5\u9970\u5668\u5728\u6240\u6709\u60c5\u51b5\u4e0b\u90fd\u662f\u6210\u529f\u7684\u3002
\u88c5\u9970\u5668\u662f\u4e00\u79cd\u7279\u6b8a\u5f62\u5f0f\u7684\u201c\u9876\u7ea7\u4ee3\u7801\u201d\u3002\u53c2\u8003 Main \u7684\u8bdd\u9898\u3002
\u6c38\u8fdc\u4e0d\u8981\u4f7f\u7528 staticmethod
\uff0c\u9664\u975e\u4e3a\u4e86\u4e0e\u73b0\u6709\u5e93\u4e2d\u5b9a\u4e49\u7684 API \u96c6\u6210\u800c\u88ab\u8feb\u4f7f\u7528\u3002\u53ef\u4ee5\u5199\u4e00\u4e2a\u6a21\u5757\u7ea7\u51fd\u6570\u4ee3\u66ff\u3002
\u53ea\u6709\u5728\u7f16\u5199\u547d\u540d\u6784\u9020\u51fd\u6570\u6216\u4fee\u6539\u5fc5\u8981\u7684\u5168\u5c40\u72b6\u6001\uff08\u5982\u8fdb\u7a0b\u7ea7\u7f13\u5b58\uff09\u7684\u7279\u5b9a\u7c7b\u64cd\u4f5c\u65f6\u624d\u4f7f\u7528 classmethod
\u3002
"},{"location":"standard/language_rules/#118","title":"1.18 \u7ebf\u7a0b","text":"\u4e0d\u8981\u4f9d\u8d56\u5185\u5efa\u7c7b\u578b\u7684\u539f\u5b50\u6027\u3002
\u867d\u7136 Python \u7684\u5185\u5efa\u7c7b\u578b\u4f8b\u5982\u5b57\u5178\u770b\u4e0a\u53bb\u62e5\u6709\u539f\u5b50\u64cd\u4f5c\uff0c\u4f46\u662f\u5728\u67d0\u4e9b\u60c5\u5f62\u4e0b\u5b83\u4eec\u4ecd\u7136\u4e0d\u662f\u539f\u5b50\u7684\uff08\u5373\uff0c\u5982\u679c __hash__
\u6216 __eq__
\u88ab\u5b9e\u73b0\u4e3a Python \u65b9\u6cd5\uff09\u4e14\u5b83\u4eec\u7684\u539f\u5b50\u6027\u662f\u9760\u4e0d\u4f4f\u7684\u3002\u4f60\u4e5f\u4e0d\u80fd\u6307\u671b\u539f\u5b50\u53d8\u91cf\u8d4b\u503c\uff08\u56e0\u4e3a\u8fd9\u4e2a\u53cd\u8fc7\u6765\u4f9d\u8d56\u5b57\u5178\uff09\u3002
\u4f18\u5148\u4f7f\u7528 Queue
\u6a21\u5757\u7684 Queue
\u6570\u636e\u7c7b\u578b\u4f5c\u4e3a\u7ebf\u7a0b\u95f4\u7684\u6570\u636e\u901a\u4fe1\u65b9\u5f0f\u3002\u53e6\u5916\uff0c\u4f7f\u7528 threading
\u6a21\u5757\u53ca\u5176\u9501\u539f\u8bed\uff08locking primitives
\uff09\u3002\u4e86\u89e3\u6761\u4ef6\u53d8\u91cf\u7684\u5408\u9002\u4f7f\u7528\u65b9\u5f0f\uff0c\u8fd9\u6837\u4f60\u5c31\u53ef\u4ee5\u4f7f\u7528 threading.Condition
\u6765\u53d6\u4ee3\u4f4e\u7ea7\u522b\u7684\u9501\u4e86\u3002
"},{"location":"standard/language_rules/#119","title":"1.19 \u5a01\u529b\u8fc7\u5927\u7684\u7279\u6027","text":"\u907f\u514d\u4f7f\u7528\u8fd9\u4e9b\u7279\u6027\u3002
"},{"location":"standard/language_rules/#1191","title":"1.19.1 \u5b9a\u4e49","text":"Python \u662f\u4e00\u79cd\u5f02\u5e38\u7075\u6d3b\u7684\u8bed\u8a00\uff0c\u5b83\u4e3a\u4f60\u63d0\u4f9b\u4e86\u5f88\u591a\u82b1\u54e8\u7684\u7279\u6027\uff0c\u8bf8\u5982\u5143\u7c7b\uff08metaclasses
\uff09\u3001\u5b57\u8282\u7801\u8bbf\u95ee\u3001 \u4efb\u610f\u7f16\u8bd1\uff08on-the-fly compilation
\uff09\u3001\u52a8\u6001\u7ee7\u627f\u3001\u5bf9\u8c61\u7236\u7c7b\u91cd\u5b9a\u4e49\uff08object reparenting
\uff09\u3001\u5bfc\u5165\u4fee\u6539\uff08import hacks
\uff09\u3001 \u53cd\u5c04\uff08\u4f8b\u5982 getattr()
\u7684\u4e00\u4e9b\u4f7f\u7528\uff09\u3001\u7cfb\u7edf\u5185\u4fee\u6539\uff08modification of system internals
\uff09\u3001\u65b9\u6cd5\u5b9e\u73b0\u81ea\u5b9a\u4e49\u6e05\u7406\uff08__del__
\uff09\u7b49\u7b49\u3002
"},{"location":"standard/language_rules/#1192","title":"1.19.2 \u4f18\u70b9","text":"\u5f3a\u5927\u7684\u8bed\u8a00\u7279\u6027\uff0c\u80fd\u8ba9\u4f60\u7684\u4ee3\u7801\u66f4\u7d27\u51d1\u3002
"},{"location":"standard/language_rules/#1193","title":"1.19.3 \u7f3a\u70b9","text":"\u4f7f\u7528\u8fd9\u4e9b\u5f88\u201c\u9177\u201d\u7684\u7279\u6027\u5341\u5206\u8bf1\u4eba\uff0c\u4f46\u4e0d\u662f\u7edd\u5bf9\u5fc5\u8981\u3002\u4f7f\u7528\u5947\u6280\u6deb\u5de7\u7684\u4ee3\u7801\u5c06\u66f4\u52a0\u96be\u4ee5\u9605\u8bfb\u548c\u8c03\u8bd5\u3002\u5f00\u59cb\u53ef\u80fd\u8fd8\u597d\uff08\u5bf9\u539f\u4f5c\u8005\u800c\u8a00\uff09, \u4f46\u5f53\u4f60\u56de\u987e\u4ee3\u7801, \u5b83\u4eec\u53ef\u80fd\u4f1a\u6bd4\u90a3\u4e9b\u7a0d\u957f\u4e00\u70b9\u4f46\u662f\u5f88\u76f4\u63a5\u7684\u4ee3\u7801\u66f4\u52a0\u96be\u4ee5\u7406\u89e3.
"},{"location":"standard/language_rules/#1194","title":"1.19.4 \u7ed3\u8bba","text":"\u5728\u4f60\u7684\u4ee3\u7801\u4e2d\u907f\u514d\u8fd9\u4e9b\u7279\u6027\u3002
\u5185\u90e8\u9700\u8981\u4f7f\u7528\u8fd9\u4e9b\u7279\u6027\u7684\u6807\u51c6\u5e93\u6a21\u5757\u548c\u7c7b\u53ef\u4ee5\u4f7f\u7528\uff08\u4f8b\u5982\uff0c abc.ABCMeta
\u3001 dataclasses
\u548c enum
\uff09\u3002
"},{"location":"standard/language_rules/#120-pythonfrom-__future__-imports","title":"1.20 \u65b0\u7248 Python:from __future__ imports
","text":"\u53ef\u4ee5\u4f7f\u7528\u5bfc\u5165 future \u8fd9\u79cd\u7279\u6b8a\u64cd\u5728\u8001\u7248\u672c\u4e2d\u4f7f\u7528\u65b0\u7248\u672c\u7684\u8bed\u6cd5\u7279\u6027\u3002
"},{"location":"standard/language_rules/#1201","title":"1.20.1 \u5b9a\u4e49","text":"\u4f7f\u7528 from __future__ import
\u8bed\u53e5\u53ef\u4ee5\u5728\u8001\u7248\u672c\u4e2d\u542f\u7528\u65b0\u7248\u672c\u7684\u529f\u80fd\u3002
"},{"location":"standard/language_rules/#1202","title":"1.20.2 \u4f18\u70b9","text":"\u7ecf\u9a8c\u8bc1\u660e\uff0c\u5728\u58f0\u660e\u517c\u5bb9\u6027\u5e76\u9632\u6b62\u8fd9\u4e9b\u6587\u4ef6\u4e2d\u7684\u56de\u5f52\u7684\u540c\u65f6\uff0c\u5bf9\u6bcf\u4e2a\u6587\u4ef6\u8fdb\u884c\u66f4\u6539\uff0c\u53ef\u4ee5\u4f7f\u8fd0\u884c\u65f6\u7248\u672c\u5347\u7ea7\u66f4\u52a0\u5e73\u6ed1\u3002 \u73b0\u4ee3\u4ee3\u7801\u66f4\u6613\u4e8e\u7ef4\u62a4\uff0c\u56e0\u4e3a\u5b83\u4e0d\u592a\u53ef\u80fd\u5728\u5c06\u6765\u7684\u8fd0\u884c\u65f6\u5347\u7ea7\u671f\u95f4\u79ef\u7d2f\u6280\u672f\u503a\u52a1\u3002
"},{"location":"standard/language_rules/#1203","title":"1.20.3 \u7f3a\u70b9","text":" - \u6ca1\u6709\u5f15\u5165\u6240\u9700\u7684 future \u8bed\u53e5\u65f6\uff0c\u8fd9\u4e9b\u4ee3\u7801\u53ef\u80fd\u65e0\u6cd5\u5728\u8001\u7248\u672c\u7684\u89e3\u91ca\u5668\u7248\u672c\u4e0a\u8fd0\u884c\u3002
- \u5728\u652f\u6301\u5404\u79cd\u73af\u5883\u7684\u9879\u76ee\u4e2d\uff0c\u8fd9\u79cd\u9700\u6c42\u66f4\u4e3a\u5e38\u89c1\u3002
"},{"location":"standard/language_rules/#1204","title":"1.20.4 \u7ed3\u8bba","text":"from __future__ imports
\u63a8\u8350\u4f7f\u7528 from __future__ import
\u8bed\u53e5\u3002\u6240\u6709\u7684\u65b0\u4ee3\u7801\u90fd\u5e94\u8be5\u5305\u542b\u4ee5\u4e0b\u5185\u5bb9\uff0c\u73b0\u6709\u7684\u4ee3\u7801\u4e5f\u5e94\u8be5\u5728\u6709\u6761\u4ef6\u7684\u60c5\u51b5\u4e0b\u8fdb\u884c\u517c\u5bb9\u66f4\u65b0\u3002
\u5728 3.5 \u6216\u66f4\u65e9\u7684\u7248\u672c\uff08\u800c\u4e0d\u662f >= 3.7\uff09\u4e0a\u6267\u884c\u7684\u4ee3\u7801\u4e2d\uff0c\u5bfc\u5165\uff1a
from __future__ import generator_stop\n
\u6709\u5173\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u9605\u8bfb Python future \u8bed\u53e5\u5b9a\u4e49\u6587\u6863\u3002
\u4e0d\u8981\u5220\u9664\u8fd9\u4e9b\u5bfc\u5165\uff0c\u9664\u975e\u60a8\u786e\u4fe1\u4ee3\u7801\u5728\u5f53\u524d\u73af\u5883\u8fd0\u884c\u6ca1\u6709\u95ee\u9898\u3002\u5373\u4f7f\u60a8\u73b0\u5728\u6ca1\u6709\u4f7f\u7528\u5f53\u524d\u4ee3\u7801\u4e2d\u7279\u5b9a\u7684 future \u5bfc\u5165\u542f\u7528\u7684\u7279\u6027\uff0c \u4fdd\u7559\u8fd9\u4e9b\u5bfc\u5165\u4fbf\u4e8e\u4ee5\u540e\u4fee\u6539\u4ee3\u7801\u65f6\u76f4\u63a5\u4f7f\u7528\u3002
\u8fd8\u6709\u4e00\u4e9b\u5176\u4ed6\u7684 from __future__
\u8bed\u53e5\uff0c\u53ef\u4ee5\u5728\u9700\u8981\u7684\u65f6\u5019\u4f7f\u7528\u3002
"},{"location":"standard/language_rules/#121_1","title":"1.21 \u4ee3\u7801\u7c7b\u578b\u6807\u6ce8","text":"\u53ef\u4ee5\u6839\u636e PEP-484 \u4f7f\u7528\u7c7b\u578b\u6807\u6ce8\uff0c\u5e76\u4f7f\u7528\u7c7b\u4f3c pytype \u7684\u7c7b\u578b\u68c0\u67e5\u5de5\u5177\u5728\u6784\u5efa\u65f6\u5bf9\u4ee3\u7801\u8fdb\u884c\u68c0\u67e5\u3002
\u7c7b\u578b\u6807\u6ce8\u53ef\u4ee5\u5728\u6e90\u7801\u4e2d\uff0c\u4e5f\u53ef\u4ee5\u5728 stub pyi\u6587\u4ef6\u4e2d\u3002 \u5c3d\u53ef\u80fd\u5728\u6e90\u4ee3\u7801\u4e2d\u8fdb\u884c\u6807\u6ce8\uff0c\u5bf9\u4e8e\u7b2c\u4e09\u65b9\u5e93\u6216\u6269\u5c55\u6a21\u5757\u53ef\u4ee5\u4f7f\u7528 pyi
\u6587\u4ef6\u3002
"},{"location":"standard/language_rules/#1211","title":"1.21.1 \u5b9a\u4e49","text":"\u7c7b\u578b\u6807\u6ce8\uff08\u6216\u7c7b\u578b\u63d0\u793a\uff09\u53ef\u4ee5\u7528\u4e8e\u51fd\u6570\u6216\u65b9\u6cd5\u7684\u53c2\u6570\u548c\u8fd4\u56de\u503c
def func(a: int) -> List[int]:\n
\u8fd8\u53ef\u4ee5\u4f7f\u7528\u7c7b\u4f3c PEP-526 \u7684\u8bed\u6cd5\u58f0\u660e\u53d8\u91cf\u7684\u7c7b\u578b\uff1a
a: SomeType = some_func()\n
"},{"location":"standard/language_rules/#1212","title":"1.21.2 \u4f18\u70b9","text":"\u7c7b\u578b\u6807\u6ce8\u53ef\u4ee5\u63d0\u9ad8\u4ee3\u7801\u7684\u53ef\u8bfb\u6027\u548c\u53ef\u7ef4\u62a4\u6027\u3002\u7c7b\u578b\u68c0\u67e5\u5668\u53ef\u4ee5\u628a\u8bb8\u591a\u8fd0\u884c\u65f6\u9519\u8bef\u8f6c\u6362\u4e3a\u6784\u5efa\u65f6\u9519\u8bef\uff0c\u5e76\u51cf\u5c11\u5a01\u529b\u8fc7\u5927\u7279\u6027\u5730\u4f7f\u7528\u3002
"},{"location":"standard/language_rules/#1213","title":"1.21.3 \u7f3a\u70b9","text":" - \u5fc5\u987b\u4fdd\u6301\u7c7b\u578b\u6807\u6ce8\u66f4\u65b0\u3002
- \u60a8\u53ef\u80fd\u4f1a\u770b\u5230\u60a8\u8ba4\u4e3a\u662f\u6b63\u786e\u4ee3\u7801\u7684\u9519\u8bef\u4fe1\u606f\u3002
- \u4f7f\u7528\u7c7b\u578b\u68c0\u67e5\u5668\u53ef\u80fd\u4f1a\u51cf\u5c11\u5a01\u529b\u8fc7\u5927\u7279\u6027\u5730\u4f7f\u7528\u3002
"},{"location":"standard/language_rules/#1214","title":"1.21.4 \u7ed3\u8bba","text":"\u5f3a\u70c8\u5efa\u8bae\u60a8\u5728\u66f4\u6539\u4ee3\u7801\u65f6\u542f\u7528 Python \u7c7b\u578b\u5206\u6790\u3002\u5f53\u6dfb\u52a0\u6216\u4fee\u6539\u516c\u5171 API \u65f6\uff0c\u8bf7\u5305\u542b\u7c7b\u578b\u6807\u6ce8\uff0c\u5e76\u5728\u6784\u5efa\u7cfb\u7edf\u4e2d\u542f\u7528 pytype
\u8fdb\u884c\u68c0\u67e5\u3002 \u7531\u4e8e\u9759\u6001\u5206\u6790\u5bf9 Python \u6765\u8bf4\u76f8\u5bf9\u8f83\u65b0\uff0c\u6211\u4eec\u627f\u8ba4\u4f1a\u6709\u4e00\u4e9b\u526f\u4f5c\u7528\uff08\u6bd4\u5982\u9519\u8bef\u7684\u7c7b\u578b\u63a8\u65ad\uff09\u53ef\u80fd\u4f1a\u963b\u6b62\u4e00\u4e9b\u9879\u76ee\u91c7\u7528\u3002 \u56e0\u6b64\uff0c\u6211\u4eec\u9f13\u52b1\u4f5c\u8005\u6dfb\u52a0\u4e00\u4e2a\u5e26\u6709 TODO
\u7684\u6ce8\u91ca\uff0c\u6216\u8005\u5728 BUILD
\u6587\u4ef6\u6216\u4ee3\u7801\u672c\u8eab\u4e2d\u901a\u8fc7 bug \u94fe\u63a5\u63cf\u8ff0\u5f53\u524d\u4e0d\u91c7\u7528\u7c7b\u578b\u6807\u6ce8\u7684\u95ee\u9898\u3002
"},{"location":"standard/style_rules/","title":"Python \u98ce\u683c\u89c4\u8303","text":"\u672c\u6587\u6863\u4e3a Google Python Style Guide \u7b2c\u4e09\u7ae0 Python Style Rules \u7684\u8bd1\u6587\u3002
\u6700\u540e\u66f4\u65b0\u65f6\u95f4\uff1a 2023-06-26
\u5982\u679c\u6709\u7ffb\u8bd1\u9519\u8bef\u6216\u8868\u8ff0\u4e0d\u51c6\u786e\u7684\u95ee\u9898\uff0c\u6b22\u8fce\u63d0\u4ea4 PR\uff0c\u611f\u8c22\u60a8\u7684\u53c2\u4e0e\u3002
"},{"location":"standard/style_rules/#31","title":"3.1 \u5206\u53f7","text":"\u4e0d\u8981\u5728\u884c\u5c3e\u52a0\u5206\u53f7\uff0c\u4e5f\u4e0d\u8981\u7528\u5206\u53f7\u5c06\u4e24\u6761\u547d\u4ee4\u653e\u5728\u540c\u4e00\u884c\u3002
"},{"location":"standard/style_rules/#32","title":"3.2 \u884c\u957f\u5ea6","text":"\u6bcf\u884c\u4e0d\u8d85\u8fc780\u4e2a\u5b57\u7b26\u3002
\u4f8b\u5916\uff1a
- \u957f\u7684\u5bfc\u5165\u6a21\u5757\u8bed\u53e5
- \u6ce8\u91ca\u91cc\u7684 URL \u3001\u8def\u5f84\u540d\u548c\u957f\u6807\u8bc6
- \u4e0d\u5305\u542b\u7a7a\u683c\uff0c\u4e0d\u65b9\u4fbf\u8de8\u884c\u62c6\u5206\u7684\u957f\u5b57\u7b26\u4e32\u6a21\u5757\u7ea7\u5e38\u91cf\uff0c\u5982 URL \u6216\u8def\u5f84\u540d
- Pylint \u7981\u7528\u6ce8\u91ca\u3002\uff08\u4f8b\u5982\uff1a
# pylint: disable=invalid-name
\uff09
\u4e0d\u8981\u4f7f\u7528\u53cd\u659c\u6760\u6765\u663e\u5f0f\u5ef6\u7eed\u884c\u3002
\u76f8\u53cd\uff0cPython \u4f1a\u5c06\u5706\u62ec\u53f7\u3001\u65b9\u62ec\u53f7\u548c\u82b1\u62ec\u53f7\u4e2d\u7684\u884c\u9690\u5f0f\u7684\u8fde\u63a5\u8d77\u6765\uff0c\u4f60\u53ef\u4ee5\u5229\u7528\u8fd9\u4e2a\u7279\u70b9\u3002\u5982\u679c\u9700\u8981\uff0c\u4f60\u53ef\u4ee5\u5728\u8868\u8fbe\u5f0f\u5916\u56f4\u589e\u52a0\u4e00\u5bf9\u989d\u5916\u7684\u5706\u62ec\u53f7\u3002
\u8bf7\u6ce8\u610f\uff0c\u6b64\u89c4\u5219\u5e76\u4e0d\u7981\u6b62\u5b57\u7b26\u4e32\u4e2d\u53cd\u659c\u6760\u8f6c\u4e49\u7684\u6362\u884c\u7b26\uff08\u89c1\u4e0b\u6587\uff09\u3002
\u63a8\u8350
foo_bar(self, width, height, color='black', design=None, x='foo',\nemphasis=None, highlight=0)\nif (width == 0 and height == 0 and\ncolor == 'red' and emphasis == 'strong'):\n(bridge_questions.clarification_on\n.average_airspeed_of.unladen_swallow) = 'African or European?'\nwith (\nvery_long_first_expression_function() as spam,\nvery_long_second_expression_function() as beans,\nthird_thing() as eggs,\n):\nplace_order(eggs, beans, spam, beans)\n
\u4e0d\u63a8\u8350
if width == 0 and height == 0 and \\\n color == 'red' and emphasis == 'strong':\nbridge_questions.clarification_on \\\n .average_airspeed_of.unladen_swallow = 'African or European?'\nwith very_long_first_expression_function() as spam, \\\n very_long_second_expression_function() as beans, \\\n third_thing() as eggs:\nplace_order(eggs, beans, spam, beans)\n
\u5982\u679c\u4e00\u4e2a\u6587\u672c\u5b57\u7b26\u4e32\u5728\u4e00\u884c\u653e\u4e0d\u4e0b\uff0c\u53ef\u4ee5\u4f7f\u7528\u5706\u62ec\u53f7\u6765\u5b9e\u73b0\u9690\u5f0f\u884c\u8fde\u63a5\u3002
x = ('This will build a very long long '\n'long long long long long long string')\n
\u5c3d\u53ef\u80fd\u9ad8\u7684\u5728\u53e5\u6cd5\u6c34\u5e73\u4e0a\u6362\u884c\uff0c\u5982\u679c\u5fc5\u987b\u6253\u65ad\u4e00\u884c\u4e24\u6b21\uff0c\u90a3\u4e48\u4e24\u6b21\u90fd\u8981\u5728\u76f8\u540c\u7684\u53e5\u6cd5\u6c34\u5e73\u4e0a\u6253\u65ad\u3002
\u63a8\u8350
bridgekeeper.answer(\nname=\"Arthur\", quest=questlib.find(owner=\"Arthur\", perilous=True))\nanswer = (a_long_line().of_chained_methods()\n.that_eventually_provides().an_answer())\nif (\nconfig is None\nor 'editor.language' not in config\nor config['editor.language'].use_spaces is False\n):\nuse_tabs()\n
\u4e0d\u63a8\u8350
bridgekeeper.answer(name=\"Arthur\", quest=questlib.find(\nowner=\"Arthur\", perilous=True))\nanswer = a_long_line().of_chained_methods().that_eventually_provides(\n).an_answer()\nif (config is None or 'editor.language' not in config or config[\n'editor.language'].use_spaces is False):\nuse_tabs()\n
\u5728\u6ce8\u91ca\u4e2d\uff0c\u5982\u679c\u5fc5\u8981\uff0c\u5c06\u957f\u7684 URL \u653e\u5728\u4e00\u884c\u4e0a\u3002
\u63a8\u8350
# See details at\n# http://www.example.com/us/developer/documentation/api/content/v2.0/csv_file_name_extension_full_specification.html\n
\u4e0d\u63a8\u8350
# See details at\n# http://www.example.com/us/developer/documentation/api/content/\\\n# v2.0/csv_file_name_extension_full_specification.html\n
\u6ce8\u610f\u4e0a\u9762\u4f8b\u5b50\u4e2d\u7684\u5143\u7d20\u7f29\u8fdb\u3002\u4f60\u53ef\u4ee5\u5728\u672c\u6587\u7684\u7f29\u8fdb\u90e8\u5206\u627e\u5230\u89e3\u91ca\u3002
\u5728\u6240\u6709\u5176\u4ed6\u60c5\u51b5\u4e0b\uff0c\u5982\u679c\u4e00\u884c\u8d85\u8fc780\u4e2a\u5b57\u7b26\uff0c\u5e76\u4e14 Black\u6216Pyink \u81ea\u52a8\u683c\u5f0f\u5316\u7a0b\u5e8f\u65e0\u6cd5\u5e2e\u52a9\u4f7f\u8be5\u884c\u4f4e\u4e8e\u9650\u5236\uff0c\u5219\u5141\u8bb8\u8be5\u884c\u8d85\u8fc7\u6b64\u6700\u5927\u503c\u3002\u5efa\u8bae\u4f5c\u8005\u5728\u5408\u7406\u7684\u60c5\u51b5\u4e0b\uff0c\u6839\u636e\u4e0a\u8ff0\u6ce8\u91ca\u624b\u52a8\u62c6\u5206\u884c\u3002
"},{"location":"standard/style_rules/#33","title":"3.3 \u62ec\u53f7","text":"\u5b81\u7f3a\u6bcb\u6ee5\u7684\u4f7f\u7528\u62ec\u53f7\u3002
\u9664\u975e\u662f\u7528\u4e8e\u5b9e\u73b0\u884c\u8fde\u63a5\uff0c\u5426\u5219\u4e0d\u8981\u5728\u8fd4\u56de\u8bed\u53e5\u6216\u6761\u4ef6\u8bed\u53e5\u4e2d\u4f7f\u7528\u62ec\u53f7\uff0c\u9690\u5f0f\u7684\u884c\u8fde\u63a5\u6216\u8005\u5143\u7ec4\u4e24\u8fb9\u4f7f\u7528\u62ec\u53f7\u9664\u5916\u3002
\u63a8\u8350
if foo:\nbar()\nwhile x:\nx = bar()\nif x and y:\nbar()\nif not x:\nbar()\n# For a 1 item tuple the ()s are more visually obvious than the comma.\nonesie = (foo,)\nreturn foo\nreturn spam, beans\nreturn (spam, beans)\nfor (x, y) in dict.items(): ...\n
\u4e0d\u63a8\u8350
if (x):\nbar()\nif not(x):\nbar()\nreturn (foo)\n
"},{"location":"standard/style_rules/#34","title":"3.4 \u7f29\u8fdb","text":"\u75284\u4e2a\u7a7a\u683c\u6765\u7f29\u8fdb\u4ee3\u7801\u3002
\u7edd\u5bf9\u4e0d\u8981\u7528 tab
\uff0c\u4e5f\u4e0d\u8981 tab
\u548c\u7a7a\u683c\u6df7\u7528\u3002\u5bf9\u4e8e\u884c\u8fde\u63a5\u7684\u60c5\u51b5\uff0c\u4f60\u5e94\u8be5\u8981\u4e48\u5782\u76f4\u5bf9\u9f50\u6362\u884c\u7684\u5143\u7d20\uff08\u89c1\u884c\u957f \u90e8\u5206\u7684\u793a\u4f8b\uff09\uff0c\u6216\u8005\u4f7f\u75284\u7a7a\u683c\u7684\u60ac\u6302\u5f0f\u7f29\u8fdb\uff08\u8fd9\u65f6\u7b2c\u4e00\u884c\u4e0d\u5e94\u8be5\u6709\u53c2\u6570\uff09\u3002
\u63a8\u8350
# Aligned with opening delimiter\nfoo = long_function_name(var_one, var_two,\nvar_three, var_four)\nmeal = (spam,\nbeans)\n# Aligned with opening delimiter in a dictionary\nfoo = {\nlong_dictionary_key: value1 +\nvalue2,\n...\n}\n# 4-space hanging indent; nothing on first line.\nfoo = long_function_name(\nvar_one, var_two, var_three,\nvar_four)\nmeal = (\nspam,\nbeans)\n# 4-space hanging indent; nothing on first line\n# closing parenthesis on a new line.\nfoo = long_function_name(\nvar_one, var_two, var_three,\nvar_four\n)\nmeal = (\nspam,\nbeans,\n)\n# 4-space hanging indent in a dictionary\nfoo = {\nlong_dictionary_key:\nlong_dictionary_value,\n...\n}\n
\u4e0d\u63a8\u8350
# Stuff on first line forbidden\nfoo = long_function_name(var_one, var_two,\nvar_three, var_four)\nmeal = (spam,\nbeans)\n# 2-space hanging indent forbidden\nfoo = long_function_name(\nvar_one, var_two, var_three,\nvar_four)\n# No hanging indent in a dictionary\nfoo = {\n'long_dictionary_key':\nlong_dictionary_value,\n...\n}\n
"},{"location":"standard/style_rules/#341","title":"3.4.1 \u5728\u5e8f\u5217\u7684\u672b\u5c3e\u662f\u5426\u52a0\u9017\u53f7\uff1f","text":"\u53ea\u6709\u5728\u5e8f\u5217\u7ed3\u675f\u7b26 ]
\u3001 )
\u6216 }
\u4e0e\u6700\u540e\u4e00\u4e2a\u5143\u7d20\u4e0d\u5728\u540c\u4e00\u884c\u65f6\u624d\u5efa\u8bae\u4f7f\u7528\u3002\u672b\u5c3e\u9017\u53f7\u7684\u5b58\u5728\u8fd8\u7528\u4f5c\u5bf9\u4ee3\u7801\u81ea\u52a8\u683c\u5f0f\u5316\u7a0b\u5e8f\u7684\u63d0\u793a\uff0c\u4ee5\u5f15\u5bfc\u5b83\u5728\u6700\u540e\u4e00\u4e2a\u5143\u7d20\u4e4b\u540e\u51fa\u73b0\u65f6\uff0c \u81ea\u52a8\u5c06\u5bb9\u5668\u4e2d\u6bcf\u4e2a\u6761\u76ee\u683c\u5f0f\u5316\u4e3a\u4e00\u884c\u3002
\u63a8\u8350
golomb3 = [0, 1, 3]\n
golomb4 = [\n0,\n1,\n4,\n6,\n]\n
\u4e0d\u63a8\u8350
golomb4 = [\n0,\n1,\n4,\n6\n]\n
"},{"location":"standard/style_rules/#35","title":"3.5 \u7a7a\u884c","text":"\u9876\u7ea7\u5b9a\u4e49\u4e4b\u95f4\u7a7a\u4e24\u884c, \u65b9\u6cd5\u5b9a\u4e49\u4e4b\u95f4\u7a7a\u4e00\u884c
- \u9876\u7ea7\u5b9a\u4e49\u4e4b\u95f4\u7a7a\u4e24\u884c\uff0c\u6bd4\u5982\u51fd\u6570\u6216\u8005\u7c7b\u5b9a\u4e49\u3002
- \u65b9\u6cd5\u5b9a\u4e49\uff0c\u7c7b\u5b9a\u4e49\u4e0e\u7b2c\u4e00\u4e2a\u65b9\u6cd5\u4e4b\u95f4\uff0c\u90fd\u5e94\u8be5\u7a7a\u4e00\u884c\u3002
- \u5728
def
\u51fd\u6570\u5b9a\u4e49\u4e4b\u540e\u4e0d\u9700\u8981\u7a7a\u884c\u3002 - \u51fd\u6570\u6216\u65b9\u6cd5\u4e2d\uff0c\u67d0\u4e9b\u5730\u65b9\u8981\u662f\u4f60\u89c9\u5f97\u5408\u9002\uff0c\u5c31\u7a7a\u4e00\u884c\u3002
"},{"location":"standard/style_rules/#36","title":"3.6 \u7a7a\u683c","text":"\u6309\u7167\u6807\u51c6\u7684\u6392\u7248\u89c4\u8303\u6765\u4f7f\u7528\u6807\u70b9\u4e24\u8fb9\u7684\u7a7a\u683c\u3002
\u62ec\u53f7\u5185\u4e0d\u8981\u6709\u7a7a\u683c\u3002
\u63a8\u8350
spam(ham[1], {eggs: 2}, [])\n
\u4e0d\u63a8\u8350
spam( ham[ 1 ], { eggs: 2 }, [ ] )\n
\u4e0d\u8981\u5728\u9017\u53f7\uff0c\u5206\u53f7\uff0c\u5192\u53f7\u524d\u9762\u52a0\u7a7a\u683c\u3002\u4f46\u5e94\u8be5\u5728\u5b83\u4eec\u540e\u9762\u52a0\uff08\u9664\u4e86\u5728\u884c\u5c3e\uff09\u3002
\u63a8\u8350
if x == 4:\nprint(x, y)\nx, y = y, x\n
\u4e0d\u63a8\u8350
if x == 4 :\nprint(x , y)\nx , y = y , x\n
\u53c2\u6570\u5217\u8868\u3001\u7d22\u5f15\u6216\u5207\u7247\u7684\u5de6\u62ec\u53f7\u524d\u4e0d\u5e94\u52a0\u7a7a\u683c\u3002
\u63a8\u8350
spam(1)\n
\u4e0d\u63a8\u8350
spam (1)\n
\u63a8\u8350
dict['key'] = list[index]\n
\u4e0d\u63a8\u8350
dict ['key'] = list [index]\n
\u884c\u5c3e\u4e0d\u9700\u8981\u7a7a\u683c\u3002
\u5728\u4e8c\u5143\u64cd\u4f5c\u7b26\u4e24\u8fb9\u90fd\u52a0\u4e0a\u4e00\u4e2a\u7a7a\u683c\uff0c\u6bd4\u5982\u8d4b\u503c\uff08=
\uff09\u3001\u6bd4\u8f83\uff08==
\u3001<
\u3001>
\u3001!=
\u3001<>
\u3001<=
\u3001>=
\u3001in
\u3001not in
\u3001is
\u3001is not
\uff09\uff0c\u5e03\u5c14\uff08and
\u3001or
\u3001not
\uff09\u3002 \u81f3\u4e8e\u7b97\u672f\u64cd\u4f5c\u7b26\uff08+
\u3001-
\u3001*
\u3001/
\u3001//
\u3001%
\u3001**
\u3001@
\uff09\u4e24\u8fb9\u7684\u7a7a\u683c\u8be5\u5982\u4f55\u4f7f\u7528\uff0c\u9700\u8981\u4f60\u81ea\u5df1\u597d\u597d\u5224\u65ad\u3002\u4e0d\u8fc7\u4e24\u4fa7\u52a1\u5fc5\u8981\u4fdd\u6301\u4e00\u81f4\u3002
\u63a8\u8350
x == 1\n
\u4e0d\u63a8\u8350
x<1\n
\u5f53 =
\u7528\u4e8e\u6307\u793a\u5173\u952e\u5b57\u53c2\u6570\u6216\u9ed8\u8ba4\u53c2\u6570\u503c\u65f6\uff0c\u4e0d\u8981\u5728\u5176\u4e24\u4fa7\u4f7f\u7528\u7a7a\u683c\u3002\u4f46\u6709\u4e00\u4e2a\u4f8b\u5916\uff1a\u5f53\u5b58\u5728\u7c7b\u578b\u6ce8\u91ca\u65f6\uff0c\u5728\u9ed8\u8ba4\u53c2\u6570\u503c\u7684 =
\u5468\u56f4\u4f7f\u7528\u7a7a\u683c\u3002
\u63a8\u8350
def complex(real, imag=0.0): return Magic(r=real, i=imag)\n
def complex(real, imag: float = 0.0): return Magic(r=real, i=imag)\n
\u4e0d\u63a8\u8350
def complex(real, imag = 0.0): return Magic(r = real, i = imag)\n
def complex(real, imag: float=0.0): return Magic(r = real, i = imag)\n
\u4e0d\u8981\u7528\u7a7a\u683c\u6765\u5782\u76f4\u5bf9\u9f50\u591a\u884c\u95f4\u7684\u6807\u8bb0\uff0c\u56e0\u4e3a\u8fd9\u4f1a\u9020\u6210\u7ef4\u62a4\u7684\u8d1f\u62c5\uff08\u9002\u7528\u4e8e :
\u3001#
\u3001=
\u7b49\uff09\uff1a
\u63a8\u8350
foo = 1000 # comment\nlong_name = 2 # comment that should not be aligned\ndictionary = {\n'foo': 1,\n'long_name': 2,\n}\n
\u4e0d\u63a8\u8350
foo = 1000 # comment\nlong_name = 2 # comment that should not be aligned\ndictionary = {\n'foo' : 1,\n'long_name': 2,\n}\n
"},{"location":"standard/style_rules/#37-shebang","title":"3.7 Shebang","text":"\u5927\u90e8\u5206 .py
\u6587\u4ef6\u4e0d\u5fc5\u4ee5 #!
\u4f5c\u4e3a\u6587\u4ef6\u7684\u5f00\u59cb\u3002\u6839\u636e PEP-394\uff0c\u7a0b\u5e8f\u7684 main
\u6587\u4ef6\u5e94\u8be5\u4ee5 #!/usr/bin/env python3
\uff08\u7528\u4e8e\u652f\u6301\u865a\u62df\u73af\u5883\uff09\u6216\u8005 #!/usr/bin/python3
\u5f00\u59cb\u3002
\u5185\u6838\u4f7f\u7528\u8fd9\u4e00\u884c\u6765\u67e5\u627e Python \u89e3\u91ca\u5668\uff0c\u4f46\u662f Python \u5728\u5bfc\u5165\u6a21\u5757\u65f6\u4f1a\u5ffd\u7565\u8fd9\u4e00\u884c\u3002\u56e0\u6b64\u53ea\u6709\u5728\u6253\u7b97\u76f4\u63a5\u6267\u884c\u7684\u6587\u4ef6\u4e0a\u6dfb\u52a0\u624d\u6709\u5fc5\u8981\u3002
"},{"location":"standard/style_rules/#38","title":"3.8 \u6ce8\u91ca\u548c\u6587\u6863\u5b57\u7b26\u4e32","text":"\u786e\u4fdd\u5bf9\u6a21\u5757, \u51fd\u6570, \u65b9\u6cd5\u548c\u884c\u5185\u6ce8\u91ca\u4f7f\u7528\u6b63\u786e\u7684\u98ce\u683c\u3002
"},{"location":"standard/style_rules/#381","title":"3.8.1 \u6587\u6863\u5b57\u7b26\u4e32","text":"Python \u6709\u4e00\u79cd\u72ec\u4e00\u65e0\u4e8c\u7684\u7684\u6ce8\u91ca\u65b9\u5f0f\uff1a \u4f7f\u7528\u6587\u6863\u5b57\u7b26\u4e32\u3002\u6587\u6863\u5b57\u7b26\u4e32\u662f\u5305\u3001\u6a21\u5757\u3001\u7c7b\u6216\u51fd\u6570\u91cc\u7684\u7b2c\u4e00\u4e2a\u8bed\u53e5\u3002\u8fd9\u4e9b\u5b57\u7b26\u4e32\u53ef\u4ee5\u901a\u8fc7\u5bf9\u8c61\u7684 __doc__
\u6210\u5458\u88ab\u81ea\u52a8\u63d0\u53d6\uff0c\u5e76\u4e14\u88ab pydoc
\u6240\u7528\uff08\u4f60\u53ef\u4ee5\u5728\u4f60\u7684\u6a21\u5757\u4e0a\u8fd0\u884c pydoc
\u8bd5\u4e00\u628a\uff0c\u770b\u770b\u5b83\u957f\u4ec0\u4e48\u6837\uff09\u3002 \u6211\u4eec\u5bf9\u6587\u6863\u5b57\u7b26\u4e32\u7684\u60ef\u4f8b\u662f\u4f7f\u7528\u4e09\u91cd\u53cc\u5f15\u53f7 \"\"\"
\uff08\u53c2\u89c1\uff1a PEP-257 \uff09\u3002\u4e00\u4e2a\u6587\u6863\u5b57\u7b26\u4e32\u5e94\u8be5\u8fd9\u6837\u7ec4\u7ec7\uff08\u901a\u5e38\u4e00\u884c\u4e0d\u8d85\u8fc7 80 \u4e2a\u5b57\u7b26\uff09\uff0c\u5148\u662f\u4e00\u884c\u4ee5\u53e5\u53f7\uff0c\u95ee\u53f7\u6216\u60ca\u53f9\u53f7\u7ed3\u5c3e\u7684\u6982\u8ff0\uff08\u6216\u8005\u8be5\u6587\u6863\u5b57\u7b26\u4e32\u5355\u7eaf\u53ea\u6709\u4e00\u884c\uff09\u3002\u63a5\u7740\u662f\u4e00\u4e2a\u7a7a\u884c\uff0c\u63a5\u7740\u662f\u6587\u6863\u5b57\u7b26\u4e32\u5269\u4e0b\u7684\u90e8\u5206\uff0c\u5b83\u5e94\u8be5\u4e0e\u6587\u6863\u5b57\u7b26\u4e32\u7684\u7b2c\u4e00\u884c\u7684\u7b2c\u4e00\u4e2a\u5f15\u53f7\u5bf9\u9f50\u3002\u4e0b\u9762\u6709\u66f4\u591a\u6587\u6863\u5b57\u7b26\u4e32\u7684\u683c\u5f0f\u5316\u89c4\u8303\u3002
"},{"location":"standard/style_rules/#382","title":"3.8.2 \u6a21\u5757","text":"\u6bcf\u4e2a\u6587\u4ef6\u5e94\u8be5\u5305\u542b\u4e00\u4e2a\u8bb8\u53ef\u6837\u677f\u3002\u6839\u636e\u9879\u76ee\u4f7f\u7528\u7684\u8bb8\u53ef\uff08\u4f8b\u5982\uff1aApache 2.0
\u3001BSD
\u3001LGPL
\u3001GPL
\uff09\uff0c\u9009\u62e9\u5408\u9002\u7684\u6837\u677f\u3002
\u6587\u4ef6\u5e94\u8be5\u4ee5\u63cf\u8ff0\u6a21\u5757\u5185\u5bb9\u548c\u7528\u6cd5\u7684\u6587\u6863\u5b57\u7b26\u4e32\u5f00\u59cb\u3002
\"\"\"A one line summary of the module or program, terminated by a period.\nLeave one blank line. The rest of this docstring should contain an\noverall description of the module or program. Optionally, it may also\ncontain a brief description of exported classes and functions and/or usage\nexamples.\n Typical usage example:\n foo = ClassFoo()\n bar = foo.FunctionBar()\n\"\"\"\n
\u6d4b\u8bd5\u6a21\u5757\uff0c\u6d4b\u8bd5\u6587\u4ef6\u7684\u6a21\u5757\u7ea7\u6587\u6863\u5b57\u7b26\u4e32\u4e0d\u662f\u5fc5\u987b\u7684\uff0c\u4ec5\u5f53\u53ef\u4ee5\u63d0\u4f9b\u9644\u52a0\u4fe1\u606f\u65f6\u53ef\u5305\u542b\u3002
\u793a\u4f8b\u5305\u62ec\u6709\u5173\u5982\u4f55\u8fd0\u884c\u6d4b\u8bd5\u7684\u4e00\u4e9b\u7ec6\u8282\u3001\u5bf9\u4e0d\u5bfb\u5e38\u8bbe\u7f6e\u6a21\u5f0f\u7684\u89e3\u91ca\u3001\u5bf9\u5916\u90e8\u73af\u5883\u7684\u4f9d\u8d56\u7b49\u3002
\"\"\"This blaze test uses golden files.\nYou can update those files by running\n`blaze run //foo/bar:foo_test -- --update_golden_files` from the `google3`\ndirectory.\n\"\"\"\n
\u4e0d\u5e94\u4f7f\u7528\u4e0d\u63d0\u4f9b\u4efb\u4f55\u65b0\u4fe1\u606f\u7684\u6587\u6863\u5b57\u7b26\u4e32\u3002
\"\"\"Tests for foo.bar.\"\"\"\n
"},{"location":"standard/style_rules/#383","title":"3.8.3 \u51fd\u6570\u548c\u65b9\u6cd5","text":"\u4e0b\u6587\u6240\u6307\u7684\u51fd\u6570\uff0c\u5305\u62ec\u51fd\u6570\uff0c\u65b9\u6cd5\uff0c\u751f\u6210\u5668\u4ee5\u53ca\u5c5e\u6027\u3002
\u6bcf\u4e2a\u5177\u6709\u4ee5\u4e0b\u4e00\u9879\u6216\u591a\u9879\u7279\u6027\u7684\u51fd\u6570\u90fd\u5fc5\u987b\u6709\u6587\u6863\u5b57\u7b26\u4e32\uff1a
- \u516c\u5171 API \u7684\u4e00\u90e8\u5206
- \u89c4\u6a21\u5927
- \u903b\u8f91\u590d\u6742
\u6587\u6863\u5b57\u7b26\u4e32\u5e94\u8be5\u63d0\u4f9b\u8db3\u591f\u7684\u4fe1\u606f\uff0c\u5f53\u522b\u4eba\u7f16\u5199\u4ee3\u7801\u8c03\u7528\u8be5\u51fd\u6570\u65f6\uff0c\u4ed6\u4e0d\u9700\u8981\u770b\u4e00\u884c\u4ee3\u7801\uff0c\u53ea\u8981\u770b\u6587\u6863\u5b57\u7b26\u4e32\u5c31\u53ef\u4ee5\u4e86\u3002 \u6587\u6863\u5b57\u7b26\u4e32\u5e94\u63cf\u8ff0\u51fd\u6570\u7684\u8c03\u7528\u8bed\u6cd5\u548c\u8bed\u4e49\uff0c\u4f46\u901a\u5e38\u4e0d\u63cf\u8ff0\u5176\u5b9e\u73b0\u7ec6\u8282\uff0c\u9664\u975e\u8fd9\u4e9b\u7ec6\u8282\u4e0e\u51fd\u6570\u7684\u4f7f\u7528\u65b9\u5f0f\u76f8\u5173\u3002 \u4f8b\u5982\uff0c\u4f5c\u4e3a\u526f\u4f5c\u7528\u4f1a\u6539\u53d8\u5176\u53c2\u6570\u7684\u51fd\u6570\u5e94\u5728\u5176\u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u6ce8\u660e\u8fd9\u4e00\u70b9\u3002\u5426\u5219\uff0c\u5bf9\u4e8e\u8c03\u7528\u8005\u4e0d\u76f8\u5173\u7684\u51fd\u6570\u5b9e\u73b0\u7684\u5fae\u5999\u4f46\u91cd\u8981\u7684\u7ec6\u8282\uff0c \u6700\u597d\u5c06\u5176\u8868\u8fbe\u4e3a\u4ee3\u7801\u65c1\u8fb9\u7684\u6ce8\u91ca\uff0c\u800c\u4e0d\u662f\u5728\u51fd\u6570\u7684\u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u3002
\u6587\u6863\u5b57\u7b26\u4e32\u5e94\u8be5\u662f\u63cf\u8ff0\u6027\u7684\uff08 \"\"\"Fetches rows from a Bigtable.\"\"\"
\uff09 \u6216\u8005\u547d\u4ee4\u5f0f\u7684\uff08 \"\"\"Fetch rows from a Bigtable.\"\"\"
\uff09\uff0c \u4f46\u662f\u5728\u4e00\u4e2a\u6587\u4ef6\u4e2d\uff0c\u98ce\u683c\u5e94\u8be5\u4fdd\u6301\u4e00\u76f4\u3002\u5bf9\u4e8e @property \u6570\u636e\u63cf\u8ff0\u7b26\u7684\u6587\u6863\u5b57\u7b26\u4e32\u5e94\u8be5\u4f7f\u7528\u4e0e\u5c5e\u6027\u6216\u51fd\u6570\u53c2\u6570\u7684\u6587\u6863\u5b57\u7b26\u4e32\u76f8\u540c\u7684\u98ce\u683c \uff08 \"\"\"The Bigtable path.\"\"\"
\u800c\u4e0d\u662f \"\"\"Returns the Bigtable path.\"\"\"
\uff09\u3002
\u91cd\u5199\u57fa\u7c7b\u4e2d\u7684\u65b9\u6cd5\u65f6\uff0c\u7528\u4e00\u4e2a\u7b80\u5355\u7684\u6587\u6863\u5b57\u7b26\u4e32\u5f15\u5bfc\u8bfb\u8005\u67e5\u770b\u88ab\u8986\u76d6\u65b9\u6cd5\u7684\u6587\u6863\u5b57\u7b26\u4e32\uff0c\u4f8b\u5982\uff1a \"\"\"See base class.\"\"\"
\u3002\u8fd9\u6837\u505a\u7684\u597d\u5904\u662f\uff0c\u65e0\u9700\u91cd\u590d\u57fa\u672c\u65b9\u6cd5\u4e2d\u7684\u6587\u6863\u5b57\u7b26\u4e32\u4fe1\u606f\u3002\u4f46\u662f\uff0c\u5982\u679c\u91cd\u5199\u65b9\u6cd5\u7684\u884c\u4e3a\u53d1\u751f\u4e86\u6539\u53d8\uff0c\u6216\u8005\u9700\u8981\u63d0\u4f9b\u8be6\u7ec6\u4fe1\u606f\uff08\u4f8b\u5982\uff1a\u8bb0\u5f55\u989d\u5916\u526f\u4f5c\u7528\uff09\uff0c\u90a3\u4e48\u91cd\u5199\u65b9\u6cd5\u81f3\u5c11\u9700\u8981\u901a\u8fc7\u6587\u6863\u5b57\u7b26\u4e32\u6765\u63cf\u8ff0\u8fd9\u4e9b\u5dee\u5f02\u3002
\u5173\u4e8e\u51fd\u6570\u7684\u51e0\u4e2a\u65b9\u9762\u5e94\u8be5\u5728\u7279\u5b9a\u7684\u5c0f\u8282\u4e2d\u8fdb\u884c\u63cf\u8ff0\u8bb0\u5f55\u3002\u8fd9\u51e0\u4e2a\u65b9\u9762\u5982\u4e0b\u6587\u6240\u8ff0\uff0c\u6bcf\u8282\u5e94\u8be5\u4ee5\u4e00\u4e2a\u6807\u9898\u884c\u5f00\u59cb\uff0c\u6807\u9898\u884c\u4ee5\u5192\u53f7\u7ed3\u5c3e\u3002\u9664\u6807\u9898\u884c\u5916\uff0c\u5c0f\u8282\u7684\u5176\u4ed6\u5185\u5bb9\u5e94\u88ab\u7f29\u8fdb\u4e24\u4e2a\u6216\u56db\u4e2a\u7a7a\u683c\uff08\u5728\u6587\u4ef6\u5185\u4fdd\u6301\u4e00\u81f4\uff09\u3002\u5982\u679c\u51fd\u6570\u7684\u540d\u79f0\u548c\u7b7e\u540d\u5177\u6709\u8db3\u591f\u7684\u4fe1\u606f\uff0c\u53ef\u4ee5\u4f7f\u7528\u5355\u884c\u6587\u6863\u5b57\u7b26\u4e32\u8fdb\u884c\u9002\u5f53\u63cf\u8ff0\uff0c\u90a3\u5c31\u53ef\u4ee5\u7701\u7565\u8fd9\u4e9b\u90e8\u5206\u3002
"},{"location":"standard/style_rules/#args","title":"Args:","text":"\u5217\u51fa\u6bcf\u4e2a\u53c2\u6570\u7684\u540d\u5b57\uff0c\u5728\u540d\u5b57\u540e\u4f7f\u7528\u4e00\u4e2a\u5192\u53f7\u548c\u4e00\u4e2a\u7a7a\u683c\uff0c\u5206\u9694\u5bf9\u8be5\u53c2\u6570\u7684\u63cf\u8ff0\u3002\u5982\u679c\u63cf\u8ff0\u592a\u957f\u8d85\u8fc7\u4e86\u5355\u884c80\u5b57\u7b26\uff0c\u4f7f\u75282\u6216\u80054\u4e2a\u7a7a\u683c\u7684\u60ac\u6302\u7f29\u8fdb\uff08\u4e0e\u6587\u4ef6\u5176\u4ed6\u90e8\u5206\u4fdd\u6301\u4e00\u81f4\uff09\u3002\u63cf\u8ff0\u5e94\u8be5\u5305\u62ec\u6240\u9700\u7684\u7c7b\u578b\u548c\u542b\u4e49\u3002\u5982\u679c\u4e00\u4e2a\u51fd\u6570\u63a5\u53d7 *foo
\uff08\u53ef\u53d8\u957f\u5ea6\u53c2\u6570\u5217\u8868\uff09\u6216\u8005 **bar
\uff08\u4efb\u610f\u5173\u952e\u5b57\u53c2\u6570\uff09\uff0c\u5e94\u8be5\u8be6\u7ec6\u5217\u51fa *foo
\u548c **bar
\u3002
"},{"location":"standard/style_rules/#returns-yields","title":"Returns:\uff08\u6216\u8005 Yields: \u7528\u4e8e\u751f\u6210\u5668\uff09","text":"\u8fd4\u56de\u503c\u7684\u8bed\u4e49\u5e94\u8be5\u88ab\u63cf\u8ff0\u6e05\u695a\uff0c\u5305\u62ec\u7c7b\u578b\u6ce8\u91ca\u6240\u4e0d\u80fd\u63d0\u4f9b\u7684\u4efb\u4f55\u7c7b\u578b\u4fe1\u606f\u3002 \u5982\u679c\u51fd\u6570\u53ea\u8fd4\u56de None\uff0c\u5219\u4e0d\u9700\u8981\u6b64\u90e8\u5206\u3002\u5982\u679c\u6587\u6863\u5b57\u7b26\u4e32\u4ee5 Returns
\u6216 Yields
\u5f00\u5934 \uff08\u4f8b\u5982 \"\"\"Returns row from Bigtable as a tuple of strings.\"\"\"
\uff09\uff0c\u5e76\u4e14\u5f00\u5934\u7684\u53e5\u5b50\u8db3\u4ee5\u63cf\u8ff0\u8fd4\u56de\u503c\uff0c\u5219\u53ef\u4ee5\u7701\u7565\u6b64\u90e8\u5206\u3002 \u4e0d\u8981\u6a21\u4eff\u50cf NumPy\u98ce\u683c
\uff0c\u8be5\u98ce\u683c\u901a\u5e38\u5c06\u5143\u7ec4\u8fd4\u56de\u503c\u8bb0\u5f55\u4e3a\u591a\u4e2a\u5e26\u6709\u5355\u72ec\u540d\u79f0\u7684\u8fd4\u56de\u503c\uff08\u4ece\u4e0d\u63d0\u5230\u5143\u7ec4\uff09\u3002 \u76f8\u53cd\uff0c\u5e94\u5c06\u6b64\u7c7b\u8fd4\u56de\u503c\u63cf\u8ff0\u4e3a\uff1aReturns: A tuple (mat_a, mat_b), where mat_a is \u2026, and \u2026
\u3002 \u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u7684\u8f85\u52a9\u540d\u79f0\u4e0d\u4e00\u5b9a\u9700\u8981\u4e0e\u51fd\u6570\u4f53\u4e2d\u4f7f\u7528\u7684\u4efb\u4f55\u5185\u90e8\u540d\u79f0\u76f8\u5bf9\u5e94\uff08\u56e0\u4e3a\u5b83\u4eec\u4e0d\u662f API \u7684\u4e00\u90e8\u5206\uff09\u3002
"},{"location":"standard/style_rules/#raises","title":"Raises:","text":"\u5217\u51fa\u4e0e\u63a5\u53e3\u6709\u5173\u7684\u6240\u6709\u5f02\u5e38\uff0c\u7136\u540e\u7ed9\u51fa\u8bf4\u660e\u3002\u4f7f\u7528\u7c7b\u4f3c\u7684\u5f02\u5e38\u540d\u79f0 + \u5192\u53f7 + \u7a7a\u683c\u6216\u6362\u884c\u7b26\uff0c\u5e76\u6309 Args\uff1a
\u4e2d\u6240\u8ff0\u60ac\u6302\u7f29\u8fdb\u6837\u5f0f\u3002\u5982\u679c\u8fdd\u53cd\u4e86\u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u6307\u5b9a\u7684 API\uff0c\u5219\u4e0d\u5e94\u8be5\u8bb0\u5f55\u5f15\u53d1\u7684\u5f02\u5e38\uff08\u56e0\u4e3a\u8fd9\u4f1a\u4f7f\u8fdd\u53cd API \u7684\u884c\u4e3a\u6210\u4e3a API \u7684\u4e00\u90e8\u5206\uff09\u3002
def fetch_smalltable_rows(\ntable_handle: smalltable.Table,\nkeys: Sequence[bytes | str],\nrequire_all_keys: bool = False,\n) -> Mapping[bytes, tuple[str, ...]]:\n\"\"\"Fetches rows from a Smalltable.\n Retrieves rows pertaining to the given keys from the Table instance\n represented by table_handle. String keys will be UTF-8 encoded.\n Args:\n table_handle: An open smalltable.Table instance.\n keys: A sequence of strings representing the key of each table\n row to fetch. String keys will be UTF-8 encoded.\n require_all_keys: If True only rows with values set for all keys will be\n returned.\n Returns:\n A dict mapping keys to the corresponding table row data\n fetched. Each row is represented as a tuple of strings. For\n example:\n {b'Serak': ('Rigel VII', 'Preparer'),\n b'Zim': ('Irk', 'Invader'),\n b'Lrrr': ('Omicron Persei 8', 'Emperor')}\n Returned keys are always bytes. If a key from the keys argument is\n missing from the dictionary, then that row was not found in the\n table (and require_all_keys must have been False).\n Raises:\n IOError: An error occurred accessing the smalltable.\n \"\"\"\n
\u5982\u4e0b\u6240\u793a\uff0c Args \u4e2d\u53c2\u6570\u6362\u884c\u4e5f\u662f\u5141\u8bb8\u7684\uff1a
def fetch_smalltable_rows(\ntable_handle: smalltable.Table,\nkeys: Sequence[bytes | str],\nrequire_all_keys: bool = False,\n) -> Mapping[bytes, tuple[str, ...]]:\n\"\"\"Fetches rows from a Smalltable.\n Retrieves rows pertaining to the given keys from the Table instance\n represented by table_handle. String keys will be UTF-8 encoded.\n Args:\n table_handle:\n An open smalltable.Table instance.\n keys:\n A sequence of strings representing the key of each table row to\n fetch. String keys will be UTF-8 encoded.\n require_all_keys:\n If True only rows with values set for all keys will be returned.\n Returns:\n A dict mapping keys to the corresponding table row data\n fetched. Each row is represented as a tuple of strings. For\n example:\n {b'Serak': ('Rigel VII', 'Preparer'),\n b'Zim': ('Irk', 'Invader'),\n b'Lrrr': ('Omicron Persei 8', 'Emperor')}\n Returned keys are always bytes. If a key from the keys argument is\n missing from the dictionary, then that row was not found in the\n table (and require_all_keys must have been False).\n Raises:\n IOError: An error occurred accessing the smalltable.\n \"\"\"\n
"},{"location":"standard/style_rules/#384","title":"3.8.4 \u7c7b","text":"\u7c7b\u5e94\u8be5\u5728\u5176\u5b9a\u4e49\u4e0b\u6709\u4e00\u4e2a\u7528\u4e8e\u63cf\u8ff0\u8be5\u7c7b\u7684\u6587\u6863\u5b57\u7b26\u4e32\u3002\u5982\u679c\u4f60\u7684\u7c7b\u6709\u516c\u5171\u5c5e\u6027\uff08Attributes
\uff09\uff0c\u90a3\u4e48\u6587\u6863\u4e2d\u5e94\u8be5\u6709\u4e00\u4e2a\u5c5e\u6027\uff08Attributes
\uff09\u6bb5\uff0c\u5e76\u4e14\u5e94\u8be5\u9075\u5b88\u548c\u51fd\u6570\u53c2\u6570\u76f8\u540c\u7684\u683c\u5f0f\uff1a
class SampleClass:\n\"\"\"Summary of class here.\n Longer class information...\n Longer class information...\n Attributes:\n likes_spam: A boolean indicating if we like SPAM or not.\n eggs: An integer count of the eggs we have laid.\n \"\"\"\ndef __init__(self, likes_spam: bool = False):\n\"\"\"Initializes the instance based on spam preference.\n Args:\n likes_spam: Defines if instance exhibits this preference.\n \"\"\"\nself.likes_spam = likes_spam\nself.eggs = 0\ndef public_method(self):\n\"\"\"Performs operation blah.\"\"\"\n
\u6240\u6709\u7c7b\u6587\u6863\u5b57\u7b26\u4e32\u90fd\u5e94\u4ee5\u4e00\u884c\u6458\u8981\u5f00\u5934\uff0c\u63cf\u8ff0\u7c7b\u5b9e\u4f8b\u6240\u4ee3\u8868\u7684\u5185\u5bb9\u3002\u8fd9\u610f\u5473\u7740 Exception \u7684\u5b50\u7c7b\u8fd8\u5e94\u8be5\u63cf\u8ff0\u5f02\u5e38\u4ee3\u8868\u4ec0\u4e48\uff0c\u800c\u4e0d\u662f\u5b83\u53ef\u80fd\u53d1\u751f\u7684\u4e0a\u4e0b\u6587\u3002 \u7c7b\u6587\u6863\u5b57\u7b26\u4e32\u4e0d\u5e94\u91cd\u590d\u4e0d\u5fc5\u8981\u7684\u4fe1\u606f\uff0c\u4f8b\u5982\u8be5\u7c7b\u662f\u4e00\u4e2a\u7c7b\u3002
\u63a8\u8350
class CheeseShopAddress:\n\"\"\"The address of a cheese shop.\n ...\n \"\"\"\nclass OutOfCheeseError(Exception):\n\"\"\"No more cheese is available.\"\"\"\n
!!! fail \"\u4e0d\u63a8\u8350
```python\nclass CheeseShopAddress:\n \"\"\"Class that describes the address of a cheese shop.\n\n ...\n \"\"\"\n\nclass OutOfCheeseError(Exception):\n \"\"\"Raised when no more cheese is available.\"\"\"\n```\n
"},{"location":"standard/style_rules/#385","title":"3.8.5 \u5757\u6ce8\u91ca\u548c\u884c\u6ce8\u91ca","text":"\u6700\u9700\u8981\u5199\u6ce8\u91ca\u7684\u662f\u4ee3\u7801\u4e2d\u90a3\u4e9b\u6280\u5de7\u6027\u7684\u90e8\u5206\u3002\u5982\u679c\u4f60\u5728\u4e0b\u6b21\u4ee3\u7801\u5ba1\u67e5 \u7684\u65f6\u5019\u5fc5\u987b\u89e3\u91ca\u4e00\u4e0b\uff0c\u90a3\u4e48\u4f60\u5e94\u8be5\u73b0\u5728\u5c31\u7ed9\u5b83\u5199\u6ce8\u91ca\u3002\u5bf9\u4e8e\u590d\u6742\u7684\u64cd\u4f5c\uff0c\u5e94\u8be5\u5728\u5176\u64cd\u4f5c\u5f00\u59cb\u524d\u5199\u4e0a\u82e5\u5e72\u884c\u6ce8\u91ca\uff0c\u5bf9\u4e8e\u4e0d\u662f\u4e00\u76ee\u4e86\u7136\u7684\u4ee3\u7801\uff0c\u5e94\u5728\u5176\u884c\u5c3e\u6dfb\u52a0\u6ce8\u91ca\u3002
# We use a weighted dictionary search to find out where i is in\n# the array. We extrapolate position based on the largest num\n# in the array and the array size and then do binary search to\n# get the exact number.\nif i & (i - 1) == 0: # True if i is 0 or a power of 2.\n
\u4e3a\u4e86\u63d0\u9ad8\u53ef\u8bfb\u6027\uff0c\u6ce8\u91ca\u5b57\u7b26 #
\u5e94\u8be5\u81f3\u5c11\u79bb\u5f00\u4ee3\u7801\u4e24\u4e2a\u7a7a\u683c\uff0c\u7136\u540e\u5728\u6ce8\u91ca\u672c\u8eab\u7684\u6587\u672c\u4e4b\u524d\u81f3\u5c11\u6709\u4e00\u4e2a\u7a7a\u683c\u3002
\u53e6\u4e00\u65b9\u9762\uff0c\u7edd\u4e0d\u8981\u63cf\u8ff0\u4ee3\u7801\u3002\u5047\u8bbe\u9605\u8bfb\u4ee3\u7801\u7684\u4eba\u6bd4\u4f60\u66f4\u61c2 Python\uff0c\u4ed6\u53ea\u662f\u4e0d\u77e5\u9053\u4f60\u7684\u4ee3\u7801\u8981\u505a\u4ec0\u4e48\u3002
# BAD COMMENT: Now go through the b array and make sure whenever i occurs\n# the next element is i+1\n
"},{"location":"standard/style_rules/#386","title":"3.8.6 \u6807\u70b9\u7b26\u53f7\u3001\u62fc\u5199\u548c\u8bed\u6cd5","text":"\u6ce8\u610f\u6807\u70b9\u7b26\u53f7\u3001\u62fc\u5199\u548c\u8bed\u6cd5\u3002\u597d\u7684\u6ce8\u91ca\u66f4\u5bb9\u6613\u9605\u8bfb\u3002
\u6ce8\u91ca\u5e94\u8be5\u50cf\u53d9\u4e8b\u6587\u672c\u4e00\u6837\u53ef\u8bfb\uff0c\u6709\u9002\u5f53\u7684\u5927\u5199\u548c\u6807\u70b9\u7b26\u53f7\u3002\u5728\u8bb8\u591a\u60c5\u51b5\u4e0b\uff0c\u5b8c\u6574\u7684\u53e5\u5b50\u6bd4\u53e5\u5b50\u7247\u6bb5\u66f4\u5177\u53ef\u8bfb\u6027\u3002\u8f83\u77ed\u7684\u6ce8\u91ca\uff0c\u4f8b\u5982\u4ee3\u7801\u884c\u672b\u5c3e\u7684\u6ce8\u91ca\uff0c\u6709\u65f6\u53ef\u80fd\u4e0d\u90a3\u4e48\u6b63\u5f0f\uff0c\u4f46\u5e94\u8be5\u4e0e\u4f60\u7684\u98ce\u683c\u4fdd\u6301\u4e00\u81f4\u3002
\u867d\u7136\u88ab\u4ee3\u7801\u5ba1\u9605\u8005\u6307\u51fa\u6807\u70b9\u7b26\u53f7\u4f7f\u7528\u4e0d\u51c6\u786e\uff08\u5728\u7528\u5206\u53f7\u7684\u5730\u65b9\u7528\u4e86\u9017\u53f7\uff09\u7684\u611f\u89c9\u4f1a\u5f88\u4e0d\u723d\uff0c\u4f46\u6e90\u4ee3\u7801\u4fdd\u6301\u9ad8\u5ea6\u7684\u6e05\u6670\u6027\u548c\u53ef\u8bfb\u6027\u662f\u975e\u5e38\u91cd\u8981\u7684\u3002\u6b63\u786e\u7684\u6807\u70b9\u3001\u62fc\u5199\u548c\u8bed\u6cd5\u6709\u52a9\u4e8e\u5b9e\u73b0\u8fd9\u4e00\u76ee\u6807\u3002
"},{"location":"standard/style_rules/#310","title":"3.10 \u5b57\u7b26\u4e32","text":"\u5373\u4f7f\u53c2\u6570\u90fd\u662f\u5b57\u7b26\u4e32\uff0c\u4e5f\u8981\u4f7f\u7528 f-string\uff0c %
\u64cd\u4f5c\u7b26\u6216\u8005 format
\u65b9\u6cd5\u683c\u5f0f\u5316\u5b57\u7b26\u4e32\u3002\u4e0d\u8fc7\u4e5f\u4e0d\u80fd\u4e00\u6982\u800c\u8bba\uff0c\u4f60\u9700\u8981\u5728 +
\u548c %
\uff08\u6216 format
\uff09\u4e4b\u95f4\u597d\u597d\u5224\u5b9a\u3002\u4e0d\u8981\u5c06 %
\u6216 format
\u65b9\u6cd5\u7528\u4e8e\u7eaf\u8fde\u63a5\u3002
\u63a8\u8350
x = f'name: {name}; score: {n}'\nx = '%s, %s!' % (imperative, expletive)\nx = '{}, {}'.format(first, second)\nx = 'name: %s; score: %d' % (name, n)\nx = 'name: %(name)s; score: %(score)d' % {'name':name, 'score':n}\nx = 'name: {}; score: {}'.format(name, n)\nx = a + b\n
\u4e0d\u63a8\u8350
x = first + ', ' + second\nx = 'name: ' + name + '; score: ' + str(n)\n
\u907f\u514d\u5728\u5faa\u73af\u4e2d\u7528 +
\u548c +=
\u64cd\u4f5c\u7b26\u6765\u7d2f\u52a0\u5b57\u7b26\u4e32\u3002
\u7531\u4e8e\u5b57\u7b26\u4e32\u662f\u4e0d\u53ef\u53d8\u7684\uff0c\u8fd9\u6837\u505a\u4f1a\u521b\u5efa\u4e0d\u5fc5\u8981\u7684\u4e34\u65f6\u5bf9\u8c61\uff0c\u4e14\u5bfc\u81f4\u4e8c\u6b21\u65b9\u800c\u4e0d\u662f\u7ebf\u6027\u7684\u8fd0\u884c\u65f6\u95f4\u3002\u5c3d\u7ba1\u8fd9\u79cd\u5e38\u89c1\u7684\u7d2f\u52a0\u53ef\u4ee5\u5728 CPython \u4e0a\u8fdb\u884c\u4f18\u5316\uff0c\u4f46\u8fd9\u662f\u4e00\u4e2a\u5b9e\u73b0\u7ec6\u8282\u3002\u5e94\u7528\u4f18\u5316\u7684\u6761\u4ef6\u4e0d\u5bb9\u6613\u9884\u6d4b\uff0c\u5e76\u4e14\u53ef\u80fd\u4f1a\u6539\u53d8\u3002\u4f5c\u4e3a\u66ff\u4ee3\u65b9\u6848\uff0c\u4f60\u53ef\u4ee5\u5c06\u6bcf\u4e2a\u5b50\u4e32\u52a0\u5165\u5217\u8868\uff0c\u7136\u540e\u5728\u5faa\u73af\u7ed3\u675f\u540e\u7528 ''.join
\u8fde\u63a5\u5217\u8868\uff08\u4e5f\u53ef\u4ee5\u5c06\u6bcf\u4e2a\u5b50\u4e32\u5199\u5165\u4e00\u4e2a io.StringIO
\u7f13\u5b58\u4e2d\uff09\u3002
\u63a8\u8350
items = ['<table>']\nfor last_name, first_name in employee_list:\nitems.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))\nitems.append('</table>')\nemployee_table = ''.join(items)\n
\u4e0d\u63a8\u8350
employee_table = '<table>'\nfor last_name, first_name in employee_list:\nemployee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)\nemployee_table += '</table>'\n
\u5728\u540c\u4e00\u4e2a\u6587\u4ef6\u4e2d\uff0c\u4fdd\u6301\u4f7f\u7528\u5b57\u7b26\u4e32\u5f15\u53f7\u7684\u4e00\u81f4\u6027\u3002\u4f7f\u7528\u5355\u5f15\u53f7 '
\u6216\u8005\u53cc\u5f15\u53f7 \"
\u4e4b\u4e00\u7528\u4ee5\u5f15\u7528\u5b57\u7b26\u4e32\uff0c\u5e76\u5728\u540c\u4e00\u6587\u4ef6\u4e2d\u6cbf\u7528\u3002\u5728\u5b57\u7b26\u4e32\u5185\u53ef\u4ee5\u4f7f\u7528\u53e6\u5916\u4e00\u79cd\u5f15\u53f7\uff0c\u4ee5\u907f\u514d\u5728\u5b57\u7b26\u4e32\u4e2d\u4f7f\u7528 \\\\
\u8f6c\u4e49\u3002
\u63a8\u8350
Python('Why are you hiding your eyes?')\nGollum(\"I'm scared of lint errors.\")\nNarrator('\"Good!\" thought a happy Python reviewer.')\n
\u4e0d\u63a8\u8350
Python(\"Why are you hiding your eyes?\")\nGollum('The lint. It burns. It burns us.')\nGollum(\"Always the great lint. Watching. Watching.\")\n
\u4e3a\u591a\u884c\u5b57\u7b26\u4e32\u4f7f\u7528\u4e09\u91cd\u53cc\u5f15\u53f7 \"\"\"
\u800c\u975e\u4e09\u91cd\u5355\u5f15\u53f7 '''
\u3002\u5f53\u4e14\u4ec5\u5f53\u9879\u76ee\u4e2d\u4f7f\u7528\u5355\u5f15\u53f7 '
\u6765\u5f15\u7528\u5b57\u7b26\u4e32\u65f6\uff0c\u624d\u53ef\u80fd\u4f1a\u4f7f\u7528\u4e09\u91cd '''
\u4e3a\u975e\u6587\u6863\u5b57\u7b26\u4e32\u7684\u591a\u884c\u5b57\u7b26\u4e32\u6765\u6807\u8bc6\u5f15\u7528\u3002\u6587\u6863\u5b57\u7b26\u4e32\u5fc5\u987b\u4f7f\u7528\u4e09\u91cd\u53cc\u5f15\u53f7 \"\"\"
\u3002
\u591a\u884c\u5b57\u7b26\u4e32\u4e0d\u4f1a\u968f\u7a0b\u5e8f\u5176\u4f59\u90e8\u5206\u7684\u7f29\u8fdb\u800c\u7f29\u8fdb\u3002\u5982\u679c\u8981\u907f\u514d\u5728\u5b57\u7b26\u4e32\u4e2d\u5d4c\u5165\u989d\u5916\u7684\u7a7a\u767d\uff0c\u53ef\u4ee5\u4f7f\u7528\u4e32\u8054\u7684\u5355\u884c\u5b57\u7b26\u4e32\u6216\u5e26\u6709 textwrap.dedent()
\u7684\u591a\u884c\u5b57\u7b26\u4e32\u6765\u5220\u9664\u6bcf\u884c\u4e0a\u7684\u521d\u59cb\u7a7a\u767d\u3002
\u63a8\u8350
long_string = \"\"\"This is fine if your use case can accept\n extraneous leading spaces.\"\"\"\n
long_string = (\"And this is fine if you cannot accept\\n\" +\n\"extraneous leading spaces.\")\n
long_string = textwrap.dedent(\"\"\"\\\n This is also fine, because textwrap.dedent()\n will collapse common leading spaces in each line.\"\"\")\n
import textwrap\nlong_string = textwrap.dedent(\"\"\"\\\n This is also fine, because textwrap.dedent()\n will collapse common leading spaces in each line.\"\"\")\n
\u4e0d\u63a8\u8350
long_string = \"\"\"This is pretty ugly.\nDon't do this.\n\"\"\"\n
\u8bf7\u6ce8\u610f\uff0c\u6b64\u5904\u4f7f\u7528\u53cd\u659c\u6760\u5e76\u4e0d\u8fdd\u53cd\u7981\u6b62\u663e\u5f0f\u7eed\u884c\u7684\u89c4\u5b9a\uff1b\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u53cd\u659c\u6760\u6b63\u5728\u8f6c\u4e49\u5b57\u7b26\u4e32\u6587\u5b57\u4e2d\u7684\u6362\u884c\u7b26\u3002
"},{"location":"standard/style_rules/#3101","title":"3.10.1 \u65e5\u5fd7","text":"\u5bf9\u4e8e\u671f\u671b\u4ee5\u6a21\u5f0f\u5b57\u7b26\u4e32\uff08\u5e26\u6709 %
- \u5360\u4f4d\u7b26\uff09\u4f5c\u4e3a\u7b2c\u4e00\u4e2a\u53c2\u6570\u7684\u65e5\u5fd7\u51fd\u6570\uff1a\u59cb\u7ec8\u4f7f\u7528\u5b57\u7b26\u4e32\u6587\u672c\uff08\u800c\u4e0d\u662f f-string
\uff09\u4f5c\u4e3a\u5b83\u4eec\u7684\u7b2c\u4e00\u4e2a\u53c2\u6570\uff0c\u5e76\u4f7f\u7528\u6a21\u5f0f\u53c2\u6570\uff08pattern-parameters\uff09\u4f5c\u4e3a\u540e\u7eed\u53c2\u6570\u3002\u4e00\u4e9b\u65e5\u5fd7\u5b9e\u73b0\u5c06\u672a\u5c55\u5f00\u7684\u6a21\u5f0f\u5b57\u7b26\u4e32\u6536\u96c6\u4e3a\u53ef\u67e5\u8be2\u5b57\u6bb5\u3002\u5b83\u8fd8\u9632\u6b62\u82b1\u8d39\u65f6\u95f4\u5448\u73b0\u6ca1\u6709\u914d\u7f6e\u8bb0\u5f55\u5668\u8f93\u51fa\u7684\u6d88\u606f\u3002
\u63a8\u8350
import tensorflow as tf\nlogger = tf.get_logger()\nlogger.info('TensorFlow Version is: %s', tf.__version__)\n
\u63a8\u8350
import os\nfrom absl import logging\nlogging.info('Current $PAGER is: %s', os.getenv('PAGER', default=''))\nhomedir = os.getenv('HOME')\nif homedir is None or not os.access(homedir, os.W_OK):\nlogging.error('Cannot write to home directory, $HOME=%r', homedir)\n
\u4e0d\u63a8\u8350
import os\nfrom absl import logging\nlogging.info('Current $PAGER is:')\nlogging.info(os.getenv('PAGER', default=''))\nhomedir = os.getenv('HOME')\nif homedir is None or not os.access(homedir, os.W_OK):\nlogging.error(f'Cannot write to home directory, $HOME={homedir!r}')\n
"},{"location":"standard/style_rules/#3102","title":"3.10.2 \u9519\u8bef\u6d88\u606f","text":"\u9519\u8bef\u6d88\u606f\uff08\u4f8b\u5982\uff1aValueError
\u7b49\u5f02\u5e38\u7684\u6d88\u606f\u5b57\u7b26\u4e32\uff0c\u6216\u663e\u793a\u7ed9\u7528\u6237\u7684\u6d88\u606f\uff09\u5e94\u9075\u5faa\u4e09\u4e2a\u51c6\u5219\uff1a
- \u6d88\u606f\u9700\u8981\u4e0e\u5b9e\u9645\u9519\u8bef\u6761\u4ef6\u7cbe\u786e\u5339\u914d\u3002
- \u63d2\u5165\u7684\u7247\u6bb5\u5fc5\u987b\u59cb\u7ec8\u80fd\u591f\u6e05\u695a\u5730\u8bc6\u522b\u3002
- \u5b83\u4eec\u5e94\u8be5\u5141\u8bb8\u7b80\u5355\u7684\u81ea\u52a8\u5316\u5904\u7406\uff08\u4f8b\u5982
grepping
\uff09\u3002
\u63a8\u8350
if not 0 <= p <= 1:\nraise ValueError(f'Not a probability: {p!r}')\ntry:\nos.rmdir(workdir)\nexcept OSError as error:\nlogging.warning('Could not remove directory (reason: %r): %r',\nerror, workdir)\n
\u4e0d\u63a8\u8350
if p < 0 or p > 1: # PROBLEM: also false for float('nan')!\nraise ValueError(f'Not a probability: {p!r}')\ntry:\nos.rmdir(workdir)\nexcept OSError:\n# PROBLEM: Message makes an assumption that might not be true:\n# Deletion might have failed for some other reason, misleading\n# whoever has to debug this.\nlogging.warning('Directory already was deleted: %s', workdir)\ntry:\nos.rmdir(workdir)\nexcept OSError:\n# PROBLEM: The message is harder to grep for than necessary, and\n# not universally non-confusing for all possible values of `workdir`.\n# Imagine someone calling a library function with such code\n# using a name such as workdir = 'deleted'. The warning would read:\n# \"The deleted directory could not be deleted.\"\nlogging.warning('The %s directory could not be deleted.', workdir)\n
"},{"location":"standard/style_rules/#311-sockets","title":"3.11 \u6587\u4ef6,Sockets\u548c\u7c7b\u4f3c\u7684\u72b6\u6001\u8d44\u6e90","text":"\u5728\u6587\u4ef6\u548c sockets \u7ed3\u675f\u65f6\uff0c\u663e\u5f0f\u7684\u5173\u95ed\u5b83\u3002 \u6b64\u89c4\u5219\u81ea\u7136\u6269\u5c55\u5230\u5185\u90e8\u4f7f\u7528\u5957\u63a5\u5b57\u7684\u53ef\u5173\u95ed\u8d44\u6e90\uff0c\u4f8b\u5982\u6570\u636e\u5e93\u8fde\u63a5\uff0c\u4ee5\u53ca\u9700\u8981\u4ee5\u7c7b\u4f3c\u65b9\u5f0f\u5173\u95ed\u7684\u5176\u4ed6\u8d44\u6e90\u3002\u4ec5\u4e3e\u51e0\u4e2a\u4f8b\u5b50\uff0c \u8fd9\u8fd8\u5305\u62ec mmap mappings, h5py File objects\u548c matplotlib.pyplot figure windows\u3002
\u9664\u6587\u4ef6\u5916\uff0csockets \u6216\u5176\u4ed6\u7c7b\u4f3c\u6587\u4ef6\u7684\u5bf9\u8c61\u5728\u6ca1\u6709\u5fc5\u8981\u7684\u60c5\u51b5\u4e0b\u6253\u5f00\uff0c\u4f1a\u6709\u8bb8\u591a\u526f\u4f5c\u7528\uff0c\u4f8b\u5982\uff1a
- \u5b83\u4eec\u53ef\u80fd\u4f1a\u6d88\u8017\u6709\u9650\u7684\u7cfb\u7edf\u8d44\u6e90\u3002\u5982\u6587\u4ef6\u63cf\u8ff0\u7b26\u3002\u5982\u679c\u8fd9\u4e9b\u8d44\u6e90\u5728\u4f7f\u7528\u540e\u6ca1\u6709\u53ca\u65f6\u5f52\u8fd8\u7cfb\u7edf\uff0c\u90a3\u4e48\u7528\u4e8e\u5904\u7406\u8fd9\u4e9b\u5bf9\u8c61\u7684\u4ee3\u7801\u4f1a\u5c06\u8d44\u6e90\u6d88\u8017\u6b86\u5c3d\u3002
- \u6301\u6709\u6587\u4ef6\u5c06\u4f1a\u963b\u6b62\u5bf9\u4e8e\u6587\u4ef6\u7684\u5176\u4ed6\u8bf8\u5982\u79fb\u52a8\u3001\u5220\u9664\u4e4b\u7c7b\u7684\u64cd\u4f5c\u3002
- \u4ec5\u4ec5\u662f\u4ece\u903b\u8f91\u4e0a\u5173\u95ed\u6587\u4ef6\u548c Sockets\uff0c\u90a3\u4e48\u5b83\u4eec\u4ecd\u7136\u53ef\u80fd\u4f1a\u88ab\u5176\u5171\u4eab\u7684\u7a0b\u5e8f\u5728\u65e0\u610f\u4e2d\u8fdb\u884c\u8bfb\u6216\u8005\u5199\u64cd\u4f5c\u3002\u53ea\u6709\u5f53\u5b83\u4eec\u771f\u6b63\u88ab\u5173\u95ed\u540e\uff0c\u5bf9\u4e8e\u5b83\u4eec\u5c1d\u8bd5\u8fdb\u884c\u8bfb\u6216\u8005\u5199\u64cd\u4f5c\u5c06\u4f1a\u629b\u51fa\u5f02\u5e38\uff0c\u5e76\u4f7f\u5f97\u95ee\u9898\u5feb\u901f\u663e\u73b0\u51fa\u6765\u3002
\u800c\u4e14\uff0c\u5e7b\u60f3\u5f53\u6587\u4ef6\u5bf9\u8c61\u6790\u6784\u65f6\uff0c\u6587\u4ef6\u548c sockets \u4f1a\u81ea\u52a8\u5173\u95ed\uff0c \u8bd5\u56fe\u5c06\u6587\u4ef6\u5bf9\u8c61\u7684\u751f\u547d\u5468\u671f\u548c\u6587\u4ef6\u7684\u72b6\u6001\u7ed1\u5b9a\u5728\u4e00\u8d77\u7684\u60f3\u6cd5\uff0c\u90fd\u662f\u4e0d\u73b0\u5b9e\u7684\u3002\u56e0\u4e3a\u6709\u5982\u4e0b\u539f\u56e0\uff1a
- \u6ca1\u6709\u4efb\u4f55\u65b9\u6cd5\u53ef\u4ee5\u786e\u4fdd\u8fd0\u884c\u73af\u5883\u4f1a\u771f\u6b63\u7684\u6267\u884c\u6587\u4ef6\u7684\u6790\u6784\u3002\u4e0d\u540c\u7684 Python \u5b9e\u73b0\u91c7\u7528\u4e0d\u540c\u7684\u5185\u5b58\u7ba1\u7406\u6280\u672f\uff0c\u6bd4\u5982\u5ef6\u65f6\u5783\u573e\u5904\u7406\u673a\u5236\u3002\u5ef6\u65f6\u5783\u573e\u5904\u7406\u673a\u5236\u53ef\u80fd\u4f1a\u5bfc\u81f4\u5bf9\u8c61\u751f\u547d\u5468\u671f\u88ab\u4efb\u610f\u65e0\u9650\u5236\u7684\u5ef6\u957f\u3002
- \u5bf9\u4e8e\u6587\u4ef6\u610f\u5916\u7684\u5f15\u7528\uff0c\u4f1a\u5bfc\u81f4\u5bf9\u4e8e\u6587\u4ef6\u7684\u6301\u6709\u65f6\u95f4\u8d85\u51fa\u9884\u671f\uff08\u6bd4\u5982\u5bf9\u4e8e\u5f02\u5e38\u7684\u8ddf\u8e2a\uff0c\u5305\u542b\u6709\u5168\u5c40\u53d8\u91cf\u7b49\uff09\u3002
\u591a\u6b21\u53d1\u73b0\uff0c\u4f9d\u8d56\u4e8e\u81ea\u52a8\u6e05\u7406\u673a\u5236\u5728\u8fd1\u51e0\u5341\u5e74\u5185\u7684\u591a\u4e2a\u8bed\u8a00\u4e2d\u5bfc\u81f4\u4e86\u91cd\u5927\u95ee\u9898\uff08\u4f8b\u5982: java\u4e2d\u7684 this article\uff09
\u7ba1\u7406\u6587\u4ef6\u7684\u9996\u9009\u65b9\u6cd5\u662f\u4f7f\u7528 with
\u8bed\u53e5\uff1a
with open(\"hello.txt\") as hello_file:\nfor line in hello_file:\nprint(line)\n
\u5bf9\u4e8e\u4e0d\u652f\u6301 with
\u8bed\u53e5\u7684\u7c7b\u6587\u4ef6\u5bf9\u8c61\uff0c\u8bf7\u4f7f\u7528 contextlib.closing()
\uff1a
import contextlib\nwith contextlib.closing(urllib.urlopen(\"http://www.python.org/\")) as front_page:\nfor line in front_page:\nprint(line)\n
"},{"location":"standard/style_rules/#312-todo","title":"3.12 TODO \u6ce8\u91ca","text":"\u4e3a\u4e34\u65f6\u4ee3\u7801\u4f7f\u7528 TODO
\u6ce8\u91ca\uff0c\u5b83\u662f\u4e00\u79cd\u77ed\u671f\u89e3\u51b3\u65b9\u6848\uff0c\u4e0d\u7b97\u5b8c\u7f8e\uff0c\u4f46\u591f\u597d\u4e86\u3002
TODO
\u6ce8\u91ca\u5e94\u8be5\u5728\u6240\u6709\u5f00\u5934\u5904\u5305\u542b TODO
\u5b57\u7b26\u4e32\uff0c\u7d27\u8ddf\u7740\u662f\u7528\u62ec\u53f7\u62ec\u8d77\u6765\u7684\u4f60\u7684\u540d\u5b57\uff0c\u90ae\u7bb1\u5730\u5740\u6216\u5176\u5b83\u6807\u8bc6\u7b26\u3002\u7136\u540e\u662f\u4e00\u4e2a\u53ef\u9009\u7684\u5192\u53f7\u3002\u63a5\u7740\u5fc5\u987b\u6709\u4e00\u884c\u6ce8\u91ca\uff0c\u89e3\u91ca\u8981\u505a\u4ec0\u4e48\u3002
\u4e3b\u8981\u76ee\u7684\u662f\u4e3a\u4e86\u6709\u4e00\u4e2a\u7edf\u4e00\u7684 TODO
\u683c\u5f0f\uff0c\u8fd9\u6837\u6dfb\u52a0\u6ce8\u91ca\u7684\u4eba\u5c31\u53ef\u4ee5\u641c\u7d22\u5230\uff08\u5e76\u53ef\u4ee5\u6309\u9700\u63d0\u4f9b\u66f4\u591a\u7ec6\u8282\uff09\u3002\u5199\u4e86 TODO
\u6ce8\u91ca\u5e76\u4e0d\u4fdd\u8bc1\u5199\u7684\u4eba\u4f1a\u4eb2\u81ea\u89e3\u51b3\u95ee\u9898\u3002\u5f53\u4f60\u5199\u4e86\u4e00\u4e2a TODO
\uff0c\u8bf7\u6ce8\u4e0a\u4f60\u7684\u540d\u5b57\u3002
# TODO(crbug.com/192795): Investigate cpufreq optimizations.\n# TODO(yourusername): File an issue and use a '*' for repetition.\n
\u5982\u679c\u4f60\u7684 TODO
\u662f \u201c\u5c06\u6765\u505a\u67d0\u4e8b\u201d \u7684\u5f62\u5f0f\uff0c\u90a3\u4e48\u8bf7\u786e\u4fdd\u4f60\u5305\u542b\u4e86\u4e00\u4e2a\u6307\u5b9a\u7684\u65e5\u671f\uff082009\u5e7411\u6708\u89e3\u51b3\uff09\u6216\u8005\u4e00\u4e2a\u7279\u5b9a\u7684\u4e8b\u4ef6\uff08\u7b49\u5230\u6240\u6709\u7684\u5ba2\u6237\u90fd\u53ef\u4ee5\u5904\u7406 XML \u8bf7\u6c42\u5c31\u79fb\u9664\u8fd9\u4e9b\u4ee3\u7801\uff09\u3002
"},{"location":"standard/style_rules/#313","title":"3.13 \u5bfc\u5165\u683c\u5f0f","text":"\u6bcf\u4e2a\u5bfc\u5165\u5e94\u8be5\u72ec\u5360\u4e00\u884c\uff0ctyping
\u5bfc\u5165\u662f\u4e2a\u4f8b\u5916\u3002
\u63a8\u8350
from collections.abc import Mapping, Sequence\nimport os\nimport sys\nfrom typing import Any, NewType\n
\u4e0d\u63a8\u8350
import os, sys\n
\u5bfc\u5165\u603b\u5e94\u8be5\u653e\u5728\u6587\u4ef6\u9876\u90e8\uff0c\u4f4d\u4e8e\u6a21\u5757\u6ce8\u91ca\u548c\u6587\u6863\u5b57\u7b26\u4e32\u4e4b\u540e\uff0c\u6a21\u5757\u5168\u5c40\u53d8\u91cf\u548c\u5e38\u91cf\u4e4b\u524d\u3002\u5bfc\u5165\u5e94\u8be5\u6309\u7167\u4ece\u6700\u901a\u7528\u5230\u6700\u4e0d\u901a\u7528\u7684\u987a\u5e8f\u5206\u7ec4\uff1a
-
Future \u5bfc\u5165\u8bed\u53e5\uff1a
from __future__ import annotations\n
\u8bf7\u53c2\u9605\u4e0a\u9762\u7684\u66f4\u591a\u4fe1\u606f\u3002
-
\u6807\u51c6\u5e93\u5bfc\u5165\uff1a
import sys\n
-
\u7b2c\u4e09\u65b9\u6a21\u5757\u6216\u5305\u5bfc\u5165\uff1a
import tensorflow as tf\n
-
\u4ee3\u7801\u5e93\u5b50\u5305\u5bfc\u5165\uff1a
from otherproject.ai import mind\n
-
\u5df2\u5f03\u7528\uff1a \u4e0e\u6b64\u6587\u4ef6\u5c5e\u4e8e\u540c\u4e00\u9876\u7ea7\u5b50\u5305\u7684\u5e94\u7528\u7a0b\u5e8f\u7279\u5b9a\u5bfc\u5165\u3002\u4f8b\u5982\uff1a
from myproject.backend.hgwells import time_machine\n
\u60a8\u53ef\u80fd\u4f1a\u53d1\u73b0\u4e4b\u524d\u7684 Google Python \u98ce\u683c\u662f\u8fd9\u4e48\u505a\u7684\uff0c\u4f46\u73b0\u5728\u5df2\u7ecf\u4e0d\u63a8\u8350\u4e86\u3002\u65b0\u7684\u4ee3\u7801\u4e0d\u8981\u8fd9\u4e48\u505a \u3002\u53ea\u9700\u5c06\u7279\u5b9a\u4e8e\u5e94\u7528\u7a0b\u5e8f\u7684\u5b50\u5305\u5bfc\u5165\u4e0e\u5176\u4ed6\u5b50\u5305\u5bfc\u5165\u4e00\u6837\u5bf9\u5f85\u5373\u53ef\u3002
\u6bcf\u79cd\u5206\u7ec4\u4e2d\uff0c\u5e94\u8be5\u6839\u636e\u6bcf\u4e2a\u6a21\u5757\u7684\u5b8c\u6574\u5305\u8def\u5f84\uff08from path import ...
\u4e2d\u7684 path
\uff09\u6309\u5b57\u5178\u5e8f\u6392\u5e8f\uff0c\u5ffd\u7565\u5927\u5c0f\u5199\u3002\u4ee3\u7801\u53ef\u4ee5\u9009\u62e9\u5728\u5bfc\u5165\u8282\u4e4b\u95f4\u653e\u7f6e\u4e00\u4e2a\u7a7a\u884c\u3002
import collections\nimport queue\nimport sys\nfrom absl import app\nfrom absl import flags\nimport bs4\nimport cryptography\nimport tensorflow as tf\nfrom book.genres import scifi\nfrom myproject.backend import huxley\nfrom myproject.backend.hgwells import time_machine\nfrom myproject.backend.state_machine import main_loop\nfrom otherproject.ai import body\nfrom otherproject.ai import mind\nfrom otherproject.ai import soul\n# \u4e4b\u524d\u98ce\u683c\u7684\u4ee3\u7801\u53ef\u80fd\u4f1a\u5c06\u4e00\u4e9b\u5bfc\u5165\u653e\u5728\u8fd9\u91cc:\n# from myproject.backend.hgwells import time_machine\n# from myproject.backend.state_machine import main_loop\n
"},{"location":"standard/style_rules/#314","title":"3.14 \u8bed\u53e5","text":"\u901a\u5e38\u6bcf\u4e2a\u8bed\u53e5\u5e94\u8be5\u72ec\u5360\u4e00\u884c\u3002
\u4e0d\u8fc7\uff0c\u5982\u679c\u6d4b\u8bd5\u7ed3\u679c\u4e0e\u6d4b\u8bd5\u8bed\u53e5\u5728\u4e00\u884c\u653e\u5f97\u4e0b\uff0c\u4f60\u4e5f\u53ef\u4ee5\u5c06\u5b83\u4eec\u653e\u5728\u540c\u4e00\u884c\u3002\u5982\u679c\u662f if
\u8bed\u53e5\uff0c\u53ea\u6709\u5728\u6ca1\u6709 else
\u65f6\u624d\u80fd\u8fd9\u6837\u505a\u3002\u7279\u522b\u5730\uff0c\u7edd\u4e0d\u8981\u5bf9 try/except
\u8fd9\u6837\u505a\uff0c\u56e0\u4e3a try
\u548c except
\u4e0d\u80fd\u653e\u5728\u540c\u4e00\u884c\u3002
\u63a8\u8350
if foo: bar(foo)\n
\u4e0d\u63a8\u8350
if foo: bar(foo)\nelse: baz(foo)\ntry: bar(foo)\nexcept ValueError: baz(foo)\ntry:\nbar(foo)\nexcept ValueError: baz(foo)\n
"},{"location":"standard/style_rules/#315-getters-and-setters","title":"3.15 Getters and Setters","text":"\u5f53 getter \u548c setter \u51fd\u6570\uff08\u4e5f\u79f0\u4e3a\u8bbf\u95ee\u5668\u548c\u4fee\u6539\u5668\uff09\u4e3a\u83b7\u53d6\u6216\u8bbe\u7f6e\u53d8\u91cf\u7684\u503c\u63d0\u4f9b\u6709\u610f\u4e49\u7684\u89d2\u8272\u6216\u884c\u4e3a\u65f6\uff0c\u5219\u5e94\u8be5\u4f7f\u7528\u5b83\u4eec\u3002
\u7279\u522b\u662f\uff0c\u5f53\u5f53\u524d\u6216\u5408\u7406\u7684\u672a\u6765\u83b7\u53d6\u6216\u8bbe\u7f6e\u53d8\u91cf\u5f88\u590d\u6742\u6216\u6210\u672c\u5f88\u9ad8\u65f6\uff0c\u5e94\u8be5\u4f7f\u7528\u5b83\u4eec\u3002
\u4f8b\u5982\uff0c\u5982\u679c\u4e00\u5bf9 getter/setter \u53ea\u662f\u8bfb\u53d6\u548c\u5199\u5165\u5185\u90e8\u5c5e\u6027\uff0c\u5219\u5e94\u5c06\u5185\u90e8\u5c5e\u6027\u516c\u5f00\u3002\u76f8\u6bd4\u4e4b\u4e0b\uff0c\u5982\u679c\u8bbe\u7f6e\u4e00\u4e2a\u53d8\u91cf\u610f\u5473\u7740\u67d0\u4e9b\u72b6\u6001\u65e0\u6548\u6216\u91cd\u5efa\uff0c \u90a3\u4e48\u5b83\u5e94\u8be5\u662f\u4e00\u4e2a setter \u51fd\u6570\u3002\u51fd\u6570\u8c03\u7528\u6697\u793a\u6b63\u5728\u53d1\u751f\u6f5c\u5728\u7684\u91cd\u8981\u64cd\u4f5c\u3002\u6216\u8005\uff0c\u5f53\u9700\u8981\u7b80\u5355\u903b\u8f91\u6216\u91cd\u6784\u4ee5\u4e0d\u518d\u9700\u8981 getter \u548c setter \u65f6\uff0c property
\u53ef\u80fd\u662f\u4e00\u4e2a\u9009\u9879\u3002
Getter \u548c setter \u5e94\u8be5\u9075\u5faa\u547d\u540d\u89c4\u8303\uff0c\u4f8b\u5982\uff1a get_foo()
\u548c set_foo()
\u3002
\u5982\u679c\u4e4b\u524d\u7684\u4ee3\u7801\u884c\u4e3a\u5141\u8bb8\u901a\u8fc7\u5c5e\u6027\uff08property
\uff09\u8bbf\u95ee\uff0c\u90a3\u4e48\u5c31\u4e0d\u8981\u5c06\u65b0\u7684\u8bbf\u95ee\u51fd\u6570\u4e0e\u5c5e\u6027\u7ed1\u5b9a\u3002\u8fd9\u6837\uff0c\u4efb\u4f55\u8bd5\u56fe\u901a\u8fc7\u8001\u65b9\u6cd5\u8bbf\u95ee\u53d8\u91cf\u7684\u4ee3\u7801\u5c31\u6ca1\u6cd5\u8fd0\u884c\uff0c\u4f7f\u7528\u8005\u4e5f\u5c31\u4f1a\u610f\u8bc6\u5230\u590d\u6742\u6027\u53d1\u751f\u4e86\u53d8\u5316\u3002
"},{"location":"standard/style_rules/#316","title":"3.16 \u547d\u540d","text":"module_name
\u3001package_name
\u3001ClassName
\u3001method_name
\u3001ExceptionName
\u3001function_name
\u3001GLOBAL_CONSTANT_NAME
\u3001global_var_name
\u3001instance_var_name
\u3001function_parameter_name
\u3001local_var_name
, query_proper_noun_for_thing
\u3001send_acronym_via_https
\u3002
\u51fd\u6570\u540d\u3001\u53d8\u91cf\u540d\u548c\u6587\u4ef6\u540d\u5e94\u8be5\u90fd\u662f\u63cf\u8ff0\u6027\u7684\uff0c\u907f\u514d\u4f7f\u7528\u7f29\u5199\u3002\u7279\u522b\u662f\uff0c\u4e0d\u8981\u4f7f\u7528\u5bf9\u9879\u76ee\u4ee5\u5916\u7684\u8bfb\u8005\u6765\u8bf4\u6a21\u68f1\u4e24\u53ef\u6216\u4e0d\u719f\u6089\u7684\u7f29\u5199\uff0c\u4e5f\u4e0d\u8981\u901a\u8fc7\u5220\u9664\u5355\u8bcd\u4e2d\u7684\u5b57\u6bcd\u6765\u7f29\u5199\u3002
\u603b\u662f\u4f7f\u7528 .py
\u6587\u4ef6\u6269\u5c55\u540d\uff0c\u4e0d\u8981\u4f7f\u7528\u8fde\u5b57\u7b26\u3002
"},{"location":"standard/style_rules/#3161","title":"3.16.1 \u5e94\u8be5\u907f\u514d\u7684\u540d\u79f0","text":" -
\u5355\u5b57\u7b26\u540d\u79f0\uff0c\u9664\u4e86\u4ee5\u4e0b\u7279\u6b8a\u60c5\u51b5\uff1a
- \u8ba1\u6570\u5668\u548c\u8fed\u4ee3\u5668\uff08\u4f8b\u5982\uff1a
i
\uff0c j
\uff0c k
\uff0c v
\u7b49\u7b49\uff09 - \u4f5c\u4e3a
try/except
\u8bed\u53e5\u7684\u5f02\u5e38\u6807\u8bc6\u7b26 e
\u3002 - \u4f5c\u4e3a
with
\u8bed\u53e5\u58f0\u660e\u7684\u6587\u4ef6\u5bf9\u8c61 f
- \u6ca1\u6709\u7ea6\u675f\u7684\u79c1\u6709\u7c7b\u578b\u53d8\u91cf\uff08\u4f8b\u5982
_T = TypeVar(\"_T\")\u3001_P = ParamSpec(\"_P\")
\uff09
\u6ce8\u610f\u4e0d\u8981\u6ee5\u7528\u5355\u5b57\u7b26\u547d\u540d\u3002\u4e00\u822c\u6765\u8bf4\uff0c\u63cf\u8ff0\u6027\u5e94\u4e0e\u540d\u79f0\u7684\u53ef\u89c1\u6027\u8303\u56f4\u6210\u6bd4\u4f8b\u3002\u4f8b\u5982\uff1a i
\u53ef\u80fd\u662f\u4e94\u884c\u4ee3\u7801\u5757\u7684\u597d\u540d\u79f0\uff0c\u4f46\u5728\u591a\u4e2a\u5d4c\u5957\u8303\u56f4\u5185\uff0c\u5b83\u53ef\u80fd\u592a\u6a21\u7cca\u4e86\u3002
- \u5305/\u6a21\u5757\u540d\u4e2d\u7684\u8fde\u5b57\u7b26\uff08
-
\uff09 __double_leading_and_trailing_underscore__
\u53cc\u4e0b\u5212\u7ebf\u5f00\u5934\u5e76\u7ed3\u5c3e\u7684\u540d\u79f0\uff08Python\u4fdd\u7559\uff09 - \u4e0d\u793c\u8c8c\u7684\u7528\u8bed
- \u4e0d\u9700\u8981\u5305\u542b\u53d8\u91cf\u7c7b\u578b\u7684\u540d\u79f0\uff08\u4f8b\u5982\uff1a
id_to_name_dict
\uff09
"},{"location":"standard/style_rules/#3162","title":"3.16.2 \u547d\u540d\u7ea6\u5b9a","text":" - \u6240\u8c13\u201c\u5185\u90e8\uff08
Internal
\uff09\u201d\u8868\u793a\u4ec5\u6a21\u5757\u5185\u53ef\u7528\uff0c\u6216\u8005\u5728\u7c7b\u5185\u662f\u4fdd\u62a4\u6216\u79c1\u6709\u7684\u3002 - \u5728\u6a21\u5757\u53d8\u91cf\u548c\u51fd\u6570\u524d\u52a0\u4e00\u4e2a\u4e0b\u5212\u7ebf(
_
)\uff0c\u53ef\u4ee5\u5728\u4e00\u5b9a\u7a0b\u5ea6\u4e0a\u4fdd\u62a4\u5b83\u4eec\uff08\u4ee3\u7801\u68c0\u67e5\u5de5\u5177\u4f1a\u6807\u8bb0\u8bbf\u95ee\u53d7\u4fdd\u62a4\u7684\u6210\u5458\uff09\u3002 - \u7528\u53cc\u4e0b\u5212\u7ebf\uff08
__
\uff09\u5f00\u5934\u7684\u5b9e\u4f8b\u53d8\u91cf\u6216\u65b9\u6cd5\u8868\u793a\u7c7b\u5185\u79c1\u6709\uff0c\u4f46\u5e76\u4e0d\u63a8\u8350\u8fd9\u4e48\u505a\uff0c\u56e0\u4e3a\u4f1a\u5f71\u54cd\u4ee3\u7801\u7684\u53ef\u8bfb\u6027\u6216\u53ef\u6d4b\u8bd5\u6027\uff0c\u800c\u4e14\u4e5f\u4e0d\u662f\u771f\u6b63\u7684\u79c1\u6709\u3002\u5efa\u8bae\u4f7f\u7528 \u5355\u4e0b\u5212\u7ebf_
\u3002 - \u5c06\u76f8\u5173\u7684\u7c7b\u548c\u9876\u7ea7\u51fd\u6570\u653e\u5728\u540c\u4e00\u4e2a\u6a21\u5757\u91cc\u3002\u4e0d\u50cf Java \uff0c\u6ca1\u5fc5\u8981\u9650\u5236\u4e00\u4e2a\u7c7b\u4e00\u4e2a\u6a21\u5757\u3002
- \u5bf9\u7c7b\u540d\u4f7f\u7528\u5927\u5199\u5b57\u6bcd\u5f00\u5934\u7684\u5355\u8bcd\uff08\u5982
CapWords
\uff0c\u5373 Pascal \u98ce\u683c\uff09\uff0c\u4f46\u662f\u6a21\u5757\u540d\u5e94\u8be5\u7528\u5c0f\u5199\u52a0\u4e0b\u5212\u7ebf\u7684\u65b9\u5f0f\uff08\u5982 lower_with_under.py
\uff09\u3002 \u5c3d\u7ba1\u5df2\u7ecf\u6709\u5f88\u591a\u73b0\u5b58\u7684\u6a21\u5757\u4f7f\u7528\u7c7b\u4f3c\u4e8e CapWords.py
\u8fd9\u6837\u7684\u547d\u540d\uff0c\u4f46\u73b0\u5728\u5df2\u7ecf\u4e0d\u9f13\u52b1\u8fd9\u6837\u505a\uff0c\u56e0\u4e3a\u5982\u679c\u6a21\u5757\u540d\u78b0\u5de7\u548c\u7c7b\u540d\u4e00\u81f4\uff0c\u8fd9\u4f1a\u8ba9\u4eba\u56f0\u6270\u3002\uff08\u201c\u60f3\u60f3 \u6211\u5e94\u8be5\u7528 import StringIO
\u8fd8\u662f from StringIO import StringIO
\uff1f\u201d\uff09 - \u65b0\u7684\u5355\u5143\u6d4b\u8bd5\u6587\u4ef6\u9075\u5faa PEP 8 \u517c\u5bb9\u7684\u4e0b\u5212\u7ebf\u547d\u540d\u6cd5\uff0c\u4f8b\u5982\uff0c
test_<\u88ab\u6d4b\u8bd5\u7684\u65b9\u6cd5><\u72b6\u6001>
\u3002\u4e3a\u4e86\u4e0e\u9075\u5faa CapWords
\u51fd\u6570\u540d\u79f0\u7684\u65e7\u6a21\u5757\u4fdd\u6301\u4e00\u81f4\uff0c\u65b9\u6cd5\u540d\u79f0\u4e2d\u53ef\u4ee5\u51fa\u73b0\u4e0b\u5212\u7ebf\uff0c\u4ee5\u4fbf\u5206\u9694\u540d\u79f0\u7684\u903b\u8f91\u7ec4\u4ef6\uff0c\u5176\u4e2d\u4ee5 test \u5f00\u5934\u7684\u65b9\u6cd5\u540d\u53ef\u80fd\u91c7\u7528 test<\u88ab\u6d4b\u8bd5\u7684\u65b9\u6cd5><\u72b6\u6001
> \u7684\u6a21\u5f0f\u3002
"},{"location":"standard/style_rules/#3163","title":"3.16.3 \u6587\u4ef6\u547d\u540d","text":"Python \u6587\u4ef6\u540d\u5fc5\u987b\u4ee5 .py
\u6269\u5c55\u540d\u7ed3\u5c3e\uff0c\u5e76\u4e14\u4e0d\u8981\u5305\u542b\u8fde\u5b57\u7b26\uff08-
\uff09\u3002\u8fd9\u6837\u53ef\u4ee5\u65b9\u4fbf\u5bfc\u5165\u548c\u5355\u5143\u6d4b\u8bd5\u3002\u5982\u679c\u4f60\u5e0c\u671b\u4f7f\u7528\u6ca1\u6709\u6269\u5c55\u540d\u7684\u53ef\u6267\u884c\u6587\u4ef6\uff0c\u53ef\u4ee5\u4f7f\u7528\u8f6f\u8fde\u63a5\u65b9\u5f0f\u6216\u8005\u5305\u542b exec \"$0.py\" \"$@\"
\u7684\u7b80\u5355\u5305\u88c5\u811a\u672c\u3002
"},{"location":"standard/style_rules/#3164-guidos","title":"3.16.4 \u57fa\u4e8e Guido\u2019s \u63a8\u8350\u7684\u6d3e\u751f\u51c6\u5219","text":"Type Public Internal Packages lower_with_under Modules lower_with_under _lower_with_under Classes CapWords _CapWords Exceptions CapWords Functions lower_with_under() _lower_with_under() Global/Class Constants CAPS_WITH_UNDER _CAPS_WITH_UNDER Global/Class Variables lower_with_under _lower_with_under Instance Variables lower_with_under _lower_with_under (protected) Method Names lower_with_under() _lower_with_under() (protected) Function/Method Parameters lower_with_under Local Variables lower_with_under"},{"location":"standard/style_rules/#3165","title":"3.16.5 \u6570\u5b66\u7b26\u53f7","text":"\u5bf9\u4e8e\u504f\u6570\u5b66\u8fd0\u7b97\u7684\u4ee3\u7801\uff0c\u5f53\u5b83\u4eec\u5339\u914d\u53c2\u8003\u8bba\u6587\u6216\u7b97\u6cd5\u4e2d\u5df2\u5efa\u7acb\u7684\u7b26\u53f7\u65f6\uff0c\u8f83\u77ed\u7684\u53d8\u91cf\u540d\u4f1a\u8fdd\u53cd\u6837\u5f0f\u6307\u5357\u3002\u6267\u884c\u6b64\u64cd\u4f5c\u65f6\uff0c\u8bf7\u5728\u6ce8\u91ca\u6216\u6587\u6863\u5b57\u7b26\u4e32\u4e2d\u5f15\u7528\u6240\u6709\u547d\u540d\u7ea6\u5b9a\u7684\u6765\u6e90\uff0c\u5982\u679c\u6765\u6e90\u65e0\u6cd5\u8bbf\u95ee\uff0c\u8bf7\u6e05\u695a\u5730\u8bb0\u5f55\u547d\u540d\u7ea6\u5b9a\u3002\u5bf9\u4e8e\u516c\u5171 API\uff0c\u6700\u597d\u4f7f\u7528\u7b26\u5408 PEP8 \u7684\u63cf\u8ff0\u6027\u540d\u79f0\uff08descriptive_names
\uff09\uff0c\u8fd9\u6837\u66f4\u5bb9\u6613\u8131\u79bb\u4e0a\u4e0b\u6587\u3002
"},{"location":"standard/style_rules/#317-main","title":"3.17 Main","text":"\u5728 Python \u4e2d\uff0c pydoc
\u4ee5\u53ca\u5355\u5143\u6d4b\u8bd5\u8981\u6c42\u6a21\u5757\u5fc5\u987b\u662f\u53ef\u5bfc\u5165\u7684\u3002\u5982\u679c\u6587\u4ef6\u6253\u7b97\u4f5c\u4e3a\u53ef\u6267\u884c\u6587\u4ef6\u4f7f\u7528\uff0c\u90a3\u4e48\u5b83\u7684\u4e3b\u8981\u529f\u80fd\u5e94\u8be5\u653e\u5728 main()
\u51fd\u6570\u4e2d\u3002\u4f60\u7684\u4ee3\u7801\u5e94\u8be5\u5728\u6267\u884c\u4e3b\u7a0b\u5e8f\u524d\u603b\u662f\u68c0\u67e5 if __name__ == '__main__'
\uff0c\u8fd9\u6837\u5f53\u6a21\u5757\u88ab\u5bfc\u5165\u65f6\u4e3b\u7a0b\u5e8f\u5c31\u4e0d\u4f1a\u88ab\u6267\u884c\u3002
\u5f53\u4f7f\u7528 absl \u65f6\uff0c\u8bf7\u4f7f\u7528 app.run
\uff1a
from absl import app\n...\ndef main(argv):\n# process non-flag arguments\n...\nif __name__ == '__main__':\napp.run(main)\n
\u6216\u8005\uff1a
def main():\n...\nif __name__ == '__main__':\nmain()\n
\u6240\u6709\u7684\u9876\u7ea7\u4ee3\u7801\u5728\u6a21\u5757\u5bfc\u5165\u65f6\u90fd\u4f1a\u88ab\u6267\u884c\u3002\u8981\u5c0f\u5fc3\u4e0d\u8981\u53bb\u8c03\u7528\u51fd\u6570\u3001\u521b\u5efa\u5bf9\u8c61\u6216\u8005\u6267\u884c\u90a3\u4e9b\u4e0d\u5e94\u8be5\u5728\u4f7f\u7528 pydoc
\u65f6\u6267\u884c\u7684\u64cd\u4f5c\u3002
"},{"location":"standard/style_rules/#318","title":"3.18 \u51fd\u6570\u957f\u5ea6","text":"\u559c\u6b22\u5c0f\u800c\u7f8e\u7684\u51fd\u6570\u3002
\u957f\u51fd\u6570\u6709\u65f6\u5019\u662f\u53ef\u4ee5\u63a5\u53d7\u7684\uff0c\u5bf9\u51fd\u6570\u7684\u957f\u5ea6\u6ca1\u6709\u786c\u6027\u9650\u5236\u3002\u5982\u679c\u4e00\u4e2a\u51fd\u6570\u8d85\u8fc7\u4e8640\u884c\uff0c\u5c31\u9700\u8981\u601d\u8003\u4e00\u4e0b\uff0c\u5728\u4e0d\u7834\u574f\u7a0b\u5e8f\u7ed3\u6784\u7684\u60c5\u51b5\u4e0b\u662f\u5426\u9700\u8981\u62c6\u5206\u3002
\u5373\u4f7f\u4f60\u7684\u957f\u51fd\u6570\u73b0\u5728\u8fd0\u884c\u826f\u597d\uff0c\u5c06\u6765\u4fee\u6539\u5b83\u7684\u4eba\u4e5f\u53ef\u80fd\u4f1a\u6dfb\u52a0\u65b0\u7684\u529f\u80fd\uff0c\u8fd9\u53ef\u80fd\u4f1a\u5bfc\u81f4 BUG \u5f88\u96be\u67e5\u627e\u3002\u4fdd\u6301\u51fd\u6570\u7684\u7b80\u77ed\u548c\u7b80\u5355\u53ef\u4ee5\u4f7f\u5176\u4ed6\u4eba\u66f4\u5bb9\u6613\u9605\u8bfb\u548c\u4fee\u6539\u4f60\u7684\u4ee3\u7801\u3002
\u5728\u5904\u7406\u67d0\u4e9b\u4ee3\u7801\u65f6\uff0c\u60a8\u53ef\u80fd\u4f1a\u53d1\u73b0\u957f\u5e76\u4e14\u590d\u6742\u7684\u51fd\u6570\u3002\u5148\u4e0d\u8981\u88ab\u4fee\u6539\u8fd9\u4e9b\u4ee3\u7801\u6240\u5413\u5012\uff1a\u5982\u679c\u611f\u5230\u51fd\u6570\u4f7f\u7528\u56f0\u96be\uff0c\u9519\u8bef\u4e5f\u5f88\u96be\u8c03\u8bd5\uff0c\u6216\u8005\u60f3\u5728\u51e0\u4e2a\u4e0d\u540c\u7684\u5730\u65b9\u4f7f\u7528\u76f8\u540c\u7684\u529f\u80fd\uff0c\u53ef\u4ee5\u8003\u8651\u5c06\u51fd\u6570\u62c6\u5206\u6210\u66f4\u5c0f\u548c\u66f4\u6613\u4e8e\u7ba1\u7406\u7684\u4ee3\u7801\u6bb5\u3002
"},{"location":"standard/style_rules/#319","title":"3.19 \u7c7b\u578b\u6807\u6ce8","text":""},{"location":"standard/style_rules/#3191","title":"3.19.1 \u901a\u7528\u89c4\u5219","text":" - \u719f\u6089 PEP-484\u3002
-
\u5728\u65b9\u6cd5\u4e2d\uff0c\u53ea\u6709\u5728\u9700\u8981\u6b63\u786e\u7684\u7c7b\u578b\u4fe1\u606f\u65f6\u624d\u6807\u6ce8 self
\u6216 cls
\u3002\u4f8b\u5982\uff1a
@classmethod \ndef create(cls: Type[T]) -> T: \nreturn cls()\n
-
\u540c\u6837\uff0c\u4e0d\u5fc5\u5f3a\u5236\u6ce8\u91ca __init__
\u7684\u8fd4\u56de\u503c\uff08\u5176\u4e2d None \u662f\u552f\u4e00\u6709\u6548\u7684\u9009\u9879\uff09\u3002
- \u5982\u679c\u65e0\u6cd5\u8868\u793a\u4efb\u4f55\u5176\u4ed6\u53d8\u91cf\u6216\u8fd4\u56de\u7c7b\u578b\uff0c\u8bf7\u4f7f\u7528
Any
\u3002 - \u4f60\u4e0d\u9700\u8981\u6807\u6ce8\u6a21\u5757\u4e2d\u7684\u6240\u6709\u51fd\u6570\u3002
- \u81f3\u5c11\u8981\u6807\u6ce8\u516c\u5171 API\u3002
- \u5728\u5b89\u5168\u6027\u548c\u6e05\u6670\u6027\u4e0e\u7075\u6d3b\u6027\u4e4b\u95f4\u627e\u5230\u4e00\u4e2a\u5e73\u8861\u70b9\u3002
- \u6807\u6ce8\u90a3\u4e9b\u5bb9\u6613\u51fa\u73b0\u7c7b\u578b\u76f8\u5173\u9519\u8bef\uff08\u4ee5\u524d\u7684 BUG \u6216\u590d\u6742\u6027\uff09\u7684\u4ee3\u7801
- \u6807\u6ce8\u90a3\u4e9b\u96be\u4ee5\u7406\u89e3\u7684\u4ee3\u7801\u3002
- \u6807\u6ce8\u90a3\u4e9b\u4ece\u7c7b\u578b\u7684\u89d2\u5ea6\u6765\u770b\u5df2\u7ecf\u7a33\u5b9a\u7684\u4ee3\u7801\u3002\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u4f60\u53ef\u4ee5\u5728\u7a33\u5b9a\u7684\u4ee3\u7801\u4e2d\u6807\u6ce8\u6240\u6709\u51fd\u6570\uff0c\u800c\u4e0d\u4f1a\u5931\u53bb\u592a\u591a\u7075\u6d3b\u6027\u3002
"},{"location":"standard/style_rules/#3192","title":"3.19.2 \u65ad\u884c","text":"\u9075\u5faa\u73b0\u6709\u7f29\u8fdb\u89c4\u5219\u3002
\u5728\u6ce8\u91ca\u4e4b\u540e\uff0c\u8bb8\u591a\u51fd\u6570\u7b7e\u540d\u5c06\u53d8\u4e3a\u201c\u6bcf\u884c\u4e00\u4e2a\u53c2\u6570\u201d\u3002\u4e3a\u786e\u4fdd\u8fd4\u56de\u7c7b\u578b\u4e5f\u6709\u81ea\u5df1\u7684\u4e00\u884c\uff0c\u53ef\u4ee5\u5728\u6700\u540e\u4e00\u4e2a\u53c2\u6570\u540e\u9762\u52a0\u4e0a\u4e00\u4e2a\u9017\u53f7\u3002
def my_method(\nself,\nfirst_var: int,\nsecond_var: Foo,\nthird_var: Bar | None,\n) -> int:\n...\n
\u5c3d\u91cf\u5728\u53d8\u91cf\u4e4b\u95f4\u65ad\u884c\uff0c\u4e0d\u8981\u5728\u53d8\u91cf\u540d\u548c\u7c7b\u578b\u6807\u6ce8\u4e4b\u95f4\u65ad\u884c\u3002\u5982\u679c\u6240\u6709\u5185\u5bb9\u90fd\u5728\u4e00\u884c\u4e0a\uff0c\u5c31\u4e0d\u8981\u7ba1\u4e86\u3002
def my_method(self, first_var: int) -> int:\n...\n
\u5982\u679c\u51fd\u6570\u540d\u3001\u6700\u540e\u4e00\u4e2a\u53c2\u6570\u548c\u8fd4\u56de\u7c7b\u578b\u7ec4\u5408\u8d77\u6765\u592a\u957f\u4e86\uff0c\u53ef\u4ee5\u65b0\u6362\u4e00\u884c\u5e76\u7f29\u8fdb4\u4e2a\u5b57\u7b26\u3002 \u5728\u4f7f\u7528\u6362\u884c\u7b26\u65f6\uff0c\u5efa\u8bae\u5c06\u6bcf\u4e2a\u53c2\u6570\u548c\u8fd4\u56de\u7c7b\u578b\u653e\u5728\u81ea\u5df1\u7684\u884c\u4e0a\uff0c\u5e76\u5c06\u53f3\u62ec\u53f7\u4e0e def
\u5bf9\u9f50\u3002
def my_method(\nself,\nother_arg: MyLongType | None,\n) -> tuple[MyLongType1, MyLongType1]:\n...\n
\u6216\u8005\uff0c\u8fd4\u56de\u7c7b\u578b\u53ef\u4ee5\u4e0e\u6700\u540e\u4e00\u4e2a\u53c2\u6570\u653e\u5728\u540c\u4e00\u884c\uff1a
\u63a8\u8350
def my_method(\nself,\nfirst_var: int,\nsecond_var: int) -> dict[OtherLongType, MyLongType]:\n...\n
Pylint \u5141\u8bb8\u60a8\u5c06\u53f3\u62ec\u53f7\u79fb\u5230\u65b0\u884c\uff0c\u5e76\u4e0e\u5de6\u62ec\u53f7\u5bf9\u9f50\uff0c\u4f46\u8fd9\u4e48\u505a\u53ef\u8bfb\u6027\u4f1a\u6bd4\u8f83\u5dee\u3002
\u4e0d\u63a8\u8350
def my_method(self,\nother_arg: Optional[MyLongType]\n) -> Dict[OtherLongType, MyLongType]:\n...\n
\u5c31\u50cf\u4e0a\u9762\u7684\u4f8b\u5b50\u4e00\u6837\uff0c\u6211\u4eec\u4e0d\u5e0c\u671b\u622a\u65ad\u7c7b\u578b\u3002\u4f46\u662f\uff0c\u6709\u65f6\u5019\u5b83\u4eec\u653e\u5728\u4e00\u884c\u4e0a\u5b9e\u5728\u592a\u957f\u4e86\uff08\u5c3d\u91cf\u4fdd\u6301\u5b50\u7c7b\u578b\u4e0d\u88ab\u622a\u65ad\uff09\uff1a
def my_method(\nself,\nfirst_var: Tuple[List[MyLongType1],\nList[MyLongType2]],\nsecond_var: List[Dict[\nMyLongType3, MyLongType4]],\n) -> None:\n...\n
\u5982\u679c\u5355\u4e2a\u540d\u79f0\u548c\u7c7b\u578b\u592a\u957f\uff0c\u8bf7\u8003\u8651\u4f7f\u7528\u7c7b\u578b\u7684\u522b\u540d\u3002\u6700\u540e\u4e00\u79cd\u65b9\u6cd5\u662f\u5728\u5192\u53f7\u540e\u9762\u622a\u65ad\uff0c\u5e76\u7f29\u8fdb4\u4e2a\u5b57\u7b26\u3002
\u63a8\u8350
def my_function(\nlong_variable_name:\nlong_module_name.LongTypeName,\n) -> None:\n...\n
\u4e0d\u63a8\u8350
def my_function(\nlong_variable_name: long_module_name.\nLongTypeName,\n) -> None:\n...\n
"},{"location":"standard/style_rules/#3193","title":"3.19.3 \u524d\u7f6e\u58f0\u660e","text":"\u5982\u679c\u4f60\u9700\u8981\u4f7f\u7528\u4e00\u4e2a\u5c1a\u672a\u5b9a\u4e49\u7684\u7c7b\u540d\uff08\u6765\u81ea\u540c\u4e00\u6a21\u5757\uff09\uff0c\u4f8b\u5982\uff0c\u5982\u679c\u4f60\u9700\u8981\u5728\u8be5\u7c7b\u7684\u58f0\u660e\u5185\u90e8\u4f7f\u7528\u7c7b\u540d\uff0c\u6216\u8005\u5982\u679c\u4f60\u4f7f\u7528\u7684\u7c7b\u662f\u5728\u4ee3\u7801\u540e\u9762\u5b9a\u4e49\u7684\uff0c \u90a3\u4e48\u53ef\u4ee5\u4f7f\u7528 from future import annotations
\u6216\u4f7f\u7528\u5b57\u7b26\u4e32\u4f5c\u4e3a\u7c7b\u540d\u3002
\u63a8\u8350
from __future__ import annotations\nclass MyClass:\ndef __init__(self, stack: Sequence[MyClass], item: OtherClass) -> None:\nclass OtherClass:\n
\u63a8\u8350
class MyClass:\ndef __init__(self, stack: Sequence['MyClass'], item: 'OtherClass') -> None:\nclass OtherClass:\n...\n
"},{"location":"standard/style_rules/#3194","title":"3.19.4 \u9ed8\u8ba4\u503c","text":"\u6839\u636e PEP-008 \uff0c\u4ec5\u5728\u540c\u65f6\u5177\u6709\u7c7b\u578b\u6807\u6ce8\u548c\u9ed8\u8ba4\u503c\u53c2\u6570\u7684 =
\u5de6\u53f3\u4e24\u8fb9\u4f7f\u7528\u7a7a\u683c\u3002
\u63a8\u8350
def func(a: int = 0) -> int:\n...\n
\u4e0d\u63a8\u8350
def func(a:int=0) -> int:\n...\n
"},{"location":"standard/style_rules/#3195-nonetype","title":"3.19.5 NoneType","text":"\u5728 Python \u7c7b\u578b\u7cfb\u7edf\u4e2d\uff0cNoneType
\u662f\u201c\u4e00\u7b49\u516c\u6c11\u201d\u7c7b\u578b\uff0c\u800c\u5728\u7c7b\u578b\u6ce8\u91ca\u4e2d\uff0cNone \u662f NoneType \u7684\u522b\u540d\u3002\u5982\u679c\u4e00\u4e2a\u53c2\u6570\u53ef\u4ee5\u662f None\uff0c \u90a3\u4e48\u5fc5\u987b\u58f0\u660e\u5b83\uff01\u60a8\u53ef\u4ee5\u4f7f\u7528 |
\u8054\u5408\u7c7b\u578b\u8868\u8fbe\u5f0f\uff08\u5efa\u8bae\u5728\u65b0\u7684 Python 3.10+ \u4ee3\u7801\u4e2d\u4f7f\u7528\uff09\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528\u65e7\u7684 Optional
\u548c Union
\u8bed\u6cd5\u3002
\u8bf7\u4f7f\u7528\u663e\u5f0f\u7684 X | None
\u800c\u4e0d\u662f\u9690\u5f0f\u7684\u3002PEP 484 \u7684\u65e9\u671f\u7248\u672c\u5141\u8bb8\u5c06 a: str = None
\u89e3\u91ca\u4e3a a: str | None = None
\uff0c\u4f46\u73b0\u5728\u5df2\u7ecf\u4e0d\u63a8\u8350\u4e86\u3002
\u63a8\u8350
def modern_or_union(a: str | int | None, b: str | None = None) -> str:\n...\ndef union_optional(a: Union[str, int, None], b: Optional[str] = None) -> str:\n...\n
\u4e0d\u63a8\u8350
def nullable_union(a: Union[None, str]) -> str:\n...\ndef implicit_optional(a: str = None) -> str:\n...\n
"},{"location":"standard/style_rules/#3196","title":"3.19.6 \u7c7b\u578b\u522b\u540d","text":"\u53ef\u4ee5\u4e3a\u590d\u6742\u7c7b\u578b\u58f0\u660e\u522b\u540d\u3002\u522b\u540d\u5e94\u8be5\u662f\u5927\u5199\u7684\uff08CapWorded
\uff09\u3002\u5982\u679c\u522b\u540d\u4ec5\u5728\u6a21\u5757\u4e2d\u4f7f\u7528\uff0c\u90a3\u4e48\u5e94\u8be5\u4f7f\u7528\u524d\u7f6e\u4e0b\u5212\u7ebf\u8ba9\u5176\u53d8\u6210\u79c1\u6709\u7684\uff08\u5982 _Private
\uff09\u3002
\u8bf7\u6ce8\u610f\uff0c\u4ec5 3.10+ \u7248\u672c\u652f\u6301 :TypeAlias
\u6ce8\u91ca\u3002
from typing import TypeAlias\n_LossAndGradient: TypeAlias = tuple[tf.Tensor, tf.Tensor]\nComplexTFMap: TypeAlias = Mapping[str, _LossAndGradient]\n
\u5176\u4ed6\u4f8b\u5b50\u8fd8\u6709\u590d\u6742\u7684\u5d4c\u5957\u7c7b\u578b\u548c\u51fd\u6570\u7684\u591a\u4e2a\u8fd4\u56de\u53d8\u91cf\uff08\u4f5c\u4e3a\u5143\u7ec4\uff09\u3002
"},{"location":"standard/style_rules/#3197","title":"3.19.7 \u5ffd\u7565\u7c7b\u578b","text":"\u53ef\u4ee5\u5728\u884c\u4e0a\u4f7f\u7528\u7279\u6b8a\u6ce8\u91ca # type: ignore
\u7981\u7528\u7c7b\u578b\u68c0\u67e5\u3002
pytype
\u6709\u4e00\u4e2a\u9488\u5bf9\u7279\u5b9a\u9519\u8bef\u7684\u7981\u7528\u9009\u9879\uff08\u7c7b\u4f3c\u4e8e lint\uff09
# pytype: disable=attribute-error\n
"},{"location":"standard/style_rules/#3198","title":"3.19.8 \u6807\u6ce8\u53d8\u91cf","text":"\u8d4b\u503c\u6807\u6ce8
\u5982\u679c\u4e00\u4e2a\u5185\u90e8\u53d8\u91cf\u7684\u7c7b\u578b\u5f88\u96be\u6216\u65e0\u6cd5\u63a8\u65ad\u51fa\uff0c\u5219\u4f7f\u7528\u5e26\u6ce8\u91ca\u7684\u8d4b\u503c\u6307\u5b9a\u5b83\u7684\u7c7b\u578b - \u5728\u53d8\u91cf\u540d\u548c\u503c\u4e4b\u95f4\u4f7f\u7528 :
\u548c type
\uff08\u4e0e\u5177\u6709\u9ed8\u8ba4\u503c\u7684\u51fd\u6570\u53c2\u6570\u76f8\u540c\u7684\u505a\u6cd5\uff09\uff1a
a: Foo = SomeUndecoratedFunction()\n
\u7c7b\u578b\u6ce8\u91ca
\u867d\u7136\u4f60\u53ef\u80fd\u4f1a\u770b\u5230\u8fd9\u4e9b\u6ce8\u91ca\u5728\u4ee3\u7801\u5e93\u4e2d\u4ecd\u7136\u5b58\u5728\uff08\u5b83\u4eec\u5728 Python 3.6 \u4e4b\u524d\u662f\u5fc5\u8981\u7684\uff09\uff0c\u4f46\u4e0d\u8981\u518d\u5728\u884c\u672b\u6dfb\u52a0\u4efb\u4f55 # type: <type name>
\u7684\u6ce8\u91ca\u4e86\uff1a
a = SomeUndecoratedFunction() # type: Foo\n
"},{"location":"standard/style_rules/#3199-vs","title":"3.19.9 \u5143\u7ec4 vs \u5217\u8868","text":"\u7c7b\u578b\u5316\u5217\u8868\u53ea\u80fd\u5305\u542b\u5355\u4e00\u7c7b\u578b\u7684\u5bf9\u8c61\u3002\u7c7b\u578b\u5316\u5143\u7ec4\u53ef\u4ee5\u5177\u6709\u5355\u4e2a\u91cd\u590d\u7c7b\u578b\uff0c\u4e5f\u53ef\u4ee5\u5177\u6709\u4e00\u7ec4\u4e0d\u540c\u7c7b\u578b\u7684\u5143\u7d20\u3002\u540e\u8005\u901a\u5e38\u7528\u4f5c\u51fd\u6570\u7684\u8fd4\u56de\u7c7b\u578b\u3002
a: list[int] = [1, 2, 3]\nb: tuple[int, ...] = (1, 2, 3)\nc: tuple[int, str, float] = (1, \"2\", 3.5)\n
"},{"location":"standard/style_rules/#31910-typevars","title":"3.19.10 TypeVars","text":"Python \u7c7b\u578b\u7cfb\u7edf\u4e2d\u6709\u6cdb\u578b\uff0c\u5de5\u5382\u51fd\u6570 TypeVar
\u662f\u4f7f\u7528\u5b83\u4eec\u7684\u5e38\u7528\u65b9\u6cd5\u3002
\u4f8b\u5982\uff1a
from collections.abc import Callable\nfrom typing import ParamSpec, TypeVar\n_P = ParamSpec(\"_P\")\n_T = TypeVar(\"_T\")\n...\ndef next(l: list[_T]) -> _T:\nreturn l.pop()\ndef print_when_called(f: Callable[_P, _T]) -> Callable[_P, _T]:\ndef inner(*args: P.args, **kwargs: P.kwargs) -> R:\nprint('Function was called')\nreturn f(*args, **kwargs)\nreturn inner\n
TypeVar \u53ef\u4ee5\u88ab\u7ea6\u675f\uff1a
AddableType = TypeVar(\"AddableType\", int, float, str)\ndef add(a: AddableType, b: AddableType) -> AddableType:\nreturn a + b\n
typing
\u6a21\u5757\u4e2d\u4e00\u4e2a\u5e38\u89c1\u7684\u9884\u5b9a\u4e49\u7c7b\u578b\u53d8\u91cf\u662f AnyStr
\u3002\u53ef\u4ee5\u7528\u4e8e\u6807\u6ce8 bytes
\u6216 unicode
\uff0c\u4f46\u662f\u5fc5\u987b\u662f\u5728\u76f8\u540c\u7c7b\u578b\u4e2d\u4f7f\u7528\u3002
from typing import AnyStr\ndef check_length(x: AnyStr) -> AnyStr:\nif len(x) <= 42:\nreturn x\nraise ValueError()\n
\u7c7b\u578b\u53d8\u91cf\u5fc5\u987b\u5177\u6709\u63cf\u8ff0\u6027\u540d\u79f0\uff0c\u9664\u975e\u5b83\u6ee1\u8db3\u4ee5\u4e0b\u6240\u6709\u6761\u4ef6\uff1a
- \u5916\u90e8\u4e0d\u53ef\u89c1
- \u4e0d\u53d7\u7ea6\u675f
\u63a8\u8350
_T = TypeVar(\"_T\")\n_P = ParamSpec(\"_P\")\nAddableType = TypeVar(\"AddableType\", int, float, str)\nAnyFunction = TypeVar(\"AnyFunction\", bound=Callable)\n
\u4e0d\u63a8\u8350
T = TypeVar(\"T\")\nP = ParamSpec(\"P\")\n_T = TypeVar(\"_T\", int, float, str)\n_F = TypeVar(\"_F\", bound=Callable)\n
"},{"location":"standard/style_rules/#31911","title":"3.19.11 \u5b57\u7b26\u4e32\u7c7b\u578b","text":"\u4e0d\u8981\u5728\u65b0\u4ee3\u7801\u4e2d\u4f7f\u7528 typing.Text
\u3002\u5b83\u4ec5\u7528\u4e8e Python 2/3 \u517c\u5bb9\u6027\u3002
\u4f7f\u7528 str
\u4f5c\u4e3astring
/ text
\u6570\u636e\u3002\u5bf9\u4e8e\u5904\u7406\u4e8c\u8fdb\u5236\u6570\u636e\u7684\u4ee3\u7801\uff0c\u8bf7\u4f7f\u7528 bytes
\u3002
def deals_with_text_data(x: str) -> str:\n...\ndef deals_with_binary_data(x: bytes) -> bytes:\n...\n
\u5982\u679c\u51fd\u6570\u7684\u6240\u6709\u5b57\u7b26\u4e32\u7c7b\u578b\u59cb\u7ec8\u76f8\u540c\uff0c\u4f8b\u5982\uff0c\u5982\u679c\u8fd4\u56de\u7c7b\u578b\u4e0e\u4e0a\u9762\u4ee3\u7801\u4e2d\u7684\u53c2\u6570\u7c7b\u578b\u76f8\u540c\uff0c\u8bf7\u4f7f\u7528 AnyStr\u3002
"},{"location":"standard/style_rules/#31912","title":"3.19.12 \u7c7b\u578b\u5bfc\u5165","text":"\u5bf9\u4e8e\u7528\u4e8e\u652f\u6301\u9759\u6001\u5206\u6790\u548c\u7c7b\u578b\u68c0\u67e5\u7684 types
\u548c collections.abc
\u6a21\u5757\u4e2d\u7684\u7b26\u53f7\uff0c\u8bf7\u59cb\u7ec8\u5bfc\u5165\u7b26\u53f7\u672c\u8eab\u3002 \u8fd9\u4f7f\u5f97\u5e38\u89c1\u6ce8\u91ca\u66f4\u52a0\u7b80\u6d01\uff0c\u5e76\u7b26\u5408\u4e16\u754c\u5404\u5730\u4f7f\u7528\u7684\u6253\u5b57\u4e60\u60ef\u3002\u660e\u786e\u5141\u8bb8\u4ece typing
\u548c collections.abc
\u6a21\u5757\u5728\u4e00\u884c\u4e2d\u5bfc\u5165\u591a\u4e2a\u7279\u5b9a\u7c7b\u3002
from collections.abc import Mapping, Sequence\nfrom typing import Any, Generic\n
\u65e2\u7136\u8fd9\u79cd\u4ece typing
\u6a21\u5757\u5bfc\u5165\u7684\u65b9\u5f0f\u4f1a\u5c06\u5bfc\u5165\u9879\u6dfb\u52a0\u5230\u672c\u5730\u547d\u540d\u7a7a\u95f4\uff0c \u90a3\u4e48 typing
\u6216 collections.abc
\u4e2d\u7684\u4efb\u4f55\u540d\u79f0\u90fd\u5e94\u8be5\u7c7b\u4f3c\u4e8e\u5173\u952e\u5b57\uff0c\u800c\u4e14\u4e0d\u8981\u5728\u4f60\u7684 Python \u4ee3\u7801\u4e2d\u53bb\u5b9a\u4e49\uff08\u65e0\u8bba\u662f\u5426\u6709\u7c7b\u578b\uff09\u3002\u5982\u679c\u6a21\u5757\u4e2d\u7684\u7c7b\u578b\u548c\u73b0\u6709\u540d\u79f0\u4e4b\u95f4\u5b58\u5728\u51b2\u7a81\uff0c\u8bf7\u4f7f\u7528 import x as y
\u5bfc\u5165\u3002
from typing import Any as AnyType\n
\u63a8\u8350\u4f7f\u7528\u5185\u7f6e\u7c7b\u578b\u4f5c\u4e3a\u6ce8\u91ca\uff08\u5982\u679c\u53ef\u7528\uff09\u3002 Python \u901a\u8fc7 Python 3.9 \u4e2d\u5f15\u5165\u7684 PEP-585 \u652f\u6301\u4f7f\u7528\u53c2\u6570\u5bb9\u5668\u7c7b\u578b\u7684\u7c7b\u578b\u6ce8\u91ca\u3002
def generate_foo_scores(foo: set[str]) -> list[float]:\n...\n
"},{"location":"standard/style_rules/#31913","title":"3.19.13 \u6761\u4ef6\u5bfc\u5165","text":"\u4ec5\u5728\u7279\u6b8a\u60c5\u51b5\u4e0b\u624d\u4f7f\u7528\u6761\u4ef6\u5bfc\u5165\uff0c\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u5fc5\u987b\u5728\u8fd0\u884c\u65f6\u907f\u514d\u7c7b\u578b\u68c0\u67e5\u6240\u9700\u7684\u5176\u4ed6\u5bfc\u5165\u3002\u4e0d\u63a8\u8350\u8fd9\u79cd\u65b9\u5f0f\uff1b\u5e94\u8be5\u9996\u9009\u5176\u4ed6\u65b9\u6cd5\uff0c\u6bd4\u5982\u91cd\u6784\u4ee3\u7801\u4ee5\u5141\u8bb8\u9876\u7ea7\u5bfc\u5165\u3002
\u53ef\u4ee5\u5c06\u4ec5\u7528\u4e8e\u7c7b\u578b\u6807\u6ce8\u7684\u5bfc\u5165\u653e\u5728 if TYPE_CHECKING:
\u4ee3\u7801\u5757\u4e2d\u3002
- \u6709\u6761\u4ef6\u5bfc\u5165\u7684\u7c7b\u578b\u9700\u8981\u4f5c\u4e3a\u5b57\u7b26\u4e32\u5f15\u7528\uff0c\u4ee5\u4fbf\u6807\u6ce8\u8868\u8fbe\u5f0f\u5b9e\u9645\u8fd0\u884c\u65f6\u80fd\u5411\u524d\u517c\u5bb9 Python 3.6\u3002
- \u8fd9\u91cc\u53ea\u5e94\u8be5\u5b9a\u4e49\u7528\u4e8e\u7c7b\u578b\u6807\u6ce8\u7684\u5b9e\u4f53\uff1b\u5305\u62ec\u522b\u540d\u3002\u5426\u5219\u5c06\u4f1a\u6709\u4e00\u4e2a\u8fd0\u884c\u65f6\u9519\u8bef\uff0c\u56e0\u4e3a\u6a21\u5757\u5c06\u4e0d\u4f1a\u5728\u8fd0\u884c\u65f6\u5bfc\u5165\u3002
- \u6240\u6709\u6b63\u5e38\u5bfc\u5165\u540e\u7684\u4ee3\u7801\u5757\u5e94\u8be5\u662f\u6b63\u786e\u7684\u3002
- \u7c7b\u578b\u5bfc\u5165\u5217\u8868\u4e2d\u4e0d\u5e94\u8be5\u6709\u7a7a\u884c\u3002
- \u5c06\u6b64\u5217\u8868\u6309\u7167\u5e38\u89c4\u5bfc\u5165\u5217\u8868\u8fdb\u884c\u6392\u5e8f\u3002
import typing\nif typing.TYPE_CHECKING:\nimport sketch\ndef f(x: \"sketch.Sketch\"): ...\n
"},{"location":"standard/style_rules/#31914","title":"3.19.14 \u5faa\u73af\u4f9d\u8d56","text":"\u7531\u7c7b\u578b\u5f15\u8d77\u7684\u5faa\u73af\u4f9d\u8d56\u662f\u4e00\u79cd\u4ee3\u7801\u5473\u9053\u3002\u8fd9\u4e9b\u4ee3\u7801\u9700\u8981\u8fdb\u884c\u91cd\u6784\u3002\u867d\u7136\u5728\u6280\u672f\u4e0a\u53ef\u4ee5\u4fdd\u6301\u5faa\u73af\u4f9d\u8d56\u5173\u7cfb\uff0c\u4f46\u662f\u5404\u79cd\u6784\u5efa\u7cfb\u7edf\u4e0d\u5141\u8bb8\u8fd9\u6837\u505a\uff0c\u56e0\u4e3a\u6bcf\u4e2a\u6a21\u5757\u90fd\u5fc5\u987b\u4f9d\u8d56\u4e8e\u5176\u4ed6\u6a21\u5757\u3002
\u5c06\u5f15\u8d77\u5faa\u73af\u4f9d\u8d56\u5bfc\u5165\u7684\u6a21\u5757\u66ff\u6362\u4e3a Any
\u3002\u8bbe\u7f6e\u4e00\u4e2a\u6709\u610f\u4e49\u7684\u522b\u540d \uff0c\u5e76\u4f7f\u7528\u6b64\u6a21\u5757\u4e2d\u7684\u5b9e\u9645\u7c7b\u578b\u540d\u79f0\uff08Any \u7684\u4efb\u4f55\u5c5e\u6027\u90fd\u662f Any\uff09\u3002\u522b\u540d\u5b9a\u4e49\u5e94\u8be5\u4e0e\u6700\u540e\u5bfc\u5165\u5206\u5f00\u4e00\u884c\u3002
from typing import Any\nsome_mod = Any # some_mod.py imports this module.\n...\ndef my_method(self, var: \"some_mod.SomeType\") -> None:\n...\n
"},{"location":"standard/style_rules/#31915","title":"3.19.15 \u6cdb\u578b","text":"\u8fdb\u884c\u6807\u6ce8\u65f6\uff0c\u6700\u597d\u4e3a\u6cdb\u578b\u7c7b\u578b\u6307\u5b9a\u7c7b\u578b\u53c2\u6570\uff1b\u5426\u5219\uff0c\u6cdb\u578b\u53c2\u6570\u5c06\u88ab\u5047\u5b9a\u4e3a Any
\u3002
\u63a8\u8350
def get_names(employee_ids: Sequence[int]) -> Mapping[int, str]:\n...\n
\u4e0d\u63a8\u8350
# This is interpreted as get_names(employee_ids: Sequence[Any]) -> Mapping[Any, Any]\ndef get_names(employee_ids: Sequence) -> Mapping:\n...\n
\u5982\u679c\u6cdb\u578b\u7684\u6700\u4f73\u7c7b\u578b\u53c2\u6570\u662f Any
\uff0c\u8bf7\u4f7f\u7528\u663e\u5f0f\u8bbe\u7f6e\u3002\u4f46\u8bf7\u8bb0\u4f4f\uff0c\u5728\u8bb8\u591a\u60c5\u51b5\u4e0b TypeVar
\u53ef\u80fd\u66f4\u5408\u9002\u3002
\u4e0d\u63a8\u8350
def get_names(employee_ids: Sequence[Any]) -> Mapping[Any, str]:\n\"\"\"Returns a mapping from employee ID to employee name for given IDs.\"\"\"\n
\u63a8\u8350
_T = TypeVar('_T')\ndef get_names(employee_ids: Sequence[_T]) -> Mapping[_T, str]:\n\"\"\"Returns a mapping from employee ID to employee name for given IDs.\"\"\"\n
"}]}
\ No newline at end of file
diff --git a/sitemap.xml b/sitemap.xml
index a9fb6fe..c235648 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -2,147 +2,147 @@
https://pyloong.github.io/pythonic-project-guidelines/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/quick_start/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/datadevelop/quick_start/etl_develop/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/datadevelop/quick_start/initialization/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/datadevelop/quick_start/preparation/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/datadevelop/quick_start/release/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/datadevelop/quick_start/tests/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/configuration/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/exception/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/logging/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/plugin/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/signal_decouple/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/test/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/type_hint/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/project_management/code_lint/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/project_management/distribution/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/project_management/document/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/project_management/project_structure/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/tutorial/develop/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/tutorial/init_project/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/tutorial/publish/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/tutorial/test/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/guidelines/tutorial/tutorial/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/introduction/ides/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/introduction/install/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/introduction/virtualenv/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/practices/web/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/standard/language_rules/
- 2023-09-28
+ 2023-12-28
daily
https://pyloong.github.io/pythonic-project-guidelines/standard/style_rules/
- 2023-09-28
+ 2023-12-28
daily
\ No newline at end of file
diff --git a/sitemap.xml.gz b/sitemap.xml.gz
index a31fc4d..dc8c762 100644
Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ