文章目录
23.字符串的格式化
Resource
自定义字符串格式, 可以定义自己的格式字符串语法-官方中文文档
格式字符串语法 format 和 % 的对比 - 官方中文文档
模板字符串 string.Template(template)-官方中文文档
时间格式代码 ——1989 C标准所需的所有格式代码的列表,这些代码适用于具有标准C实现的所有平台。比如 cpython
F-STRING - Literal String Interpolation PEP-498
format()
讲解
format 的 优点是 理论上 可以 填充任何字符串
% 则 只能填充 0 和 一些特定的字符 , 根据你 填入的 flag 转换标志来决定
replacement_field 语法说明
如果需要在文本中包含 大括号字符
,则可以通过加倍对其进行转义
: {{{{
和 }}}}
.
语法:
"{" [field_name] ["!" conversion] [":" format_spec] "}"
replacement_field ::= "{" [field_name] ["!" conversion] [":" format_spec] "}"
# 主要 有 3 个 可选项
field_name ::= arg_name ("." attribute_name | "[" element_index "]")* # xxx.属性名字 或者 xxx[索引值]
arg_name ::= [identifier | digit+] # 标识符 或者 数字
attribute_name ::= identifier # 属性名字 为 标识符
element_index ::= digit+ | index_string # 数字 或者 是 索引字符串 比如 dict['key']
index_string ::= <any source character except "]"> +
conversion ::= "r" | "s" | "a" # repr 或者 str 或者 ascii 比如 这样写 {!r} {!s} {!a}
format_spec ::= <described in the next section> # 查看后面的 详细说明
format_spec 语法说明
{:冒号的右边就是format_spec}
填入了 fill
填充内容的 时候 就 必须 填写 对齐方式 {:0>}
或者 其他 几个 对齐方式<, ^, =
其他的选项都是 可选项
语法:
[[fill]align][sign][#][0][width][grouping_option][.precision][type]
format_spec ::= [[fill]align] [sign][#][0][width][grouping_option][.precision][type]
fill ::= <any character> # fill 可以是任何字符的字符,如果省略,则默认为空格。
align ::= "<" | ">" | "=" | "^" # 4 个 对齐模式
sign ::= "+" | "-" | " " # 这个 sign 选项仅对数字类型有效 , 为了 表示 有符号数. 简单的说就是 正负
width ::= digit+ # 宽度 是 一个 数字
grouping_option ::= "_" | "," # 分组选项 , 可以理解为 千分位 的 分隔符
precision ::= digit+ # 精度 一个数字 也是 和 % 的一样的 .精度 注意这个 点点 嘻嘻
type ::= "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%" # 转换类型
这个 ‘#
’ 选项使转换使用“替代形式”。
对于不同的类型,替代形式的定义不同。此选项仅对
integer、float、complex和decimal
类型有效。
后面%
格式化 字符串 的时候 , 也会 聊到这个 符号.
符号系统
sign | 描述 |
---|---|
+ | 指示正负数字都应使用符号。 |
- | 指示符号只能用于负数(这是默认行为)。 |
空格 | 指示应在正数上使用前导空格,在负数上使用减号。 |
分组选项(关于整个的语法 如何对应)
这个','
选项表示使用逗号作为千位分隔符。
对于支持区域设置的分隔符,请使用 ‘n’ 改为整数表示类型。
print("{:x=+#025,d}".format(-120000000000000))
-xxxxx120,000,000,000,000
# ####################### 说明 #############################
# 从 冒号 开始 都属于 format_spec 的语法
: 代表 format_spec 开始
x 代表 语法中 的 fill
= 代表 语法中 的 align 也就是 对齐方式
+ 代表 sign 也就是说 我们是 要 使用 正号 和 负号的
# 井号是 一个可选项 ‘#’ 选项使转换使用“替代形式”。 此选项仅对integer、float、complex和decimal类型有效, 比如 前缀 0x, 0o,0b 等等
0 代表的是 一个 对齐 方式 0= , 这个的对齐 优先级 没有 fill 和 align 高, 所以 我们的 这个例子, 可以把0 去掉, 结果也是一样的
25 代表 宽度
, 代表 grouping_option 也就是 千分位 分隔符
d 代表 type 类型
# #########################################################
tips: 这个例子 没有 放入 精度
精度就是 在 type 前面 加一个 .数字,
用我们的这个例子来举例 ,
d 类型 没有 精度 , 所以换成 f 浮点数
加了 一个 .2 也就是 精度 为 2
.2 代表的就是 precision , 这些 都是 结合 上面的语法来看的.
print("{:x=+#025,.2f}".format(-120000000000000))
输出结果: -xx120,000,000,000,000.00
# ################# 实验 ##########################
# 我们改成 x 16进制 的转换类型 来看看, 由于 x 类型 不支持 , 分隔符 所以换成 _ 下划线
# 为了 区分 0x 的前缀 我把 填充字符改为了 a
print("{:a=+#025_x}".format(-120000000000000))
输出结果: '-0xaaaaaaaa6d23_ad5f_8000'
# 把 #0 去掉 , 发现没有了 0x 前缀
print("{:a=+25_x}".format(-120000000000000))
输出结果: '-aaaaaaaaaa6d23_ad5f_8000'
# 去掉 a 字母 填充 和 = 对齐
# 这两个是一起的 fill 和 align 是绑定的
# 也就是 去掉 a=
# 可以看到的是 0x 依然 有 .
# 但是 0x 和 数字中间的 填充 变为了 0
print("{:+#025_x}".format(-120000000000000))
输出结果: '-0x00_0000_6d23_ad5f_8000'
# 我们在这个基础上去掉 0
# 由于我们设置的是 25 的长度, 达不到的时候, 会使用 默认的 空白 来填充
# 如果我们取消 井号 那么 0x 也没有了
# 结论是 这个 0 就是 填充 0
print("{:+#25_x}".format(-120000000000000))
输出结果: ' -0x6d23_ad5f_8000'
# 那么为了验证 填充 0 和 前面的 fill 和 align 的 优先级
# 经过下面 的 测试, 发现 fill 和 align 的优先级 更高
# 0 的作用是 0= 对齐方式
# 以下 两个 输出一致
print("{:0=+25_x}".format(-120000000000000))
print("{:+025_x}".format(-120000000000000))
输出结果:
-0000_0000_6d23_ad5f_8000
-0000_0000_6d23_ad5f_8000
这个 ‘_
’ 选项表示对浮点数表示类型和 整型
表示类型的 千分位
隔符使用下划线
。 d
对于整数表示类型 ‘b
’, ‘o
’ , ‘x
’ 和 ‘X
’ ,下划线将每隔4位插入。
对于其他演示文稿类型,指定此选项是错误的。
print("{:x=+#05_d}".format(-120000000000000))
-120_000_000_000_000
精度
print("{:x=+#5_.8f}".format(-120000000000000))
-120_000_000_000_000.00000000
format 基本使用
Python2.6 开始,新增了一种格式化字符串的函数
str.format()
,它增强了字符串格式化的 功能。
基本语法是通过 { }
和 :
来代替以前的 %
。
format 函数可以接受不限个参数,位置可以不按顺序。
我们通过示例进行格式化的学习。
In [6]: a = "名字是: {0},年龄是: {1}"
# 这里的 0 和 1 就是 上面 语法 中的 arg_name
In [7]: a.format("onepis",18)
Out[7]: '名字是: onepis,年龄是: 18'
In [8]: a.format("海贼王",6)
Out[8]: '名字是:海贼王,年龄是:6'
# -----------------------------------------------
In [9]: b = "名字是:{0},年龄是{1}。{0}是个好小伙"
In [10]: b.format("onepis",18)
Out[10]: '名字是:onepis,年龄是18。onepis是个好小伙'
# -----------------------------------------------
In [11]: c = "名字是{name},年龄是{age}"
# 这里 name 和 age 就是 arg_name
# 如果 name 是 一个字典 , 那么 也可以 使用 name[age]
# 那么 这里 的 name 就是 所谓的 arg_name
# age 就是 index_str
# 如果 传进去的参数 是一个 对象 那么也就可以 使用 .attr 属性名字的方式来访问了.
In [12]: print("name is {data[name]}, age is {data[age]}".format(data={'name':'onepis', 'age':18}))
Out[12]: name is onepis, age is 18
In [12]: c.format(age=19,name='Yaphets')
Out[12]: '名字是Yaphets,年龄是19'
# -----------------------------------------------
In [13]: x=0.999
In [14]: "{0:.3%}".format(x)
'99.900%'
In [15]: format(x,'.2%') # 在转换 百分比的时候 不要写 .2f% .2d% 这些都会报错的哈
'99.90%' # .2% 代表两位数的 小数
我们可以通过{索引}/{参数名},直接映射参数值,实现对字符串的格式化,非常方便。
关于时间的字符格式化 来回转换
import datetime
now = datetime.datetime.now
time_tmp = "{:%y/%m/%d %H:%M:%S}"
time_tmp = "{:%D %X}"
time_tmp.format(now())
"{:%y/%m/%d}".format(now())
# '21/06/12'
"{:%Y/%m/%d}".format(now())
# '2021/06/12'
"{:%I:%M:%S}".format(now())# 12 小时制
"{:%H:%M:%S}".format(now())# 24 小时制
"{:%c}".format(now())
# 'Sat Jun 12 04:26:32 2021'
"{:%X}".format(now())
# '04:28:36'
"{:%x}".format(now())
# '06/12/21
"{:%D}".format(now())
#'06/12/21'
"{:%x %X}".format(now())
# '06/12/21 04:31:26'
# 对象 转换回去 也很简单 , 第二个 符号 传递 相应的 符号 即可。
datetime.datetime.strptime("06/12/21", "%x")
# datetime.datetime(2021, 6, 12, 0, 0)
# 其他的我就 不一一测试了。
杂
符号 | 描述 |
---|---|
%p | AM PM 上午 下午 |
%G | iso 8601 年, 可以简单的理解为 年份。 如果是 2021年 显示 2021 |
%g | 这个是 比如 2021 年 这里 会显示 21 |
关于时区
符号 | 描述 |
---|---|
%Z | 时区名称 如果 没有的话为 空字符 |
%z | utc 偏移量 |
关于星期
符号 | 描述 |
---|---|
%u | 小写u 表示 数字 表示 的星期几 从1 开始 , 1,2,…, 7 |
%w | 表示 星期几 0 表示 星期天 , 6表示 星期六 0, 1…6 |
%a | 星期几 的缩写, 缩写 |
%A | 星期几 的全称 |
%U | 一年中的 第几周(星期日为一周中的第一天) |
%W | 一年中的 第几周 (星期一为 一周的第一天) |
关于 年
符号 | 描述 |
---|---|
%Y | 以世纪为十进制数的年份, 也就是 4 位数的 年份 |
%y | 没有世纪的年份作为零填充十进制数 也就是两位数的年份 |
关于月
符号 | 描述 |
---|---|
%m | 以零填充的十进制数表示的月份。数字 |
%b | 月份的 缩写 英文 |
%B | 月份的 英文全称 |
关于 日
符号 | 描述 |
---|---|
%d | "以零填充的十进制数字表示的月份日期。 当前月份的 第几天 。 数字 |
%j | 一年中的 第几天 |
关于 时间
符号 | 描述 |
---|---|
%S | 秒数 |
%M | 分钟 |
%H | 24 小时制 时 |
%I | 12 小时制 时 |
%f | 微秒 |
常用日期时间格式简写
符号 | 描述 |
---|---|
%c | ’ Sat Jun 12 03:45:22 2021’ 区域设置的 适当的 时间日期 表示 |
%x | 区域设置的 适当的日期表示 |
%X | 区域设置的 适当的 时间表示 |
%D | 日期格式 y/m/d 年/月/日 这个文档中 没有, 我自己 试出来的。其实和 小写 x 一样 |
日常使用的 完整表示表示
符号 | 描述 |
---|---|
日常使用的 年月日 => 2位数年 | “{:%y/%m/%d}”.format(now()) |
日常使用的 年月日 => 4位数年 | “{:%Y/%m/%d}”.format(now()) |
日常使用的 12小时制 时分秒 | “{:%I:%M:%S}”.format(now()) |
日常使用的 24小时制 时分秒 | “{:%H:%M:%S}”.format(now()) |
填充与对齐
填充常跟对齐一起使用
^、<、>
分别是居中、左对齐、右对齐,后面带宽度
=
强制填充在符号(如果有)之后但在数字之前。这用于以“+000000120
”格式打印字段。此对齐选项仅对数字类型有效。当“0”紧跟在字段宽度之前时,它将成为默认值。
(正数和负数等, 可能会有其他我不知道的有符号数字? )
类似于 这种感觉
print("{:x=+#05d}".format(+12))
+xx12
print("{:x=+#05d}".format(-12))
-xx12
:
号后面带填充的字符,只能是一个字符,不指定的话默认是用空格填充
In [13]: "{:*>8}".format("245")
Out[13]: '*****245'
In [14]: "我是{0},我喜欢数字{1:*^8}".format("Yamateh","666")# 这里是指用*来填充
Out[14]: '我是Yamateh,我喜欢数字**666***'
数字格式化
浮点数通过
f
,整数通过d
进行需要的格式化。案例如下:
In [15]: a = "我是{0},我的存款有{1:.2f}"
In [16]: a.format("负二代",-9999999999.234342)
Out[16]: '我是负二代,我的存款有-9999999999.23'
In [17]: print('i am {0},age:{1:.2f}'.format('tom',3.1415926))
i am tom,age:3.14
In [18]: print('i am {0},age:{1}'.format('tom',18))
i am tom,age:18
其他格式,供大家参考:
3.1415926 | {:.2f} | 3.14 | 保留小数点后两位 |
---|---|---|---|
3.1415926 | {:+.2f} | +3.14 | 带符号保留小数点后两位 |
-1 | {:+.2f} | -1.00 | 带符号保留小数点后两位 |
2.71828 | {:.0f} | 3 | 不带小数 |
5 | {:0>2d} | 05 | 数字补零 (填充左边, 宽度为2) |
5 | {:x<4d} | 5xxx | 数字补x (填充右边, 宽度为4) |
10 | {:x<4d} | 10xx | 数字补x (填充右边, 宽度为4) |
1000000 | {:,} | 1,000,000 | 以逗号分隔的数字格式 |
0.25 | {:.2%} | 25.00% | 百分比格式 |
1000000000 | {:.2e} | 1.00e+09 | 指数记法 |
18 | {:>10d} | ’ 18’ | 右对齐 (默认, 宽度为10) |
18 | {:<10d} | '18 ’ | 左对齐 (宽度为10) |
18 | {:^10d} | ’ 18 ’ | 中间对齐 (宽度为10) |
最简单的 format 理解方式
# 以这种 方式 来理解
# 这些名字 我特意 和语法 里面 保持一致
# 非常容易理解
# fill 填充 align 对齐 width 宽度 grouping_option 分割符 precision 精度 type 类型
# 这里 没有 写 0 因为 #0 井号 后面的 这个 0 代表的 其实 是 fill = '0' align = '='
format_spec = "{:{fill}{align}#{width}{grouping_option}{precision}{type}}"
format_spec.format(
-120000000000000,
fill='x',
align='=',
width=25,
grouping_option='_',
precision='.2',
type='f',
)
# 这里和上面的其实 是 一样的, 但是 我把 值 给了 一个 变量 , 这个变量 在 语法中 叫做 field_name
format_spec = "{val:{fill}{align}#{width}{grouping_option}{precision}{type}}"
format_spec.format(
val=-120000000000000,
fill='x',
align='=',
width=25,
grouping_option='_',
precision='.2',
type='f',
)
# 要记住 conversion => !r !a !s 这 3 个 是 无法动态传入的.
# 因为 冒号 左侧不允许 有 {} 这个 符号出现
format_spec = ":{fill}{align}{width}{grouping_option}{precision}{type}"
# 变通的方式 是 这样 利用 字符串拼接
# 但是太麻烦了. 一般 也 用不到.
conversion = "!r"
template = f"{{{conversion}{format_spec}}}"
# 字符串 格式 不允许使用 = 对齐 以及 #
# 字符串 格式 也不允许 使用 其他类型
# 要记住 # 只能在 数字类型里面使用
template.format(
's',
fill='x',
align='>',
width=25,
grouping_option='',
precision='',
type='s',
)
%
符号 格式化字符串
一些简单示例
语法: %[映射键][转换标志][最小字段宽度, * ][精度 点 后面是精度][长度修改器] 转换类型
使用字典填充 , 映射键
# %(name) name 是映射键 s是转换类型
# 最后一个字母就是转换类型
print('name: %(name)s id: %(id)03d !' %{'name': "onepis", "id": 2}) # 使用字典填充
name: onepis id: 002 !
# -----------------------------------------------
元组填充 以及转换标志使用
转换标志可以给出多个
%的缺点
%无法填充其他的任意字符 , 只有 0 和 空白, 也没有 format 的相对丰富的对齐方式
并且 0 填充只能针对 字符类型. 不能针对 数字
只有 宽度 和 精度 可以 通过 * 动态传入
不过 我们其实 可以 混合 使用, 虽然 大部分的 场景 % 和 format 和 f-string , 以及 Template 都可以适应.
如果还是不行, 那么 几种手段 混合 使用 或者 混合 着 正则 一起 使用
实际示例
# 井号的作用是 使用 16进制等类型的时候 会 0x , 0o , 0b 的前缀
'%+-#5x' %99
Out[_79]: '+0x63'
# 默认是 左边填充
'%+#5d' %99
Out[_76]: ' +99'
# 右边填充 空白
'%+-#5d' %99
Out[_78]: '+99 '
# 空格的作用是 正数的时候 在前面放一个空格, 负数的时候就 使用 负号
'%0 #5d' %99
Out[_80]: ' 0099'
'%0 #5d' %-99
Out[_81]: '-0099'
# 对于 用0 填充 以及格式为d , 无需使用 # ,除非你有格式需要
# 比如下面这个
# 8进制 的 10 长度 为4 用 0 填充
# %0#4o
# 0 和 # 是 转换标志 可以同时给出
# 井号代表 使用 进制 的数字 变成 0x , 0o, 0b 这样的
# 0 代表 对于 数字而言 使用 0 填充
# 4 是 最小字段宽度
# o 是转换类型
print('name: %s id: %0#6o !' %("onepis",10))
name: onepis id: 0o0012 !
# 0 是 转换标志 , 用 0 填充
# 3 是最小字段宽度
# d 是转换类型
In [22]: print('name: %s id: %03d !' %("onepis",2)) # 使用元组 在 对应位置填充
name: onepis id: 002 !
# 仅仅多出了 一个 井号 转换标志
In [29]: print('name: %s id: %0#3d !' %("onepis",2)) # 可以看到这种情况, 两个方式 输出是一样的.
name: onepis id: 002 !
# 最小字段宽度 变为了 20
In [31]: print('name: %s id: %#20d !' %("onepis",2))
name: onepis id: 2 !
# 的作用
# --------------------- # 的作用 --------------------------
print('name: %s id: %x !' %("onepis",16)) # 16 进制
name: onepis id: 10 !
print('name: %s id: %#x !' %("onepis",16)) # 输出这个格式的 16进制
name: onepis id: 0x10 !
print('name: %s id: %#0o !' %("onepis",16)) # 8 进制
name: onepis id: 0o20 !
print('name: %s id: %2o !' %("onepis",16)) # 有井号 和 没井号 是 前面符号的区别
name: onepis id: 20 !
精度使用
# -----------------------------------------------
In [32]: print('name: %s id: %.2f !' %("onepis",2))
name: onepis id: 2.00 !
# %.2f
# 转换标志没有
# 精度 为 2
# 类型 为 float
print('name: %s id: %0#5.2f !' %("onepis",2))
name: onepis id: 02.00 !
# 小数点 也是 占用长度的
# %0#5.2f
# 用 0 填充为 5 的长度, 两位 小数.
# 转换标志 0 和 #
# 最小字段宽度 为 5
# 精度 为 2
# 类型为 float
# 可以看到下面两种写法是一样的.
print('name: %s id: %#10.2f !' %("onepis",2))
name: onepis id: 2.00 !
print('name: %s id: %10.2f !' %("onepis",2))
name: onepis id: 2.00 !
%转义
In [33]: x=11
# %% 相当于 转义 输出的就是 %
In [34]: "%.2d%%"%(x)
'11%'
In [35]: "%.3d%%"%(x)
'011%'
In [36]: "%.3f%%"%(x)
'11.000%'
In [37]: x=0.11
In [38]: "%.3f%%"%(x*100)
'11.000%'
语法
语法: %[映射键][转换标志][最小字段宽度, * ][精度 点 后面是精度][长度修改器] 转换类型
-
这个 ‘%’ 字符,标记说明符的开头。
-
映射键(可选),由带括号的字符序列组成(例如, (somename) )
-
转换标志(可选),影响某些转换类型的结果。
-
最小字段宽度(可选)。如果指定为 ’
*
’ (星号),从中的元组的下一个元素读取实际宽度。 价值观 ,要转换的对象在最小字段宽度和可选精度之后。
动态传入 宽度和精度
print('%0*.*f <' %(5,3,2))
# 5 是 宽度
# 3 是 精度
# 2 是 值
2.000 <
这个就表示 长度 为 5 , 精度 为 3 的 一个 浮点数
- 精度(可选),作为 ‘
.
’ (点),然后是精度。如果指定为 ‘*
’ (星号),实际精度
从中的元组的下一个元素读取。 要转换的值在精度之后。
print('%.*hd <' %(100,2))
# 这里的 100 就 是 放在 * 里面 作为 精度的
- 长度修改器(可选)。
长度修改器 (h , l 或 L )可能存在,但会被忽略,因为这对于Python来说是不必要的——例如。 %ld 相同 %d .
目前我没有发现 有什么作用 , 这个长度修改器
- 转换类型。
% 的转换标志(和 # 类似)
下面是 5 个 转换标志
’#'
值转换将使用“替代形式”
’0’
对于数值,转换将被零填充。
’-
'
转换后的值进行左调整(覆盖 ‘0’ 如果两者都给出,则转换)。
其实 就是填充 从右边 填充了.
’’ 空格
(空格)在有符号转换产生的正数(或空字符串)之前应该留一个空白。
’+
'
符号字符 (’+’ 或 ‘-’ )将在转换之前(覆盖“空格”标志)。
这个是 处理正负的 标志
覆盖关系
+ 会 覆盖 空格
- 会覆盖 0
所以 可能出现的 3 个 在一起的 有效场景是, 最多 3 个一起出现
+-#
0 #
print('%+20d <' %100)
out: +100 <
print('%+20d <' %-100)
out: -100 <
type 列表(通用,支持类型的格式化方式中通用)
下面是 转换类型
字符串常量
import string
In [34]: string.ascii_letters # 输出大小写字母
Out[34]: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
In [35]: string.ascii_lowercase # 输出 小写字母
Out[35]: 'abcdefghijklmnopqrstuvwxyz'
In [36]: string.digits # 输出数字
Out[36]: '0123456789'
In [38]: string.punctuation # 输出标点符号
Out[38]: '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
# 还有很多 大家 可以去 官网看
自定义字符串格式(可以定义自己的格式字符串语法)
内置的字符串类 str
提供了通过使用 PEP 3101 所描述的 format()
方法进行复杂变量替换和值格式化的能力。 string
模块中的 Formatter
类允许你使用与内置 format()
方法相同的实现来创建并定制你自己的字符串格式化行为。
import string
>>> class Person:
name="default name"
def __init__(self,name=""):
self.name=name
>>> me=Person("onpis")
>>> data=[me]
>>> s="my name is {data[0].name:^30}"
>>> fmt=string.Formatter() # 实例化一个 对象 fmt
>>> fmt.format(s,data=data) # string.Formatter().format 函数 , 调用实例对象的 method
'my name is onpis '
>>> s="my name is {data[0].name}"
>>> fmt.format(s,data=data)
'my name is onpis'
>>> s="my name is {data[0].name:<10}"
>>> fmt.format(s,data=data)
'my name is onpis '
>>> fmt.vformat(s,(),{"data":data}) # vformat 函数
'my name is onpis '
# 2个方法的结果是完全一样的,只是一个包装了另一个,
# 在 Formatter.vformat方法中必须有4个参数,中间的空元组和最后的空字典必须存在,
# 因为Formatter.vformat的参数不是 (*args, **kwargs) 而是 (format_string, args, kwargs)
# 现在继续说上面4个方法的调用顺序,
# get_value调用了get_field,
# get_field调用parse,
# vformat调用了 get_value, 一般情况下我们只需要调用format就足够了,
# 上面的4个方法都是给需要继承Formatter创建自己的格式化语法的时候来覆盖掉的,
# 不过我们可以从这4个方法分析出很多东西。
>>> for i,v in enumerate(fmt.parse(s)):
# s="my name is {data[0].name:<10}"
# parse函数 返回 可迭代对象组成的 元组 ('my name is ', 'data[0].name', '<10', None)
temp=v
print(temp)
>>> fmt.get_field(temp[1],(),{"data":data}) # 调用 get_field 返回 一个元组
('onpis', 'data')
>>> ret = fmt.get_value(fmt.get_field(temp[1], (), {'data':data})[1], (), {'data':data})
>>> ret
[<__main__.Person at 0x1164cb0>] # 这个就和一开始 定义的 data 是 一样的 又回到了 最初
模板字符串
不使用 Template 模块 构建 模板
其实和 普通的使用 format
没有太大的差别
In [20]: import datetime
In [21]: template=('\ndate : "{}", Temperature : {:.1f}, Condition :"{}"')
In [22]: log=template.format(datetime.datetime.now(),23,17,"good")
In [23]: print(log)
date : "2020-08-02 01:16:59.545826", Temperature : 23.0, Condition : "17"
使用 string.Template
$$
是一种转义 , 本质是 $
这种方式 只能进行一些 简单的 替换 工作
无法使用 填充 .
In [24]: from string import Template
In [25]: s = Template('$who likes $what')
In [26]: s.substitute(who='tim', what='kung pao')
Out[26]: 'tim likes kung pao'
# 执行模板替换,返回新字符串。
# safe_substitute 函数
# 当其他异常仍然可能发生时,此方法被称为“safe”,因为它总是尝试返回可用的字符串而不是引发异常。
In [34]: s = Template('$who likes $what') # 也可这样写
In [35]: s = Template('${who} likes $what')
In [36]: d=dict(who='tim',what='pp')
In [37]: s.substitute(d)
Out[37]: 'tim likes pp'
# 一种偷懒的 参数传递方式
# 只要你 有一个 key 能 对的上 参数的 字典 那么你 就可以 将其 传进去.
dic = {'who':'tim', 'what':'pic'}
s.substitute(**dic)
out: 'tim likes pic'
# 属性访问 , 一般来说,不应该更改它,但不强制使用只读访问。
s.template
'$who likes $what'
s.substitute(dic, who='b')
# 添加 关键字参数, 后面添加的 会优先使用. 如果 和 占位符 重复的话, 如果没有命中的话, 则 不会 出现什么.
'b likes pic'
F-string 格式化字符串
我觉得 这个和 format
的那个 语法是 极其 相似的.
A formatted string literal 或 f-string 是前缀为的字符串文本 ‘f’ 或 ‘F’ . 这些字符串可能包含替换字段,替换字段是由大括号分隔的表达式。
解码后,字符串内容的语法为:
在 F-string 中 如果 要使用 {
那么可以 {{
双倍 快乐 , 就是 转义.
缺点, 必须事先 有变量 .
以及 无法直接在 f-string expression 中使用 反斜杠
以及实在是太灵活了. 看到后面 就 知道了.
这句话非常重要
顶级格式说明符可以包括嵌套替换字段。这些嵌套字段可能包括它们自己的转换字段和 format specifiers ,但可能不包括嵌套更深的替换字段。
因为 可以使用 format 的 语法, 在replacement_field
中 ,
也就是 说 , 在
{}
里面 可以使用format
的 小型 语法系统. .replacement_field
和format_spec
这两个 都可以使用.
而且还多了 f_expression 这个东西
语法:
"{" f_expression ["="] ["!" conversion] [":" format_spec] "}"
f_string ::= (literal_char | "{{" | "}}" | replacement_field)*
# 我们主要还是 看 replacement_field 替换字段
# 3.8 新版功能: 等号 '='
replacement_field ::= "{" f_expression ["="] ["!" conversion] [":" format_spec] "}"
f_expression ::= (conditional_expression | "*" or_expr)
("," conditional_expression | "," "*" or_expr)*
[","]
| yield_expression
# 这里的 条件表达式 就是 三元
# 可以 not or and 等等
# or_expr 是 按位 或 运算
# 包括在里面进行循环, 以及函数调用
conversion ::= "s" | "r" | "a" # 这个都是 通用的吧 在 这里 讨论的 几个 格式化方式
format_spec ::= (literal_char | NULL | replacement_field)* # 这里看到了 这个 是可以递归的. 无线嵌套 真刺激
literal_char ::= <any code point except "{", "}" or NULL> # 除了 '{' '}' 或者 NULL 这 3个 其他都是 合法的
yield_atom ::= "(" yield_expression ")"
yield_expression ::= "yield" [expression_list | "from" expression]
In [47]: name = "onepis"
In [48]: f"He said his name is {name!r}." #原始字符串 raw
Out[48]: "He said his name is 'onepis'."
In [49]: f"He said his name is {repr(name)}." # repr() is 相当于!r
Out[49]: "He said his name is 'onepis'."
# -------------------- 我说的就是 这个 嵌套 ------------------------------
In [50]: width = 10
In [51]: precision = 4
In [55]: value =float("12.34567")
In [56]: value
Out[56]: 12.34567
In [57]: f"result: {value:{width}.{precision}}"
# 这个就是 动态 的 传递 精度 和 宽度
Out[57]: 'result: 12.35' # 嵌套字段
# 用 % 实现类似效果
# 类似这个效果
# 填充长度 为 6
# 精度 为 3
# 填充字符 为 空格
# 传递的参数 为 2
[ins] In [_i313]: "%#*.*f"%(6,3, 2)
Out[_313]: ' 2.000'
# 填充长度 为 6
# 精度 为 3
# 填充字符 为 0
# 传递的参数 为 2
[ins] In [_i314]: "%0#*.*f"%(6,3, 2)
Out[_314]: '02.000'
# 使用 format 进行类似的操作
"{:0>{}.{}f}".format(2,6,3)
# f-string 的话 实际上 是 这样的, 换成变量就可以 动态 传递 这些 参数进去
f"{2:0>{6}.{3}f}"
# ------------------- 进制转换 -------------------------------
In [75]: number = 1024
In [76]: f"{number:#0x}"
Out[76]: '0x400' # 转换
f"{2:0=#18b}"
# 这个是 二进制的 显示
'0b0000000000000010'
f"{255:0=#22_b}"
# 对于 16bit 两个 字节长度的 数字 我会 填充 为 这个长度
# 然后 加上 _ 分隔符 看起来方便一些
'0b0_0000_0000_1111_1111'
这些都是 是可以在 f-string 中使用的
format 语法 可以使用的 这里都可以使用 , 包括前面讲的 那些
format_spec
里面那些
3.1415926 | {:.2f} | 3.14 | 保留小数点后两位 |
---|---|---|---|
3.1415926 | {:+.2f} | +3.14 | 带符号保留小数点后两位 |
-1 | {:+.2f} | -1.00 | 带符号保留小数点后两位 |
2.71828 | {:.0f} | 3 | 不带小数 |
5 | {:0>2d} | 05 | 数字补零 (填充左边, 宽度为2) |
5 | {:x<4d} | 5xxx | 数字补x (填充右边, 宽度为4) |
10 | {:x<4d} | 10xx | 数字补x (填充右边, 宽度为4) |
1000000 | {:,} | 1,000,000 | 以逗号分隔的数字格式 |
0.25 | {:.2%} | 25.00% | 百分比格式 |
1000000000 | {:.2e} | 1.00e+09 | 指数记法 |
18 | {:>10d} | ’ 18’ | 右对齐 (默认, 宽度为10) |
18 | {:<10d} | '18 ’ | 左对齐 (宽度为10) |
18 | {:^10d} | ’ 18 ’ | 中间对齐 (宽度为10) |
F-string 骚操作
字符串使用与str.format相同的格式说明符迷你语言。 也就是 我前面说的 那个语法 .
与str.format()类似,可选的格式说明符可能包含在f-string中,
与表达式(或类型转换,如果指定的话)用冒号分隔。如果没有提供格式说明符,则使用空字符串。
允许的转换是'!s', '!r' , '!a'
它们与str.format()
中的相同:'!s'
调用表达式上的str()
,
'!r
'调用表达式上的repr()
,
并且'!a'
对表达式调用ascii()
。在调用format()
之前应用这些转换。
表达式不能包含“:”
或“!”
在字符串、圆括号、方括号或大括号之外
。
唯一的例外是
!=
’运算符是允许的特殊情况。
f ' <text文本> { <expression 表达式> <optional选项 !s, !r, or !a> <optional : format specifier格式说明符> } <text文本> ... '
# 转义 {{
f'{{ {4*10} }}'
# 原始字符
# 原生字符
fr'x={4*10}\n'
# 直接调用函数
def foo():
return 20
f'result={foo()}'
# 表达式内的Lambda
f'{(lambda x:x*2) (3)}'
# 直接调用函数
def fn(l, incr):
result = l[0]
l[0] += incr
return result
lst = [0]
f'{fn(lst,2)} {fn(lst,3)}'
f'{fn(lst,2)} {fn(lst,3)}'
lst
# 直接将 字典 或者 元组 或者 其他 容器 放入其中
msg = ('disk failure', 32)
f'error: {msg}'
# 推导式
# 元组 , 列表 也是一样的
f'mapping is { {a:b for (a, b) in ((1, 2), (3, 4))} }'
# map配合 lamda 和 列表对导式
f"result:{[i*2 for i in range(10)]},map配合lambda{(list(map(lambda x:x*2,[1,2,3])))}"
In [87]: f"result:{[i*2 for i in range(10)]},map配合lambda {(list(map(lambda x:x*2,[1,2,3])))}"
Out[87]: 'result:[0, 2, 4, 6, 8, 10, 12, 14, 16, 18],map配合lambda[2, 4, 6]'
# 三元表达式
[nav] In [_i363]: a=1
...: b=0
...: f"{'true' if b else 'false':0>10.5s}"
Out[_363]: '00000false'
# 按位 与 表达式 元组 调用 .__format__()
# 因为 元组 没有 .__format()
# 所以要用 !a 或 !r 或 !s 模式 转换之后 调用 format
f"{2|1,!a:<6}"
Out[_34]: '(3,) '
# ------------------- 一些特殊写法 -------------------------------
In [77]: foo = "bar"
In [78]: f"{ foo = }" # 保留空白 # 需要版本支持 3.8 以上才可以 这样写
Out[78]: "foo ='bar'"
>>> f"{line = }" # 需要版本支持 3.8 以上才可以 这样写
'line = "The mill\'s closed"'
>>> f"{line = :20}" # 需要版本支持 3.8 以上才可以 这样写
"line = The mill's closed "
>>> f"{line = !r:20}" # 需要版本支持 3.8 以上才可以 这样写
'line = "The mill\'s closed" '
# ------------------- 日期转换 -------------------------------
# tips 这种 操作 format 也是 可以 使用的。
from datetime import datetime
today=datetime.today()
today # 输出今天的日期
Out[69]: datetime.datetime(2020, 8, 2, 1, 35, 11, 12516)
f"{today:%m %d, %Y}" # 格式化日期
Out[72]: '08 02, 2020' # 关于这个 时间日期 代码 , 链接 我放在了 最前面
import datetime
now = datetime.datetime.now
time_tmp = "{:%D %X}"
time_tmp.format(now())
f"{now():%D %X}"
输出:
'06/12/21 03:15:03'
# datetime.strftime(today,'%y/%m/%d %H:%M:%S %A')
# 用 datetime 就比较麻烦了.
f"{today:%B %d, %Y}"
Out[73]: 'August 02, 2020'
f"{today:%y/%m/%d %H:%M:%S %A}" │
Out[_51]: '21/05/23 07:57:10 Sunday'
In [74]: f"{today=:%B %d, %Y}" # 3.8 以上才可以使用 海象
# ------------------- 不支持的特性 -------------------------------
>>> f"newline: {ord('\n')}" # 报错 ,不允许反斜杠
# 要包含需要反斜杠转义的值,请创建一个临时变量。
>>> newline = ord('\n')
>>> f"newline: {newline}"
'newline: 10'
来自python源代码的示例
Lib/asyncio/locks.py:
# 来自python源代码的示例
# Lib/asyncio/locks.py:
extra = '{},waiters:{}'.format(extra, len(self._waiters))
extra = f'{extra},waiters:{len(self._waiters)}'
# Lib/configparser.py:
message.append(" [line {0:2d}]".format(lineno))
message.append(f" [line {lineno:2d}]")
# Tools/clinic/clinic.py:
methoddef_name = "{}_METHODDEF".format(c_basename.upper())
methoddef_name = f"{c_basename.upper()}_METHODDEF"
一些示例
按位置访问参数
In [1]: '{0}, {1}, {2}'.format('a', 'b', 'c')
Out[1]: 'a, b, c'
In [2]: '{}, {}, {}'.format('a', 'b', 'c') # 3.1+ only
Out[2]: 'a, b, c'
In [3]: '{2}, {1}, {0}'.format('a', 'b', 'c')
Out[3]: 'c, b, a'
In [4]: '{2}, {1}, {0}'.format(*'abc') # unpacking argument sequence 解包 参数序列
Out[4]: 'c, b, a'
In [5]: '{0}{1}{0}'.format('abra', 'cad') # arguments' indices can be repeated 参数的索引可以重复
Out[5]: 'abracadabra'
按名称访问
In [6]: 'Coordinates: {latitude}, {longitude}'.format(latitude='37.24
...: N', longitude='-115.81W')
Out[6]: 'Coordinates: 37.24N, -115.81W'
In [7]: coord = {'latitude': '37.24N', 'longitude': '-115.81W'}
In [8]: 'Coordinates: {latitude}, {longitude}'.format(**coord) # 字典解包 传递参数
Out[8]: 'Coordinates: 37.24N, -115.81W'
访问参数的属性
In [10]: 'X: {0[0]}; Y: {0[1]}'.format(coord) # 索引访问, 可以在 模板中 访问 传入参数的 索引
Out[10]: 'X: 3; Y: 5'
替代 %s
和 %r
和 %a
和 format 的 conversion 一样
In [11]: "repr() shows quotes: {!r}; str() doesn't: {!s}".format('test1', 'test2')
Out[11]: "repr() shows quotes: 'test1'; str() doesn't: test2" # {!r} 原生字符 带引号, {!s} 代表字符串 不带引号
In [12]: "ascii() show quotes:{!a};".format("tes1") # {!a} 代表 ascii 带有引号
Out[12]: "ascii() show quotes:'tes1';"
对齐文本指定宽度
In [14]: '{:<30}'.format('left aligned')
Out[14]: 'left aligned ' #左对齐
In [15]: '{:>30}'.format('right aligned') # 右对齐
Out[15]: ' right aligned'
In [16]: '{:^30}'.format('centered') # 居中对齐
Out[16]: ' centered '
In [17]: '{:*^30}'.format('centered') # use '*' as a fill char 星号填充 居中对齐
Out[17]: '***********centered***********'