Skip to content

Latest commit

 

History

History
1312 lines (919 loc) · 58.1 KB

File metadata and controls

1312 lines (919 loc) · 58.1 KB

一、让我们开始我们的自动化之旅

在本章中,我们将介绍以下配方:

  • 创建虚拟环境
  • 安装第三方软件包
  • 使用格式化值创建字符串
  • 操纵字符串
  • 从结构化字符串中提取数据
  • 使用第三方工具解析
  • 引入正则表达式
  • 深入研究正则表达式
  • 添加命令行参数

介绍

本章的目的是通过本书阐述一些有用的基本技巧。其主要思想是能够创建一个良好的 Python 环境来运行接下来的自动化任务,并能够将文本输入解析为结构化数据。

Python 在默认情况下安装了大量工具,但它也使安装第三方工具变得容易,这些工具可以简化处理文本时的常见操作。在本章中,我们将看到如何从外部源导入模块,并使用它们充分利用 Python 的潜力。

在任何自动化任务中,构造输入数据的能力都是至关重要的。我们将在本书中处理的大多数数据将来自未格式化的源,如网页或文本文件。正如古老的计算机格言所说,垃圾输入,垃圾输出,使得输入的消毒成为一项非常重要的任务。

创建虚拟环境

使用 Python 的第一步是明确定义工作环境。这有助于从操作系统解释器和环境中分离,并正确定义将要使用的依赖项。不这样做往往会产生混乱的局面。记住,显式比隐式好!

这在两种情况下尤其重要:

  • 在同一台计算机上处理多个项目时,因为它们可能具有不同的依赖项,在某一点上会发生冲突。例如,同一模块的两个版本不能安装在同一环境中。
  • 例如,在处理将在不同计算机上使用的项目时,在个人笔记本电脑中开发一些代码,这些代码最终将在远程服务器上运行。

A common joke among developers is responding to a bug with it runs on my machine, meaning that it appears to work on their laptop, but not on the production servers. Although a huge number of factors can produce this error, a good practice is to produce an automatically replicable environment, reducing uncertainty over what dependencies are really being used.

这很容易通过virtualenv模块实现,该模块设置了一个虚拟环境,因此安装的依赖项不会与机器上安装的 Python 版本共享。

In Python3, the virtualenv tool is installed automatically, which was not the case in previous versions.

准备

要创建新的虚拟环境,请执行以下操作:

  1. 转到包含项目的主目录。
  2. 键入以下命令:
$ python3 -m venv .venv

This creates a subdirectory called .venv that contains the virtual environment. The directory containing the virtual environment can be located anywhere. Keeping it on the same root keeps it handy, and adding a dot in front of it avoids it being displayed when running ls or other commands.

  1. 在激活虚拟环境之前,请检查pip中安装的版本。这取决于您的操作系统,例如,MacOS High Sierra 10.13.4 的 9.0.3。稍后将进行升级。还要检查引用的 Python 解释器,它将是主操作系统:
$ pip --version
pip 9.0.3 from /usr/local/lib/python3.6/site-packages/pip (python 3.6)
$ which python3
/usr/local/bin/python3

现在,您的虚拟环境已准备就绪。

怎么做。。。

  1. 通过运行以下命令激活虚拟环境:
$ source .venv/bin/activate

您会注意到提示将显示(.venv),表明虚拟环境处于活动状态。

  1. 请注意,使用的 Python 解释器是虚拟环境中的解释器,而不是准备步骤 3 中的通用操作系统解释器。正在检查虚拟环境中的位置:
(.venv) $ which python
/root_dir/.venv/bin/python
(.venv) $ which pip
/root_dir/.venv/bin/pip
  1. 升级pip版本并检查版本:
(.venv) $ pip install --upgrade pip
...
Successfully installed pip-10.0.1
(.venv) $ pip --version
pip 10.0.1 from /root_dir/.venv/lib/python3.6/site-packages/pip (python 3.6)
  1. 离开环境,运行pip检查版本,版本将返回之前的环境。在激活虚拟环境之前,检查pip版本和 Python 解释器以显示它们,如准备部分的步骤 3 所示。请注意,它们是不同的 pip 版本!
(.venv) $ deactivate 
$ which python3
/usr/local/bin/python3
$ pip --version
pip 9.0.3 from /usr/local/lib/python3.6/site-packages/pip (python 3.6)

它是如何工作的。。。

请注意,在虚拟环境中,您可以使用python而不是python3,尽管python3也可用。这将使用环境中定义的 Python 解释器。

In some systems like Linux, it's possible that you need to use python3.7 instead of python3. Verify that the Python interpreter you're using is 3.7 or higher.

在虚拟环境中,*如何操作的第 3 步。。。*部分安装pip的最新版本,不影响外部安装。

虚拟环境包含.venv目录中的所有 Python 数据,activate脚本将所有环境变量指向该目录。最棒的是,它可以很容易地被删除和重新创建,消除了在独立沙箱中进行实验的恐惧。

Remember that the directory name is displayed in the prompt. If you need to differentiate the environment, use a descriptive directory name, such as .my_automate_recipe, or use the --prompt option.

还有更多。。。

要删除虚拟环境,请将其停用并删除目录:

(.venv) $ deactivate
$ rm -rf .venv

venv模块有更多选项,可通过-h标志显示:

$ python3 -m venv -h
usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear]
 [--upgrade] [--without-pip] [--prompt PROMPT]
 ENV_DIR [ENV_DIR ...]
Creates virtual Python environments in one or more target directories.
positional arguments:
 ENV_DIR A directory to create the environment in.

optional arguments:
 -h, --help show this help message and exit
 --system-site-packages
 Give the virtual environment access to the system
 site-packages dir.
 --symlinks Try to use symlinks rather than copies, when symlinks
 are not the default for the platform.
 --copies Try to use copies rather than symlinks, even when
 symlinks are the default for the platform.
 --clear Delete the contents of the environment directory if it
 already exists, before environment creation.
 --upgrade Upgrade the environment directory to use this version
 of Python, assuming Python has been upgraded in-place.
 --without-pip Skips installing or upgrading pip in the virtual
 environment (pip is bootstrapped by default)
 --prompt PROMPT Provides an alternative prompt prefix for this
 environment.
Once an environment has been created, you may wish to activate it, for example, by
sourcing an activate script in its bin directory.

处理虚拟环境的一种便捷方法是使用virtualenvwrapper模块,尤其是当您经常需要在虚拟环境之间进行交换时:

  1. 要安装它,请运行以下命令:
$ pip install virtualenvwrapper
  1. 然后,将以下变量添加到工作表启动脚本中,这些变量通常为.bashrc.bash_profile。虚拟环境将安装在WORKON_HOME目录下,而不是与项目相同的目录下,如前所示:
export WORKON_HOME=~/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh

寻找启动脚本或打开新终端将允许您创建新的虚拟环境:

