正则表达式——普通字符组(续)
正则表达式提供了-范围表示法(range),它更直观,能进一步简化字符组。
所谓"-范围表示法",就是用[x-y]的形式表示x到y整个范围内的字符,省去一一列出的麻烦,这样[0123456789]就可以表示为[0-9]。如果你觉得这不算什么,那么确实比[abcdefghijklmnopqrstuvwxyz]简单太多了。
你可能会问,"-范围表示法"的范围是如何确定的?为什么要写作[0-9],而不写作[9-0]?
要回答这个问题,必须了解范围表示法的实质。在字符组中,-表示的范围,一般是根据字符对应的码值(Code Point,也就是字符在对应编码表中的编码的数值)来确定的,码值小的字符在前,码值大的字符在后。在ASCII编码中(包括各种兼容ASCII的编码中),字符0的码值是48(十进制),字符9的码值是57(十进制),所以[0-9]等价于[0123456789];而[9-0]则是错误的范围,因为9的码值大于0,所以会报错。
例1-5 [0-9]是合法的,[9-0]会报错
re.search("^[0-9]$", "2") != None # => True re.search("^[9-0]$", "2") != None Traceback (most recent call last): error: bad character range
如果知道0~9的码值是48~57,a~z的码值是97~122,A~Z的码值是65~90,能不能用[0-z]统一表示数字字符、小写字母、大写字母呢?
答案是:勉强可以,但不推荐这么做。根据惯例,字符组的范围表示法都表示一类字符(数字字符是一类,字母字符也是一类),所以虽然[0-9]、[a-z]都是很好理解的,但[0-z]却很难理解,不熟悉ASCII编码表的人甚至不知道这个字符组还能匹配大写字母,更何况,在码值48到122之间,除去数字字符(码值48~57)、小写字母(码值97~122)、大写字母(码值65~90),还有不少标点符号(参见表1-1),从字符组[0-z]中却很难看出来,使用时就容易引起误会,例1-6所示的程序就很可能让人莫名其妙。
表1-1 ASCII编码表(片段)
码值 |
字符 |
码值 |
字符 |
码值 |
字符 |
码值 |
字符 |
码值 |
字符 |
48 |
0 |
63 |
? |
78 |
N |
93 |
] |
108 |
l |
49 |
1 |
64 |
@ |
79 |
O |
94 |
^ |
109 |
m |
50 |
2 |
65 |
A |
80 |
P |
95 |
_ |
110 |
n |
51 |
3 |
66 |
B |
81 |
Q |
96 |
` |
111 |
o |
52 |
4 |
67 |
C |
82 |
R |
97 |
a |
112 |
p |
53 |
5 |
68 |
D |
83 |
S |
98 |
b |
113 |
q |
54 |
6 |
69 |
E |
84 |
T |
99 |
c |
114 |
r |
55 |
7 |
70 |
F |
85 |
U |
100 |
d |
115 |
s |
56 |
8 |
71 |
G |
86 |
V |
101 |
e |
116 |
t |
57 |
9 |
72 |
H |
87 |
W |
102 |
f |
117 |
u |
58 |
: |
73 |
I |
88 |
X |
103 |
g |
118 |
v |
59 |
; |
74 |
J |
89 |
Y |
104 |
h |
119 |
w |
60 |
< |
75 |
K |
90 |
Z |
105 |
i |
120 |
x |
61 |
= |
76 |
L |
91 |
[ |
106 |
j |
121 |
y |
62 |
> |
77 |
M |
92 |
\ |
107 |
k |
122 |
z
|
例1-6 [0-z] 的奇怪匹配
re.search("^[0-z]$", "A") != None # => True re.search("^[0-z]$", ":") != None # => True
在字符组中可以同时并列多个"-范围表示法",字符组[0-9a-zA-Z]可以匹配数字、大写字母或小写字母;字符组 [0-9a-fA-F]可以匹配数字,大、小写形式的a~f,它可以用来验证十六进制字符,代码见例1-7
例1-7 [0-9a-fA-F]准确判断十六进制字符
re.search("^[0-9a-fA-F]$", "0") != None # => True re.search("^[0-9a-fA-F]$", "c") != None # => True re.search("^[0-9a-fA-F]$", "i") != None # => False re.search("^[0-9a-fA-F]$", "C") != None # => True re.search("^[0-9a-fA-F]$", "G") != None # => False
在不少语言中,还可以用转义序列\xhex来表示一个字符,其中\x是固定前缀,表示转义序列的开头,num是字符对应的码值(Code Point,详见第127页,下文用?127表示),是一个两位的十六进制数值。比如字符A的码值是41(十进制则为65),所以也可以用\x41表示。
字符组中有时会出现这种表示法,它可以表现一些难以输入或者难以显示的字符,比如\x7F;也可以用来方便地表示某个范围,比如所有ASCII字符对应的字符组就是[\x00-\x7F],代码见例1-8。这种表示法很重要,在第120页还会讲到它,依靠这种表示法可以很方便地匹配所有的中文字符。
例1-8 [\x00-\x7F]准确判断ASCII字符
re.search("^[\x00-\x7F]$", "c") != None # => True re.search("^[\x00-\x7F]$", "I") != None # => True re.search("^[\x00-\x7F]$", "0") != None # => True re.search("^[\x00-\x7F]$", "<") != None # => True