From 181bc195a5eedbcfc34d2d6cc86e68b9bc022ca0 Mon Sep 17 00:00:00 2001 From: whg517 Date: Thu, 3 Aug 2023 02:26:30 +0000 Subject: [PATCH] deploy: 32305c43f1223356c2ccd2771971049ca627ff4b --- 404.html | 2 +- .../quick_start/etl_develop/index.html | 2 +- .../quick_start/initialization/index.html | 2 +- .../quick_start/preparation/index.html | 2 +- datadevelop/quick_start/release/index.html | 2 +- datadevelop/quick_start/tests/index.html | 2 +- guidelines/advanced/configuration/index.html | 2 +- guidelines/advanced/exception/index.html | 2 +- guidelines/advanced/logging/index.html | 2 +- guidelines/advanced/plugin/index.html | 2 +- .../advanced/signal_decouple/index.html | 2 +- guidelines/advanced/test/index.html | 2 +- guidelines/advanced/type_hint/index.html | 2 +- .../project_management/code_lint/index.html | 2 +- .../distribution/index.html | 2 +- .../project_management/document/index.html | 2 +- .../project_structure/index.html | 2 +- guidelines/tutorial/develop/index.html | 2 +- guidelines/tutorial/init_project/index.html | 2 +- guidelines/tutorial/publish/index.html | 2 +- guidelines/tutorial/test/index.html | 2 +- guidelines/tutorial/tutorial/index.html | 2 +- index.html | 2 +- introduction/ides/index.html | 2 +- introduction/install/index.html | 2 +- introduction/virtualenv/index.html | 2 +- practices/web/index.html | 8 +-- quick_start/index.html | 2 +- search/search_index.json | 2 +- sitemap.xml | 58 +++++++++--------- sitemap.xml.gz | Bin 494 -> 495 bytes standard/language_rules/index.html | 2 +- standard/style_rules/index.html | 2 +- 33 files changed, 63 insertions(+), 63 deletions(-) diff --git a/404.html b/404.html index 530c173..758d625 100644 --- a/404.html +++ b/404.html @@ -11,7 +11,7 @@ - + diff --git a/datadevelop/quick_start/etl_develop/index.html b/datadevelop/quick_start/etl_develop/index.html index 625493e..46e4e2e 100644 --- a/datadevelop/quick_start/etl_develop/index.html +++ b/datadevelop/quick_start/etl_develop/index.html @@ -17,7 +17,7 @@ - + diff --git a/datadevelop/quick_start/initialization/index.html b/datadevelop/quick_start/initialization/index.html index 60fb349..b934976 100644 --- a/datadevelop/quick_start/initialization/index.html +++ b/datadevelop/quick_start/initialization/index.html @@ -17,7 +17,7 @@ - + diff --git a/datadevelop/quick_start/preparation/index.html b/datadevelop/quick_start/preparation/index.html index d3a6232..7493388 100644 --- a/datadevelop/quick_start/preparation/index.html +++ b/datadevelop/quick_start/preparation/index.html @@ -17,7 +17,7 @@ - + diff --git a/datadevelop/quick_start/release/index.html b/datadevelop/quick_start/release/index.html index b06e20e..0b99b99 100644 --- a/datadevelop/quick_start/release/index.html +++ b/datadevelop/quick_start/release/index.html @@ -15,7 +15,7 @@ - + diff --git a/datadevelop/quick_start/tests/index.html b/datadevelop/quick_start/tests/index.html index ff392db..b1ea45c 100644 --- a/datadevelop/quick_start/tests/index.html +++ b/datadevelop/quick_start/tests/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/advanced/configuration/index.html b/guidelines/advanced/configuration/index.html index 207040b..f03e120 100644 --- a/guidelines/advanced/configuration/index.html +++ b/guidelines/advanced/configuration/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/advanced/exception/index.html b/guidelines/advanced/exception/index.html index f64e0df..b22fa50 100644 --- a/guidelines/advanced/exception/index.html +++ b/guidelines/advanced/exception/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/advanced/logging/index.html b/guidelines/advanced/logging/index.html index 0ea9ff7..6b50d2b 100644 --- a/guidelines/advanced/logging/index.html +++ b/guidelines/advanced/logging/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/advanced/plugin/index.html b/guidelines/advanced/plugin/index.html index dcbec22..45dea5f 100644 --- a/guidelines/advanced/plugin/index.html +++ b/guidelines/advanced/plugin/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/advanced/signal_decouple/index.html b/guidelines/advanced/signal_decouple/index.html index 8695101..efbe1cb 100644 --- a/guidelines/advanced/signal_decouple/index.html +++ b/guidelines/advanced/signal_decouple/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/advanced/test/index.html b/guidelines/advanced/test/index.html index ab749a2..d1815cc 100644 --- a/guidelines/advanced/test/index.html +++ b/guidelines/advanced/test/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/advanced/type_hint/index.html b/guidelines/advanced/type_hint/index.html index 1d8417d..811c5bf 100644 --- a/guidelines/advanced/type_hint/index.html +++ b/guidelines/advanced/type_hint/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/project_management/code_lint/index.html b/guidelines/project_management/code_lint/index.html index 96bf9f4..618efd2 100644 --- a/guidelines/project_management/code_lint/index.html +++ b/guidelines/project_management/code_lint/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/project_management/distribution/index.html b/guidelines/project_management/distribution/index.html index 1525434..3cf3c78 100644 --- a/guidelines/project_management/distribution/index.html +++ b/guidelines/project_management/distribution/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/project_management/document/index.html b/guidelines/project_management/document/index.html index 757a94e..9c4ec4f 100644 --- a/guidelines/project_management/document/index.html +++ b/guidelines/project_management/document/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/project_management/project_structure/index.html b/guidelines/project_management/project_structure/index.html index bb0be1d..8f41bae 100644 --- a/guidelines/project_management/project_structure/index.html +++ b/guidelines/project_management/project_structure/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/tutorial/develop/index.html b/guidelines/tutorial/develop/index.html index b5f2544..5f12556 100644 --- a/guidelines/tutorial/develop/index.html +++ b/guidelines/tutorial/develop/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/tutorial/init_project/index.html b/guidelines/tutorial/init_project/index.html index 1a7939b..a1d437c 100644 --- a/guidelines/tutorial/init_project/index.html +++ b/guidelines/tutorial/init_project/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/tutorial/publish/index.html b/guidelines/tutorial/publish/index.html index cddafbc..6d409ae 100644 --- a/guidelines/tutorial/publish/index.html +++ b/guidelines/tutorial/publish/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/tutorial/test/index.html b/guidelines/tutorial/test/index.html index f64bed1..fd1f573 100644 --- a/guidelines/tutorial/test/index.html +++ b/guidelines/tutorial/test/index.html @@ -17,7 +17,7 @@ - + diff --git a/guidelines/tutorial/tutorial/index.html b/guidelines/tutorial/tutorial/index.html index 4bb8e49..8fc7551 100644 --- a/guidelines/tutorial/tutorial/index.html +++ b/guidelines/tutorial/tutorial/index.html @@ -17,7 +17,7 @@ - + diff --git a/index.html b/index.html index 4fc1839..e695810 100644 --- a/index.html +++ b/index.html @@ -15,7 +15,7 @@ - + diff --git a/introduction/ides/index.html b/introduction/ides/index.html index 0830983..e6d84a2 100644 --- a/introduction/ides/index.html +++ b/introduction/ides/index.html @@ -17,7 +17,7 @@ - + diff --git a/introduction/install/index.html b/introduction/install/index.html index bb3bff0..9585fb7 100644 --- a/introduction/install/index.html +++ b/introduction/install/index.html @@ -17,7 +17,7 @@ - + diff --git a/introduction/virtualenv/index.html b/introduction/virtualenv/index.html index 643eba9..54fd42c 100644 --- a/introduction/virtualenv/index.html +++ b/introduction/virtualenv/index.html @@ -17,7 +17,7 @@ - + diff --git a/practices/web/index.html b/practices/web/index.html index 5ab91c8..44b95a6 100644 --- a/practices/web/index.html +++ b/practices/web/index.html @@ -17,7 +17,7 @@ - + @@ -1177,7 +1177,7 @@
  • - 4.3 测试试图层 + 4.3 测试视图层
  • @@ -1647,7 +1647,7 @@
  • - 4.3 测试试图层 + 4.3 测试视图层
  • @@ -3052,7 +3052,7 @@

    4.2 测试服务层

    git add .
     git commit -m "test: Add service test."
     
    -

    4.3 测试试图层

    +

    4.3 测试视图层

    编辑 tests/conftest.py ,创建测试配置:

    from fastapi.testclient import TestClient
     
    diff --git a/quick_start/index.html b/quick_start/index.html
    index 0ca5cb5..0a84afa 100644
    --- a/quick_start/index.html
    +++ b/quick_start/index.html
    @@ -17,7 +17,7 @@
             
           
           
    -      
    +      
         
         
           
    diff --git a/search/search_index.json b/search/search_index.json
    index 30dac22..646fd53 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.csvCSV\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":"
    1. \u6570\u636e\u8d44\u6e90\u5e94\u8be5\u88ab\u9a8c\u8bc1\uff0c\u6765\u786e\u4fdd\u6b63\u786e\u7684\u6570\u636e\u88ab\u52a0\u8f7d\u8fdb\u7cfb\u7edf

    2. \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":"
    1. \u6d4b\u8bd5\u8f6c\u6362\u89c4\u5219\u53ef\u6b63\u786e\u6267\u884c

    2. \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":"
    1. \u68c0\u67e5\u8f6c\u6362(Transformation)\u89c4\u5219\u88ab\u6b63\u786e\u5e94\u7528

    2. \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\u610f\u5411\u6216\u591a\u60f3\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\u91d1\u5eb8\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
    1. \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\u8bd5\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
    1. https://www.python.org/doc/sunset-python-2/\u00a0\u21a9

    2. \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

    1. Future \u5bfc\u5165\u8bed\u53e5\uff1a

      from __future__ import annotations\n

    \u8bf7\u53c2\u9605\u4e0a\u9762\u7684\u66f4\u591a\u4fe1\u606f\u3002

    1. \u6807\u51c6\u5e93\u5bfc\u5165\uff1a

      import sys\n
    2. \u7b2c\u4e09\u65b9\u6a21\u5757\u6216\u5305\u5bfc\u5165\uff1a

      import tensorflow as tf\n
    3. \u4ee3\u7801\u5e93\u5b50\u5305\u5bfc\u5165\uff1a

      from otherproject.ai import mind\n
    4. \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\uff1aid_to_name_dict\uff09
    "},{"location":"standard/style_rules/#3162","title":"3.16.2 \u547d\u540d\u7ea6\u5b9a","text":"
    • \u6240\u8c13\u201c\u5185\u90e8\uff08Internal\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\uff0ctest_<\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.csvCSV\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":"
    1. \u6570\u636e\u8d44\u6e90\u5e94\u8be5\u88ab\u9a8c\u8bc1\uff0c\u6765\u786e\u4fdd\u6b63\u786e\u7684\u6570\u636e\u88ab\u52a0\u8f7d\u8fdb\u7cfb\u7edf

    2. \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":"
    1. \u6d4b\u8bd5\u8f6c\u6362\u89c4\u5219\u53ef\u6b63\u786e\u6267\u884c

    2. \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":"
    1. \u68c0\u67e5\u8f6c\u6362(Transformation)\u89c4\u5219\u88ab\u6b63\u786e\u5e94\u7528

    2. \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\u610f\u5411\u6216\u591a\u60f3\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\u91d1\u5eb8\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
    1. \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
    1. https://www.python.org/doc/sunset-python-2/\u00a0\u21a9

    2. \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

    1. Future \u5bfc\u5165\u8bed\u53e5\uff1a

      from __future__ import annotations\n

    \u8bf7\u53c2\u9605\u4e0a\u9762\u7684\u66f4\u591a\u4fe1\u606f\u3002

    1. \u6807\u51c6\u5e93\u5bfc\u5165\uff1a

      import sys\n
    2. \u7b2c\u4e09\u65b9\u6a21\u5757\u6216\u5305\u5bfc\u5165\uff1a

      import tensorflow as tf\n
    3. \u4ee3\u7801\u5e93\u5b50\u5305\u5bfc\u5165\uff1a

      from otherproject.ai import mind\n
    4. \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\uff1aid_to_name_dict\uff09
    "},{"location":"standard/style_rules/#3162","title":"3.16.2 \u547d\u540d\u7ea6\u5b9a","text":"
    • \u6240\u8c13\u201c\u5185\u90e8\uff08Internal\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\uff0ctest_<\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 6cc54c9..385c131 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,147 +2,147 @@ https://pyloong.github.io/pythonic-project-guidelines/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/quick_start/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/datadevelop/quick_start/etl_develop/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/datadevelop/quick_start/initialization/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/datadevelop/quick_start/preparation/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/datadevelop/quick_start/release/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/datadevelop/quick_start/tests/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/configuration/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/exception/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/logging/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/plugin/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/signal_decouple/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/test/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/advanced/type_hint/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/project_management/code_lint/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/project_management/distribution/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/project_management/document/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/project_management/project_structure/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/tutorial/develop/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/tutorial/init_project/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/tutorial/publish/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/tutorial/test/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/guidelines/tutorial/tutorial/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/introduction/ides/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/introduction/install/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/introduction/virtualenv/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/practices/web/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/standard/language_rules/ - 2023-07-07 + 2023-08-03 daily https://pyloong.github.io/pythonic-project-guidelines/standard/style_rules/ - 2023-07-07 + 2023-08-03 daily \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index be2cc94ad9a0797ee54f427fb27beed01f981ddf..2414c59cbd6c614de422393be30cd3f52b2c977f 100644 GIT binary patch literal 495 zcmV(KpURy(yp0^!Z#rfheMZ;V3r|uJ{pnK#5YNtMX~L^HpAV9$Liq2 zI*s0XHLP9|y9r0@y)&Ibn)1Fw&k9Z6p=v|)KS5DzGHS5sAcC`G`?`2Kn#8ea)zTjfrHW(n8mgq=p5o4}W0* z+3})Rf`kqUrNwtid=6@%3K4>gRUt*NAQRMrLy)Mrly+rGy-24(3+3Glnq1~)y9ESq z3PLR~)*HhGy#|zEGo#c%lF&H0R#N42uuucin$D`=I0hIebS1-_pN_M1(!dN1!@u<~ z+Ul@0CW^Svg=gzqWM1WyQ5Dri;#JFvax6LZQ6?X`a}b3#M7L1ZW@tRmt$`=xy~Q-u zfKqsY{ljrF3S;5g8NKGLwU!!IQ`rEHyHH#9A}kmwOABr(4I@O!2#T!(>`M!tH;&UX la%;&S%Us#Vm~DB-;S(d#(f&7v(qnN7LV|cq|S+ zq~qwUSN-B8v72zP-aFG8q%rSW^sJ%LJ5=is{R=2sn~WMPI!K}u%eR-VWTMII{%&)3 zzutV~i%m|t3o|uJXW%))TdyVBLnkgYA2Ev0Apc#sZyD9=kVr-S(k?`)XXzAZp}c!WlgnIe*MQ(% zL8t}BdSket*MJgiW|SI85*jC$N~(Mg7HU9R(peQ8hXBKfu4I_g+hLMU8km4#_*f5v ztqx0LqKLa(c(%Sq=2bolRZ*Q)JZo7|jwPo)%H$(=4x-S8=o-q>3=JpS8hAq9SxjRM zD1{f;zZ_?wFcz+z(QD3HOQ~UPDjUFY7i!C%gc&1cX~E5