正则表达式提供了-范围表示法(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