现代 Python 秘籍(一)
原文:
zh.annas-archive.org/md5/185a6e8218e2ea258a432841b73d4359
译者:飞龙
前言
Python 是开发人员、工程师、数据科学家和爱好者的首选。它是一种强大的脚本语言,可以为您的应用程序提供强大的速度、安全性和可扩展性。通过将 Python 公开为一系列简单的配方,您可以深入了解特定上下文中的语言特性。具体的上下文有助于更容易理解语言或标准库特性。
本书采用了基于配方的方法,每个配方都解决特定的问题和问题。
本书涵盖的内容
第一章,数字、字符串和元组,将介绍不同类型的数字,处理字符串,使用元组,并使用 Python 中的基本内置类型。我们还将充分利用 Unicode 字符集的全部功能。
第二章,语句和语法,将首先涵盖创建脚本文件的一些基础知识。然后,我们将继续研究一些复杂的语句,包括 if、while、for、try、with 和 raise。
第三章,函数定义,将介绍一些函数定义技术。我们还将研究 Python 3.5 的 typing 模块,看看如何为我们的函数创建更正式的注释。
第四章,内置数据结构-列表、集合、字典,将概述可用的各种结构及其解决的问题。从那里,我们可以详细了解列表、字典和集合,还可以研究一些与 Python 如何处理对象引用相关的更高级主题。
第五章,用户输入和输出,将解释如何使用 print()函数的不同特性。我们还将研究用于提供用户输入的不同函数。
第六章,类和对象的基础,将创建实现多个统计公式的类。
第七章,更高级的类设计,将更深入地研究 Python 类。我们将结合之前学到的一些特性来创建更复杂的对象。
第八章,函数式和响应式编程特性,为我们提供了编写小型、表达力强的函数来执行所需数据转换的方法。接下来,您将了解响应式编程的概念,即在输入可用或更改时评估处理规则。
第九章,输入/输出、物理格式、逻辑布局,将使用不同的文件格式,如 JSON、XML 和 HTML。
第十章,统计编程和线性回归,将介绍我们可以使用 Python 内置库和数据结构进行的一些基本统计计算。我们将讨论相关性、随机性和零假设的问题。
第十一章,测试,将详细描述 Python 中使用的不同测试框架。
第十二章,Web Services,将介绍创建 RESTful web 服务和提供静态或动态内容的一些方法。
第十三章 ,应用集成 ,将探讨我们可以设计应用程序的方式,以便组合创建更大、更复杂的复合应用程序。我们还将研究复合应用程序可能出现的复杂性,以及需要集中一些功能,例如命令行解析的需求。
您需要为这本书做些什么
您只需要一台运行任何最新版本的 Python 的计算机,就可以按照本书中的示例进行操作。虽然所有示例都使用 Python 3,但只需进行少量更改即可使其适用于 Python 2。
这本书是为谁准备的
这本书适用于 Web 开发人员,程序员,企业程序员,工程师和大数据科学家。如果您是初学者,Python Cookbook 将帮助您入门。如果您有经验,它将扩展您的知识基础。对编程的基本了解会有所帮助。
约定
在本书中,您将找到一些文本样式,用于区分不同类型的信息。以下是这些样式的一些示例及其含义的解释。
文本中的代码词,数据库表名,文件夹名,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄显示如下:“我们可以通过使用包含指令来包含其他上下文。”
代码块设置如下:
if distance is None:
distance = rate * time
elif rate is None:
rate = distance / time
elif time is None:
time = distance / rate
任何命令行输入或输出都写成如下形式:
**>>> circumference_diameter_ratio = 355/113**
**>>> target_color_name = 'FireBrick'**
**>>> target_color_rgb = (178, 34, 34)**
新术语和重要单词以粗体显示。
注意
警告或重要说明显示在这样的框中。
提示
提示和技巧显示如下。
第一章:数字,字符串和元组
我们将介绍这些食谱来介绍基本的 Python 数据类型:
-
创建有意义的名称并使用变量
-
处理大整数和小整数
-
在浮点数,小数和分数之间进行选择
-
在真除法和地板除法之间进行选择
-
重写不可变字符串
-
使用正则表达式解析字符串
-
使用“template”。格式()构建复杂的字符串
-
从字符列表构建复杂的字符串
-
使用不在键盘上的 Unicode 字符
-
编码字符串-创建 ASCII 和 UTF-8 字节
-
解码字节-如何从一些字节中获取正确的字符
-
使用项目的元组
介绍
本章将介绍 Python 对象的一些中心类型。我们将研究不同类型的数字,处理字符串和使用元组。我们首先研究这些,因为它们是 Python 处理的最简单的数据类型。在后面的章节中,我们将研究数据集合。
这些大多数食谱假设初学者对 Python 3 有一定的理解。我们将研究如何使用 Python 中提供的基本内置类型-数字,字符串和元组。Python 有各种各样的数字和两种不同的除法运算符,因此我们需要仔细研究可用的选择。
在处理字符串时,有几个重要的常见操作。我们将探讨字节(由操作系统文件使用)和字符串(由 Python 使用)之间的一些区别。我们将看看如何利用 Unicode 字符集的全部功能。
在本章中,我们将展示食谱,就好像我们是从交互式 Python 的>>>
提示符中工作一样。这有时被称为读取-求值-打印循环(REPL)。在后面的章节中,我们将更仔细地研究编写脚本文件。目标是鼓励交互式探索,因为这是学习语言的好方法。
创建有意义的名称并使用变量
我们如何确保我们的程序是有意义的?制作表达性代码的核心元素之一是使用有意义的名称。但什么算是有意义的?在这个食谱中,我们将回顾一些创建有意义的 Python 名称的常见规则。
我们还将研究一些 Python 的赋值语句变体。例如,我们可以在单个语句中分配多个变量。
做好准备
创建名称时的核心问题是问自己一个问题这是什么东西?对于软件,我们希望一个描述对象的名称。显然,像x
这样的名称并不是很描述性,它似乎并不指代实际的东西。
在一些编程中,模糊的,不具描述性的名称是令人沮丧的常见现象。当我们使用它们时,对他人并不有帮助。描述性的名称有助于每个人。
在命名事物时,将问题域-我们真正想要实现的目标-与解决方案域分开也很重要。解决方案域包括 Python,操作系统和互联网的技术细节。任何阅读代码的人都可以看到解决方案;它不需要深入解释。然而,问题域可能会被技术细节所掩盖。我们的工作是使问题清晰可见。选择恰当的名称将有所帮助。
如何做...
我们将首先看名称。然后我们将转向赋值。
明智地选择名称
从纯技术层面上讲,Python 名称必须以字母开头。它们可以包括任意数量的字母,数字和 _ 字符。Python 3 基于 Unicode,因此字母不限于拉丁字母表。虽然 A-Z 拉丁字母表通常被使用,但并非必需。
在创建描述性变量时,我们希望创建既具体又能表达程序中事物之间关系的名称。一个广泛使用的技术是创建更长的名称,风格从具体到一般。
选择名称的步骤如下:
-
名称的最后部分是对这个事物的一个非常广泛的总结。在一些情况下,这可能是我们所需要的全部;上下文会提供其余的部分。我们稍后会提出一些典型的广泛总结类别。
-
使用前缀来围绕你的应用程序或问题域缩小这个名称。
-
如果需要,可以在这个名称上加上更窄和专业的前缀,以澄清它与其他类、模块、包、函数和其他对象的区别。如果对于加前缀感到犹豫,可以回想一下域名是如何工作的。想想
mail.google.com
——名称从具体到一般。命名的三个级别并没有什么神奇之处,但通常情况下会按照这种方式进行。 -
根据 Python 中的使用方式来格式化名称。我们将给出三种我们会给名称的东西的广泛类别,如下所示:
-
类:类有一个总结了属于该类的对象的名称。这些名称通常会使用
CapitalizedCamelCase
。类名的第一个字母大写是为了强调它是一个类,而不是类的实例。类通常是一个通用的概念,很少是一个具体的东西的描述。 -
对象:对象的名称通常使用
snake_case
——所有小写,单词之间用多个_
字符分隔。在 Python 中,这包括变量、函数、模块、包、参数、对象的属性、类的方法,几乎所有其他东西。 -
脚本和模块文件:这些实际上是 Python 所看到的操作系统资源。因此,文件名应该遵循 Python 对象的约定,使用字母、
_
字符,并以.py
扩展名结尾。技术上可以有相当狂野和自由的文件名。不遵循 Python 规则的文件名可能很难作为模块或包使用。
我们如何选择名称的广泛类别部分?一般的类别取决于我们是在谈论一个事物还是一个事物的属性。虽然世界充满了事物,但我们可以创建一些有用的广泛分类。一些例子是 Document、Enterprise、Place、Program、Product、Process、Person、Asset、Rule、Condition、Plant、Animal、Mineral 等等。
然后我们可以用限定词来缩小这些范围:
FinalStatusDocument
ReceivedInventoryItemName
第一个例子是一个名为Document
的类。我们通过添加前缀稍微缩小了它,称其为StatusDocument
。我们甚至进一步缩小了它,称其为FinalStatusDocument
。第二个例子是一个Name
,我们通过指定它是一个ReceivedInventoryItemName
来缩小了它。这个例子需要一个四级名称来澄清这个类。
一个对象通常有属性。这些属性有一个基于所表示信息类型的分解。一些应该作为完整名称的一部分的术语的例子是 amount、code、identifier、name、text、date、time、datetime、picture、video、sound、graphic、value、rate、percent、measure 等等。
思路是先放窄、更详细的描述,然后是广泛的信息类型:
measured_height_value
estimated_weight_value
scheduled_delivery_date
location_code
在第一个例子中,height
缩小了更一般的表示术语value
。而measured_height_value
进一步缩小了这个范围。根据这个名称,我们可以期待看到关于 height 的其他变化。类似的思路也适用于weight_value
,delivery_date
和location_code
。每个名称都有一个缩小的前缀或两个。
注意
一些需要避免的事情:
不要使用编码的前缀或后缀包含详细的技术类型信息。这通常被称为匈牙利命名法;我们不使用f_measured_height_value
,其中f
应该表示浮点数。像measured_height_value
这样的变量可以是任何数字类型,Python 会进行所有必要的转换。技术装饰对于阅读我们的代码的人并没有太多帮助,因为类型规范可能是误导性的,甚至是错误的。
不要浪费大量精力让名称看起来像是属于一起的。我们不需要让SpadesCardSuit
,ClubsCardSuit
等看起来像是属于一起的。Python 有许多不同类型的命名空间,包括包、模块和类,以及命名空间对象来将相关名称聚集在一起。如果你将这些名称组合在一个CardSuit
类中,你可以使用CardSuit.Spades
,这样可以使用类作为命名空间将这些名称与其他类似的名称分开。
给对象命名
Python 不使用静态变量定义。当一个名称被赋予一个对象时,变量就被创建了。重要的是要把对象看作是我们处理的中心,而变量只是标识对象的便利贴。以下是我们如何使用基本的赋值语句:
-
创建一个对象。在许多例子中,我们将创建对象作为文字。我们将使用
355
或113
作为 Python 中整数对象的文字表示。我们可能会使用像FireBrick
这样的字符串,或者像(178, 34, 34)
这样的元组。 -
写下以下类型的陈述:变量 = 对象。以下是一些例子:
**>>> circumference_diameter_ratio = 355/113**
**>>> target_color_name = 'FireBrick'**
**>>> target_color_rgb = (178, 34, 34)**
我们已经创建了一些对象并将它们分配给变量。第一个对象是计算的结果。接下来的两个对象是简单的文字。通常,对象是通过涉及函数或类的表达式创建的。
这种基本的语句并不是唯一一种赋值方式。我们可以使用一种重复赋值的方式将单个对象分配给多个变量,就像这样:
**>>> target_color_name = first_color_name = 'FireBrick'**
这创建了同一个字符串对象的两个名称。我们可以通过检查 Python 使用的内部 ID 值来确认这一点:
**>>> id(target_color_name) == id(first_color_name)**
**True**
这种比较告诉我们,这两个对象的内部标识符是相同的。
注意
相等的测试使用==
。简单的赋值使用=
。
当我们查看数字和集合时,我们会发现我们可以将赋值与运算符结合起来。我们可以做这样的事情:
**>>> total_count = 0**
**>>> total_count += 5**
**>>> total_count += 6**
**>>> total_count**
**11**
我们已经使用运算符增强了赋值。total_count += 5
与total_count = total_count + 5
是一样的。这种技术的优点是更短。
它是如何工作的...
创建名称的这种方法遵循了首先使用狭窄、更具体的限定词,然后是更广泛、不太具体的类别的模式。这遵循了用于域名和电子邮件地址的常见约定。
例如,像mail.google.com
这样的域名具有特定的服务,更一般的企业,最后是一个非常普遍的域。这遵循了由狭窄到更广泛的原则。
作为另一个例子,[email protected] 以特定的目的地名称开头,具有更一般的企业,最后是一个非常普遍的域。甚至目的地的名称(PacktPub)也是一个由狭窄的企业名称(Packt)和更广泛的行业(Pub,缩写为publishing)组成的两部分名称。(我们不同意那些认为它代表公共场所的人。)
赋值语句是将名称放在对象上的唯一方法。我们注意到我们可以为同一个基础对象有两个名称。目前这并不太有用。但是在第四章中,内置数据结构-列表、集合、字典,我们将看到单个对象的多个名称的一些有趣的后果。
还有更多...
我们将尝试在所有的食谱中展示描述性的名称。
提示
我们必须对不遵循这种模式的现有软件做出例外。与强加新规则相比,与遗留软件保持一致通常更好,即使新规则更好。
几乎每个例子都涉及对变量的赋值。这对于有状态的面向对象编程至关重要。
我们将在第六章中查看类和类名,类和对象的基础;我们将在第十三章中查看模块,应用集成。
另请参阅
描述性命名的主题是持续研究和讨论的来源。有两个方面——语法和语义。关于 Python 语法的思考的起点是著名的Python Enhancement Proposal number 8(PEP-8)。这导致使用CamelCase
和snake_case
名称。
另外,一定要这样做:
**>>> import this**
这将更多地了解 Python 的理想。
注意
有关语义的信息,请参阅传统的 UDEF 和 NIEM 命名和设计规则标准(www.opengroup.org/udefinfo/AboutTheUDEF.pdf
)。ISO11179 中还有更多详细信息(en.wikipedia.org/wiki/ISO/IEC_11179
),其中详细讨论了元数据和命名。
使用大和小整数
许多编程语言区分整数、字节和长整数。一些语言包括有符号和无符号整数的区别。我们如何将这些概念映射到 Python 呢?
简单的答案是我们不需要。Python 以统一的方式处理所有大小的整数。从字节到数百位数的巨大数字,对 Python 来说都只是整数。
准备工作
想象一下,你需要计算一个非常大的东西。例如,计算 52 张牌的牌组的排列方式。数字 52! = 52 × 51 × 50 × ... × 2 × 1,是一个非常非常大的数字。我们可以在 Python 中做到这一点吗?
如何做...
别担心。真的。Python 的行为就好像它有一种通用类型的整数,这涵盖了从字节到填满所有内存的数字的所有基础。以下是使用整数的步骤:
-
写下你需要的数字。以下是一些小数字:355,113。没有实际的上限。
-
创建一个非常小的值——一个字节——看起来像这样:
**>>> 2**
**2**
或者,如果你想使用 16 进制,可能是这样:
**>>> 0xff**
**255**
在后续的示例中,我们将看到一个字节序列,其中只有一个值:
**>>> b'\xfe'**
**b'\xfe'**
从技术上讲,这不是一个整数。它有一个b'
的前缀,表明它是一个 1 字节序列。
- 使用计算创建一个非常大的数字可能看起来像这样:
**>>> 2**2048**
**323...656**
这个数字有 617 位。我们没有展示所有的位数。
它是如何工作的...
在内部,Python 使用两种类型的数字。这两者之间的转换是无缝和自动的。
对于小整数,Python 通常会使用 4 或 8 字节的整数值。细节被埋在 CPython 的内部,并且取决于用于构建 Python 的 C 编译器的功能。
对于大整数,超过sys.maxsize
,Python 将切换到大整数数字,这些数字是数字序列。在这种情况下,数字通常意味着 30 位值。
我们可以对标准的 52 张牌的牌组进行多少种排列?答案是 52! ≈ 8 × 10⁶⁷。以下是我们如何计算这个大数字。我们将使用math
模块中的阶乘函数,如下所示:
**>>> import math**
**>>> math.factorial(52)**
**80658175170943878571660636856403766975289505440883277824000000000000**
是的,这些巨大的数字完美地工作。
我们计算 52!(从 52×51×50×...到约 42)的前几部分可以完全使用较小的整数。之后,计算的其余部分必须切换到较大的整数。我们看不到切换;我们只看到结果。
关于整数的内部细节,我们可以看看这个:
**>>> import sys**
**>>> import math**
**>>> math.log(sys.maxsize, 2)**
**63.0**
**>>> sys.int_info**
**sys.int_info(bits_per_digit=30, sizeof_digit=4)**
sys.maxsize
值是小整数值中最大的值。我们计算了以 2 为底的对数,以找出这个数字需要多少位。
这告诉我们,我们的 Python 对于小整数使用 63 位值。小整数的范围是从-2⁶⁴ ... 2⁶³ - 1。在此范围之外,将使用大整数。
sys.int_info
中的值告诉我们,大整数是使用 30 位数字的数字序列,每个数字占据 4 个字节。
像 52!这样的大值由 8 个这样的 30 位大小的数字组成。将一个数字表示为需要 30 位来表示可能有点令人困惑。与用于表示十进制数的 10 个符号不同,我们需要2**30个不同的符号来表示这些大数字的每个数字。
涉及大量大整数值的计算可能会消耗大量内存。那么小数字呢?Python 如何管理跟踪像 1 和 0 这样的许多小数字?
对于常用的数字(-5 到 256),Python 实际上创建了一个秘密的对象池来优化内存管理。当您检查整数对象的id()
值时,您可以看到这一点:
**>>> id(1)**
**4297537952**
**>>> id(2)**
**4297537984**
**>>> a=1+1**
**>>> id(a)**
**4297537984**
我们展示了整数1
和整数2
的内部id
。当我们计算一个值时,结果对象实际上是在池中找到的相同整数2
对象。
当您尝试这样做时,您的id()
值可能会有所不同。但是,每次使用2
的值时,它都将是相同的对象;在作者的笔记本电脑上,它的 id = 4297537984
。这样可以节省很多 2 对象的副本占用内存。
这是一个查看一个数字有多大的小技巧:
**>>> len(str(2**2048))**
**617**
我们从一个计算出的数字创建了一个字符串。然后我们询问字符串的长度。响应告诉我们这个数字有 617 位。
还有更多...
Python 为我们提供了一套广泛的算术运算符:+
,-
,*
,/
,//
,%
和**
。/
和//
用于除法;我们将在一个名为在真除法和地板除法之间进行选择的单独配方中查看这些。**
将一个数提高到幂。
对于处理单个位,我们有一些额外的操作。我们可以使用&
,^
,|
,<<
和>>
。这些运算符在整数的内部二进制表示上逐位运算。这些分别计算二进制AND,二进制异或,或,左移和右移。
虽然这些将适用于非常大的整数,但在个别字节的世界之外,它们实际上并没有太多意义。一些二进制文件和网络协议将涉及查看数据的单个字节中的位。
我们可以通过使用bin()
函数来玩弄这些运算符,看看发生了什么。
这是一个快速的例子:
**>>> xor = 0b0011 ^ 0b0101**
**>>> bin(xor)**
**'0b110'**
我们使用了0b0011
和0b0101
作为我们的两个比特字符串。这有助于准确地澄清这两个数字的二进制表示。我们对这两个比特序列应用了异或(^
)运算符。我们使用bin()
函数将结果作为比特字符串查看。我们可以仔细地对齐比特,看看运算符做了什么。
我们可以将一个字节分解成几部分。假设我们想要将最左边的两位与其他六位分开。一种方法是使用这样的位操作表达式:
**>>> composite_byte = 0b01101100**
**>>> bottom_6_mask = 0b00111111**
**>>> bin(composite_byte >> 6)**
**'0b1'**
**>>> bin(composite_byte & bottom_6_mask)**
**'0b101100'**
我们定义了一个复合字节,其中最高的两位是01
,最低的六位是101100
。我们使用>>
移位运算符将该值向右移动六个位置,删除最低有效位并保留最高的两位。我们使用了一个掩码的&
运算符。掩码中有 1 位时,结果中保留了位置的值,掩码中有0
位时,结果位置设置为0
。
另请参阅
-
我们将在在真除法和地板除法之间进行选择配方中查看两个除法运算符
-
我们将在在浮点数、十进制和分数之间进行选择配方中查看其他类型的数字
-
有关整数处理的详细信息,请参见
www.python.org/dev/peps/pep-0237/
在浮点数、十进制和分数之间进行选择
Python 为我们提供了几种处理有理数和无理数近似的方法。我们有三种基本选择:
-
浮点数
-
十进制
-
分数
有了这么多选择,我们什么时候使用每种选择呢?
准备就绪
确定我们的核心数学期望是很重要的。如果我们不确定我们有什么样的数据,或者我们想要得到什么样的结果,我们真的不应该编码。我们需要退一步,用铅笔和纸重新审视事情。
涉及超出整数的数字的数学有三种一般情况,即:
-
货币:美元、分或欧元。货币通常有固定的小数位数。有舍入规则用于确定 7.25%的$2.95 是多少。
-
有理数或分数:当我们使用英尺和英寸的美国单位,或者使用杯和液体盎司的烹饪度量时,我们经常需要使用分数。例如,当我们将为八个人的食谱缩小到五个人时,我们正在使用 5/8 的缩放因子进行分数运算。我们如何将这应用到 2/3 杯米饭,并且仍然得到一个适合美国厨房工具的度量?
-
无理数:这包括所有其他类型的计算。重要的是要注意,数字计算机只能近似这些数字,我们偶尔会看到这种近似的奇怪小瑕疵。浮点数近似非常快,但有时会遇到截断问题。
当我们有前两种情况之一时,我们应该避免使用浮点数。
如何做...
我们将分别查看这三种情况。首先,我们将看一下使用货币进行计算。然后我们将看一下有理数,最后是无理数或浮点数。最后,我们将看一下在这些各种类型之间进行明确转换。
进行货币计算
在处理货币时,我们应该始终使用decimal
模块。如果我们尝试使用 Python 内置的float
值,我们将遇到舍入和截断数字的问题。
- 要处理货币,我们将这样做。从
decimal
模块导入Decimal
类:
**>>> from decimal import Decimal**
- 从字符串或整数创建
Decimal
对象:
**>>> from decimal import Decimal**
**>>> tax_rate = Decimal('7.25')/Decimal(100)**
**>>> purchase_amount = Decimal('2.95')**
**>>> tax_rate * purchase_amount**
**Decimal('0.213875')**
我们从两个Decimal
对象创建了tax_rate
。一个是基于字符串的,另一个是基于整数的。我们可以使用Decimal('0.0725')
而不是显式地进行除法。
结果是超过$0.21。它被正确计算到了完整的小数位数。
- 如果您尝试从浮点数值创建十进制对象,您将看到浮点数近似的不愉快的副作用。避免混合
Decimal
和float
。要舍入到最接近的一分钱,创建一个penny
对象:
**>>> penny=Decimal('0.01')**
- 使用这个 penny 对象对数据进行量化:
**>>> total_amount = purchase_amount + tax_rate*purchase_amount**
**>>> total_amount.quantize(penny)**
**Decimal('3.16')**
这显示了我们如何使用ROUND_HALF_EVEN
的默认舍入规则。
每个财务巫师都有不同的舍入风格。Decimal
模块提供了每种变化。例如,我们可能会做这样的事情:
**>>> import decimal**
**>>> total_amount.quantize(penny, decimal.ROUND_UP)**
**Decimal('3.17')**
这显示了使用不同的舍入规则的后果。
分数计算
当我们进行具有精确分数值的计算时,我们可以使用fractions
模块。这为我们提供了方便的有理数,我们可以使用。要处理分数,我们将这样做:
- 从
fractions
模块导入Fraction
类:
**>>> from fractions import Fraction**
- 从字符串、整数或整数对创建
Fraction
对象。如果从浮点数值创建分数对象,可能会看到浮点数近似的不愉快的副作用。当分母是 2 的幂时,事情可能会完全解决:
**>>> from fractions import Fraction**
**>>> sugar_cups = Fraction('2.5')**
**>>> scale_factor = Fraction(5/8)**
**>>> sugar_cups * scale_factor**
**Fraction(25, 16)**
我们从一个字符串2.5
创建了一个分数。我们从一个浮点计算5/8
创建了第二个分数。因为分母是 2 的幂,这完全解决了。
结果 25/16 是一个看起来复杂的分数。附近可能更简单的分数是什么?
**>>> Fraction(24,16)**
**Fraction(3, 2)**
我们可以看到,我们将使用将近一杯半来将为八个人的食谱缩小到五个人。
浮点数近似
Python 的内置float
类型能够表示各种各样的值。这里的权衡是,float 通常涉及近似值。在某些情况下——特别是在涉及 2 的幂的除法时,它可以像fraction
一样精确。在所有其他情况下,可能会有一些小的差异,揭示了float
的实现和无理数的数学理想之间的差异。
- 要使用
float
,我们经常需要四舍五入值使其看起来合理。请认识到所有计算都是近似值:
**>>> (19/155)*(155/19)**
**0.9999999999999999**
- 从数学上讲,该值应为
1
。由于float
的近似值,答案并不精确。它并不错得很多,但是错了。当我们适当四舍五入时,该值更有用:
**>>> answer= (19/155)*(155/19)**
**>>> round(answer, 3)
1.0**
- 了解误差项。在这种情况下,我们知道应该是什么精确答案,所以我们可以将我们的计算与已知的正确答案进行比较。这给了我们可能会渗入浮点数的一般误差值:
**>>> 1-answer**
**1.1102230246251565e-16**
对于大多数浮点错误,这是典型值——约为 10^(-16)。Python 有巧妙的规则,通过一些自动四舍五入来隐藏这种错误。然而,对于这个计算,错误并没有被隐藏。
这是一个非常重要的结果。
提示
不要将浮点值进行精确相等的比较。
当我们看到使用浮点数之间的精确==
测试的代码时,当近似值相差一个位时,就会出现问题。
将数字从一种类型转换为另一种类型
我们可以使用float()
函数从另一个值创建一个float
值。它看起来像这样:
**>>> float(total_amount)**
**3.163875**
**>>> float(sugar_cups * scale_factor)**
**1.5625**
在第一个示例中,我们将Decimal
值转换为float
。在第二个示例中,我们将Fraction
值转换为float
。
正如我们刚才看到的,我们永远无法将float
转换为Decimal
或Fraction
:
**>>> Fraction(19/155)**
**Fraction(8832866365939553, 72057594037927936)**
**>>> Decimal(19/155)**
**Decimal('0.12258064516129031640279123394066118635237216949462890625')**
在第一个示例中,我们进行了整数之间的计算,以创建一个已知截断问题的float
值。当我们从该截断的float
值创建一个Fraction
时,我们得到了一些暴露了截断细节的可怕数字。
同样,第二个示例试图从float
创建一个Decimal
值。
它是如何工作的...
对于这些数字类型,Python 为我们提供了各种运算符:+
,-
,*
,/
,//
,%
和**
。这些是用于加法、减法、乘法、真除法、截断除法、模数和乘方的。我们将在选择真除法和地板除法的示例中查看这两个除法运算符。
Python 擅长在各种类型之间转换数字。我们可以混合int
和float
值;整数将被提升为浮点以提供尽可能准确的答案。同样,我们可以混合int
和Fraction
,结果将是Fractions
。我们也可以混合int
和Decimal
。我们不能随意混合Decimal
和float
或Fraction
;我们需要提供显式转换。
注意
重要的是要注意,float
值实际上是近似值。Python 语法允许我们将数字写成小数值;这不是它们在内部处理的方式。
我们可以在 Python 中写出这样的值,使用普通的十进制值:
**>>> 8.066e+67**
**8.066e+67**
内部使用的实际值将涉及我们写的十进制值的二进制近似值。
这个例子的内部值8.066e+67
是这样的:
**>>> 6737037547376141/2**53*2**226**
**8.066e+67**
分子是一个大数,6737037547376141
。分母始终是2⁵³。由于分母是固定的,结果分数只能有 53 个有意义的数据位。由于没有更多的位可用,值可能会被截断。这导致我们理想化抽象和实际数字之间的微小差异。指数(2²²⁶)是将分数放大到适当范围所必需的。
从数学上讲,6737037547376141 * 2²²⁶ /2⁵³。
我们可以使用math.frexp()
来查看数字的这些内部细节:
**>>> import math**
**>>> math.frexp(8.066E+67)**
**(0.7479614202861186, 226)**
这两部分被称为尾数和指数。如果我们将尾数乘以2⁵³,我们总是得到一个整数,这是二进制分数的分子。
注意
我们之前注意到的误差非常匹配:10^(-16) ≈ 2^(-53)。
与内置的float
不同,Fraction
是两个整数值的精确比率。正如我们在使用大整数和小整数配方中看到的,Python 中的整数可以非常大。我们可以创建涉及具有大量数字的整数的比率。我们不受固定分母的限制。
类似地,Decimal
值是基于一个非常大的整数值和一个缩放因子来确定小数点的位置。这些数字可以非常庞大,并且不会受到奇怪的表示问题的影响。
注意
为什么使用浮点数?两个原因:
并非所有可计算的数字都可以表示为分数。这就是为什么数学家引入(或者也许是发现)无理数的原因。内置的浮点类型是我们可以接近数学抽象的无理数的方式。例如,像√2 这样的值不能表示为分数。
此外,浮点值非常快。
还有更多...
Python 的math
模块包含了许多专门用于处理浮点数值的函数。这个模块包括了常见的函数,比如平方根、对数和各种三角函数。它还有一些其他函数,比如 gamma、阶乘和高斯误差函数。
math
模块包括几个函数,可以帮助我们进行更准确的浮点计算。例如,math.fsum()
函数将比内置的sum()
函数更谨慎地计算浮点和。它不太容易受到近似问题的影响。
我们还可以利用math.isclose()
函数来比较两个浮点值,看它们是否几乎相等:
**>>> (19/155)*(155/19) == 1.0**
**False**
**>>> math.isclose((19/155)*(155/19), 1)**
**True**
这个函数为我们提供了一种有意义地比较浮点数的方法。
Python 还提供了复数数据。这涉及到一个实部和一个虚部。在 Python 中,我们写3.14+2.78j
来表示复数 3.14 + 2.78 √-1。Python 可以很舒适地在浮点数和复数之间转换。我们有一组通常的操作符可用于复数。
为了支持复数,有一个cmath
包。例如,cmath.sqrt()
函数将返回一个复数值,而不是在提取负数的平方根时引发异常。这里有一个例子:
**>>> math.sqrt(-2)**
**Traceback (most recent call last):**
**File "<stdin>", line 1, in <module>**
**ValueError: math domain error**
**>>> cmath.sqrt(-2)**
**1.4142135623730951j**
这在处理复数时是必不可少的。
另请参阅
-
我们将在在真除法和地板除法之间进行选择配方中更多地讨论浮点数和分数
在真除法和地板除法之间进行选择
Python 为我们提供了两种类型的除法运算符。它们是什么,我们如何知道该使用哪一个?我们还将看一下 Python 的除法规则以及它们如何适用于整数值。
准备工作
有几种一般情况可以进行除法:
-
一个div-mod对:我们想要两部分——商和余数。当我们将值从一种基数转换为另一种基数时,我们经常使用这种方法。当我们将秒转换为小时、分钟和秒时,我们将进行div-mod类型的除法。我们不想要准确的小时数,我们想要截断的小时数,余数将被转换为分钟和秒。
-
真实值:这是典型的浮点值——它将是商的一个很好的近似值。例如,如果我们计算几个测量的平均值,我们通常希望结果是浮点数,即使输入值都是整数。
-
一个有理分数值:这在使用英尺、英寸和杯的美国单位时经常需要。对于这一点,我们应该使用
Fraction
类。当我们除以Fraction
对象时,我们总是得到精确的答案。
我们需要决定哪些情况适用,这样我们就知道要使用哪个除法运算符。
如何做...
我们将分别查看三种情况。首先我们将查看截断的地板除法。然后我们将查看真正的浮点除法。最后,我们将查看分数的除法。
进行地板除法
当我们进行div-mod类型的计算时,我们可能会使用地板除法,//
,和模数,%
。或者,我们可以使用divmod()
函数。
- 我们将把秒数除以 3600 得到
小时
的值;余数可以分别转换为分钟
和秒
:
**>>> total_seconds = 7385**
**>>> hours = total_seconds//3600**
**>>> remaining_seconds = total_seconds % 3600**
- 再次使用剩余值,我们将把秒数除以 60 得到
分钟
;余数是小于 60 的秒数:
**>>> minutes = remaining_seconds//60**
**>>> seconds = remaining_seconds % 60**
**>>> hours, minutes, seconds**
**(2, 3, 5)**
这是另一种方法,使用divmod()
函数:
- 同时计算商和余数:
**>>> total_seconds = 7385**
**>>> hours, remaining_seconds = divmod(total_seconds, 3600)**
- 再次计算商和余数:
**>>> minutes, seconds = divmod(remaining_seconds, 60)**
**>>> hours, minutes, seconds**
**(2, 3, 5)**
进行真正的除法
真值计算给出一个浮点近似值。例如,7386 秒大约是多少小时?使用真正的除法运算符进行除法:
**>>> total_seconds = 7385**
**>>> hours = total_seconds / 3600**
**>>> round(hours,4)**
**2.0514**
注意
我们提供了两个整数值,但得到了一个浮点数的精确结果。与我们之前使用浮点数值的配方一致,我们四舍五入了结果,以避免查看微小的误差值。
这种真正的除法是 Python 3 的一个特性。我们将在接下来的章节中从 Python 2 的角度来看这一点。
有理数分数计算
我们可以使用Fraction
对象和整数进行除法。这将强制结果成为一个数学上精确的有理数:
- 创建至少一个
Fraction
值:
**>>> from fractions import Fraction**
**>>> total_seconds = Fraction(7385)**
- 在计算中使用
Fraction
值。任何整数都将被提升为Fraction
:
**>>> hours = total_seconds / 3600**
**>>> hours**
**Fraction(1477, 720)**
- 如果必要,将精确分数转换为浮点数近似值:
**>>> round(float(hours),4)**
**2.0514**
首先,我们为总秒数创建了一个Fraction
对象。当我们对分数进行算术运算时,Python 会将任何整数提升为分数;这种提升意味着尽可能精确地进行数学运算。
它是如何工作的...
Python 3 有两个除法运算符。
-
/
真正的除法运算符总是尝试产生一个真正的浮点结果。即使两个操作数是整数,它也会这样做。在这方面,这是一个不寻常的运算符。所有其他运算符都试图保留数据的类型。真正的除法操作-当应用于整数时-产生一个float
结果。 -
//
截断除法运算符总是尝试产生一个截断的结果。对于两个整数操作数,这是截断的商。对于两个浮点操作数,这是一个截断的浮点结果:
**>>> 7358.0 // 3600.0**
**2.0**
默认情况下,Python 2 只有一个除法运算符。对于仍在使用 Python 2 的程序员,我们可以开始使用这些新的除法运算符:
**>>> from __future__ import division**
这个导入将安装 Python 3 除法规则。
另请参阅
-
有关浮点数和分数之间的选择,请参阅在浮点数、小数和分数之间进行选择配方
重写一个不可变的字符串
如何重写一个不可变的字符串?我们无法改变字符串内部的单个字符:
**>>> title = "Recipe 5: Rewriting, and the Immutable String"**
**>>> title[8]= ''**
**Traceback (most recent call last):**
**File "<stdin>", line 1, in <module>**
**TypeError: 'str' object does not support item assignment**
由于这不起作用,我们如何对字符串进行更改?
准备好
假设我们有这样一个字符串:
**>>> title = "Recipe 5: Rewriting, and the Immutable String"**
我们想做两个转换:
-
删除
:
之前的部分 -
用
_
替换标点符号,并将所有字符转换为小写
由于我们无法替换字符串对象中的字符,我们必须想出一些替代方案。以下是一些常见的事情,如下所示:
-
使用切片和连接字符串的组合来创建一个新的字符串。
-
在缩短时,我们经常使用
partition()
方法。 -
我们可以用
replace()
方法替换一个字符或子字符串。 -
我们可以将字符串扩展为字符列表,然后再次将字符串连接成单个字符串。这是一个单独配方的主题,使用字符列表构建复杂字符串。
如何做...
由于我们无法直接更新字符串,因此必须用每个修改后的结果替换字符串变量的对象。我们将使用类似于以下的语句:
some_string = some_string.method()
或者我们甚至可以使用:
some_string = some_string[:chop_here]
我们将看一些特定变体的这个一般主题。我们将切片字符串的一部分,我们将替换字符串中的单个字符,并且我们将应用像使字符串小写之类的全面转换。我们还将看看如何删除出现在最终字符串中的额外的_
。
切片字符串的一部分
以下是我们如何通过切片缩短字符串:
- 找到边界:
**>>> colon_position = title.index(':')**
索引函数定位特定的子字符串并返回该子字符串的位置。如果子字符串不存在,它会引发一个异常。这总是true
的结果title[colon_position] == ':'
。
- 选择子字符串:
**>>> discard_text, post_colon_text = title[:colon_position], title[colon_position+1:]**
**>>> discard_text**
**'Recipe 5'**
**>>> post_colon_text**
**' Rewriting, and the Immutable String'**
我们使用切片表示法显示要选择的字符的start:end
。我们还使用多重赋值从两个表达式中分配两个变量discard_text
和post_colon_text
。
我们可以使用partition()
以及手动切片。找到边界并分区:
**>>> pre_colon_text, _, post_colon_text = title.partition(':')**
**>>> pre_colon_text**
**'Recipe 5'**
**>>> post_colon_text**
**' Rewriting, and the Immutable String'**
partition
函数返回三个东西:目标之前的部分,目标和目标之后的部分。我们使用多重赋值将每个对象分配给不同的变量。我们将目标分配给一个名为_
的变量,因为我们将忽略结果的这一部分。这是一个常见的习惯用法,用于我们必须提供一个变量的地方,但我们不关心使用该对象。
使用替换更新字符串
我们可以使用replace()
来删除标点符号。在使用replace
切换标点符号时,将结果保存回原始变量。在这种情况下,post_colon_text
:
**>>> post_colon_text = post_colon_text.replace(' ', '_')**
**>>> post_colon_text = post_colon_text.replace(',', '_')**
**>>> post_colon_text**
**'_Rewriting__and_the_Immutable_String'**
这已经用所需的_
字符替换了两种标点符号。我们可以将其推广到所有标点符号。这利用了for
语句,我们将在第二章中进行讨论,语句和语法。
我们可以遍历所有标点字符:
**>>> from string import whitespace, punctuation**
**>>> for character in whitespace + punctuation:**
**... post_colon_text = post_colon_text.replace(character, '_')**
**>>> post_colon_text**
**'_Rewriting__and_the_Immutable_String'**
当每种标点符号字符被替换时,我们将最新和最好的字符串版本分配给post_colon_text
变量。
使字符串全部小写
另一个转换步骤是将字符串更改为全部小写。与前面的例子一样,我们将结果分配回原始变量。使用lower()
方法,将结果分配给原始变量:
**>>> post_colon_text = post_colon_text.lower()**
删除额外的标点符号
在许多情况下,我们可能会遵循一些额外的步骤。我们经常希望删除前导和尾随的_
字符。我们可以使用strip()
来实现这一点:
**>>> post_colon_text = post_colon_text.strip('_')**
在某些情况下,我们可能会有多个_
字符,因为我们有多个标点符号。最后一步将是这样清理多个_
字符:
**>>> while '__' in post_colon_text:**
**... post_colon_text = post_colon_text.replace('__', '_')**
这是另一个例子,我们一直在使用相同的模式来修改字符串。这取决于while
语句,我们将在第二章中进行讨论,语句和语法。
它是如何工作的...
从技术上讲,我们不能直接修改字符串。字符串的数据结构是不可变的。但是,我们可以将新的字符串赋回原始变量。这种技术的行为与直接修改字符串相同。
当变量的值被替换时,先前的值不再有任何引用,并且被垃圾回收。我们可以通过使用id()
函数跟踪每个单独的字符串对象来看到这一点:
**>>> id(post_colon_text)**
**4346207968**
**>>> post_colon_text = post_colon_text.replace('_','-')**
**>>> id(post_colon_text)**
**4346205488**
您的实际 ID 号可能不同。重要的是,分配给post_colon_text
的原始字符串对象具有一个 ID。分配给post_colon_text
的新字符串对象具有不同的 ID。这是一个新的字符串对象。
当旧字符串没有更多引用时,它会自动从内存中删除。
我们使用了切片表示法来分解字符串。切片有两部分:[start:end]
。切片始终包括起始索引。字符串索引始终从零开始作为第一项。它永远不包括结束索引。
提示
切片中的项目从start
到end-1
具有索引。有时这被称为半开放区间。
想象一下切片是这样的:所有的字符都在索引i的范围内start ≤ i < end。
我们简要提到我们可以省略开始或结束索引。我们实际上可以两者都省略。以下是各种可用的选项:
-
title[colon_position]
:一个单独的项目,我们使用title.index(':')
找到的:
。 -
title[:colon_position]
:省略了开始。它从第一个位置开始,索引为零。 -
title[colon_position+1:]
:省略了结束。它以字符串的结束结束,就好像我们说len(title)
一样。 -
title[:]
:由于开始和结束都被省略了,这是整个字符串。实际上,这是整个字符串的副本。这是复制字符串的快速简单方法。
还有更多...
Python 集合中的索引有更多特性,如字符串。正常索引从左端以 0 开始。我们有一组使用负名称的替代索引,从字符串的右端开始工作。
-
title[-1]
是标题中的最后一个字符,g
-
title[-2]
是倒数第二个字符,n
-
title[-6:]
是最后六个字符,String
我们有很多方法可以从字符串中选择片段和部分。
Python 提供了几十种修改字符串的方法。Python 标准库的第 4.7 节描述了我们可以使用的不同类型的转换。字符串方法有三大类。我们可以询问字符串,我们可以解析字符串,我们可以转换字符串。例如,isnumeric()
等方法告诉我们字符串是否全是数字。
这里有一个例子:
**>>> 'some word'.isnumeric()**
**False**
**>>> '1298'.isnumeric()**
**True**
我们已经使用partition()
方法进行了解析。我们已经使用lower()
方法进行了转换。
另请参阅
-
我们将在从字符列表构建复杂字符串的示例中使用字符串作为列表的技术来修改字符串。
-
有时我们的数据只是一串字节。为了理解它,我们需要将其转换为字符。这是解码字节-如何从一些字节中获取适当的字符的主题。
使用正则表达式进行字符串解析
我们如何分解复杂字符串?如果我们有复杂的、棘手的标点符号怎么办?或者更糟的是,如果我们没有标点符号,而必须依靠数字模式来定位有意义的信息怎么办?
准备工作
分解复杂字符串的最简单方法是将字符串泛化为模式,然后编写描述该模式的正则表达式。
正则表达式可以描述的模式有限。当我们面对像 HTML、XML 或 JSON 这样的深度嵌套的文档时,我们经常遇到问题,无法使用正则表达式。
re
模块包含了我们需要创建和使用正则表达式的各种类和函数。
假设我们想要从食谱网站分解文本。每行看起来像这样:
**>>> ingredient = "Kumquat: 2 cups"**
我们想要将成分与测量分开。
如何做...
要编写和使用正则表达式,我们经常这样做:
- 将示例泛化。在我们的情况下,我们有一些可以泛化的东西:
**(ingredient words): (amount digits) (unit words)**
- 我们用两部分摘要替换了文字:它的含义和它的表示方式。例如,成分表示为单词,数量表示为数字。导入
re
模块:
**>>> import re**
- 将模式重写为正则表达式(RE)表示法:
**>>> pattern_text = r'(?P<ingredient>\w+):\s+(?P<amount>\d+)\s+(?P<unit>\w+)'**
我们已经用\w+
替换了单词等表示提示。我们用\d+
替换了数字。我们用\s+
替换了单个空格,以允许使用一个或多个空格作为标点符号。我们保留了冒号,因为在正则表达式符号中,冒号与自身匹配。
对于数据的每个字段,我们使用了?P<name>
来提供一个标识我们想要提取的数据的名称。我们没有在冒号或空格周围这样做,因为我们不想要这些字符。
REs 使用了很多\
字符。为了使其在 Python 中工作得很好,我们几乎总是使用原始字符串。r'
前缀告诉 Python 不要查看\
字符,也不要用我们键盘上没有的特殊字符替换它们。
- 编译模式:
**>>> pattern = re.compile(pattern_text)**
- 根据输入文本匹配模式。如果输入与模式匹配,我们将得到一个显示匹配详细信息的匹配对象:
**>>> match = pattern.match(ingredient)**
**>>> match is None**
**False**
**>>> match.groups()**
**('Kumquat', '2', 'cups')**
这本身就很酷:我们有一个元组,其中包含字符串中的不同字段。我们将在名为使用元组的食谱中再次使用元组。
- 从匹配对象中提取命名组的字符:
**>>> match.group('ingredient')**
**'Kumquat'**
**>>> match.group('amount')**
**'2'**
**>>> match.group('unit')**
**'cups'**
每个组都由我们在 RE 的(?P<name>...)
部分中使用的名称标识。
它是如何工作的...
我们可以用 RE 描述许多不同类型的字符串模式。
我们展示了许多字符类:
-
\w
匹配任何字母数字字符(a 到 z,A 到 Z,0 到 9) -
\d
匹配任何十进制数字 -
\s
匹配任何空格或制表符
这些类也有反义:
-
\W
匹配任何不是字母或数字的字符 -
\D
匹配任何不是数字的字符 -
\S
匹配任何不是某种空格或制表符的字符
许多字符与自身匹配。然而,一些字符具有特殊含义,我们必须使用\
来逃离这种特殊含义:
-
我们看到
+
作为后缀意味着匹配一个或多个前面的模式。\d+
匹配一个或多个数字。要匹配一个普通的+
,我们需要使用\+
。 -
我们还有
*
作为后缀,它匹配零个或多个前面的模式。\w*
匹配零个或多个字符。要匹配一个*
,我们需要使用\*
。 -
我们有
?
作为后缀,它匹配前面表达式的零次或一次。这个字符在其他地方也有用,意义略有不同。我们在(?P<name>...)
中看到它,其中它在()
内定义了分组的特殊属性。 -
.
匹配任何单个字符。要匹配一个.
,我们需要使用\
。
我们可以使用[]
来创建我们自己独特的字符集,以括起集合的元素。我们可能会有这样的东西:
(?P<name>\w+)\s*[=:]\s*(?P<value>.*)
这里有一个\w+
来匹配任意数量的字母数字字符。这将被收集到一个名为name
的组中。
它使用\s*
来匹配可选的空格序列。
它匹配集合[=:]
中的任何字符。这个集合中的两个字符之一必须存在。
它再次使用\s*
来匹配一个可选的空格序列。
最后,它使用.*
来匹配字符串中的其他所有内容。这被收集到一个名为value
的组中。
我们可以用这个来解析这样的字符串:
size = 12
weight: 14
通过在标点符号上灵活使用,我们可以使程序更容易使用。我们将容忍任意数量的空格,并且=
或:
作为分隔符。
还有更多...
一个很长的正则表达式可能很难阅读。我们有一个巧妙的 Python 技巧,可以以更容易阅读的方式呈现表达式:
**>>> ingredient_pattern = re.compile(**
**... r'(?P<ingredient>\w+):\s+' # name of the ingredient up to the ":"**
**... r'(?P<amount>\d+)\s+' # amount, all digits up to a space**
**... r'(?P<unit>\w+)' # units, alphanumeric characters**
**... )**
这利用了三个语法规则:
-
直到
()
字符匹配完成,语句才算结束 -
相邻的字符串文字会被静默连接成一个长字符串
-
#
和行尾之间的任何内容都是注释,会被忽略
我们在正则表达式的重要子句后面放置了 Python 注释。这可以帮助我们理解我们做了什么,也许帮助我们以后诊断问题。
另请参阅
-
解码字节-如何从一些字节中获取正确的字符食谱
-
有许多关于正则表达式和特别是 Python 正则表达式的书籍,比如掌握 Python 正则表达式(
www.packtpub.com/application-development/mastering-python-regular-expressions
)
使用"template".format()构建复杂的字符串
创建复杂的字符串,在许多方面,与解析复杂的字符串截然相反。通常,我们会使用带有替换规则的模板,将数据放入更复杂的格式中。
准备好了
假设我们有一些数据需要转换为格式良好的消息。我们可能有包括以下内容的数据:
**>>> id = "IAD"**
**>>> location = "Dulles Intl Airport"**
**>>> max_temp = 32**
**>>> min_temp = 13**
**>>> precipitation = 0.4**
我们想要一行看起来像这样的:
**IAD : Dulles Intl Airport : 32 / 13 / 0.40**
如何做到...
- 从结果中创建一个模板字符串,用
{}
占位符替换所有数据项。在每个占位符内,放入数据项的名称。
**'{id} : {location} : {max_temp} / {min_temp} / {precipitation}'**
- 对于每个数据项,在模板字符串的占位符后附加
:数据类型
信息。基本数据类型代码有:
-
s
代表字符串 -
d
代表十进制数 -
浮点数的
f
它会看起来像这样:
**'{id:s} : {location:s} : {max_temp:d} / {min_temp:d} / {precipitation:f}'**
- 在必要的地方添加长度信息。长度并不总是必需的,在某些情况下甚至是不可取的。但在这个例子中,长度信息确保每条消息具有一致的格式。对于字符串和十进制数,前缀格式为长度,如
19s
或3d
。对于浮点数,请使用两部分前缀,如5.2f
,以指定总长度为五个字符,小数点右边为两个。这是整个格式:
**'{id:3d} : {location:19s} : {max_temp:3d} / {min_temp:3d} / {precipitation:5.2f}'**
- 使用此字符串的
format()
方法创建最终字符串:
**>>> '{id:3s} : {location:19s} : {max_temp:3d} / {min_temp:3d} / {precipitation:5.2f}'.format(**
**... id=id, location=location, max_temp=max_temp,**
**... min_temp=min_temp, precipitation=precipitation**
**... )**
**'IAD : Dulles Intl Airport : 32 / 13 / 0.40'**
我们已经按名称在模板字符串的format()
方法中提供了所有变量。这可能会变得乏味。在某些情况下,我们可能想要构建一个带有变量的字典对象。在这种情况下,我们可以使用format_map()
方法:
**>>> data = dict(**
**... id=id, location=location, max_temp=max_temp,**
**... min_temp=min_temp, precipitation=precipitation**
**... )**
**>>> '{id:3s} : {location:19s} : {max_temp:3d} / {min_temp:3d} / {precipitation:5.2f}'.format_map(data)**
**'IAD : Dulles Intl Airport : 32 / 13 / 0.40'**
我们将在第四章中返回到字典,内置数据结构 - 列表,集合,字典。
内置的vars()
函数为我们构建了所有本地变量的字典:
**>>> '{id:3s} : {location:19s} : {max_temp:3d} / {min_temp:3d} / {precipitation:5.2f}'.format_map(**
**... vars()**
**... )**
**'IAD : Dulles Intl Airport : 32 / 13 / 0.40'**
vars()
函数非常方便,可以自动构建字典。
它是如何工作的...
字符串format()
和format_map()
方法可以为我们执行相对复杂的字符串组装。
基本功能是根据关键字参数的名称或字典中的键将数据插入到字符串中。变量也可以按位置插入 - 我们可以提供位置数字而不是名称。我们可以使用格式规范,如{0:3s}
来使用format()
的第一个位置参数。
我们已经看到了三种格式转换 - s
,d
,f
- 还有许多其他的。详细信息请参阅Python 标准库的第 6.1.3 节。这里是我们可能使用的一些格式转换:
-
b
代表二进制,基数为 2。 -
c
代表 Unicode 字符。值必须是一个数字,它将被转换为一个字符。通常,我们使用十六进制数,所以你可能想尝试一些有趣的值,比如0x2661
到0x2666
。 -
d
代表十进制数。 -
E
和e
代表科学计数法。6.626E-34
或6.626e-34
取决于使用了哪个 E 或 e 字符。 -
F
和f
代表浮点数。对于非数字,f
格式显示小写nan
;F
格式显示大写NAN
。 -
G
和g
代表通用。这会自动在E
和F
(或e
和f
)之间切换,以保持输出在给定的大小字段中。对于格式为20.5G
,将使用F
格式显示最多 20 位数字。较大的数字将使用E
格式。 -
n
代表特定于区域设置的十进制数。这将根据当前区域设置插入,
或.
字符。默认区域设置可能没有定义千位分隔符。有关更多信息,请参见locale
模块。 -
o
代表八进制,基数为 8。 -
s
代表字符串。 -
X
和x
用于十六进制,基数 16。数字包括大写A-F
和小写a-f
,具体取决于使用哪个X
或x
格式字符。 -
%
是用于百分比的。数字乘以 100 并包括%
。
我们有许多前缀可以用于这些不同类型。最常见的是长度。我们可能会使用{name:5d}
来输入一个 5 位数。前面类型有几个前缀:
-
填充和对齐:我们可以指定特定的填充字符(默认为空格)和对齐方式。数字通常右对齐,字符串左对齐。我们可以使用
<
,>
或^
来改变这一点。这会强制左对齐、右对齐或居中。有一个奇特的=
对齐方式,用于在前导符号后放置填充。 -
符号:默认规则是在需要时使用前导负号。我们可以使用
+
在所有数字上加上符号,使用-
只在负数上加上符号,使用空格代替正数的加号。在科学输出中,我们必须使用{value: 5.3f}
。空格确保留有空间放置符号,确保所有小数点排列得很好。 -
备用形式:我们可以使用
#
来获得备用形式。我们可能会有类似{0:#x}
,{0:#o}
,{0:#b}
的东西,以获得十六进制、八进制或二进制值的前缀。有了前缀,数字看起来像0xnnn
,0onnn
或0bnnn
。默认情况下省略了两个字符的前缀。 -
前导零:我们可以包括
0
来获得前导零以填充数字的前部。类似{code:08x}
将产生一个十六进制值,前面有前导零以将其填充到八个字符。 -
宽度和精度:对于整数值和字符串,我们只提供宽度。对于浮点值,我们经常提供
width.precision
。
有时我们不会使用{name:format}
规范。有时我们需要使用{name!conversion}
规范。只有三种转换可用。
-
{name!r}
显示了由repr(name)
产生的表示 -
{name!s}
显示了由str(name)
产生的字符串值 -
{name!a}
显示了由ascii(name)
产生的 ASCII 值
在第六章,类和对象的基础中,我们将利用{name!r}
格式规范的想法来简化显示有关相关对象的信息。
还有更多...
一个方便的调试技巧:
**print("some_variable={some_variable!r}".format_map(vars()))**
vars()
函数——没有参数——将所有局部变量收集到一个映射中。我们为format_map()
提供该映射。格式模板可以使用许多{variable_name!r}
来显示有关我们在局部变量中拥有的各种对象的详细信息。
在类定义内部,我们可以使用诸如vars(self)
的技术。这预示着第六章,类和对象的基础:
**>>> class Summary:**
**... def __init__(self, id, location, min_temp, max_temp, precipitation):**
**... self.id= id**
**... self.location= location**
**... self.min_temp= min_temp**
**... self.max_temp= max_temp**
**... self.precipitation= precipitation**
**... def __str__(self):**
**... return '{id:3s} : {location:19s} : {max_temp:3d} / {min_temp:3d} / {precipitation:5.2f}'.format_map(**
**... vars(self)**
**... )**
**>>> s= Summary('IAD', 'Dulles Intl Airport', 13, 32, 0.4)**
**>>> print(s)**
**IAD : Dulles Intl Airport : 32 / 13 / 0.40**
我们的类定义包括一个__str__()
方法。这个方法依赖于vars(self)
来创建一个有用的对象属性字典。
另请参阅
- Python 标准库,第 6.1.3 节中有关字符串格式方法的所有细节。
从字符列表构建复杂字符串
我们如何对不可变字符串进行非常复杂的更改?我们可以从单个字符组装一个字符串吗?
在大多数情况下,我们已经看到的配方为我们创建和修改字符串提供了许多工具。我们还有更多的方法来解决字符串操作问题。我们将使用列表对象。这将与第四章中的一些配方相契合,内置数据结构-列表、集合、字典。
准备工作
这是一个我们想重新排列的字符串:
**>>> title = "Recipe 5: Rewriting an Immutable String"**
我们想做两个转换:
-
删除
:
之前的部分 -
用
_
替换标点符号,并将所有字符转换为小写
我们将利用string
模块:
**>>> from string import whitespace, punctuation**
这有两个重要的常数:
-
string.whitespace
列出了所有常见的空白字符,包括空格和制表符 -
string.punctuation
列出了常见的 ASCII 标点符号。Unicode 有一个更大的标点符号列表;也可以根据您的区域设置使用
如何做...
我们可以处理分解为列表的字符串。我们将在第四章中更深入地研究列表,内置数据结构-列表、集合、字典。
- 将字符串分解为
列表
对象:
**>>> title_list = list(title)**
- 找到分区字符。列表的
index()
方法与列表的index()
方法具有相同的语义。它定位具有给定值的位置:
**>>> colon_position = title_list.index(':')**
- 删除不再需要的字符。
del
语句可以从列表中删除项目。列表是可变数据结构:
**>>> del title_list[:colon_position+1]**
我们不需要仔细处理原始字符串的有用部分。我们可以从列表中删除项目。
- 通过遍历每个位置来替换标点符号。在这种情况下,我们将使用
for
语句访问字符串中的每个索引:
**>>> for position in range(len(title_list)):**
**... if title_list[position] in whitespace+punctuation:**
**... title_list[position]= '_'**
- 表达式
range(len(title_list))
生成0
和len(title_list)-1
之间的所有值。这确保了位置的值将是列表中每个值的索引。连接字符列表以创建新字符串。当将字符串连接在一起时,使用零长度字符串''
作为分隔符似乎有点奇怪。但是,它完美地工作:
**>>> title = ''.join(title_list)**
**>>> title**
**'_Rewriting_an_Immutable_String'**
我们将结果字符串分配回原始变量。原始字符串对象,该对象已被该变量引用,不再需要:它已从内存中删除。新的字符串对象替换了变量的值。
它是如何工作的...
这是一种表示变化的技巧。由于字符串是不可变的,我们无法更新它。但是,我们可以将其转换为可变形式;在这种情况下,是列表。我们可以对可变列表对象进行任何所需的更改。完成后,我们可以将表示从列表更改回字符串。
字符串提供了一些列表没有的功能。相反,字符串提供了列表没有的一些功能。我们无法像转换字符串那样将列表转换为小写。
这里有一个重要的权衡:
-
字符串是不可变的,这使它们非常快。字符串专注于 Unicode 字符。当我们查看映射和集合时,我们可以使用字符串作为映射的键和集合中的项目,因为该值是不可变的。
-
列表是可变的。操作速度较慢。列表可以容纳任何类型的项目。我们不能使用列表作为映射的键或集合中的项目,因为值可能会改变。
字符串和列表都是特殊类型的序列。因此,它们具有许多共同的特征。基本的项目索引和切片功能是共享的。同样,列表使用与字符串相同类型的负索引值:list[-1]
是列表对象中的最后一个项目。
我们将在第四章中再次使用可变数据结构,内置数据结构-列表、集合、字典。
有更多
一旦我们开始处理字符列表而不是字符串,我们就不再具有字符串处理方法。我们有许多可用的列表处理技术。除了能够从列表中删除项目外,我们还可以附加项目,用另一个列表扩展列表,并将字符插入列表中。
我们也可以稍微改变我们的观点,看看字符串列表而不是字符列表。当我们有一个字符串列表时,做''.join(list)
的技巧也会起作用。例如,我们可能会这样做:
**>>> title_list.insert(0, 'prefix')**
**>>> ''.join(title_list)**
**'prefix_Rewriting_an_Immutable_String'**
我们的title_list
对象将被改变为一个包含六个字符的字符串前缀,以及 30 个单独字符的列表。
另请参阅
-
我们还可以使用字符串的内部方法来处理字符串。有关更多技术,请参见重写不可变字符串配方。
-
有时,我们需要构建一个字符串,然后将其转换为字节。查看编码字符串 - 创建 ASCII 和 UTF-8 字节配方,了解我们可以如何做到这一点。
-
其他时候,我们需要将字节转换为字符串。参见解码字节 - 如何从一些字节中获取正确的字符配方。
使用不在键盘上的 Unicode 字符
一个大键盘可能有近 100 个单独的键。其中不到 50 个是字母、数字和标点符号。至少有十几个是功能键,除了简单地插入字母到文档之外还可以做其他事情。一些键是不同类型的修饰符,意味着要与另一个键一起使用——我们可能有Shift,Ctrl,Option 和Command。
大多数操作系统都接受简单的键组合,可以创建大约 100 个左右的字符。更复杂的键组合可能会创建另外大约 100 个不太受欢迎的字符。这甚至无法涵盖世界各种语言的百万字符。我们的计算机字体中还有图标、表情符号和特殊符号。我们如何才能获得所有这些字形?
准备工作
Python 使用 Unicode。有数百万个可用的 Unicode 字符。
我们可以在en.wikipedia.org/wiki/List_of_Unicode_characters 和 http://www.unicode.org/charts/
上看到所有可用的字符。
我们需要 Unicode 字符编号。我们可能还需要 Unicode 字符名称。
我们计算机上的某个字体可能没有设计为提供所有这些字符的字形。特别是,Windows 计算机字体可能无法显示其中一些字符。有时需要使用 Windows 命令更改到代码页 65001:
**chcp 65001**
Linux 和 Mac OS X 很少出现 Unicode 字符的问题。
如何做...
Python 使用转义序列来扩展我们可以输入的普通字符,以涵盖 Unicode 字符的广阔空间。转义序列以\
字符开头。下一个字符准确告诉 Unicode 字符将如何表示。找到所需的字符。获取名称或数字。数字总是以十六进制、16 进制给出。它们通常写为U+2680
。名称可能是DIE FACE-1
。使用\unnnn,最多使用四位数字。或使用\N{name}与拼写的名称。如果数字超过四位数,使用\Unnnnnnnn,数字填充到八位数:
是的,我们可以在 Python 输出中包含各种字符。要在字符串中放置\
字符,我们需要使用\\
。例如,我们可能需要这个用于 Windows 文件名。
工作原理...
Python 在内部使用 Unicode。我们可以直接使用键盘输入的 128 个左右字符都有方便的内部 Unicode 编号。
当我们写:
**'HELLO'**
Python 将其视为此的简写:
**'\u0048\u0045\u004c\u004c\u004f'**
一旦我们超出键盘上的字符,剩下的数百万个字符只能通过它们的编号来识别。
当 Python 编译字符串时,\uxx,\Uxxxxxxxx 和\N{name}都将被正确的 Unicode 字符替换。如果我们有一些语法错误,例如\N{name
没有闭合}
,我们将立即从 Python 的内部语法检查中获得错误。
回到使用正则表达式解析字符串配方,我们注意到正则表达式使用了很多\
字符,我们特别不希望 Python 的正常编译器去处理它们;我们在正则表达式字符串上使用r'
前缀,以防止\
被视为转义并可能转换为其他内容。
如果我们需要在正则表达式中使用 Unicode?我们需要在正则表达式中到处使用\\
。我们可能会看到这个'\\w+[\u2680\u2681\u2682\u2683\u2684\u2685]\\d+'
。我们省略了字符串的r'
前缀。我们将用于正则表达式的\
加倍。我们使用\uxxxx
表示模式中的 Unicode 字符。Python 的内部编译器将用 Unicode 字符替换\uxxxx
,并在内部用单个\
替换\\
。
注意
当我们在>>>
提示符下查看一个字符串时,Python 会以规范形式显示字符串。Python 更喜欢使用'
作为分隔符,尽管我们可以使用'
或"
作为字符串分隔符。Python 通常不显示原始字符串,而是将所有必要的转义序列放回字符串中:
>>> r"\w+"
'\\w+'
我们提供了一个原始形式的字符串。Python 以规范形式显示它。
另请参阅
-
在编码字符串 - 创建 ASCII 和 UTF-8 字节和解码字节 - 如何从一些字节中获取正确的字符中,我们将看看如何将 Unicode 字符转换为字节序列,以便将它们写入文件。我们将看看如何将文件中的字节(或从网站下载的字节)转换为 Unicode 字符,以便进行处理。
-
如果你对历史感兴趣,你可以在这里阅读 ASCII 和 EBCDIC 以及其他老式字符编码的相关内容
www.unicode.org/charts/
。
编码字符串 - 创建 ASCII 和 UTF-8 字节
我们的计算机文件是字节。当我们上传或下载文件时,通信是以字节为单位的。一个字节只有 256 个不同的值。我们的 Python 字符是 Unicode。Unicode 字符远远超过 256 个。
如何将 Unicode 字符映射到字节以便写入文件或传输?
准备工作
从历史上看,一个字符占用 1 个字节。Python 利用旧的 ASCII 编码方案进行字节处理;这有时会导致字节和正确的 Unicode 字符之间的混淆。
Unicode 字符被编码为字节序列。我们有许多标准编码和许多非标准编码。
此外,我们还有一些只适用于小部分 Unicode 字符的编码。我们尽量避免这种情况,但有些情况下我们需要使用子集编码方案。
除非我们有一个非常好的理由,我们几乎总是使用 UTF-8 编码来处理 Unicode 字符。它的主要优势是它是拉丁字母表的紧凑表示,用于英语和一些欧洲语言。
有时,互联网协议需要 ASCII 字符。这是一个特殊情况,需要一些小心,因为 ASCII 编码只能处理 Unicode 字符的一个小子集。
如何做...
Python 通常会使用我们操作系统的默认编码进行文件和互联网通信。细节因操作系统而异:
- 我们可以使用
PYTHONIOENCODING
环境变量进行一般设置。我们在 Python 之外设置这个变量,以确保在任何地方都使用特定的编码。设置环境变量如下:
**export PYTHONIOENCODING=UTF-8**
- 运行 Python:
**python3.5**
- 有时候我们需要在脚本中打开文件时进行特定的设置。我们将在第九章中返回这个问题,输入/输出、物理格式、逻辑布局。使用给定的编码打开文件。读取或写入 Unicode 字符到文件中:
**>>> with open('some_file.txt', 'w', encoding='utf-8') as output:**
**... print( 'You drew \U0001F000', file=output )**
**>>> with open('some_file.txt', 'r', encoding='utf-8') as input:**
**... text = input.read()**
**>>> text**
**'You drew �'**
在罕见的情况下,我们也可以手动编码字符,如果我们需要以字节模式打开文件;如果我们使用wb
模式,我们需要手动编码:
**>>> string_bytes = 'You drew \U0001F000'.encode('utf-8')**
**>>> string_bytes**
**b'You drew \xf0\x9f\x80\x80'**
我们可以看到一系列字节(\xf0\x9f\x80\x80
)被用来编码一个 Unicode 字符U+1F000
,。
工作原理...
Unicode 定义了许多编码方案。虽然 UTF-8 是最流行的,但还有 UTF-16 和 UTF-32。数字是每个字符的典型位数。一个包含 1000 个字符的 UTF-32 编码文件将是 4000 个 8 位字节。一个包含 1000 个字符的 UTF-8 编码文件可能只有 1000 个字节,具体取决于字符的确切混合。在 UTF-8 编码中,Unicode 编号大于U+007F
的字符需要多个字节。
各种操作系统都有自己的编码方案。Mac OS X 文件通常以Mac Roman
或Latin-1
编码。Windows 文件可能使用CP1252
编码。
所有这些方案的要点是有一个字节序列,可以映射到一个 Unicode 字符。而且-反过来-一种将每个 Unicode 字符映射到一个或多个字节的方法。理想情况下,所有的 Unicode 字符都被考虑在内。实际上,一些编码方案是不完整的。棘手的部分是避免写入比必要的更多的字节。
历史上的ASCII
编码只能表示大约 250 个 Unicode 字符作为字节。很容易创建一个不能使用 ASCII 方案编码的字符串。
这就是错误的样子:
**>>> 'You drew \U0001F000'.encode('ascii')**
**Traceback (most recent call last):**
**File "<stdin>", line 1, in <module>**
**UnicodeEncodeError: 'ascii' codec can't encode character '\U0001f000' in position 9: ordinal not in range(128)**
当我们意外地用一个选择不当的编码打开文件时,我们可能会看到这种错误。当我们看到这个错误时,我们需要改变我们的处理方式,选择一个更有用的编码;理想情况下是 UTF-8。
注意
字节 vs 字符串
字节通常使用可打印字符显示。
我们会看到b'hello'
作为一个五字节值的简写。这些字母是使用旧的 ASCII 编码方案选择的。大约从0x20
到0xFE
的许多字节值将显示为字符。
这可能会让人困惑。b'
的前缀是我们正在看字节,而不是合适的 Unicode 字符。
另见
-
有许多构建数据字符串的方法。查看使用"template".format()构建复杂字符串和从字符列表构建复杂字符串配方,了解创建复杂字符串的示例。这个想法是我们可能有一个构建复杂字符串的应用程序,然后将其编码为字节。
-
有关 UTF-8 编码的更多信息,请参阅
en.wikipedia.org/wiki/UTF-8
。 -
有关 Unicode 编码的一般信息,请参阅
unicode.org/faq/utf_bom.html
。
解码字节-如何从一些字节中获得正确的字符
我们如何处理没有正确编码的文件?我们如何处理用 ASCII 编码写的文件?
从互联网下载的几乎总是字节,而不是字符。我们如何从字节流中解码字符?
此外,当我们使用subprocess
模块时,操作系统命令的结果是字节。我们如何恢复正确的字符?
这个大部分也与第九章中的材料相关,输入/输出、物理格式、逻辑布局。我们在这里包含了这个配方,因为它是前一个配方的反向,编码字符串-创建 ASCII 和 UTF-8 字节。
准备好
假设我们对近海海洋天气预报感兴趣。也许是因为我们拥有一艘大帆船。或者是因为我们的好朋友拥有一艘大帆船,正在离开切萨皮克湾前往加勒比海。
来自国家气象局维吉尼亚州韦克菲尔德办公室的有没有特别的警告?
这里是我们可以得到警告的地方:www.nws.noaa.gov/view/national.php?prod=SMW&sid=AKQ
。
我们可以使用 Python 的urllib
模块下载这个。
**>>> import urllib.request**
**>>> warnings_uri= 'http://www.nws.noaa.gov/view/national.php?prod=SMW&sid=AKQ'**
**>>> with urllib.request.urlopen(warnings_uri) as source:**
**... warnings_text= source.read()**
或者,我们可以使用curl
或wget
等程序来获取这个。我们可以这样做:
**curl -O http://www.nws.noaa.gov/view/national.php?prod=SMW&sid=AKQ**
**mv national.php\?prod\=SMW AKQ.html**
由于curl
给我们留下了一个尴尬的文件名,我们需要重命名文件。
forecast_text
值是一系列字节。它不是一个合适的字符串。我们可以知道这一点,因为它是这样开始的:
**>>> warnings_text[:80]**
**b'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.or'**
并且提供了一段时间的细节。因为它以b'
开头,所以它是字节,而不是合适的 Unicode 字符。它可能是用 UTF-8 编码的,这意味着一些字符可能具有奇怪的\xnn
转义序列,而不是合适的字符。我们想要有合适的字符。
提示
字节与字符串
字节通常使用可打印字符显示。
我们将b'hello'
看作是一个五字节值的简写。字母是使用旧的 ASCII 编码方案选择的。大约从0x20
到0xFE
的许多字节值将显示为字符。
这可能会让人感到困惑。b'
的前缀是我们正在查看字节而不是合适的 Unicode 字符的提示。
通常,字节的行为有点像字符串。有时我们可以直接使用字节。大多数情况下,我们会想要解码字节并创建合适的 Unicode 字符。
如何做...
- 如果可能的话,确定编码方案。为了解码字节以创建合适的 Unicode 字符,我们需要知道使用了什么编码方案。当我们读取 XML 文档时,文档中提供了一个重要提示:
**<?xml version="1.0" encoding="UTF-8"?>**
在浏览网页时,通常会有包含此信息的页眉:
**Content-Type: text/html; charset=ISO-8859-4**
有时,HTML 页面可能包括这部分内容作为页眉的一部分:
**<meta http-equiv="Content-Type" content="text/html; charset=utf-8">**
在其他情况下,我们只能猜测。在美国天气数据的情况下,UTF-8 是一个很好的第一猜测。其他好的猜测包括 ISO-8859-1。在某些情况下,猜测将取决于语言。
- 第 7.2.3 节,Python 标准库列出了可用的标准编码。解码数据:
**>>> document = forecast_text.decode("UTF-8")**
**>>> document[:80]**
**'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.or'**
b'
前缀消失了。我们从字节流中创建了一个合适的 Unicode 字符字符串。
- 如果此步骤出现异常,则我们对编码的猜测是错误的。我们需要尝试另一种编码。解析生成的文档。
由于这是一个 HTML 文档,我们应该使用Beautiful Soup。请参见www.crummy.com/software/BeautifulSoup/
。
然而,我们可以从这个文档中提取一条信息而不完全解析 HTML:
**>>> import re**
**>>> title_pattern = re.compile(r"\<h3\>(.*?)\</h3\>")**
**>>> title_pattern.search( document )**
**<_sre.SRE_Match object; span=(3438, 3489), match='<h3>There are no products active at this time.</h>**
这告诉我们我们需要知道的信息:目前没有警告。这并不意味着一帆风顺,但这意味着没有任何可能引起灾难的重大天气系统。
工作原理...
有关 Unicode 以及将 Unicode 字符编码为字节流的不同方式的更多信息,请参见编码字符串-创建 ASCII 和 UTF-8 字节示例。
在操作系统的基础上,文件和网络连接是由字节构建起来的。是我们的软件解码字节来发现内容。它可能是字符、图像或声音。在某些情况下,默认的假设是错误的,我们需要自己解码。
另请参见
-
一旦我们恢复了字符串数据,我们有许多解析或重写它的方法。参见使用正则表达式解析字符串示例。
-
有关编码的更多信息,请参见
en.wikipedia.org/wiki/UTF-8
和unicode.org/faq/utf_bom.html
。
使用项目的元组
如何最好地表示简单的(x, y)和(r, g, b)值组?我们如何将诸如纬度和经度之类的成对物品保持在一起?
准备工作
在使用正则表达式解析字符串示例中,我们跳过了一个有趣的数据结构。
我们有这样的数据:
**>>> ingredient = "Kumquat: 2 cups"**
我们使用类似于这样的正则表达式将其解析为有意义的数据:
**>>> import re**
**>>> ingredient_pattern = re.compile(r'(?P<ingredient>\w+):\s+(?P<amount>\d+)\s+(?P<unit>\w+)')**
**>>> match = ingredient_pattern.match( ingredient )**
**>>> match.groups()**
**('Kumquat', '2', 'cups')**
结果是一个包含三个数据片段的元组对象。有很多地方可以使用这种分组数据。
如何做...
我们将从两个方面来看这个问题:将事物放入元组中和从元组中取出事物。
创建元组
有很多地方,Python 会为我们创建数据的元组。在使用正则表达式解析字符串配方的准备就绪部分中,我们展示了正则表达式匹配对象将创建一个从字符串中解析出的文本元组。
我们也可以创建自己的元组。以下是步骤:
-
将数据括在
()
中。 -
用
,
分隔项目。
**>>> from fractions import Fraction**
**>>> my_data = ('Rice', Fraction(1/4), 'cups')**
对于单元素元组或单例,有一个重要的特殊情况。即使元组中只有一个项目,我们也必须包含一个额外的,
。
**>>> one_tuple = ('item', )**
**>>> len(one_tuple)**
**1**
提示
()
字符并不总是必需的。有几种情况下我们可以省略它们。省略它们并不是一个好主意,但当我们有一个额外的逗号时,我们可以看到有趣的事情:
>>> 355,
(355,)
在355
后面的额外逗号将该值变成了一个单元素元组。
从元组中提取项目
元组的概念是作为一个包含一定数量项目的容器,这个数量由问题域确定:例如,(红色,绿色,蓝色)
颜色编号。项目的数量始终是三个。
在我们的例子中,我们有一个成分、一个数量和一个单位。这必须是一个三个项目的集合。我们可以以两种方式查看单个项目:
- 按索引位置:位置从左边开始编号为零:
**>>> my_data[1]**
**Fraction(1, 4)**
- 使用多重赋值:
**>>> ingredient, amount, unit = my_data**
**>>> ingredient**
**'Rice'**
**>>> unit**
**'cups'**
元组——就像字符串一样——是不可变的。我们不能改变元组中的单个项目。当我们想要将数据保持在一起时,我们使用元组。
它是如何工作的...
元组是“序列”的更一般类别的一个例子。我们可以对序列做一些事情。
以下是一个我们可以使用的示例元组:
**>>> t = ('Kumquat', '2', 'cups')**
以下是我们可以在这个元组上执行的一些操作:
t
中有多少个项目?
**>>> len(t)**
**3**
- 特定值在
t
中出现了多少次?
**>>> t.count('2')**
**1**
- 哪个位置有特定的值?
**>>> t.index('cups')**
**2**
**>>> t[2]**
**'cups'**
- 当一个项目不存在时,我们会得到一个异常:
**>>> t.index('Rice')**
**Traceback (most recent call last):**
**File "<stdin>", line 1, in <module>**
**ValueError: tuple.index(x): x not in tuple**
- 特定值是否存在?
**>>> 'Rice' in t**
**False**
还有更多
元组,就像字符串一样,是一系列项目。对于字符串,它是一系列字符。对于元组,它是一系列许多东西。因为它们都是序列,它们有一些共同的特点。我们注意到我们可以通过它们的索引位置取出单个项目。我们可以使用index()
方法来定位项目的位置。
相似之处就到此为止。字符串有许多方法来创建一个新的字符串,这是对字符串的转换,还有解析字符串的方法,以及确定字符串内容的方法。元组没有这些额外的功能。它可能是最简单的数据结构。
另请参阅...
-
我们还在从字符列表构建复杂字符串配方中查看了另一个序列,即列表。
-
我们还将在第四章中查看序列,内置数据结构-列表、元组、集合、字典