图灵教育 2019-04-09 15:22:39 本文会通过一些实例来介绍Python 的一些特性,每个实例都会解决具体的问题和难题。
创建有意义的名称和使用变量如何确保程序易于理解呢?要编写富有表现力的代码,一个核心要素就是使用有意义的名称。但什么是有意义的呢?在本实例中,我们将回顾一些创建有意义的Python名称的通用规则。 我们还将介绍Python 的一些不同形式的赋值语句,如用同一条语句为多个变量赋值。 一、准备工作 创建名称的核心问题是:被命名的是什么? 对于软件,我们需要一个描述被命名对象的名称。显然,像x 这样的名称不是很有描述性,它似乎并不指向实际的事物。模糊的非描述性名称在一些程序设计中很常见,令人十分痛苦。当使用它们时,无助于其他人理解我们的程序。描述性名称则一目了然。 在命名时,区分解决方案域和问题域(真正想要解决的问题)也很重要。解决方案域包括Python、操作系统和互联网的技术细节。不需要深入的解释,任何人在阅读代码时都可以看到解决方案。然而,问题域可能因技术细节而变得模糊。我们的任务是使问题清晰可见,而精心挑选的名称将对此有所帮助。 二、实战演练 首先看看如何命名,然后再学习如何为对象分配名称。 1. 明智地选择名称 在纯技术层面上,Python 名称必须以字母开头。它们可以包括任意数量的字母、数字和下划线。因为Python 3 基于Unicode,所以字母不限于拉丁字母。虽然通常使用拉丁字母A~Z,但这不是必须遵循的规定。 当创建一个描述性变量时,我们需要创建既具体又能表达程序中事物之间关系的名称。一种广泛使用的命名技巧就是创建“从特殊到一般”这种风格的长名称。 选择名称的步骤如下。 (1) 名称的最后一部分是事物的广义概要。有时候,仅此一部分就能满足命名的需要,靠上下文提供其余的信息。稍后将介绍一些典型的广义概要的类别。 (2) 在应用程序或问题域周围使用前缀限定名称。 (3) 如有必要,使用更精确和专用的前缀,以阐明它与其他类、模块、包、函数和其他对象的区别。对前缀有疑问时,回想一下域名的工作原理。例如,mail.google.com 这个名称表明了从特殊到一般的三个级别。三个级别的命名并不罕见,我们经常采用这种命名方法。 (4) 根据在Python 中的使用方法来命名。需要命名的事物有三大类,如下所示。
如何选择名称中广义类别的那一部分呢?通用类别取决于讨论的是事物还是事物的属性。虽然世界上有很多事物,但我们仍然可以创建一些有用的广义分类,例如文档、企业、地点、程序、产品、过程、人、资产、规则、条件、植物、动物、矿物等。 然后可以用修饰语来限定这些名称:
第一个示例是Document 类,我们通过添加一个前缀对其进行了略微的限定,即StatusDocument,又通过将其命名为FinalStatusDocument 来进一步限定。 第二个示例是Name 类,我们通过详细说明它是一个ReceivedInventoryItemName 来对其进行限定。该示例需要一个4 个级别的名称来阐明。 对象通常具有特性(property)或者属性(attribute)。它们应当是完整名称的一部分,可以根据其表示的信息类型进行分解,如数量、代码、标识符、名称、文本、日期、时间、日期时间、图片、视频、声音、图形、值、速率、百分比、尺寸等。 命名的思路就是把狭义、详细的描述放在最前面,把宽泛的信息放在最后:
在第一个示例中,height 限定了更一般的表示术语value,而measured_height_value 做了进一步限定。通过这个名称,可以思考一下其他与hight 相关的变体。类似的思想也适用于weight_value、delivery_date 和location_code。这些名称都有一个或者两个限定前缀。
2. 为对象分配名称 Python 没有使用静态变量定义。当把名称分配给对象时,就会创建变量。把对象看作处理过程的核心非常重要,变量有点像标识对象的标签。使用基本赋值语句的方法如下。 (1) 创建对象。在许多示例中,对象以字面量的形式创建。我们使用355 或113 作为Python 中整数对象的字面量表示,也可以使用FireBrick 表示字符串,或使用(178,34,34)表示元组。 (2) 编写如下类型的语句:变量 = 对象。例如: >>> circumference_diameter_ratio = 355/113 我们创建了一些对象并把它们赋值给了变量。第一个对象是数值计算的结果,接下来的两个对象是简单的字面量。对象通常由包含函数或类的表达式创建。 上面的基本赋值语句并不是唯一的赋值方式,还可以使用链式赋值的方式,将一个对象分配给多个变量,例如: >>> target_color_name = first_color_name = 'FireBrick' 上例为同一个字符串对象创建了两个名称。可以通过检查Python 使用的内部ID 值来确认这两个对象是否为同一个对象: >>> id(target_color_name) == id(first_color_name) 结果表明,这两个对象的内部ID 值是相同的。
随后介绍数字和集合时将会说明结合运算符进行赋值的方法。例如: >>> total_count = 0 我们通过运算符进行了增量赋值。total_count + = 5 与total_count = total_count + 5是等价的。增量赋值的优点在于简化了表达式。 三、工作原理 本实例创建名称的方法遵循如下模式:狭义的、更具体的限定符放在前面,更宽泛的、不太特定的类别放在最后。这种方法遵守用于域名和电子邮件地址的通用约定。 例如,域名mail.google.com 包括特定的服务、更通用的企业和最后的非常通用的域,这遵循了从窄到宽的原则。 又如,service@packtpub.com 以具体的用户名开始,然后是更通用的企业,最后是非常通用的域。甚至用户名(PacktPub)也是一个具有两部分的名称,包括限定的企业名称(Packt),以及更广泛的行业[Pub,“Publishing”(出版)的简写,而不是“Public House”(酒吧)的简写]。 赋值语句是为对象命名的唯一途径。 四、补充内容 我们将在所有实例中使用描述性名称。
几乎每个示例都涉及变量赋值。变量赋值是有状态的面向对象编程的核心。 五、延伸阅读 描述性命名是一个正在研讨的主题,涉及两个方面——语法和语义。Python 语法的设想起始于著名的PEP-8(Python Enhancement Proposal number 8)。PEP-8 建议使用CamelCase 和snake_case 命名风格。 此外,务必进行以下操作: >>> import this 这有助于领悟Python 的设计哲学。
使用大整数和小整数许多编程语言区分整数、字节和长整数,有些编程语言还存在有符号整数和无符号整数的区别。如何将这些概念与Python 联系起来呢? 答案是“不需要”。Python 以统一的方式处理所有类型的整数。对于Python,从几个字节到数百位的巨大数字,都是整数。 一、准备工作 假设我们需要计算一些非常大的数字,例如,计算一副52 张的扑克牌的排列数。52! = 52 × 51×50 × … × 2 × 1,这是一个非常大的数字。可以在Python 中实现这个运算吗? 二、实战演练 别担心!Python 表现得好像有一个通用的整数类型,涵盖了所有整数,从几个字节到填满所有内存的整数。正确使用整数的步骤如下。 (1) 写下你需要的数字,比如一些小数字:355,113。实际上,数字的大小没有上限。 (2) 创建一个非常小的值——单个字节,如下所示: >>> 2 或者使用十六进制: >>> 0xff 后面的实例中将讨论只含有一个值的字节序列: >>> b'\xfe' 严格说来,这不是一个整数。它有一个前缀b',这表明它是一个一字节序列(1-byte sequence)。 (3) 通过计算创建一个很大的数字。例如: >>> 2 ** 2048 该数字有617 个数位,这里并没有完全显示。 三、工作原理 Python 内部使用两种数字,两者之间的转换是无缝且自动的。 对于较小的数字,Python 通常使用4 字节或8 字节的整数值。细节隐藏在CPython 的内核中,并依赖于构建Python 的C 编译器。 对于超出sys.maxsize 的较大数字,Python 将其切换到大整数——数字(digit)序列。在这种情况下,一位数字通常意味着30 位(bit)的值。 一副52张的扑克牌有多少种排列方法?答案是52! ≈ 8 ×10^67。我们将使用math 模块的factorial函数计算这个大整数,如下所示: >>> import math 这些巨大的数字工作得非常完美! 计算52! 的第一部分(从52 × 51 × 50 × …一直到约42)可以完全使用较小的整数来执行。在此之后,其余的计算必须切换到大整数。我们看不到切换过程,只能看到结果。 通过下面的示例可以了解整数内部的一些细节。 >>> import sys sys.maxsize 的值是小整数中的最大值。我们通过计算以2 为底的对数来说明这个数字需要多 少位。 通过计算可知,Python 使用63 位值来表示小整数。小整数的范围是从-2^64 到2^63-1。在此范围之外,使用大整数。 通过sys.int_info 的值可知,大整数是使用30 位的数字序列,每个数字占用4 字节。 像52! 这样比较大的值,由8 个上述30 位的数字组成。一个数字需要30 位来表示可能有些令人困惑。以用10 个符号表示十进制(base 10)的数字为例,我们需要2**30 个不同的符号来表示这些大数字的每位数。 涉及多个大整数值的计算可能会消耗相当大的内存空间。小数字怎么办呢? Python 如何跟踪大量的小数字,如1 和0? 对于常用的数字(-5 到256),Python 实际上创建了一个私有的对象池来优化内存管理。你可以在检查整数对象的id()值时得到验证。 >>> id(1) 我们显示了整数1 和整数2 的内部id。当计算a 的值时,结果对象与对象池中的整数2 对象是同一个对象。 当你练习这个示例时,id()值可能跟示例不同。但是,在每次使用值2 时,将使用相同的对象。在我的笔记本电脑上,对象2 的id 等于4297537984。这种机制避免了在内存里大量存放对象2 的副本。 这里有个小技巧,可以看出一个数字到底有多大。 >>> len(str(2 ** 2048)) 通过一个计算得到的数字创建一个字符串,然后查询字符串的长度。结果表明,这个数字有617个数位。 四、补充知识 Python 提供了一组丰富的算术运算符:+、- 、*、/、//、%和**。/和//用于除法,**将执行幂运算。 对于位处理,还有其他一些运算符,比如&、^、|、<<和>>。这些运算符在整数的内部二进制表示上逐位操作。它们分别计算二进制与、二进制异或、二进制或、左移和右移。 虽然这些运算符也同样适用于大整数,但是逐位操作对于大整数来说并没有实际意义。一些二进制文件和网络协议会要查看数据的单个字节中的位。 可以通过bin()函数查看应用这些运算符的运行结果。示例如下: >>> xor = 0b0011 ^ 0b0101 先使用0b0011 和0b0101 作为两个位串。这有助于准确说明数字的二进制表示。然后将异或(^)运算符应用于这两个位序列。最后使用bin()函数查看位串形式的结果。可以通过结果仔细观察各个位,了解操作符的实际功能。 可以把一个字节分解为若干部分。假设我们要将最左边的2 个位与其他6 个位分开,其中一种方法是使用位操作(bit-fiddling)表达式,例如: >>> composite_byte = 0b01101100 这里先定义了一个composite_byte,其中最高有效的2 位为01,最低有效的6 位为101100。再使用>>移位运算符将composite_byte 的值右移6 个位置,去除最低有效位并保留2 个最高有效位。然后使用&运算符和掩码来进行操作。掩码中值为1 的位,在结果中保留对应位置的值;掩码中值为0 的位,结果中对应位置的值被设置为0。 五、延伸阅读 关于整数操作的详细信息,请参阅https://www./dev/peps/pep-0237/。 在浮点数、小数和分数之间选择Python 提供了多种处理有理数和无理数近似值的方法。3 种基本选择如下: 浮点数 小数 分数 有这么多种选择,那么怎样选择才合适呢? 一、准备工作 确定我们的核心数学期望值很重要。如果不确定已拥有的数据类型或者想要得到的结果,真的不应该开始编码。我们需要退一步,用铅笔和纸来演算一下。 除了整数,在数学中涉及的数字还有3 种。 (1) 货币:如美元、美分或欧元。货币通常具有固定的小数位数。另外还有很多舍入规则,例如,可以用这些规则确定 $2.95 的7.25% 是多少美分。
(2) 有理数或分数:使用美制单位的英尺和英寸,或在烹饪中使用杯和盎司进行测量时,经常需要使用分数。把一个8 人量的食谱缩减为5 人量时,要用5/8 作为缩放因子进行分数运算。如何将这种方法应用到2/3 杯米,并得到适用于厨房量具的测量值呢? (3) 无理数:包括所有其他类型的计算。必须注意,数字计算机只能逼近这些无理数,而我们偶尔会看到近似值的一些奇怪现象。浮点近似值运算非常快,但有时会出现截断问题。 当计算涉及前两种数字时,应当避免使用浮点数。 二、实战演练 本实例将分别讨论这3 种数字。首先讨论货币值计算。然后讨论有理数计算,以及无理数或浮点数计算。最后讨论这些类型之间的显式转换。 1. 货币值计算 在处理货币值时,应当坚持使用decimal 模块。如果使用Python 内置的浮点数,将会遇到数字的舍入和截断问题。 (1) 为了处理货币值,首先从decimal 模块导入Decimal 类。 >>> from decimal import Decimal (2) 从字符串或整数创建Decimal 对象。 >>> from decimal import Decimal tax_rate 由两个Decimal 对象构建,其中一个基于字符串,另一个基于整数。我们可以直接使用Decimal('0.0725'),而不显式地执行除法。 结果稍微大于$0.21,因为我们计算出了小数位的全部数字。 (3) 如果通过浮点数创建Decimal 对象,那么将得到异常的浮点近似值。应当避免混用 Decimal和float。为了舍入到最近的便士(penny),创建一个penny 对象。 >>> penny = Decimal('0.01') (4) 使用penny 对象量化数据。 >>> total_amount = purchase_amount + tax_rate * purchase_amount 上述示例演示了如何使用默认的ROUND_HALF_EVEN 舍入规则。 舍入规则有很多种,Decimal 模块提供了所有舍入规则。例如: >>> import decimal 本示例显示了使用另一种不同的舍入规则的结果。 2. 分数计算 当计算中含有精确分数值时,可以使用fractions 模块。该模块提供了便于使用的有理数。处理分数的流程如下。 (1) 从fractions 模块导入Fraction 类。 >>> from fractions import Fraction (2) 由字符串、整数或整数对创建Fraction 对象。如果由浮点数创建Fraction 对象,可能会遇到浮点近似值的异常现象。当分母是2 的幂时,一切正常。 >>> from fractions import Fraction 我们从字符串2.5 创建了第一个分数,从浮点计算5/8 创建了第二个分数。因为分母是2 的幂,所以计算结果非常准确。 25/16——结果是一个看起来很复杂的分数,那么它的最简分数是多少呢? >>> Fraction(24, 16) 结果表明,我们使用大约一杯半的米就可以完成5 人量的食谱。 3. 浮点近似值 Python 的内置浮点(float)类型能够表示各种各样的值。对于是否使用浮点值,选择的关键在于浮点值通常涉及近似值。在某些情况下,特别是在做涉及2 的幂的除法时,结果是一个精确的分数。 在其他情况下,浮点值和分数值之间可能存在细小的差异,这反映了浮点数的实现与无理数的数学理想之间的差异。 (1) 要使用浮点数,经常需要舍入值来使它们看起来合理。所有浮点计算的结果都是近似值。 >>>(19/155) * (155/19) (2) 上面的值在数学上应该为1。由于float 使用的是近似值,所以结果并不精确。虽然这个结果与1 相差不多,但仍然错了。当进行适当的舍入时,这个值会更有意义。 >>> answer =(19/155) * (155/19) (3) 认识误差项。在本例中,我们知道确切的答案,所以可以将计算结果与已知的正确答案进行比较。下面的示例给出的通用误差项适用于所有浮点数。 >>> 1-answer 对于大多数浮点误差,典型值约为10^16。Python 有一些聪明的规则,有时通过自动舍入隐藏这个错误。但是,对于本示例,错误并没有隐藏。 这是一个非常重要的推论。
在浮点数之间使用精确的==测试时,如果近似值相差一个位,代码就会出现问题。 4. 数字的类型转换 可以使用float()函数从其他类型的值创建一个float 值。例如: >>> float(total_amount) 在第一个示例中,我们将Decimal 值转换为float 值。在第二个示例中,我们将Fraction 值转换为float 值。 正如刚刚看到的,我们永远不想将float 转换为Decimal 或Fraction: >>> Fraction(19/155) 在第一个示例中,我们在整数之间进行计算,创建了一个具有已知截断问题的float 值。当我们从截断的float 值创建一个Fraction 时,得到的是一些暴露了截断问题的数字。 类似地,第二个示例从float 创建了 Decimal 值。 三、工作原理 对于数字类型,Python 提供了多种运算符:+、-、*、/、//、%和**。这些运算符用于加法、减法、乘法、真除法、截断除法、取模和幂运算。 Python 擅长各种数字类型之间的转换。我们可以混合使用整数(int)和浮点数(float),整数将被转换为浮点数,以提供最准确的答案。类似地,还可以混合使用整数(int)和分数(Fraction),结果将是分数(Fractions)。我们也可以混合使用整数(int)和小数( Decimal)。但是,不能随便混合使用小数( Decimal)与浮点数(float),或小数( Decimal)与分数(Fraction),在这样操作之前,需要进行显式转换。
我们可以使用普通的十进制数值在Python 中编写如下值: >>> 8.066e + 67 在内部使用的实际值将包含上述十进制值的一个二进制近似值。 该示例(8.066e + 67)中的内部值为: >>> 6737037547376141/2 ** 53 * 2 ** 226 分子是一个大数字,6737037547376141;分母总是2^53。由于分母是固定的,因而所得到的分数只能有53 位有意义的数据。由于53 位之外的位不可用,因此值可能会被截断。这导致了我们的理想化抽象和实际数字之间的微小差异。指数(2^226)需要将分数缩放到适当的范围。 在数学上,即6737037547376141 * 2^226/2^53。 可以使用math.frexp()查看数字的内部细节: >>> import math 结果的两个部分分别称为尾数(mantissa)和指数(exponent)。如果将尾数乘以2^53,那么将得到一个整数,这个整数是二进制分数的分子。
与内置的float 不同,Fraction 是两个整数值的精确比率。正如前边所示,Python 中的整数可能非常大。我们可以创建包含具有大量数位的整数的比率,并且不受固定分母的限制。 类似地,Decimal 值基于非常大的整数值和用于确定小数点位置的缩放因子。这些数字可以是巨大的,不会有特殊的表示问题。
四、补充知识 Python 的math 模块包含许多用于处理浮点值的专用函数。该模块包括了常用的函数,如平方根、对数和各种三角函数,还包括其他一些函数,如伽玛函数、阶乘函数和高斯误差函数。 math 模块也包含了一些可以精确计算浮点数的函数。例如,math.fsum()函数将比内置sum()函数更加周密地计算浮点和。math.fsum()函数很少出现近似值问题。 还可以使用math.isclose()函数比较两个浮点值是否接近相等: >>> (19/155)*(155/19) == 1.0 该函数提供了一种正确比较浮点数的方法。 Python 还提供了复数数据类型。复数由实部和虚部组成。在Python 中,3.14 + 2.78j 代表复数 3.14 + 2.78√-1。Python可以在浮点数和复数之间进行轻松的转换。Python提供了一组常用的复数运算符。 为了更好地支持复数,Python 内置了cmath 包。例如,cmath.sqrt()函数将返回一个复数值,而不是在求负数的平方根时抛出异常。示例如下: >>> math.sqrt(-2) 在操作复数时,离不开cmath 包。 五、延伸阅读 请参阅https://en./wiki/IEEE_floating_point。 在真除法和floor 除法之间选择Python 提供了两种除法运算符。本实例将介绍这两种运算符以及它们适用的情景,还将介绍Python 除法规则以及如何对整数值进行除法运算。 一、准备工作 除法运算有几种常见的使用场景。
我们首先需要确定适用哪种情况,然后就知道该使用哪种除法运算符了。 二、实战演练 我们将分别讨论这三种情况。首先讨论截断的floor 除法,然后讨论真正的浮点除法,最后讨论分数的除法。 1. floor 除法 在做div-mod 类计算时,可以使用floor 除法运算符(//)和取模运算符(%)。或者也可以使用divmod()函数。 (1) 将秒数除以3600 得到小时数,模数或余数可以分别转换为分钟数和秒数。 >>> total_seconds = 7385 (2) 将步骤(1)剩余的秒数除以60 得到分钟数,余数是小于60 的秒数。 >>> minutes = remaining_seconds//60 使用divmod()函数的示例如下。 (1) 同时计算商和余数。 >>> total_seconds = 7385 (2) 再次计算商和余数。 >>> minutes, seconds = divmod(remaining_seconds, 60) 2. 真除法 真值除法计算的结果是浮点近似值。例如,7386 秒是多少小时?使用真除法运算符进行除法运算: >>> total_seconds = 7385
这种真除法是Python 3 的特性。 3. 有理分数计算 可以用Fraction 对象和整数做除法。这将使结果是一个数学上精确的有理数。 (1) 创建至少一个Fraction 值。 >>> from fractions import Fraction (2) 在计算中使用Fraction 值,任何整数都将被提升到分数。 >>> hours = total_seconds / 3600 (3) 如有必要,将确切分数转换为浮点近似值。 >>> round(float(hours),4) 我们首先为总秒数创建了一个Fraction 对象。在对分数做算术运算时,Python 会把所有整数都转换为分数,这种转换意味着数学运算是尽可能精确地完成的。 三、工作原理 Python 3 有两个除法运算符。
>>> 7358.0 // 3600.0 默认情况下,Python 2 只有一个除法运算符。对于仍在使用Python 2 的程序员来说,可以通过以下方法使用这些新运算符: >>> from __future__ import division 这个导入将会引入Python 3 的除法规则。 五、延伸阅读 请参阅https://www./dev/peps/pep-0238/。 ——本文节选自《Python经典实例》 解决实际场景中的具体问题,全面了解Python语言特性 本书是Python经典实例解析,采用基于实例的方法编写,每个实例都会解决具体的问题和难题。主要内容有:数字、字符串和元组,语句与语法,函数定义,列表、集、字典,用户输入和输出等内置数据结构,类和对象,函数式和反应式编程,Web服务,等等。 Python 是全球的开发人员、工程师、数据科学家和编程爱好者的首选语言。它是杰出的脚本语言,可以为你的应用程序注入动力,提供出色的速度、安全性和可扩展性。本书会通过一些简单的实例剖析Python,你可以在特定的情境中深入了解其具体的语言特性。明确的情境有助于理解语言或标准库的特性。 本书采用基于实例的方法编写,每个实例都会解决具体的问题和难题。 |
|