第一条约定是不要使用Python预定义的标识符名对自定义的标识符进行命名。Python有一个内置函数dir(),可以返回对象属性列表,不带参数调用该函数时,将返回Python内置属性列表,也可以以其返回值即内置属性作为其参数。

第二条预定是关于下划线(_)的使用,应该避免在名的开头和结尾都使用下划线,这是因为其与Python定义的各种特殊方法与变量冲突。

说一千道一万,就是不要使用生僻或怪异的名称,因为这样可能会产生潜在的问题。

二、Integral类型

Python提供了两种内置的Integral类型,即int与bool。在布尔表达式中,0与False表示False,其它任意整数与true都表示true。在数字表达式中,True表示1,False表示0。这意味着用表达式i += True来对整型变量i进行递增操作也是合法的。

1、整数

整数的大小只受限于机器内存大小,默认使用十进制,也可以使用其它进制,例如:二进制数以0b引导,八进制数以0o引导,十六进制数则以0x或者0X引导。

所有常见的数学函数与操作符都可用于整数。整数转换函数包括bin(i)、hex(i)、int(x)、int(s, base)、oct(i)。

2、布尔型

有两个内置的布尔型对象:True与False。

三、浮点类型

Python提供了3中浮点值:内置的float与complex类型,以及来自标准库的decimal.Decimal类型。

1、浮点数