$ mkvirtualenv automation_cookbook
...
Installing setuptools, pip, wheel...done.
(automation_cookbook) $ deactivate
$ workon automation_cookbook
(automation_cookbook) $

有关更多信息,请查看virtualenvwrapper的文档,网址为:https://virtualenvwrapper.readthedocs.io/en/latest/index.html

Hitting the Tab key after workon autocompletes with the available environments. 

另见

  • 安装第三方软件包配方
  • 使用第三方工具解析配方

安装第三方软件包

Python 最强大的功能之一是能够使用令人印象深刻的第三方软件包目录,这些软件包涵盖了不同领域的大量领域,从专门用于执行数字运算、机器学习和网络通信的模块,到命令行便利工具、数据库访问、图像处理,还有更多!

其中大部分都可以在官方 Python 包索引(中找到 https://pypi.org/ ),有超过 130000 个软件包可供使用。在本书中,我们将安装其中的一些工具,一般来说,当试图解决问题时,花一点时间研究外部工具是值得的。很有可能是其他人创建了一个工具来解决全部或至少部分问题。

与查找和安装软件包一样重要的是跟踪正在使用的软件包。这对可复制性有很大帮助,这意味着在任何情况下都可以从头开始整个环境。

准备

首先要找到一个在我们的项目中有用的包。

一个很好的例子是requests,这是一个处理 HTTP 请求的模块,以其简单直观的界面和出色的文档而闻名。查看文档,可以在这里找到:http://docs.python-requests.org/en/master/

在处理 HTTP 连接时,我们将在本书中通篇使用requests

下一步是选择要使用的版本。在这种情况下,最新版本(撰写本文时为 2.18.4)将是完美的。如果未指定模块的版本,默认情况下,它将安装最新版本,这可能导致不同环境中的不一致

我们还将使用很棒的delorean模块进行时间处理(版本 1.0.0http://delorean.readthedocs.io/en/latest/

怎么做。。。

  1. 在我们的主目录中创建一个requirements.txt文件,该文件将指定我们项目的所有要求。让我们从deloreanrequests开始:
delorean==1.0.0
requests==2.18.4
  1. 使用pip命令安装所有要求:
$ pip install -r requirements.txt
...
Successfully installed babel-2.5.3 certifi-2018.4.16 chardet-3.0.4 delorean-1.0.0 humanize-0.5.1 idna-2.6 python-dateutil-2.7.2 pytz-2018.4 requests-2.18.4 six-1.11.0 tzlocal-1.5.1 urllib3-1.22
  1. 现在,您可以在使用虚拟环境时使用这两个模块:
$ python
Python 3.6.5 (default, Mar 30 2018, 06:41:53)
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import delorean
>>> import requests

它是如何工作的。。。

requirements.txt文件指定了模块和版本,pippypi.org上进行搜索。

请注意,从头创建一个新的虚拟环境并运行以下操作将完全重新创建您的环境,这使得可复制性非常简单:

$ pip install -r requirements.txt

注意*中的第 2 步如何做。。。*部分自动安装其他依赖模块,如urllib3

还有更多。。。

如果由于新版本可用而需要将任何模块更改为其他版本,请使用需求进行更改,然后再次运行install命令:

$ pip install -r requirements.txt

这也适用于需要包括新模块的情况。

在任何时候,freeze命令可用于显示所有已安装的模块。freeze以与requirements.txt兼容的格式返回模块,从而可以使用我们当前的环境生成文件:

$ pip freeze > requirements.txt

这将包括依赖项,因此预计文件中会有更多的模块。

Finding great third-party modules is not easy sometimes. Searching for specific functionality can work well, but sometimes there are great modules that are a surprise because they do things you never thought of. A great curated list is Awesome Python (https://awesome-python.com/), which covers a lot of great tools for common Python use cases, such as cryptography, database access, date and time handling, and  so on.

在某些情况下,安装软件包可能需要其他工具,例如编译器或支持某些功能的特定库(例如,特定的数据库驱动程序)。如果是这种情况,文档通常会解释依赖关系。

另见

  • 创建虚拟环境配方
  • 使用第三方工具解析配方

使用格式化值创建字符串

处理创建文本和文档时的一个基本能力是能够正确地将值格式化为结构化字符串。Python 在显示良好的默认值方面非常聪明,例如正确地呈现一个数字,但是有很多选项和可能性。

我们将以表格为例讨论创建格式化文本时的一些常见选项。

准备

Python 中格式化字符串的主要工具是format方法。它与定义的迷你语言配合使用,以这种方式呈现变量:

result = template.format(*parameters)

template是一个基于 mini 语言进行解释的字符串。最简单的方法是,用参数替换花括号之间的值。以下是几个例子:

>>> 'Put the value of the string here: {}'.format('STRING')
"Put the value of the string here: STRING"
>>> 'It can be any type ({}) and more than one ({})'.format(1.23, str)
"It can be any type (1.23) and more than one (<class 'str'>)"
>> 'Specify the order: {1}, {0}'.format('first', 'second')
'Specify the order: second, first'
>>> 'Or name parameters: {first}, {second}'.format(second='SECOND', first='FIRST')
'Or name parameters: FIRST, SECOND'

在 95%的情况下,这种格式将是所有需要的;保持事情简单是很棒的!但对于复杂的时间,例如自动对齐字符串和创建美观的文本表时,迷你语言格式有更多的选项。

怎么做。。。

  1. 编写以下脚本recipe_format_strings_step1.py,打印对齐的表格:
# INPUT DATA
data = [
    (1000, 10),
    (2000, 17),
    (2500, 170),
    (2500, -170),
]
# Print the header for reference
print('REVENUE | PROFIT | PERCENT')

# This template aligns and displays the data in the proper format
TEMPLATE = '{revenue:>7,} | {profit:>+7} | {percent:>7.2%}'

# Print the data rows
for revenue, profit in data:
    row = TEMPLATE.format(revenue=revenue, profit=profit, percent=profit / revenue)
    print(row)
  1. 运行它以显示以下对齐的表格。注意,PERCENT正确显示为百分比:
REVENUE | PROFIT | PERCENT
 1,000 |    +10 |   1.00%
 2,000 |    +17 |   0.85%
 2,500 |   +170 |   6.80%
 2,500 |   -170 |  -6.80%

它是如何工作的。。。

TEMPLATE常量包含三列,每列都正确命名(REVENUEPROFITPERCENT。这使得在格式调用上应用模板更加明确和直接。

在参数名称之后,有一个冒号分隔格式定义。请注意,所有选项都位于花括号内。在所有列中,格式规范将宽度设置为七个字符,并将值与>符号向右对齐:

  • Revenue 添加了一个带有,符号的千位分隔符—[{revenue:>7,}]
  • 利润为正值增加一个+符号。自动添加负片的--[{profit:>+7}]
  • 百分比显示百分比值,精度为小数点后两位-[{percent:>7.2%}]。这是通过 0.2(精度)完成的,并为百分比添加一个%符号。

还有更多。。。

您可能还看到了使用%运算符的可用 Python 格式。虽然它适用于简单的格式化,但它不如格式化的迷你语言灵活,因此不建议使用它。

自 Python 3.6 以来,一个伟大的新特性是使用 f 字符串,它使用定义的变量以如下方式执行格式化操作:

>>> param1 = 'first'
>>> param2 = 'second'
>>> f'Parameters {param1}:{param2}'
'Parameters first:second'

这简化了许多代码,并允许我们创建非常具有描述性和可读性的代码。

Be careful when using f-strings to ensure that the string is replaced at the proper time. A common problem is that the variable defined to be rendered is not yet defined. For example, TEMPLATE, defined previously, won't be defined as an f-string, as revenue and the rest of the parameters are not available at that point. 

如果你需要写一个花括号,你需要重复两次。请注意,每个副本将显示为一个大括号,加上一个大括号用于替换值,总共有三个括号:

>> value = 'VALUE'
>>> f'This is the value, in curly brackets {{{value}}}'
'This is the value, in curly brackets {VALUE}'

这使我们能够创建生成模板的元模板。在某些情况下,这将是有用的,但尽量限制它们的使用,因为它们会很快变得复杂,产生难以阅读的代码。

Python 格式规范迷你语言比这里显示的选项更多

As the language tries to be quite concise, sometimes it can be difficult to determine the position of the symbols. You may sometimes ask yourself questions like—Is the + symbol before or after than the width parameters.? Read the documentation with care and remember to always include a colon before the format specification.

请查看 Python 网站(上的完整文档和示例 https://docs.python.org/3/library/string.html#formatspec )。

另见

  • 模板报告第 5 章中的配方,生成奇妙的报告
  • 操纵弦的配方

操纵字符串

处理文本时的一个基本能力是能够正确处理该文本。这意味着能够连接它,将它拆分为常规块,或者将其更改为大写或小写。稍后我们将讨论更高级的文本解析和分隔方法,但在许多情况下,将段落划分为行、句子甚至单词都很有用。其他时候,单词必须删除一些字符或替换为规范版本,才能将其与确定的值进行比较。

准备

我们将定义一个基本文本,将其转换为主要组件,然后重建它。例如,报告需要转换为新格式,以便通过电子邮件发送。

本例中使用的输入格式如下:

    AFTER THE CLOSE OF THE SECOND QUARTER, OUR COMPANY, CASTAÑACORP
    HAS ACHIEVED A GROWTH IN THE REVENUE OF 7.47%. THIS IS IN LINE
    WITH THE OBJECTIVES FOR THE YEAR. THE MAIN DRIVER OF THE SALES HAS BEEN
    THE NEW PACKAGE DESIGNED UNDER THE SUPERVISION OF OUR MARKETING DEPARTMENT.
    OUR EXPENSES HAS BEEN CONTAINED, INCREASING ONLY BY 0.7%, THOUGH THE BOARD
    CONSIDERS IT NEEDS TO BE FURTHER REDUCED. THE EVALUATION IS SATISFACTORY
    AND THE FORECAST FOR THE NEXT QUARTER IS OPTIMISTIC. THE BOARD EXPECTS
    AN INCREASE IN PROFIT OF AT LEAST 2 MILLION DOLLARS.

我们需要修订文本,以消除对数字的任何引用。它需要通过在每个句点后添加新行来正确格式化,用 80 个字符对齐,并出于兼容性原因转换为 ASCII。

文本将存储在解释器的INPUT_TEXT变量中。

怎么做。。。

  1. 输入文本后,将其拆分为单个单词:
>>> INPUT_TEXT = '''
...     AFTER THE CLOSE OF THE SECOND QUARTER, OUR COMPANY, CASTAÑACORP
...     HAS ACHIEVED A GROWTH IN THE REVENUE OF 7.47%. THIS IS IN LINE
...
'''
>>> words = INPUT_TEXT.split()
  1. 'X'字符替换任何数字:
>>> redacted = [''.join('X' if w.isdigit() else w for w in word) for word in words]
  1. 将文本转换为纯 ASCII(注意,公司名称包含一个字母,ñ,它不是 ASCII):
>>> ascii_text = [word.encode('ascii', errors='replace').decode('ascii')
...               for word in redacted]
  1. 将单词分成 80 个字符行:
>>> newlines = [word + '\n' if word.endswith('.') else word for word in ascii_text]
>>> LINE_SIZE = 80
>>> lines = []
>>> line = ''
>>> for word in newlines:
...     if line.endswith('\n') or len(line) + len(word) + 1 > LINE_SIZE:
...         lines.append(line)
...         line = ''
...     line = line + ' ' + word
  1. 将所有行格式化为标题,并将其作为单个文本连接:
>>> lines = [line.title() for line in lines]
>>> result = '\n'.join(lines)
  1. 打印结果:
>>> print(result)
 After The Close Of The Second Quarter, Our Company, Casta?Acorp Has Achieved A
 Growth In The Revenue Of X.Xx%.

 This Is In Line With The Objectives For The Year.

 The Main Driver Of The Sales Has Been The New Package Designed Under The
 Supervision Of Our Marketing Department.

 Our Expenses Has Been Contained, Increasing Only By X.X%, Though The Board
 Considers It Needs To Be Further Reduced.

 The Evaluation Is Satisfactory And The Forecast For The Next Quarter Is
 Optimistic.

它是如何工作的。。。

每个步骤都执行文本的特定转换:

  • 第一种方法在默认分隔符、空格和新行上拆分文本。这会将其拆分为单个单词,没有行或多个空格用于分隔
  • 为了替换数字,我们检查每个单词的每个字符。对于每个数字,如果它是一个数字,则返回一个'X'。这是通过两个列表理解完成的,一个在列表上运行,另一个在每个单词上运行,仅当有数字-['X' if w.isdigit() else w for w in word]时才替换。请注意,这些单词再次连接在一起。
  • 每个单词都被编码成 ASCII 字节序列,并再次解码成 Python 字符串类型。注意使用errors参数强制替换未知字符,如ñ

The difference between strings and bytes is not very intuitive at first, especially if you never have to worry about multiple languages or encoding transformation. In Python 3, there's a strong separation between strings (internal Python representation) and bytes, so most of the tools applicable to strings won't be available in byte objects. Unless you have a good idea of why you need a byte object, always work with Python strings. If you need to perform transformations like the one in this task, encode and decode in the same line so that you keep your objects in the comfortable realm of Python strings. If you are interested in learning more about encodings, you can check out this brief article (https://eli.thegreenplace.net/2012/01/30/the-bytesstr-dichotomy-in-python-3) and this other longer and more detailed one (http://www.diveintopython3.net/strings.html).

  • 这一步首先为所有以句号结尾的单词添加一个额外的换行符(\n字符)。这标记了不同的段落。然后,它创建一行并逐个添加单词。如果一个额外的单词将使它超过 80 个字符,它将完成该行并开始新的一行。如果该行已以新行结束,则它将完成该行并开始另一行。请注意,添加了一个额外的空格来分隔单词。
  • 最后,每一行大写为标题(每个单词的第一个字母大写),所有行通过新行连接。

还有更多。。。

可以对字符串执行的其他一些有用操作如下:

  • 字符串可以像任何其他列表一样进行切片。这意味着'word'[0:2]将返回'wo'
  • 使用.splitlines()按换行符分隔行。
  • .upper().lower()方法,它们返回所有字符都设置为大写或小写的副本。其用途与.title()非常相似:
>>> 'UPPERCASE'.lower()
'uppercase'
  • 为了便于更换(例如,将所有A更改为B或将mine更改为ours,请使用.replace()。这种方法对于非常简单的情况很有用,但是替换很容易变得棘手。请注意更换顺序,以避免碰撞和区分大小写问题。请注意以下示例中的错误更换:
>>> 'One ring to rule them all, one ring to find them, One ring to bring them all and in the darkness bind them.'.replace('ring', 'necklace')
'One necklace to rule them all, one necklace to find them, One necklace to bnecklace them all and in the darkness bind them.'

这与我们将看到的正则表达式匹配代码中意外部分的问题类似。

There are more examples to follow later. Refer to the regular expressions recipes for more information.

如果您使用多种语言或任何非英语输入,学习 Unicode 和编码的基础知识非常有用。简而言之,考虑到世界上所有不同语言中的大量字符,包括与拉丁语无关的字母,如汉语或阿拉伯语,有一个标准来覆盖所有字符,以便计算机能够正确理解。Python 3 极大地改善了这种情况,使字符串成为处理所有这些字符的内部对象。Python 使用的编码,也是最常见和兼容的编码,目前是 UTF-8。

A good article to learn about the basics of UTF-8 is this blog post: (https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/).

在读取可采用不同编码的外部文件(例如,CP-1252 或 windows-1252,这是传统 Microsoft 系统产生的常用编码,或 ISO 8859-15,这是行业标准)时,处理编码仍然是相关的

另见

  • 创建带格式化值的字符串配方
  • 引入正则表达式配方
  • 深入正则表达式配方
  • 第 4 章处理编码配方搜索和读取本地文件

从结构化字符串中提取数据

在许多自动化任务中,我们需要处理特定格式的输入文本并提取相关信息。例如,电子表格可以在文本中定义一个百分比(例如 37.4%),我们希望以数字格式检索该百分比,以便稍后应用它(0.374,作为浮点)。

在这个配方中,我们将看到如何处理包含产品内联信息的销售日志,例如销售、价格、利润和其他一些信息。

准备

假设我们需要解析存储在销售日志中的信息。我们将使用具有以下结构的销售日志:

[<Timestamp in iso format>] - SALE - PRODUCT: <product id> - PRICE: $<price of the sale>

例如,特定日志可能如下所示:

[2018-05-05T10:58:41.504054] - SALE - PRODUCT: 1345 - PRICE: $09.99

请注意,价格有一个前导零。所有价格都有两位数字表示美元,两位数字表示美分。

在开始之前,我们需要激活虚拟环境:

$ source .venv/bin/activate

怎么做。。。

  1. 在 Python 解释器中,进行以下导入。记住激活您的virtualenv,如创建虚拟环境配方中所述:
>>> import delorean
>>> from decimal import Decimal
  1. 输入要分析的日志:
>>> log = '[2018-05-05T11:07:12.267897] - SALE - PRODUCT: 1345 - PRICE: $09.99'
  1. 将圆木分成若干部分,用 -分隔(注意破折号前后的空格)。我们忽略SALE部分,因为它没有添加任何相关信息:
>>> divide_it = log.split(' - ')
>>> timestamp_string, _, product_string, price_string = divide_it
  1. timestamp解析为 datetime 对象:
>>> timestamp = delorean.parse(tmp_string.strip('[]'))
  1. product_id解析为整数:
>>> product_id = int(product_string.split(':')[-1])
  1. 将价格解析为Decimal类型:
>>> price = Decimal(price_string.split('$')[-1])
  1. 现在,您已经拥有了本机 Python 格式的所有值:
>> timestamp, product_id, price
(Delorean(datetime=datetime.datetime(2018, 5, 5, 11, 7, 12, 267897), timezone='UTC'), 1345, Decimal('9.99'))

它是如何工作的。。。

其基本工作是隔离每个元素,然后将它们解析为适当的类型。第一步是将整个日志拆分为较小的部分。-字符串是一个很好的分隔符,因为它将其分割为四个部分——时间戳一个,一个只包含单词SALE、产品和价格。

在时间戳的情况下,我们需要隔离 ISO 格式,它在日志的括号中。这就是为什么它会从支架上脱落。我们使用delorean模块(前面介绍)将其解析为datetime对象。

SALE这个词被忽略了。那里没有相关信息。

为了分离产品 ID,我们在冒号处拆分产品部分。然后,我们将最后一个元素解析为整数:

>>> product_string.split(':')
['PRODUCT', ' 1345']
>>> int(' 1345')
1345

要划分价格,我们使用美元符号作为分隔符,并将其解析为一个Decimal字符:

>>> price_string.split('$')
['PRICE: ', '09.99']
>>> Decimal('09.99')
Decimal('9.99')

如下一节所述,不要将此值解析为浮点类型。

还有更多。。。

这些日志元素可以组合成一个对象,帮助解析和聚合它们。例如,我们可以用以下方式在 Python 代码中定义一个类:

class PriceLog(object):
  def __init__(self, timestamp, product_id, price):
    self.timestamp = timestamp
    self.product_id = product_id
    self.price = price
  def __repr__(self):
    return '<PriceLog ({}, {}, {})>'.format(self.timestamp,
                                            self.product_id,
                                            self.price)
  @classmethod
  def parse(cls, text_log):
    '''
    Parse from a text log with the format
    [<Timestamp>] - SALE - PRODUCT: <product id> - PRICE: $<price>
    to a PriceLog object
    '''
    divide_it = text_log.split(' - ')
    tmp_string, _, product_string, price_string = divide_it
    timestamp = delorean.parse(tmp_string.strip('[]'))
    product_id = int(product_string.split(':')[-1])
    price = Decimal(price_string.split('$')[-1])
    return cls(timestamp=timestamp, product_id=product_id, price=price)

因此,可以按如下方式进行解析:

>>> log = '[2018-05-05T12:58:59.998903] - SALE - PRODUCT: 897 - PRICE: $17.99'
>>> PriceLog.parse(log)
<PriceLog (Delorean(datetime=datetime.datetime(2018, 5, 5, 12, 58, 59, 998903), timezone='UTC'), 897, 17.99)>

避免对价格使用浮动类型。浮点数存在精度问题,在聚合多个价格时可能会产生奇怪的错误,例如:

>>> 0.1 + 0.1 + 0.1 
0.30000000000000004

请尝试以下两个选项以避免出现问题:

If you use the Decimal type, parse the results directly into Decimal from the string. If transforming it first into a float, you can carry the precision errors to the new type.

另见

  • 创建虚拟环境配方
  • 使用第三方工具解析配方
  • 引入正则表达式配方
  • 深入正则表达式配方

使用第三方工具解析

如前一个配方中所示,手动解析数据对于小字符串非常有效,但调整精确公式以处理各种输入可能非常困难。如果输入有时有额外的破折号怎么办?或者它有一个可变长度的标题,这取决于其中一个字段的大小?

更高级的选择是使用正则表达式,我们将在下一个配方中看到。但是 Python 中有一个很棒的模块叫做parsehttps://github.com/r1chardj0n3s/parse )允许我们反转字符串格式。这是一个很棒的工具,功能强大,易于使用,并大大提高了代码的可读性。

准备

将解析模块添加到我们虚拟环境中的requirements.txt文件中,并重新安装依赖项,如创建虚拟环境配方所示。

requirements.txt文件应如下所示:

delorean==1.0.0
requests==2.18.3
parse==1.8.2

然后,在虚拟环境中重新安装模块:

$ pip install -r requirements.txt
...
Collecting parse==1.8.2 (from -r requirements.txt (line 3))
 Using cached https://files.pythonhosted.org/packages/13/71/e0b5c968c552f75a938db18e88a4e64d97dc212907b4aca0ff71293b4c80/parse-1.8.2.tar.gz
...
Installing collected packages: parse
 Running setup.py install for parse ... done
Successfully installed parse-1.8.2

怎么做。。。

  1. 导入parse功能:
>>> from parse import parse
  1. 定义要解析的日志,格式与从结构化字符串中提取数据配方中的格式相同:
>>> LOG = '[2018-05-06T12:58:00.714611] - SALE - PRODUCT: 1345 - PRICE: $09.99'
  1. 分析并描述它,就像您在打印它时所做的那样,如下所示:
>>> FORMAT = '[{date}] - SALE - PRODUCT: {product} - PRICE: ${price}'
  1. 运行parse并检查结果:
>>> result = parse(FORMAT, LOG)
>>> result
<Result () {'date': '2018-05-06T12:58:00.714611', 'product': '1345', 'price': '09.99'}>
>>> result['date']
'2018-05-06T12:58:00.714611'
>>> result['product']
'1345'
>>> result['price']
'09.99'
  1. 注意,结果都是字符串。定义要分析的类型:
>>> FORMAT = '[{date:ti}] - SALE - PRODUCT: {product:d} - PRICE: ${price:05.2f}'
  1. 再次解析:
>>> result = parse(FORMAT, LOG)
>>> result
<Result () {'date': datetime.datetime(2018, 5, 6, 12, 58, 0, 714611), 'product': 1345, 'price': 9.99}>
>>> result['date']
datetime.datetime(2018, 5, 6, 12, 58, 0, 714611)
>>> result['product']
1345
>>> result['price']
9.99
  1. 为价格定义自定义类型,以避免浮动类型出现问题:
>>> from decimal import Decimal
>>> def price(string):
...   return Decimal(string)
...
>>> FORMAT = '[{date:ti}] - SALE - PRODUCT: {product:d} - PRICE: ${price:price}'
>>> parse(FORMAT, LOG, {'price': price})
<Result () {'date': datetime.datetime(2018, 5, 6, 12, 58, 0, 714611), 'product': 1345, 'price': Decimal('9.99')}>

它是如何工作的。。。

parse模块允许我们定义一种格式,例如字符串,它在解析值时反转格式方法。我们在创建字符串时讨论的许多概念在这里应用,将值放在括号中,在冒号后定义类型,等等。

默认情况下,如步骤 4 所示,值被解析为字符串。在分析文本时,这是一个很好的起点。这些值可以被解析为更有用的本机类型,如*操作方法中的步骤 5 和 6 所示。。。*节。请注意,虽然大多数解析类型与 Python 格式规范迷你语言中的解析类型相同,但还有一些其他类型可用,例如 ISO 格式的时间戳的ti

如果本机类型还不够,可以定义我们自己的解析,如*中的步骤 7 所示。。。*节。请注意,price 函数的定义得到一个字符串并返回正确的格式,在本例中为Decimal类型。

All the issues about floats and price information described in the There's more section of the Extracting data from structured strings recipe apply here as well.

还有更多。。。

时间戳也可以转换为delorean对象以保持一致性。此外,delorean对象携带时区信息。添加与上一个配方相同的结构将生成以下对象,该对象能够解析日志:

class PriceLog(object):
  def __init__(self, timestamp, product_id, price):
    self.timestamp = timestamp
    self.product_id = product_id
    self.price = price
  def __repr__(self):
    return '<PriceLog ({}, {}, {})>'.format(self.timestamp,
                                            self.product_id,
                                            self.price)
  @classmethod
  def parse(cls, text_log):
    '''
    Parse from a text log with the format
    [<Timestamp>] - SALE - PRODUCT: <product id> - PRICE: $<price>
    to a PriceLog object
    '''
    def price(string):
      return Decimal(string)
    def isodate(string):
      return delorean.parse(string)
    FORMAT = ('[{timestamp:isodate}] - SALE - PRODUCT: {product:d} - '
              'PRICE: ${price:price}')
    formats = {'price': price, 'isodate': isodate}
    result = parse.parse(FORMAT, text_log, formats)
    return cls(timestamp=result['timestamp'],
               product_id=result['product'],
               price=result['price'])

因此,解析它会返回类似的结果:

>>> log = '[2018-05-06T14:58:59.051545] - SALE - PRODUCT: 827 - PRICE: $22.25'
>>> PriceLog.parse(log)
<PriceLog (Delorean(datetime=datetime.datetime(2018, 6, 5, 14, 58, 59, 51545), timezone='UTC'), 827, 22.25)>

此代码包含在 GitHub 文件Chapter01/price_log.py中。

所有parse支持的类型可在的文档中找到 https://github.com/r1chardj0n3s/parse#format-规格

另见

  • 从结构化字符串中提取数据配方
  • 引入正则表达式配方
  • 深入正则表达式配方

引入正则表达式

正则表达式正则表达式匹配文本的模式。换句话说,它允许我们定义一个抽象字符串(通常是结构化文本的定义)来检查其他字符串是否匹配。

最好用一个例子来描述它们。考虑将文本模式定义为一个以大写字母 a 开头的单词,并且只包含小写字母 Ns,然后是。单词Anna与之匹配,但BobAliceJames不匹配。单词AaanAnaannnAaan也将匹配,但ANNA不会匹配。

如果这听起来很复杂,那是因为它很复杂。正则表达式可能是出了名的复杂,因为它们可能极其复杂且难以遵循。但它们非常有用,因为它们允许我们执行难以置信的强大模式匹配。

正则表达式的一些常见用法如下:

  • 验证输入数据:例如,一个电话号码只是数字、破折号和括号。
  • 字符串解析:从结构化字符串中检索数据,如日志或 URL。这与上一个配方中描述的相似。
  • 报废:在长文本中查找某个事件的发生。例如,查找网页中的所有电子邮件。
  • 替换:查找并替换一个或多个单词。例如,将所有者替换为约翰·史密斯

"Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems."                                                                                                                                                              – Jamie Zawinski

正则表达式在保持非常简单的情况下是最好的。一般来说,如果有一个特定的工具来实现它,最好使用它而不是正则表达式。一个非常明显的例子是 HTML 解析;查看第 3 章构建您的第一个网页抓取应用以获得更好的工具来实现这一点。

Some text editors allow us to search using regexes as well. While most are editors aimed at writing code, such as Vim, BBEdit, or Notepad++, they're also present in more general tools, such as MS Office, Open Office, or Google Documents. But be careful, as the particular syntax may be slightly different.

准备

处理正则表达式的python模块称为re。我们将介绍的主要函数是re.search(),它返回一个匹配对象,其中包含与模式匹配的信息。

As regex patterns are also defined as strings, we'll differentiate them by prefixing them with an r, such as r'pattern'. This is the Python way of labeling a text as raw string literals, meaning that the string within is taken literally, without any escaping. This means that a \ is used as a backslash instead of a sequence. For example, without the r prefix, \n means newline character.

有些字符是特殊的,指的是诸如字符串末尾任意数字任意字符任意空白字符等概念。

最简单的形式只是一个文本字符串。例如,正则表达式模式r'LOG'与字符串'LOGS'匹配,但与字符串'NOT A MATCH'不匹配。如果没有匹配项,搜索返回None

>>> import re
>>> re.search(r'LOG', 'LOGS')
<_sre.SRE_Match object; span=(0, 3), match='LOG'>
>>> re.search(r'LOG', 'NOT A MATCH')
>>>

怎么做。。。

  1. 导入re模块:
>>> import re
  1. 然后,匹配不在字符串开头的模式:
>>> re.search(r'LOG', 'SOME LOGS')
<_sre.SRE_Match object; span=(5, 8), match='LOG'>
  1. 匹配仅位于字符串开头的模式。注意^字符:
>>> re.search(r'^LOG', 'LOGS')
<_sre.SRE_Match object; span=(0, 3), match='LOG'>
>>> re.search(r'^LOG', 'SOME LOGS')
>>>
  1. 仅在字符串末尾匹配模式。注意$字符:
>>> re.search(r'LOG$', 'SOME LOG')
<_sre.SRE_Match object; span=(5, 8), match='LOG'>
>>> re.search(r'LOG$', 'SOME LOGS')
>>>
  1. 匹配单词'thing'(不排除things),但不匹配somethinganything。注意第二个模式开头的\b
>>> STRING = 'something in the things she shows me'
>>> match = re.search(r'thing', STRING)
>>> STRING[:match.start()], STRING[match.start():match.end()], STRING[match.end():]
('some', 'thing', ' in the things she shows me')
>>> match = re.search(r'\bthing', STRING)
>>> STRING[:match.start()], STRING[match.start():match.end()], STRING[match.end():]
('something in the ', 'thing', 's she shows me')
  1. 匹配仅包含数字和破折号的模式(例如,电话号码)。检索匹配的字符串:
>>> re.search(r'[0123456789-]+', 'the phone number is 1234-567-890')
<_sre.SRE_Match object; span=(20, 32), match='1234-567-890'>
>>> re.search(r'[0123456789-]+', 'the phone number is 1234-567-890').group()
'1234-567-890'
  1. 简单地匹配电子邮件地址:
>>> re.search(r'\S+@\S+', 'my email is email.123@test.com').group()
'email.123@test.com'

它是如何工作的。。。

无论模式在字符串中的位置如何,re.search函数都与模式匹配。如前所述,如果找不到模式,则返回None,或者返回match对象。

使用以下特殊字符:

  • ^:标记字符串的开头

  • $:标记字符串的结尾

  • \b:标记单词的开始或结束

  • \S:标记任何非空白字符,包括特殊字符

更多特殊字符将在下一个配方中显示。

在*的第 6 步中,如何做。。。*部分,r'[0123456789-]+'图案由两部分组成。第一个字符位于方括号之间,匹配09(任意数字)之间的任何单个字符以及破折号(-字符)。之后的+符号表示该字符可以出现一次或多次。这在正则表达式中称为量词。这将在数字和破折号的任意组合上进行匹配,无论其长度如何。

步骤 7 再次使用+符号匹配@之前和之后所需的尽可能多的字符。在这种情况下,字符匹配为\S,它匹配任何非空白字符。

请注意,这里描述的电子邮件的朴素模式是非常朴素的,因为它将匹配无效的电子邮件,如john@smith@test.com。对于大多数用途来说,更好的正则表达式是r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"。你可以去http://emailregex.com/ 查找它并链接到更多信息

Note that parsing a valid email including corner cases is actually a difficult and challenging problem. The previous regex should be fine for most uses covered in this book, but in a general framework project such as Django, email validation is a very long and very unreadable regex.

结果匹配对象返回匹配模式开始和结束的位置(使用startend方法),如步骤 5 所示,将字符串拆分为匹配部分,显示两个匹配模式之间的区别

The difference displayed in step 5 is a very common one. Trying to capture GP can end up capturing eggplant and bagpipe! Similarly, things\b won't capture things. Be sure to test and make the proper adjustments, such as capturing \bGP\b for just the word GP.

具体的匹配模式可以通过调用group()进行检索,如步骤 6 所示。请注意,结果将始终是一个字符串。可以使用我们之前看到的任何方法进一步处理,例如通过破折号将电话号码分成多个组,例如:

>>> match = re.search(r'[0123456789-]+', 'the phone number is 1234-567-890')
>>> [int(n) for n in match.group().split('-')]
[1234, 567, 890]

还有更多。。。

处理正则表达式可能既困难又复杂。请留出时间测试您的比赛,并确保它们按照您的预期工作,以避免令人不快的意外。

您可以使用一些工具以交互方式检查正则表达式。在网上免费提供的一个好方法是https://regex101.com/ ,显示每个元素并解释正则表达式。再次检查您是否正在使用 Python 风格:

请注意,解释中描述了\b与单词边界(单词的开头或结尾)匹配,而事物与这些字符字面上匹配。

Regexes, in some cases, can be very slow, or even produce what's called regex denial-of-service, a string created to confuse a particular regex so that it takes an enormous amount of time, even in the worst case blocking the computer. While automating tasks probably won't get you into those problems, keep an eye out in case a regex takes too long.

另见

  • 从结构化字符串中提取数据配方
  • 使用第三方工具解析配方
  • 深入正则表达式配方

深入研究正则表达式

在这个食谱中,我们将看到更多关于如何处理正则表达式的内容。在介绍了基础知识之后,我们将深入研究模式元素,介绍组作为检索和解析字符串的更好方法,了解如何搜索同一字符串的多个实例,以及如何处理较长的文本。

怎么做。。。

  1. 进口re
>>> import re
  1. 将电话模式作为组的一部分进行匹配(括号中)。注意使用\d作为任何数字的特殊字符:
>>> match = re.search(r'the phone number is ([\d-]+)', '37: the phone number is 1234-567-890')
>>> match.group()
'the phone number is 1234-567-890'
>>> match.group(1)
'1234-567-890'
  1. 编译模式并使用yes|no选项捕获不区分大小写的模式:
>>> pattern = re.compile(r'The answer to question (\w+) is (yes|no)', re.IGNORECASE)
>>> pattern.search('Naturaly, the answer to question 3b is YES')
<_sre.SRE_Match object; span=(10, 42), match='the answer to question 3b is YES'>
>>> _.groups()
('3b', 'YES')
  1. 匹配文本中出现的所有城市和州缩写。请注意,它们由单个字符分隔,城市名称始终以大写字母开头。为简单起见,仅匹配四种状态:
>>> PATTERN = re.compile(r'([A-Z][\w\s]+).(TX|OR|OH|MI)')
>>> TEXT ='the jackalopes are the team of Odessa,TX while the knights are native of Corvallis OR and the mud hens come from Toledo.OH; the whitecaps have their base in Grand Rapids,MI'
>>> list(PATTERN.finditer(TEXT))
[<_sre.SRE_Match object; span=(31, 40), match='Odessa,TX'>, <_sre.SRE_Match object; span=(73, 85), match='Corvallis OR'>, <_sre.SRE_Match object; span=(113, 122), match='Toledo.OH'>, <_sre.SRE_Match object; span=(157, 172), match='Grand Rapids,MI'>]
>>> _[0].groups()
('Odessa', 'TX')

它是如何工作的。。。

新引入的特殊字符如下所示。请注意,大写或小写的同一字母表示相反的匹配,例如,\d匹配一个数字,而\D匹配一个非数字:

  • \d:标记任何数字(0 到 9)。
  • \s:标记任何空白字符,包括制表符和其他空白特殊字符。请注意,这与前面的配方中介绍的\S相反。
  • \w:标记任何字母(包括数字,但不包括句点等字符)。
  • .:标记任何字符。

要定义组,请将已定义的组放在括号中。组可以单独检索,这使它们非常适合匹配更大的模式,该模式包含我们稍后将处理的可变部分,如步骤 2 所示。注意与上一配方中步骤 6 模式的差异。在这种情况下,模式不仅是数字,而且包括前缀,即使我们随后提取数字。看看这个差异,有一个数字不是我们想要捕捉的数字:

>>> re.search(r'the phone number is ([\d-]+)', '37: the phone number is 1234-567-890')
<_sre.SRE_Match object; span=(4, 36), match='the phone number is 1234-567-890'>
>>> _.group(1)
'1234-567-890'
>>> re.search(r'[0123456789-]+', '37: the phone number is 1234-567-890')
<_sre.SRE_Match object; span=(0, 2), match='37'>
>>> _.group()
'37'

请记住,第 0 组(.group().group(0)组)始终是整场比赛。其余组按显示顺序排列。

模式也可以被编译。如果需要反复匹配模式,这可以节省一些时间。要以这种方式使用它,请编译模式,然后使用该对象执行搜索,如步骤 3 和 4 所示。可以添加一些额外的标志,例如使模式不区分大小写。

步骤 4 的模式需要一些信息。它由两组组成,由一个字符分隔。特殊字符.表示它匹配所有内容,在我们的示例中是句点、空格和逗号。第二组是定义选项的直接选择,在本例中为美国州缩写。

第一组以大写字母([A-Z])开头,接受字母或空格的任意组合([\w\s]+),但不接受句点或逗号等标点符号。这与城市匹配,包括由多个单词组成的城市。

请注意,此模式从任何大写字母开始,并在找到状态之前保持匹配,除非由标点符号分隔,标点符号可能不是预期的,例如:

>>> re.search(r'([A-Z][\w\s]+).(TX|OR|OH|MI)', 'This is a test, Escanaba MI')
<_sre.SRE_Match object; span=(16, 27), match='Escanaba MI'>
>>> re.search(r'([A-Z][\w\s]+).(TX|OR|OH|MI)', 'This is a test with Escanaba MI')
<_sre.SRE_Match object; span=(0, 31), match='This is a test with Escanaba MI'>

步骤 4 还显示了如何在长文本中查找多个匹配项。当.findall()方法存在时,它不会返回完全匹配的对象,而.findalliter()会返回。在 Python3 中,.findalliter()返回一个迭代器,可用于 for 循环或列表理解。请注意,.search()仅返回模式的第一次出现,即使出现更多匹配:

>>> PATTERN.search(TEXT)
<_sre.SRE_Match object; span=(31, 40), match='Odessa,TX'>
>>> PATTERN.findall(TEXT)
[('Odessa', 'TX'), ('Corvallis', 'OR'), ('Toledo', 'OH')]

还有更多。。。

如果大小写交换,特殊字符可以反转。例如,与我们使用的相反,如下所示:

  • \D:标记任何非数字
  • \W:标记任何非字母
  • \B:标记任何不在单词开头或结尾的字符

The most commonly used special characters are typically \d (digits) and \w (letters and digits), as they mark common patterns to search for, and the plus sign for one or more.

也可以为组分配名称。这使得它们更加明确,但代价是使组在以下形状中更加详细-(?P<groupname>PATTERN)。可以使用.group(groupname)按名称引用组,也可以在保持其数字位置的同时调用.groupdict()

例如,步骤 4 模式可以如下描述:

>>> PATTERN = re.compile(r'(?P<city>[A-Z][\w\s]+?).(?P<state>TX|OR|OH|MN)')
>>> match = PATTERN.search(TEXT)
>>> match.groupdict()
{'city': 'Odessa', 'state': 'TX'}
>>> match.group('city')
'Odessa'
>>> match.group('state')
'TX'
>>> match.group(1), match.group(2)
('Odessa', 'TX')

正则表达式是一个非常广泛的主题。有很多关于它们的技术书籍,它们的深度是出了名的。Python 文档可以作为参考(https://docs.python.org/3/library/re.html )并了解更多信息。

如果一开始你感到有点害怕,这是一种非常自然的感觉。仔细分析每种模式,将其划分为不同的部分,它们就会开始有意义。不要害怕运行 regex 交互式分析器!

正则表达式可以非常强大和通用,但它们可能不是您想要实现的目标的合适工具。我们已经看到了一些有微妙之处的警告和模式。根据经验,如果一个模式开始感到复杂,那么是时候搜索另一个工具了。还记得以前的食谱以及它们提供的选项,例如parse

另见

  • 引入正则表达式配方
  • 使用第三方工具解析配方

添加命令行参数

许多任务可以最好地结构化为一个命令行界面,该界面接受不同的参数以改变其工作方式,例如,废弃一个或另一个网页。Python 在标准库中包含了一个强大的argparse模块,可以轻松创建丰富的命令行参数解析。

准备

argparse在脚本中的基本用法可以分为三个步骤:

  1. 定义脚本将接受的参数,生成新的解析器。
  2. 调用已定义的解析器,返回包含所有结果参数的对象。
  3. 使用参数调用脚本的入口点,该入口点将应用定义的行为。

尝试对脚本使用以下常规结构:

IMPORTS

def main(main parameters):
  DO THINGS

if __name__ == '__main__':
    DEFINE ARGUMENT PARSER
    PARSE ARGS
    VALIDATE OR MANIPULATE ARGS, IF NEEDED
    main(arguments)

通过main函数可以很容易地知道代码的入口点是什么。if语句下的部分仅在直接调用文件时执行,而在导入文件时不执行。我们将在所有步骤中遵循此步骤。

怎么做。。。

  1. 创建一个脚本,该脚本将接受单个整数作为位置参数,并将打印一个哈希符号,打印次数为。recipe_cli_step1.py脚本如下所示,但请注意,我们遵循前面介绍的结构,main函数只是打印参数:
import argparse

def main(number):
    print('#' * number)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('number', type=int, help='A number')
    args = parser.parse_args()

    main(args.number)
  1. 调用脚本并查看参数是如何显示的。不带参数调用脚本将显示自动帮助。使用自动参数-h显示扩展帮助:
$ python3 recipe_cli_step1.py
usage: recipe_cli_step1.py [-h] number
recipe_cli_step1.py: error: the following arguments are required: number
$ python3 recipe_cli_step1.py -h
usage: recipe_cli_step1.py [-h] number
positional arguments:
 number A number
optional arguments:
 -h, --help show this help message and exit
  1. 使用额外参数调用脚本的效果与预期一样:
$ python3 recipe_cli_step1.py 4
####
$ python3 recipe_cli_step1.py not_a_number
usage: recipe_cli_step1.py [-h] number
recipe_cli_step1.py: error: argument number: invalid int value: 'not_a_number'
  1. 更改脚本以接受要打印的字符的可选参数。默认为'#'recipe_cli_step2.py 脚本如下:
import argparse

def main(character, number):
    print(character * number)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('number', type=int, help='A number')
    parser.add_argument('-c', type=str, help='Character to print',
                        default='#')

args = parser.parse_args()
main(args.c, args.number)
  1. 帮助已更新,使用-c标志可以打印不同的字符:
$ python3 recipe_cli_step2.py -h
usage: recipe_cli_step2.py [-h] [-c C] number

positional arguments:
 number A number

optional arguments:
 -h, --help show this help message and exit
 -c C Character to print
$ python3 recipe_cli_step2.py 4
####
$ python3 recipe_cli_step2.py 5 -c m
mmmmm
  1. 添加一个标志,该标志在出现时会更改行为。recipe_cli_step3.py脚本如下:
import argparse

def main(character, number):
    print(character * number)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('number', type=int, help='A number')
    parser.add_argument('-c', type=str, help='Character to print',
                        default='#')
    parser.add_argument('-U', action='store_true', default=False,
                        dest='uppercase',
                        help='Uppercase the character')
    args = parser.parse_args()

    if args.uppercase:
        args.c = args.c.upper()

    main(args.c, args.number)
  1. 如果添加了-U标志,则将其称为大写字符:
$ python3 recipe_cli_step3.py 4 -c f
ffff
$ python3 recipe_cli_step3.py 4 -c f -U
FFFF

它是如何工作的。。。

如*如何做…*部分的步骤 1 所述,参数通过.add_arguments添加到解析器中。一旦定义了所有参数,调用parse_args()将返回一个包含结果的对象(或在出现错误时退出)。

每个参数都应该添加一个帮助说明,但它们的行为可能会发生很大变化:

  • 如果一个参数以-开头,它将被视为可选参数,如步骤 4 中的-c参数。如果不是,则是位置参数,如步骤 1 中的number参数。

For clarity, always define a default value for optional parameters. It will be None if you don't, but this may be confusing.

  • 请记住始终添加带有参数说明的帮助参数;将自动生成帮助,如步骤 2 所示。
  • 如果存在类型,则将对其进行验证,例如,在步骤 3 中number。默认情况下,类型将为字符串。
  • 操作store_truestore_false可用于生成不需要任何额外参数的标志和参数。将相应的默认值设置为相反的布尔值。这在第 6 步和第 7 步的U参数中得到了证明。
  • 默认情况下,args对象中属性的名称将是参数的名称(如果有破折号,则不带破折号)。您可以使用dest进行更改。例如,在步骤 6 中,命令行参数-U被描述为uppercase

Changing the name of an argument for internal usage is very useful when using short arguments, such as single letters. A good command-line interface will use -c, but internally it's probably a good idea to use a more verbose label, such as configuration_file. Explicit is better than implicit!

  • 一些参数可以与其他参数协同工作,如步骤 3 所示。执行所有必需的操作,以将主功能作为清晰简洁的参数传递。例如,在步骤 3 中,只传递了两个参数,但可能修改了一个参数。

还有更多。。。

也可以使用双破折号创建长参数,例如:

 parser.add_argument('-v', '--verbose', action='store_true', default=False, 
                     help='Enable verbose output')

这将同时接受-v--verbose,并存储名称verbose

Adding long names is a good way of making the interface more intuitive and easy to remember. It's easy to remember after a couple of times that there's a verbose option, and it starts with a v.

在处理命令行参数时,主要的不便之处可能是它们太多。这造成了混乱。尽量使你的论点独立,不要在它们之间建立太多的依赖关系,否则处理这些组合会很棘手。

In particular, try to not create more than a couple of positional arguments, as they won't have mnemonics. Positional arguments also accept default values, but most of the time that won't be the expected behavior.

有关高级详细信息,请查看argparse的 Python 文档 https://docs.python.org/3/library/argparse.html

另见

  • 创建虚拟环境配方
  • 安装第三方软件包配方