本节是第五讲的第四小节,上节为大家介绍了Python语言的数字类型包括(整型、布尔型、浮点型、复数型、十进制型),本节将为大家介绍项目中最常用类型字符串型,由于内容较多,分为上下两部分讲解,本篇是下半部分。
使用str.format()方法进行字符串格式化
str.format()方法提供了非常灵活而强大的创建字符串的途径。对于简单的情况,使用str.format()方法是容易的,但是如果需要进行复杂的格式化操作,就要学习该方法需要的格式化语法。
str.format()方法会返回一个新字符串,在新字符串中,原字符串的替换字段被适当格式化后的参数所替代,例如:
>>> "The novel '{0}' was published in {1}".format("Hard Times", 1854)
"The novel 'Hard Times' was published in 1854"
每个替换字段都是由包含在花括号中的字段名标识的。如果字段名是简单的整数, 就将被作为传递给str.format()方法的一个参数的索引位置。因此,在这种情况下,名为0的字段被第一个参数所替代,名为1的字段则被第二个参数所替代。
如果需要在格式化字符串中包含花括号,就需要将其复写,下面给出一个实例:
>>> "{{{0}}} {1} ;-}}".format("I'm in braces", "I'm not")
"{I'm in braces} I'm not ;-}"
如果我们试图连接字符串与数字,那么Python将产生TypeError异常,但是使用 str.format()方法可以很容易地做到这一点:
>>> ''{0}{1}".format("The amount due is #34;, 200)
'The amount due is $200'
我们也可以使用str.format()方法连接字符串(尽管str.join()方法最适合用于这一目的)。
在上面的实例中,我们使用了一对字符串变量,不过在本小节的大部分,我们在str.format()方法的应用实例中都使用字符串字面值,这就是为了方便——实际上,任何使用字符串字面值的实例中都可以使用字符串变量,方法是完全一样的。
>>> x = "three"
>>> s ="{0}{1}{2}"
>>> s = s.format("The", x, "tops")
>>> s
'The three tops'
#替换字段可以使用下面的任意一种语法格式:
{field_name}
{field_name!conversion}
{field_name:format_specification}
(field_name!conversion:format_specification}
另外需要注意的一点是,替换字段本身也可以包含替换字段,嵌套的替换字段不能有任何格式,其用途主要是格式化规约的计算。在对格式化规约进行更细致的解读时,我们将展示一个实例。现在我们将逐一研究替换字段的每一个组成部分,首先从字段名开始。
字段名(Field Names)
字段名或者是一个与某个str.format()方法参数对应的整数,或者是方法的某个关键字参数的名称。
>>> "{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'
上面的第一个实例使用了两个关键字参数,分别是who与age,第二个实例使用 了一个位置参数(到这里为止只在这里使用过)与一个关键字参数。要注意的是,在参数列表中,关键字参数总是在位置参数之后,当然,我们可以在格式化字符串内部以任何顺序使用任何参数。
字段名可以引用集合数据类型--比如:列表。在这样的情况下,我们可以包含 一个索引(不是一个分片)来标识特定的数据项:
>>> stock = ["paper", "envelopes", "notepads", "pens", "paper clips"]
>>> "We have {0[1]} and {0[2]} in stock".format(stock)
'We have envelopes and notepads in stock'
0引用的是位置参数,因此,{0[1]}是列表stock参数的第二个数据项,{0[2]}是 列表stock参数的第三个数据项。
#我们也可以存取命名的属性。假定已经导入math模块与sys模块
>>> "math.pi=={0.pi} sys.maxunicode=={1.maxunicode}".format(math, sys)
'math.pi==3.141592653589793 sys.maxunicode==1114111'
总而言之,通过字段名语法,可以引用传递给str.format()方法的位置参数与关键字参数。如果参数是集合数据类型,比如列表或字典,或参数还包含一些属性,那么可以使用[]或.表示法存取所需的部分。
从Python 3.1开始,忽略字段名成为可能,这种情况下,Python会自动进行处理 (使用从0开始的数值),比如:
>>> "{} {} {}".format("Python", "can", "count")
'Python can count'
当前还在作用范围内的局部变量可以通过内置的locals()函数访问,该函数会返回一个字典,字典的键是局部变量名,字典的值则是对变量值的引用。现在,我们可以使用映射拆分将该字典提供给str.format()方法,映射拆分操作符为**,可应用于映射 (比如字典)来产生一个适合于传递给函数的键-值列表,比如:
>>> element = "Silver"
>>> number = 47
>>> "Element {number} is {element}".format(**locals())
'Element 47 is Silver'
>>> d=dict(animal="elephant",weight=12000)
>>> "The {animal} weighs {weight}kg".format(**d)
'The elephant weighs 12000kg'
将字典拆分并提供给str.format()方法时,允许使用字典的键作为字段名。这使得字符串格式更易于理解,也易于维护,因为不需要依赖于参数的顺序。然而,要注意的是,如果需要将不止一个参数传递给str.format(),那么只有最后一个参数才可以使用映射拆分。
转换(Conversions)
在讨论decimal.Decimal数字时,我们注意到,这些数可以以两种方式输出,例如:
>>> decimal.Decimal("3.4084")
Decimal('3.4084')
>>> print(decimaI.Decimal("3.4084"))
3.4084
decimal.Decimal的第一种展示方式是其表象形式,这种形式的用途是提供一个字符串—该字符串被Python解释时将重建其表示的对象。Python程序可以评价Python代码段或整个程序,因此,这种表象形式有时候是有用的。不是所有对象都可以提供这种便于重建的表象形式,如果提供,其形式为包含在尖括号中的字符串,比如,sys 模块的表象形式为字符串”<module 'sys' (built-in)>"。
第二种是以字符串形式对decimal.Decimal进行展示的,这种形式的目标是便于阅读,因此其着眼点是展示一些读者感兴趣的东西。如果某种数据类型没有字符串表示形式,但又需要使用字符串进行表示,那么Python将使用表象形式。
Python内置的数据类型都知道str.format()方法,在作为参数传递给这一方法时, 将返回一个适当的字符串来展示自己。此外,重写数据类型的通常行为并强制其提供字符串形式或表象形式也是可能的,这是通过向字段中添加转换(conversion)指定符实现的。目前,有3个这样的指定符:s用于强制使用字符串形式;r用于强制使用表象形式; a用于强制使用表象形式,但仅限于ASCII字符。下面给出一个实例:
>>> "{0} {0!s} {0!r} {0!a}".format(decimal.Decimal("93.4"))
"93.4 93.4 Decimal('93.4') Decimal('93.4')"
在上面的实例中,decimal.Decimal的字符串形式产生的字符串与提供给str.format()的字符串是相同的。同时,在这个比较特定的实例中,由于都只使用ASCII 字符,因此,表象形式与ASCII表象形式之间没有区别。
到这里,我们讲述了如何将变量值放置在格式化字符串中,以及如何强制使用字符串形式或表象形式。现在,我们开始考虑值本身的格式问题。
格式规约(Format Specifications)
整数、浮点数以及字符串的默认格式通常都足以满足要求,但是如果需要实施更精确的控制,我们就可以通过格式规约很容易地实现。
对于字符串而言,我们可以控制的包括填充字符、字段内对齐方式以及字段宽度的最小值与最大值。
字符串格式规约是使用冒号(:)引入的,其后跟随可选的字符对一个填充字符与一个对齐字符(<用于左对齐,^用于中间对齐,>用于右对齐), 之后跟随的是可选的最小宽度(整数),如果需要指定最大宽度,就在其后使用句点,句点后跟随一个整数值。
要注意的是,如果我们指定了一个填充字符,就必须同时指定对齐字符。我们忽略了格式规约的符号与类型部分,因为对字符串没有实际影响。只使用一个冒号而没有任何其他可选的元素是无害的,但也是无用的。
>>> s ="The sword of truth"
>>> "{0}".format(s) # default formatting
'The sword of truth'
>>> "{0:25}".format(s) # minimum width 25
'The sword of truth '
>>> "{0:>25}".format(s) # right align, minimum width 25
' The sword of truth'
>>> "{0:^25}".format(s) # center align, minimum width 25
' The sword of truth '
>>> ”{0:-^25}".format(s) # - fill, center align, minimum width 25
'---The sword of truth----'
>>>"{0:.<25}".format(s) # . fill, left align, minimum width 25
'The sword of truth.......'
>>> "{0:.10}".format(s) # maximum width 10
'The sword '
在倒数第二个实例中,我们必须指定左对齐(即便这是默认的)。如果漏掉了<, 就将得到:.25,这只是意味着最大字段宽度为25个字符。
前面我们已经注意到,在格式化规约内部包括替换字段是有可能的,从而有可计算的格式也是可能的。比如,这里给出了使用maxwidth变量设置字符串最大宽度的两种方式,第一种方法使用标准的字符串分片,第二种方法使用内部替换字段。
>>> maxwidth = 12
>>> "{0}".format(s[:maxwidth])
'The sword of'
>>> "{0:.{1}}".format(s, maxwidth)
'The sword of'
对于整数,通过格式规约,可以控制填充字符、字段内对齐、符号、最小字段宽度、基数等。整数格式规约以冒号开始,其后可以跟随一个可选的字符对---一个填充字符与一个对齐字符(<用于左对齐,^用于中间对齐,>用于右对齐,=用于在符号与数字之间进行填充),之后跟随的是可选的符号字符:+表示必须输出符号,- 表示只输出负数符号,空格表示为正数输出空格;为负数输出符号-。再之后跟随的是可选的最小宽度整数值——其前可以使用字符#引导,以便获取某种基数进制为前缀的输出(对二进制、八进制、十六进制数值),也可以以0引导,以便在对齐时使用0 进行填充。如果希望输出其他进制数据,而非十进制数,就必须添加一个类型字符 —b用于表示二进制,o用于表示八进制,x用于表示小写十六进制,X用于表示大写十六进制,为了完整性,也可以使用d表示十进制整数。此外,还有两个其他类型字符:c表示输出整数对应的Unicode字符;n表示以场所敏感的方式输出数字。
>>> "{0:0=12}".format(8749203) # 0 fill, minimum width 12
'000008749203'
>>> "{0:0=12}".format(-8749203) # 0 fill, minimum width 12
'-00008749203'
>>> "{0:012}".format(8749203) # 0-pad and minimum width 12
'000008749203'
>>> "{0:012}".format(-8749203) # 0-pad and minimum width 12
'-00008749203'
前两个实例使用的填充字符为0,填充位置在符号与数字本身之间(=);后两个实例要求最小宽度为12,并使用0进行填充。
# 下面给出了一些对齐实例:
>>>"{0:*<15}".format(18340427) # * fill,left align, min width 15
‘18340427*******’
>>> "{0:*>15}".format(18340427) # * fill,right align, min width 15
'*******18340427'
>>> "{0:*^15}".format(18340427) # * fill,center align, min width 15
'***18340427****'
>>> "{0:*^15}".format(-18340427) # * fill, center align, min width 15
'***-18340427***'
#下面给出一些展示符号字符作用的实例:
>>> "[{0:}] [{1:}]".format(539802, -539802) # space or -sign
'[539802] [-539802]'
>>> "[{0:+}] [{1:+}]".format(539802, -539802) # force sign
'[+539802] [-539802]'
>>> "[{0:-}] [{1:-}]”.format(539802, -539802) # - sign if needed
'[539802] [-539802]'
#下面是两个使用某些类型字符的实例:
>>> "{0:b} {0:o} {0:x} {0:X}".format(14613198)
'110111101111101011001110 67575316 deface DEFACE'
>>>”{0:#b} {0:#o} {0:#x} {0:#X}".format(14613198)
'0b110111101111101011001110 0o67575316 0xdeface 0XDEFACE'
为整数指定最大字段宽度是不可能的,这是因为,这样做要求数字是可裁剪的, 并可能会使整数没有意义。如果我们使用Python 3.1,并在格式规范中使用一个逗号,则整数将使用逗号进行分组。例如:
>>> ”{0:,} {0:*>13,}".format(int(239432185e6))
'239,432,185,000,000 239,432,185,000,000'
最后一个可用于整数(也可用于浮点数)的格式化字符是n。在给定的字符是整数时,其作用与d相同;在给定的字符是浮点数时,其作用与g相同。n的特殊之处在于,充分考虑了当前的场所,并在其产生的输出信息中使用场所特定的十进制字符与分组字符。默认的场所称为C场所,对这种C场所,十进制字符是一个句点,分组字符是一个空字符串。
在程序的起始处添加下面两行,并将其作为最先执行的语句, 通过这种方式,可以充分考虑不同用户的场所:
import locale
locale.setlocale(locale.LC_ALL,”“)
如果将空字符串作为场所传递,那么Python会尝试自动确定用户的场所(比如, 通过检查LANG环境变量),并以C场所为默认场所。下面给出一些实例,展示了对整数与浮点数使用不同场所的影响:
x,y = (1234567890, 1234.56)
locale.setlocale(locale.LC_ALL, “C")
c = "{0:n} {1:n}".format(x, y) # c == "1234567890 1234.56”
locale.setlocale(locale.LC_ALL, "en_US.UTF-8")
en = ”{0:n} {1:n}”.format(x, y) # en == "1,234,567,890 1,234.56”
locale.setlocale(locale.LC_ALL, "de_DE.UTF-8")
de = "{0:n} {1:n}".format(x, y) # de == "1.234.567.890 1.234,56"
虽然n对于整数非常有用,但是对于浮点数的用途有限,因为随着浮点数的增大, 就会使用指数形式对其进行输出。
对于浮点数,通过格式规约,可以控制填充字符、字段对齐、符号、最小字段宽度、十进制小数点后的数字个数,以及是以标准形式、指数形式还是以百分数的形式输出数字。
用于浮点数的格式规约与用于整数的格式规约是一样的,只是在结尾处有两个差别。在可选的最小宽度后面,通过写一个句点并在其后跟随一个整数,我们可以指定在小数点后跟随的数字个数。我们也可以在结尾处添加一个类型字符:e表示使用小写字母e的指数形式,E表示使用大写字母E的指数形式,f表示标准的浮点形式,g 表示“通常”格式一一这与f的作用是相同的,除非数字特别大(在这种情况下与e 的作用相同——以及几乎与g等同的G,但总是使用f或E)。另一个可以使用的是 %-这会导致数字扩大100倍,产生的数字结果使用f并附加一个%字符的格式输出。
下面给出几个实例,展示了指数形式与标准形式:
>>> amount = (10 ** 3)*math.pi
>>> "[{0:12.2e}] [{0:12.2f}]".format(amount)
'[ 3.14e+03] [ 3141.59]'
>>>"[{0:>12.2e}] [{0:*>12.2f}]".format(amount)
'[ 3.14e+03] [*****3141.59]'
>>> "[{0:*>+12.2e}] [{0:*>+12.2f}]".format(amount)
'[***+3.14e+03] [****+3141.59]'
>>> "{:,.6f}".format(decimal.Decimal("1234567890.1234567890"))
'1,234,567,890.123457'
第一个实例中最小宽度为12个字符,在十进制小数点之后有2个数字。第二个实例构建在第一个实例之上,添加了一个填充字符*,由于使用了填充字符就必须同时也使用对齐字符,因此指定了右对齐方式(虽然对数字而言这是默认的)。第三个实例构建在前两个实例之上,添加了符号操作符+,以便在输出中使用符号。
字符编码
本质上说,计算机只能存储字节,即8比特的值,如果是无符号数,那么取值范围从 0x00到0xFF,每个字符必须都以某种形式的字节表示。在计算机技术的早期,研究者们设计的编码机制是使用一个特定字节表示某个特定的字符。例如,使用ASCII编码,就用0x41 表示A,用0x42表示B,依此类推。在西欧,通常使用的是Latin-1编码,其前127个字 符与7比特ASCII相同,其余部分则用于重音字符与欧洲人需要的其他符号。在计算机科学的发展中,研究者还设计了很多种其他编码方式,其中的大部分仍然在使用中。遗憾的是,存在太多的编码方式会带来很多不便,在编写国际化软件时更是如此。 一个几乎被最广泛采纳的标准是Unicode编码,在这种编码中,Unicode为每个字符分配一个整数,即字元,就像早期的编码方式一样,但是Unicode不局限于使用一个字节表示每个字符,因而有能力使用这种编码方式表示每种语言中的每个字符。并且, 作为对其表示能力的一种增强,其中的前127个Unicode字符与7比特ASCII表示的前127个字符是相同的。
Unicode是如何存储的?当前,定义了超过100万个Unicode字符,因此,即便使用有符号数字,一个32位整数也足以存放任何Unicode字元,因此,最简单的用于存储Unicode字符的方式是使用32位整数序列,每个整数代表一个字在内存中, 这是非常便利的,因为我们可以设计一个32位整数数组,数组中的每个元素与某个字符有一对一的对应关系。但对文件或通过网络连接发送的文本而言,尤其是在文本几乎都是7比特ASCII时,每个整数的4个字节中最多有3个字节会是0x00。为避免这样的浪费,Unicode自身有几种表示方式。
在内存中,Unicode通常以UCS-2格式(实质上是16比特的无符号整数)表示前 65535个字元,或者以USC-4格式(32位整数)表示所有的字元——本书写作时,共 有1114111个。在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对象,编码时根据我们提供的编码参数进行编码。使用这一方法,可以更好地理解不同编码格式之间的差别,以及为什么进行错误的编码假设或导致错误。在引号之前使用一个字母b,表示使用的是字节字面值,而非字符串字面值。作为一种便利,在创建字节字面值时,我们可以混合使用可打印的ASCII字符与十六进制转义字符。
>>> artist ="Tage Asén"
>>> artist.encode("Latin1")
b'Tage As\xe9n'
>>> artist.encode("CP850")
b'Tage As\x82n'
>>> artist.encode("utf8")
b'Tage As\xc3\xa9n'
>>> artist.encode("utf16")
b'\xff\xfeT\x00a\x00g\x00e\x00 \x00A\x00s\x00\xe9\x00n\x00'
我们不能使用ASCII编码方式对Tage Asén的名称进行编码,因为这种编码不包括任意重音字符,因此,这样做会导致产生一个UnicodeEncodeError异常。 Latin-1编码(即ISO-8859-1)是一种8比特的编码格式,其中包含了这一名称所需要的所有字符。另一方面,Ern B'nk这一 artist不够幸运,因为字符不是一个Latin-1字 符,不能成功进行编码。当然,这两个名称都可以使用Unicode编码格式进行编码。 要注意的是,对UTF-16而言,头两个字节表示的字节顺序标记一解码函数会根据这一标记确定数据是big-endian还是little-endian,以便分别进行相应的处理。
对str.encode()方法,还有两点值得注意。第一个参数(编码名称)是大小写不敏感的,连字符与下划线在其中是等同对待的,因此,"us-ascii"与"US_ASCH"被认为是相同的。还有很多别名,比如,"latin"、"latinl"、"latin_ 1 "、"ISO-8859-1 "、"CP819", 其他一些都是“Latin-1”。该方法也可以接受可选的第二个参数,其作用是指定错误处理方式。例如,如果第二个参数为“ignore”或“replace”,那么可以将任何字符串编码为ASCII格式——当然,这会导致数据丢失——也可以无丢失的情况,如果我们使用“backslashreplace”,就会使用\x、\u与\U等转义字符替换非ASCII字符。例如, artist.encode("ascii", "ignore")会产生 b'Tage Asn', artist.encode("ascii", "replace")会产生b'Tage As?n',而 artist.encode("ascii", "backslashreplace")会产生 b'Tage As\\xe9n' (我们也可以使"{0!a)".format(artist)得到一个 ASCII 字符串"'Tage As\\xe9n'")
str.encode()方法的 complement 是 bytes.decode()(以及 bytearray.decode()),该方法 将返回一个字符串,其中使用给定的编码格式对字节进行解码,例如:
>>> print(b"Tage \xc3\x85s\xc3\xa9n".decode("utf8"))
Tage Åsén
>>> print(b"Tage \xc5s\xe9n".decode("latin1”))
Tage Åsén
8比特Latin-1、CP850 (一种IBM PC编码格式)以及UTF-8编码之间的细弱差别使得猜测编码格式不太可行,幸运的是,UTF-8正在成为明文文本文件编码格式的事实标准,因此,后人只需要熟悉这一格式,甚至不需要知道曾经存在过其他编码格式。
Python的.py文件使用UTF-8编码,因此,Python总是知道字符串字面值要使用的编码格式。这意味着,我们可以在字符串中输入任意的Unicode字符——只要使用的编辑器支持。
在从外部源(比如socket)读取数据时,Python无法知道其使用的编码格式,因此会返回字节序列,并由程序员对其进行相应的解码。对文本文件,Python釆用一种更软化的方法,即使用本地编码——除非明确指定编码格式。