float类型存放双精度的浮点数,取值范围依赖于构建Python的C(或者C#或Java)编译器,其数值使用小数点或指数表示。

float类型精度不高,如果需要高精度浮点数则可以使用来自decimal模块的decimal.Decimal数,默认情况下到小数点后28位。

Python支持混合模式的算术运算,比如:使用int与float运算生成float数;使用float与complex运算生成complex数;但是decimal.Decimal只能与decimal.Decimal或者intS运算生成decimal.Decimal数;不兼容的数据类型进行运算会产生TypeError异常。

2、复数

复数存放的是一对浮点数,一个表示实数部分,另一个表示虚数部分,用+或者-将实数部分与虚数部分(其后跟随一个字母j)连接在一起共同构成复数。复数的两个部分都以属性名的形式存在,分别为real与imag,例如:

>>> z = 1.2 + 7.2j
>>> z.real, z.imag
(1.2, 7.2)

除了//、%、divmod()以及三个参数的pow()之外,所有数值类型操作符与函数都可用于对复数进行操作。复数提供一个方法conjugate()用于改变虚部的符号。

3、十进制数字

十进制数使用decimal.Decimal()函数创建,该函数可以接受一个整数或字符串参数,但是不能以浮点数作为参数,因为浮点数不够精度,decimal则很精确。要创建Decimal,必须先导入decimal模块。

从Python3.1开始,使用decimal.Decimals from-float()函数能将floasts转换为十进制数,该函数以float型数为参数,并返回与该float数值最为接近的decimal.Decimal。

大多数数值类型操作符与函数都可用于decimal.Decimals,但是有两个约束:如果操作符左边的操作数为decimal.Decimal,那么其右边的操作数必须为整数;同样地,如果pow()函数的第一个参数为decimal.Decimal,那么其第二个以及可选的第三个参数必须为整数。

四、字符串

字符串使用str数据类型表示,其中存放Unicode字符序列。字符串可以使用单引号、双引号、三引号,但是字符串两端必须相同。如果引号包含的字符串中又含有引号,则内层引号与外层引号相同时,需要转义内存引号,内存引号与外层引号不相同时,则可以直接使用该引号,例如:

a = "hello 'li lei', fine, think you!"
b = 'hello \'li lei\', fine, think you!'

Python使用换行作为其语句终结符,但是如果在圆括号内、方括号内、花括号内或三引号包含的字符串内则是例外。

有些情况下,比如,编写正则表达式时,需要创建带有大量字面意义反斜杠的字符串,由于需要对每个反斜杠进行转义处理,从而造成不便,解决方案是用字面意义的r引导字符串,这种字符串内部的所有字符都按其字面意义理解,而不再需要进行转义。

当字符串比较长时,建议用圆括号将跨越多行的任何语句进行封装,而不是用转义的换行符。

1、比较字符串

Python使用Unicode字符串,因此,比较字符串时存在两个问题(这不是Python特有的问题):

(1)有些Unicode字符可以用两种或更多种字节序列表示,需要对其进行编码才能解决此问题。

Python导入unicodedata模块,并以NFKD(标准化的方法即Normalization Form Compatibility Decomposition)为第一个参数调用unicodedata.normalize(),则返回以UTF-8编码字节表示的字节序列。

(2)字符排序特定于某种语言(瑞典语、德语、丹麦语等)。

Python使用字符串的内存字节表示,排序基于Unicode字元,也可以自定义排序方法。

2、字符串分片与步距

提取操作符[]不仅可以提取单个数据项或者单个字符,还能提取数据项或字符的整个分片(子序列),在这种情况下提取操作符被用作分片操作符。字符串正索引位置从0开始到字符串长度减1,负索引位置-1总是代表字符串的最后一个字符,存取超出范围的索引位置(或空字符串中的索引位置)会产生IndexError异常。

分片操作符有3中语法格式,seq可以使任意序列,比如:列表、字符串或元组,start、end与step都必须是整数,具体如下:

(1)seq[start]

提取序列从start开始的数据项。

(2)seq[start:end]

提取序列从start开始到end结束的数据项(不包含)。如果忽略起点索引值,则默认为0,如果忽略终点索引,则默认为len(seq),如果同时忽略两个索引值,则与s[0:len(s)]等价,即提取整个序列。

(3)seq[start:end:step]

从start开始到end结束每隔step个字符进行提取。如果忽略起点索引值,则默认为0,除非给定负的step值,则此时索引值默认为-1,如果忽略终点索引值,则默认为len(seq),除非给点负的step值,则此时终点索引值默认为字符串起点前面。不能忽略step,且step不能为0。step为-1意味着每个字符都将被提取,而且方向为从终点到起点,即产生反转字符串。

3、字符串操作符与方法

所有能用于固定序列的操作都可用于字符串,包括in、+=、*、*=等。此外,还有很多字符串方法,包括取子串、大小写转换、字符串分割、模式匹配、格式化、编码、字符值判断、反转、连接等。

字符串的操作中,+操作被重载用于实现字符串连接,但是如果需要连接大量的字符串,使用str.join()方法是一种更好的方案。该方法以一个序列作为参数,用str作为分隔符,将序列中的字符串用分隔符连接起来,然后存放到一个单独的字符串中,例如:

>>> test = ["I", "am", "shichangquan"]
>>> " ".join(test)
'I am shichangquan'
>>> "--".join(test)
'I--am--shichangquan'

操作符*提供了字符串复制功能:

>>> s1 = "="*5
>>> print(s1)
=====
>>> s2 *= 10
>>> print(s2)
==========

4、字符串格式化

使用str.format()方法进行字符串格式化,该方法提供了非常灵活而强大的创建字符串的途径。str.format()方法会返回一个新字符串,在新字符串中,原字符串的替换字段被适当格式化后的参数所替代,例如:

>>> "The novel '{0}' was published in '{}'".format("Hard Times", 1854)
"The novel 'Hard Times' was published in 1854"

每个替换字段都由包含在花括号中的字段名标识,如果字段名是简单的整数,就将被作为传递给str.format()方法的一个参数的索引位置,如上例中,名为0的字段被第一个参数所替代,名为1的字段则被第二个参数所替代。

替换字段可以使用下面的任意一种语法格式:

{field_name}
{field_name!conversion}
{field_name:format_specification}
{field_name!conversion:format_specification}

另外需要注意的一点是,替换字段本身也可以包含替换字段,嵌套的替换字段不能有任何格式,其用途主要是格式化规约的计算,下面是一些例子:

>>> "{who} turned {age} this year".format(who="She", age=88)
'She turned 88 this year'
>>> "The {who} was {0} last week".format(12, who="boy")
'The boy was 12 last week'
>>> stock = ["page", "envelopes", "notepads", "pens", "paper clips"]
>>> "We have {0[1]} and {0[2]} in stock".format(stock)
'We have envelopes and notepads in stock'
>>> d = dict(animal="elephant", weight=12000)
>>> "The {0[animal]} weights {0[weight]}kg".format(d)
'The elephant weights 12000kg'
>>> "math.pi=={0.pi} sys.maxunicode=={1.maxunicode}".format(math, sys)
'math.pi==3.14159265359 sys.maxunicode==65535'
>>> "{} {} {}".format("Python", "can", "count")
'Python can count'

上面的例子不过多解释,看了就会心领神会。

下面介绍一种“诡异”的技术:当前还在作用范围内的局部变量可以通过内置的locals()函数访问,该函数会返回一个字段,字段的键是局部变量名,字典的值则是对变量值的引用。现在,我们可以使用映射拆分将该字段提供给str.format()方法,映射拆分操作符为**,可应用于映射(比如字典)来产生一个适合于传递给函数的键-值列表,例如:

>>> element = "Silver"
>>> number = 47
>>> "Element {number} is {element}".format(**locals())
'Element 47 is Silver'

对于字符串而言,可以控制的包括填充字符、字段内对齐方式以及字段宽度的最小值与最大值。字符串格式规约是使用冒号(:)引入的,其后跟随可选的字符对——一个填充字符(可以不是))与一个对齐字符(用于右对齐),之后跟随的是可选的最小宽度(整数),如果需要制定最大宽度,就在其后使用句点,句点后跟随一个整数值。

