上一章中我们主要介绍了字符串格式化表达式,这一章我们继续讲字符串格式化方法。让我们再回顾一下格式化方法的形式:"...{}...".format(values),其中花括号{}用来替换目标参数,也是values的值。因为前面我们已经学会了字符串格式化表达式,所以我们讲字符串格式化方法的时候会对比给出格式化表达式的形式,给两者做对比。
1.格式化方法的基础用法
在上一章我们提到了格式化方法的基本用法,但是仅仅是提了一下,没有涉及到太多的细节。这次我们来比较全面的讨论一下这些基础用法。
1.1参数使用规则
规则1.一个{}就代表一个参数的位置,如果{}中不填写任何的参数,则{}的数量必须小于等于参数的数量,并且满足{}个数的参数不能出现关键字参数。
规则2.{}中可以填写数字来标记取哪个位置参数,数字从0开始,代表第一个参数,以此类推。如果你在一个{}中标记了参数位置,则必须其他{}中也必须给定位置参数或者关键字参数,也就是{}中不能部分为空,在这种情况下,{}可以大于参数的个数。
规则3.{}也可以放置键来代表参数,此时对应的参数应该是关键字参数,也就是key=value这样的形式。和规则2一样,一旦填入键,其他的{}必须填入内容。同样,在这种情况下,{}可以大于参数的个数
规则4. 规则3和规则4可以混用,但是前提是规则2对应的是位置参数,而规则3对应的是关键字参数,所有的位置参数应该在关键字参数之前。
基于上面这四条,我们来看看代码:
python
a = "|{}| |{}|".format("abc", "bcd")
a1 = "|%s| |%s|" % ("abc", "bcd")
b = "|{}| |{}|".format("abc", "bcd", "cde")
#TypeError: not all arguments converted during string formatting
#b1 = "|%s| |%s|" % ("abc", "bcd", "cde")
# #IndexError: Replacement index 1 out of range for positional args tuple
#c = "|{}| |{}|".format("abc")
#TypeError: not enough arguments for format string
#c1 = "|%s| |%s|" % ("abc")
#IndexError: Replacement index 1 out of range for positional args tuple
#d = "|{}| |{}|".format("abc", key="bcd")
#字符串格式化表达式不存在d的这种情况,要么用元组,要么用字典
e = "|{}| |{}|".format("abc", "bcd", key="cde")
#字符串格式化表达式不存在e的这种情况,要么用元组,要么用字典
f = "|{1}| |{0}|".format("abc", "bcd")
#ValueError: cannot switch from automatic field numbering to manual field specification
#g = "|{}| |{0}|".format("abc", "bcd")
#ValueError: cannot switch from manual field specification to automatic field numbering
#h= "|{1}| |{}|".format("abc", "bcd")
#字符串格式化表达式不存在像f使用用数字代表参数位置的语法
i = "|{0}| |{0}|".format("abc")
#TypeError: not enough arguments for format string
#i1 = "|%s| |%s|" % "abc"
j = "|{key1}| |{key2}|".format(key1="abc", key2="bcd")
j1 = "|%(key1)s| |%(key2)s|" % {"key1": "abc", "key2": "bcd"}
k = "|{0}| |{key2}|".format("abc", key2="bcd")
#字符串格式化表达式不存在h的这种情况,要么用元组,要么用字典
# IndexError: Replacement index 0 out of range for positional args tuple
#l = "|{0}| |{key2}|".format(key1="abc", key2="bcd")
l1 = "|%s| |%(key2)s|" % {"key1": "abc", "key2": "bcd"}
#IndexError: Replacement index 0 out of range for positional args tuple
#m = "|{}| |{}|".format(key1="abc", key2="bcd")
#TypeError: not enough arguments for format string
#m1 = "|%s| |%s|" % {"key1": "abc", "key2": "bcd"}
n = "|{key1}| |{key1}|".format(key1="abc")
n1 = "|%(key1)s| |%(key1)s|" % {"key1": "abc"}
print("a = {}, a1 = {}, b = {},e = {}, f = {}, i = {}, \
j = {}, j1 = {}, k = {}, l1 = {}, n = {}, n1 = {}".
format(a, a1, b, e, f, i, j, j1, k, l1, n, n1))
# 输出结果:a = |abc| |bcd|, a1 = |abc| |bcd|, b = |abc| |bcd|,e = |abc| |bcd|,
# f = |bcd| |abc|, i = |abc| |abc|, j = |abc| |bcd|, j1 = |abc| |bcd|,
# k = |abc| |bcd|, l1 = |{'key1': 'abc', 'key2': 'bcd'}| |bcd|,
# n = |abc| |abc|, n1 = |abc| |abc|
a是{}中不填任何参数,因为{}的个数和参数个数一样的,且都位置参数。对应的a1是字符串格式化表达式,和a都能达到相同的效果。a符合规则1提到的情况。
b,{}的数量小于位置参数的数量,符合规则1提到的情况,但是对应的格式化表达式会出错,如b1的情况,提示不是所有的参数在格式化串中被转化。
c,{}的数量大于参数,但是{}中为空,所以会提示错误,符合规则1提到的情况。对应的格式化表达式c1会出错,提示索引位置1超出了元组的范围。
d,{}和参数个数一样,参数第一个是位置参数,第二个是关键字参数,{}中为空,则默认取位置参数,所以会提示第二个参数超出了元组的范围。关于位置参数和关键字参数我们在讲函数的时候会详细说明,在这里可以认为位置参数就是和参数所在参数列表的位置相关,关键字参数就是key=value这种写法。在参数列表中位置参数必须全部在关键参数之前。d的情况符合规则2的约定。对应的字符串表达式没有类似这种混用的写法。
e的情况是参数多于{}的个数,虽然存在关键字参数,但是这个关键字参数不会被用到,所以{}都不写也是可以的,因为{}对应的前两个参数都是位置参数。这符合规则1的情况。对于格式化表达式,不存在这种类似混用的情况,和d的场景是一样的。
f在{}中输入参数的位置来指定参数,第一个{}中输入1,第二个{}中输入0,所以打印出来和参数的位置的颠倒的,这符合规则2。字符串表达式不存在输入位置参数这种写法。这里列出了两种类似的错误,就是g和h的写法,在{}中只填一个索引,另一个为空,会提示:自动索引和手动索引不能互相切换。这符合规则2的约定。
i是{}个数多于参数的情况,通过在{}中设置索引(因为只有一个参数,所以索引值是一样的),这属于规则2的情况,字符串表达式无法设置索引,所以不存在这个情况。
j是关键字参数,通过在{}中填入key,最后结果就是key被对应的值替换。这符合规则3的情况。字符串表达式用法类似,需要将参数从元组形式换成字典,并且用()包裹key,j1就是这种情况。记住,这个key在字符串中都没有引号。
l是都是关键字参数的情况下,在{}分别填入位置索引和key,这样会报错,因为位置索引必须对应位置参数,key对应关键字参数。l的两个参数都是关键字参数,所以会提示索引0已经超过了位置参数元组的范围。这符合规则四的约定。对于字符串表达式l1,没有出错,但是结果看上去有点奇怪,首先第一个%s将整个字典当成了一个值,第二个引用了字典key2的值,所以输出结果是整个字典和字典键key2对应的值,虽然没有出错,但是却不是我们想要的结果。原因是%s需要寻找元组中的第一个索引位置的值,所以把整个字典当成了元组的值,而第二个%(key2)s则是在字典中寻找key2对应的值,而所给出的参数都能符合,所以没有出错。自此我们可以看出表达式中的格式化占位符之间并没有严格的互斥关系,只要参数满足需求就可以,即便是第一个%s将字典当成了元组的第一个成员。
m到这里就是展示了一个我们可以总结出的规律,如果{}中什么都不填和填数字是一样的,都代表位置参数,所以必须有对应的位置参数,这符合规则1。对于m1,则说明表达式如果占位符中没有(key)这样的形式,则会把给出的参数当做元组对待,把整个字典当成元组的第一个参数了,所以第二个占位符找不到第二个元组的元素了,所以报错了,这个和上面l中谈到的情况是一样的。
n代表了{}的个数多于参数的关键字的场景,这个i中提到的情况是一样的,只是i中是位置参数的场景,这个是关键字参数的场景。n1是字符串表达式类似的场景,通过字典的方式做到参数个数少于格式化占位符的个数。
如果读到这里,你或许很惊讶,字符串格式化方法的场景居然这么多,没错,我只是从一个测试者的思维列出其使用的正常和异常场景,但是一般情况下,你只需要记住最简单的就行了。但是这一节并未结束,我们刚才只是谈了参数的一些使用规则,但是格式化方法还有一些超乎你想象的用法,这就是我们下一小节要谈的内容。
1.2键,属性和偏移量
我们从键(key)可以想到字典,从属性可以想到任何可以通过.出来的东西,通过偏移量我们想到了序列。没错,在字符串格式化方法中你仍然可以在字符串中使用这些语法,就像你在变量中使用的那样,当然,或许你还不了解除了字符串的其他类型的对象。我们可以在这里简单介绍一下。
对于字典:a= {"key1": 123,"key2": "abc"} ,这样就定义了一个字典,我们可以通过a"key1"来得到值123。这就对应我们说的键
对于属性,每个对象其实都有属性,比如我们定义一个字符串b = "abc",那这个字符串的属性我们可以通过a后面跟个点来访问,字符串有很多属性,其实我们之前也讲过很多,例如a.lower,这个属性是一个函数,我们可以直接通过a.lower()来返回一个小写字符串。
对于偏移量,我们想到序列:包含列表,元组,字符串等,都可以称之为序列,因为其内部元素都是顺序排列的,可与你通过索引下标来访问,也可以称之为偏移量。 例如定义一个列表 c = 1,2,3,c0就是访问这个列表的第一个元素。
了解上面的键,属性和偏移量,接下里我们来看代码:
python
list1 = [1, 2, 3, 4]
list2 = ["abc", 123]
dict1 = {"key1": "abc", "key2": "bcd", "key3": list1}
class class1:
dict1 = dict1
list1 = list1
a = "|{0[0]}| |{1[0]}|".format(list1, list2)
b = "|{0[key1]}|".format(dict1)
c = "|{0.dict1}".format(class1)
d = "|{0.dict1[key1]}|".format(class1)
e = "|{0.list1[0]}|".format(class1)
f = "|{dict1[key1]}|".format(dict1=dict1)
g = "|{dict1[key2].lower}|".format(dict1=dict1)
#AttributeError: 'str' object has no attribute 'lower()'.
# Did you mean: 'lower'?
#h = "|{dict1[key2].lower()}|".format(dict1=dict1)
print("a = {}, b = {}, c = {}, d = {}, e = {}, \
f = {}, g = {}".format(a, b, c, d, e, f, g))
# 输出结果:a = |1| |abc|, b = |abc|,
# c = |{'key1': 'abc', 'key2': 'bcd', 'key3': [1, 2, 3, 4]},
# d = |abc|, e = |1|, f = |abc|,
# g = |<built-in method lower of str object at 0x108f899b0>|
a有两个位置参数,分别是两个列表,我们通过位置索引引用了这两个位置参数并且通过\[\]填入偏移量来引用列表中的元素,就像我们直接在变量中那么做的一样。
b有一个位置参数,这个参数是个字典,我们通过填入索引0来引用这个位置参数并通过\[\]填入键来引用对应的值,注意,这个键也不需要带引号。
c有一个位置参数,这个参数是一个类,我们通过.来引用类中的属性dict1,这个属性是字典,所以c打印的结果是一个字典。
d有一个位置参数,这个参数是一个类,通过0.dict1引用类中的属性dict1,因为dict1是一个字典,所以进一步在字典后面跟键值,来引用对应的值。
e和d很相似,只是e引用类中的属性是一个列表,所以通过偏移量(索引)来获取列表中的成员值。
f参数是一个关键字参数,所以通过关键字来引用参数,这个参数是一个字典,所以通过加上键来访问字典中对应的值
g和f很相似,通过键来取得对应的值,这个值是一个字符串,通过.来获取字符串的属性lower,这个lower是一个函数,所以打印出来函数类型。
h只比g多了一个函数调用的写法,也就是在lower(),会提示没有lower()这个属性,可以看出来,字符串格式化方法只是支持属性的引用,而不支持调用。
从上面的例子我们可以总结,键,偏移量,属性可以随意的嵌套调用,只要当前的对象支持这种操作就行,但是仅仅是属性,如果涉及到函数调用之类的操作,就会失败。
讲到这里,我们的格式化方法的基础就讲完了,和字符串格式化表达式一样,格式化方法同样重点关注格式化,接下来我们来讲关于格式化的相关标记。
2.格式化方法的详细用法
现在我们来引入一个完整的格式化语法结构,如下所示:
{fieldname component !coversionflag :formatspec}
2.1字段名fildname:
这个我们在基础用法上已经用过了,就是参数的索引或者关键字参数名。
2.2组件componet:
这就是我们所说的键,偏移量,以及属性,这个在基础用法上已经讲过了。
2.3转换标志conversionflag:
这个以!开头,后面可以跟r,s,a这三个参数:
r:其实就是repr函数,也就是需要对参数调用repr函数
s:就是str函数,也就是需要对参数调用str函数
a:这个是ascii函数,需要对参数调红ascii函数,这个函数可能很多人接触的比较少,一般情况下我们也用不到。但是在某些场景,例如当前的字符是一个非打印字符或者是非ascii的字符,通过ascii函数可以将这个字符转化为ascii码的安全形式。或许听到这个感觉有点云里雾里。我们还没有学到unicode,现在我们接触到的字符串中的字符都是可以用ascii码表示的,但是如果这里面混入中文,那么就没法用ascii编码表示了,就得用其他的编码方式。如果你当前的系统只能查看ascii码,这时候就需要将这个非ascii编码表示的字符直接打印出其unicode的形式,从而防止出现乱码。ascii的处理过程是先通过repr得到字符串,然后再对其就是ascii转化。我们先看下面的例子,会更容易理解:
python
a = "中文"
print("repr(a) ={}, str(a) = {}, ascii(a) = {} ,ord(a[0]) = {:x}, ord(a[1] = {:x})".
format(repr(a), str(a), ascii(a), ord(a[0]), ord(a[1])))
# 输出结果:repr(a) ='中文', str(a) = 中文, ascii(a) = '\u4e2d\u6587' ,
# ord(a[0]) = 4e2d, ord(a[1] = 6587)
print(eval(repr(a)) == a) # True
#print(eval(str(a)) == a) # name '中文' is not defined
b = 123
print("repr(b) ={}, str(b) = {}, ascii(b) = {}".
format(repr(b), str(b), ascii(b)))
# 输出结果:repr(b) =123, str(b) = 123, ascii(b) = 123
#给出repr转换后真正的内容,而不是通过print之后的展示
print("repr(a) ={}, str(a) = {}, ascii(a) = {} , without\\ = {}".
format("'中文'", "中文", "'\\u4e2d\\u6587'", "'\u4e2d\u6587'"))
# 输出结果: repr(a) ='中文', str(a) = 中文, ascii(a) = '\u4e2d\u6587' ,
# without\ = '中文'
上面的代码我们用print函数打印出了repr,str,ascii调用之后的结果。
对于a,通过repr,打印出的的字符串前后都带引号,而str打印的不带,通过ascii转化后,变成了两个16个字节的unicode,这两个unicode就是分别代表中文的两个码点值,我们可以通过ord来分别获得"中"和"文"的码点值,可以看出来打印出来的值是一样的。
对于b,因为是个整数,所以repr和str值都是一样的,因为ascii是在repr的基础上再转化,123三个字符都属于acsii表示的范围,所以原样打印了出来。
说到这里,我们简单说下repr和str的区别,repr注重的是还原性,打印出来的内容可以通过eval函数再还原回原来的对象,所以我们可以看到通过repr生成的字符串通过eval还以还原回来,但是通过str调用返回的字符串,提示的"中文"未定义。当然很多人也这么理解,str得到的结果是给人看的,而repr是给机器或者程序员看的,当然,你明白我没有说程序员不是人的意思。从这里我们可以看出来repr一个整数,必然不能带引号,要不然通过eval就变成字符串了。
当然,我们通过print打印出来的东西其实具有一定的欺骗性,它还没有显式的展示repr的结果是什么。所以在代码的最后,我们直接给出repr的真正内容打印,首先是中文这两个字符前面有两个单引号,其次ascii转换后,我们发现有两个\\,这是因为需要将\转义为它本身的含义。如果去掉这个\呢,那么ascii的调用将没太多意义,我们看到without\打印出了'中文'。到这里我们应该能理解ascii转换是什么意思,它去掉了转义,表示字符本来的含义,这有点像不转义r标记,如果你已经忘记了r的用法,那应该复习前面字符串的转义章节。
由于上面说了太多ascii,所以!coversionflag的例子我不再详细说明,感兴趣的同学可以自己尝试一下,不过在本章的末尾,我仍然举了一个例子。
2.4:格式化说明formatspec
看到现在,我们才到了真正比较复杂的部分,它甚至比我们上一章学的字符串格式化表达式更加复杂。我们先看一下整体的格式:
\[fill\]align\]\[sign\]\[#\]\[0\]\[width\]\[,\]\[.prrcision\]\[typecode
这猛一看,估计很多人已经晕了,其实不用担心,我们对某些事物心存畏惧,只是我们不了解,一旦我们了解了,那这个就变得很简单了。我们还是和之前一样,采取分而治之的思想,先一部分一部分的了解,最后拼成一个完整的图画。
2.4.1对齐与填充\[fillalign]
首先看这个,这里面有两对\[\],最外层的\[\]说明align是可选的,如果你选择了align,内层\[\]说明fill是可选的。
align有以下几种标记:
<代表左对齐;>代表右对齐;=代表符号字符后的填充,也就是说=只适用于数字类型,不适用于字符串,如果设置了=,则如果这个数带符号(比如负数),那么符号部分靠左,数字部分靠右;^是居中对齐,如果fill不设置,默认填充是空格。
fill是需要填充的字符,这个字符不能是{或者是},因为这样会引起歧义。
\[fillalign]有意义的前提是你设置了宽度,这个宽度width和字符串格式化表达式中的意义是一样的,设定参数的最小占位。
我来看下面的代码,因为没有宽度设置,对齐填充就没有了意义,所以下面的代码提前会设置宽度,因为在字符串表达式中已经学习了宽度,所以理解起来应该没问题。
python
a = "|{:<8}|".format("abc")
a1 = "|%-8s|" % "abc"
b = "|{:0<8}|".format("abc")
b1 = "|%0-8s|" % "abc"
c = "|{:>8}|".format("abc")
c1 = "|%8s|" % "abc"
d = "|{:0>8}|".format("abc")
d1 = "|%08s|" % "abc"
e = "|{:^8}|".format("abc")
f = "|{:0^8}|".format("abc")
# '=' alignment not allowed in string format specifier
# g = "|{:0=8}|".format("abc")
h = "|{:0=+8}|".format(123)
h1 = "|%0+d|" % 123
i = "|{:0=8}|".format(-123)
i1 = "|%0+d|" % -123
# ValueError: unmatched '{' in format spec
# j = "|{:{=8}|".format(-123)
# ValueError: Invalid format specifier '0><8' for object of type 'str'
# k = "|{:0><8}|".format("123")
print("a = {}, a1 = {}, b = {}, b1 = {}, c = {}, c1 = {}, d = {}, \
d1 = {}, e = {}, f = {}, h = {}, h1 = {}, i = {}, i1 = {}".
format(a, a1, b, b1, c, c1, d, d1, e, f, h, h1, i, i1))
# 输出结果:a = |abc |, a1 = |abc |, b = |abc00000|, b1 = |abc |,
# c = | abc|, c1 = | abc|, d = |00000abc|, d1 = | abc|,
# e = | abc |, f = |00abc000|, h = |+0000123|, h1 = |+123|,
# i = |-0000123|, i1 = |-123|
a设定了宽度是8,靠左对齐,可以看到结果是靠左对齐,并且后面用空格填满8个宽度。字符串表达式可以达到同样的效果,如a1所示。
b设定了宽度是8,靠左对齐,设置用0来填充,结果是靠左展示用0来填充。字符串表达式做不到这一点,在靠左对齐的情况下,填充会不起作用,如b1所示。
c和d与a和b一样,只是靠右对齐,所以填充都会在左边。字符串表示可以达到同样的效果,如c1和d1所示。
e和f是居中对齐,所以abc左右都会用空格或者指定的字符填充。字符串表达式做不到居中对齐。
g会报错,因为=不能在字符串中使用。
h和i展示了=的用法,从结果我们可以看出符号在左侧,数值在右侧,如果没有指定填充字符,则默认是空格,如果指定了填充字符,则用指定字符填充。字符串表达式可以达到相同的作用,通过+和0组合来实现。
j验证了{字符不能用做填充字符,因为会产生歧义。
k验证了< > = ^这四个符号是互斥的,不能同时使用。
从上面的对齐形式来看,格式化方法比格式化表达式有更多的表现形式。
2.4.2符号sign
sign包含三个字符:+, -,空格,只能应用于数字类型。
+:表示整数之前要加+,这个字符串表达式中的+是一样的
-:是默认的形式,表示整数之前不用加+,因为在字符串表达式中-代表左对齐,所以不填写效果也是一样的。
空格:如果是正数,数值前面会添加一个空格,这和字符串表达式一致。
我们看下面的代码:
python
a = "|{:+}|".format(123)
a1 = "|%+d|" % 123
b = "|{:-}|".format(123)
b1 = "|%d|" % 123
b2 = "|{:}|".format(123)
c = "|{: }|".format(123)
c1 = "|% d|" % 123
# ValueError: Invalid format specifier '+- 8' for object of type 'int'
# d = "|{:+- 8}|".format(123)
d1 = "|%+ d|" % 123
print("a = {}, a1 = {}, b = {}, b1 = {}, b2 = {}, c = {}, c1 = {}, \
d1 = {}".format(a, a1, b, b1, b2, c, c1, d1))
# 输出结果:a = |+123|, a1 = |+123|, b = |123|, b1 = |123|,
# b2 = |123|, c = | 123|, c1 = | 123|, d1 = |+123|
a:通过+设置正数前面加+,我们看到结果是+123,字符串格式化表达式也可以做到同样的效果,也是通过加号,如a1所示。
b:设置-号,这是默认的方式,我们可以看到结果是123,前面没有加号,字符串格式化表达式也可以做到同样的效果,只需要什么都不写,默认就行了,如b1所示。当然格式化方法也可以不写,也是同样的效果,如b2所示。
c:通过设置空格,会在正数前面加一个空格,我们可以看到123前面加了一个空格。字符串格式化表达式也是同样的设置,可以达到同样的效果,如c1所示。
d,证明这三个符号不可混用,但是对于字符串格式化表达式,空格和+可以混用,这种情况下,空格不起作用,如d1所示。
对于sign,字符串格式化表达式都可以实现相同的表示,这一点上两者不分伯仲。
2.4.3显示进制前缀#
#是用来给数字类型加前缀的,在上面的例子或者之前学习字符串表达式的时候,我们打印八进制或者或者16进制的时候,前面默认是没有0x或者0o的,如果我们加上这个标志,则会在前面展示进制的前缀,这个必须和类型码配合使用,所以下面的代码会包含类型码,其实类型码我们在字符串格式化表达式已经学过了,基本上是一样的。看下面的代码:
python
a = "|{:#d}|".format(-123)
a1 = "|%d|" % -123
b = "|{:#x}|".format(123)
b1 = "|0x%x|" % 123
b2 = "|{:x}|".format(123)
c = "|{:#o}|".format(123)
c1 = "|0o%o|" % 123
print("a = {}, a1 = {}, b = {}, b1 = {}, b2 = {}, c = {}, c1 = {}"
.format(a, a1, b, b1, b2, c, c1))
# 输出结果:a = |-123|, a1 = |-123|, b = |0x7b|, b1 = |0x7b|, b2 = |7b|,
# c = |0o173|, c1 = |0o173|
a是打印类型码是十进制的数字,所以加不加#没什么区别,这点字符串表达式也是一样的。
b是打印十六进制,我们可以看到数字前面加了0x这样的前缀,对于字符串格式化表达式没有这个功能,但是我们可以在%之前加0x实现相同的效果,如b1所示。b2展示了格式化方法不加#的效果,前面不带进制的标识。
c是打印八进制,和十六进制一样。字符串格式化表达式也用了b1的方式实现了同样的效果。
2.4.4"0"字符填充0
这个0填充用于数字,在设置了宽度的情况下,如果数字的占位小于宽度,这样会在符号位和数字之间加0。这个操作就等于上面2.4.1中0=的用法,只是这个更简洁。看下面的代码:
python
a = "|{:08d}|".format(-123)
a1 = "|%08d|" % -123
a2 = "|{:0=8d}|".format(-123)
b = "|{:+08d}|".format(123)
b1 = "|%+08d|" % 123
b2 = "|{:0=+8d}|".format(123)
print("a = {}, a1 = {}, a2 = {}, b = {}, b1 = {}, b2 = {}".
format(a, a1, a2, b, b1, b2))
# 输出结果:a = |-0000123|, a1 = |-0000123|, a2 = |-0000123|,
# b = |+0000123|, b1 = |+0000123|, b2 = |+0000123|
a设置了8个字符宽度,0填充在符号和数字之间,字符串格式化表达式也可以实现同样的效果,例如a1所示,同时我们用了0=来代替0,也可以实现同样的效果,如a2所示。
b其实和a一样,只是加了+来让正数前面加符号。这里一定要注意这些符号的顺序,因为不同的顺序含义是不一样的。如果你忘记了,可以再去温习一下,格式化方法和格式化表达式的顺序都是一样的。
2.4.5宽度,千分位和精度width,.precision
这三个我们放在一讲,width表示宽度,逗号表示千分位,.precision表示精度。宽度和精度我们在讲字符串格式化表达式的时候就说了,这里的意义是相同的。
width:宽度约定了参数所占的最小字符数,如果参数本身大于这个宽度,则按照参数实际宽度来算。
豆号:千位的分隔符,这个在字符串表达式中没有对应。
.precision:精度规定个浮点数的精度,如果不设定,默认值为6,这些和字符串格式化表达式中的设定是一样的。
同时宽度和精度也支持类似字符串表达式中*的用法,从参数中读取宽度和精度数值。
python
a = "|{:s}|".format("abc")
a1 = "|%s|" % "abc"
b = "|{:5s}|".format("abc")
b1 = "|%5s|" % "abc"
c = "|{:5s}|".format("abcdefg")
c1 = "|%5s|" % "abcdefg"
d = "|{:<5s}|".format("abc")
d1 = "|%-5s|" % "abc"
e = "|{:<.5s}|".format("abc")
e1 = "|%-.5s|" % "abc"
f = "|{:<{width}s}|".format("abc", width=5)
ff = "|{:+0.{width}f}|".format(12, width=5)
f1 = "|%-*s| |%0+ .*f|" % (5, "abc", 5, 12)
# f2 = "|{:<{width}s}| |{:+0{width1}s}|".format("abc",width=5,12,width1=6)
g = "|{:<10.5f}|".format(3.14)
g1 = "|%-10.5f|" % 3.14
h = "|{:<10,f}|".format(200000)
print("a = {}, a1 = {}, b = {}, b1 = {}, c = {}, c1 = {}, d = {}, d1 = {}, \
e = {}, e1 = {}, f = {}, ff = {}, f1 = {}, g = {}, g1 = {}, h = {}"
.format(a, a1, b, b1, c, c1, d, d1, e, e1, f, ff, f1, g, g1, h))
# 输出结果:a = |abc|, a1 = |abc|, b = |abc |, b1 = | abc|,
# c = |abcdefg|, c1 = |abcdefg|, d = |abc |, d1 = |abc |,
# e = |abc|, e1 = |abc|, f = |abc |, ff = |+12.00000|,
# f1 = |abc | |+12.00000|, g = |3.14000 |, g1 = |3.14000 |,
# h = |200,000.000000|
a是不给任何设置,直接输出abc,同等的字符串格式化表达式也不给任何参数,如a1所示。
b给了一个宽度5,abc默认靠左对齐,右边补了两个空格,字符串格式化表达式同样设置宽度为5,但是和格式化方法不同的是,默认为靠右展示,如b1所示。要达到和b一样的效果,需要在前面再加一个-。
c是为了测试宽度小于参数长度时,按照参数的实际长度。字符串格式化表达式也是如此,如c1所示。
d测试了靠左对齐,因为格式化函数默认就是靠左,所以这个<可以省略。格式化表达式默认是靠右,所以需要加上-,才能和格式化方法表现一样,如d1所示。
e和e1测试了参数是字符串类型时候,精度无效。
f和ff测试了将宽度和精度当作参数传入,我们在宽度或者精度的位置用{关键字参数名}代替,因为函数的参数关键字参数必须出现在位置参数之后,所以我们无法像格式化表达式f1那样,同时格式化两个参数,如f2所示。只能f1和ff分别格式化不同的参数。需要注意的是格式化方法,被格式化的参数是在宽度或者精度对应的参数前面的,如f所示,我们需要格式化的字符串abc是第一个参数,第二个参数给出了宽度,这样做的原因,我想是因为关键字参数必须出现在位置参数之后。格式化表达式恰恰相反,如f1所示。我没有给出同时参数化宽度和精度的例子,感兴趣的同学可以自己写一下。
g和g1给出了将浮点数设置了宽度和精度以及同时靠左对齐的场景,格式化方法和格式化表达式都可以实现。
h设置了千分位,注意这个千分位是放在宽度之后和精度之前的。格式化方法没有这个功能。
从上面可以了解,精度和宽度格式化方法和格式化表达式的能力基本上的一样的,甚至格式化表达式更为突出,可以在参数化精度和宽度的的时候,同时可以格式化多个参数,就像f1展示的那样。唯一的短处是格式化表达式无法设置千分位。
2.4.6类型码typecode
类型码格式化方法和格式化表达式几乎一模一样,所以这边就不重复的介绍了,感兴趣可以参照上一章:python-字符串全解(五):字符串格式化表达式以及格式化方法(1)
当然几乎一样不代表完全相同,在上一章中我们讲到字符串没有二进制的类型码,但是格式化方法有,我们看一下下面的代码:
python
a = "|{:#b}|".format(123)
print("a = {}".format(a))
# 输出结果:a = |0b1111011|
上面的代码,我们用b表明格式化成二进制,用#加上进制的前缀。
2.5 全面展示的例子
上面我们分而治之的讲述了这些语法,读到这里,我想你已经有足够的耐心并且花费了不少精力。但是可能你不停的陷入这些细节而忘记了整体的轮廓,这一小节,我们举一些整体的例子。
python
class class1:
def func1(self):
pass
pass
a = "|{0[0]:0>+#{width},.{precision}f}|".\
format([123333, 234], width=15, precision=4)
b = "|{0[0]:0=+#{width},.{precision}f}|".\
format([123333, 234], width=15, precision=4)
c = "|{0[0]:+#0{width},.{precision}f}|".\
format([123333, 234], width=15, precision=4)
d = "|{key1[0].func1!s:0>{width}s}|".\
format(key1=[class1, 234], width=40)
print("a = {}, b = {}, c = {}, d = {}".format(a, b, c, d))
# 输出结果:a = |00+123,333.0000|, b = |+0,123,333.0000|,
# c = |+0,123,333.0000|, d = |00<function class1.func1 at 0x1061d4f40>|
a从左到右,第一个0是位置参数,这个参数是个列表,然后通过0取得该列表的第一个元素,是一个整数。然后是冒号:,后面跟的是格式化定义,冒号后的0后面跟着>,这个代表靠右对齐,并且用0作为填充。然后是+,表示如果是正数则在数字前面显示+,下来是#,显示进制的前缀,但是十进制没有前缀,所以什么都不会显示,{width}是要动态获取宽度的值,这个宽度值是15,然后是千分位逗号,最后.{precision}动态获取精度值,这个值是4。
b和a很相似,唯一的不同是a用的是靠右对齐(0>),而b是符号靠左,数值靠右(0=),所以a的0填充在符号的左边,而b的0填充在符号和数字的中间,b更符合一般的写法。
c的结果和b一样,c没有用对齐的符号,而只是用了在宽度之前的0填充,这个填充的效果等于0=。
d采用的关键字参数,这个参数对应一个列表,通过0取得列表的第一个元素calss1,然后通过"."获取了该元素的属性,这个属性是一个函数,然后通过!s,对这个函数属性调用str,可以看到打印出来是一个函数对象。后面的格式设置我们前面已经说了很多,这里不在赘述。
到这里,我们已经讨论完字符串格式化方法,并且给出了和字符串格式化表达式的对比,到底谁更优秀呢?这个很难说,我也不想加入我个人喜好去评判,既然这两种方式都存在,那就选择你最喜欢的方式就可以。说实话,两者的复杂程度差不多,而且平时可能很少去用到这些复杂的用法。花费这样的篇幅去说,更像是一个语法完整性的说明。
至此,我们已经学完字符串格式化的两个方式,但python又出了一个f-string,我不想去细说这个语法,如果你能完全弄懂格式化方法和格式化表达式,f-string看一眼就会了。下一章我们会谈论一个字符串的一个高级用法:正则表达式。正则表达式可以说是一个语言无关的技术,很多高级语言对它都支持,python也不列外。如果你弄懂了正则表达式,在处理未知文本的时候,你就多了一个好用的工具。