注意locale.setlocale()不是thread-safe的。

5、字符编码

本质上说,计算机只能存储字节,即8比特的值,如果是无符号数,那么取值范围从0x00到0xFF,每个字符必须都以某种形式的字节表示。但是由于各种原因导致实际编码种类非常多,这对编写国际化软件非常不利,而Unicode编码的出现解决了这个问题。

Unicode为每个字符分配一个整数,即字元,Unicode不局限于使用一个字节表示每个字符,为了更好的兼容性,其中前127个Unicode字符与7比特ASCII表示的前127个字符是相同的。在内存中,Unicode通常以UCS-2格式(实质上是16比特的无符号整数)表示前65535个字元,或者以USC-4格式(32整数)表示所有的字元。在Python编译时,会设置为使用某一种格式(如果sys.maxunicode为65535,Python编译时就是用UCS-2)。

对于存放在文件中或通过网络连接传送的数据,情况会更加复杂。如果使用了Unicode,那么字元可以使用UTF-8进行编码——这种编码中,对前127个字元,每个字符使用一个字节表示;对其它字元,则使用两个或更多的字节数来表示每个字符。对于英文文本而言,UTF-8是非常紧凑的,如果只使用了7个比特字符,则UTF-8文件与ASCII文件实质上是一样的。另一种常见的编码方式是UTF-16编码,这种编码方式中,对大多数字符使用两个字节表示,对其它的一些字符则使用4个字节表示。对某些亚洲语言,这种编码方式比UTF-8更紧凑,但与UTF-8不同的是,UTF-16文本应该以一个字节顺序标记开始,以便用于读取该文本的代码可以判定字节对是big-endian还是little-endian。此外,所有旧的编码格式,比如GB2312、ISO-8859-5、Latin-1等,实际上都在常规的使用中。

str.encode()方法可以返回一个字节序列——实际上是一个bytes对象,在编码时根据提供的编码参数进行编码。使用这一方法,可以更好地理解不同编码格式之间的差别,以及为什么进行错误的编码假设或导致错误。这个方法的第一个参数为必选参数用于指定编码,第二个参数为可选参数用于指定错误处理方式。该方法的具体信息后文书还会详解,或者参考Python文档。

Python的.py文件使用UTF-8编码,因此,Python总是知道字符串字面值要使用的编码格式。这意味着,我们可以在字符串中输入任意的Unicode字符——只要使用的编辑器支持。在从外部源(比如socket)读取数据时,Python无法知道其使用的编码格式,因此会返回字节序列,并由程序员对其进行相应的解码。对文本文件,Python采用一种更软化的方法,即使用本地编码——除非明确指定编码格式。

幸运的是,有些文件格式会指定其编码格式,比如:XML文件默认使用的就是UTF-8编码,除非指令明确地指定了不同的编码格式,则需要先提取比如前1000个字节以寻找其中的编码规约,如果找到,则使用指定格式解码。Python提供了用于检测文件编码格式的包,具体可以参看Python文档。