有一个更好的方法吗?
$ python
Python 2.7.9 (default, Jul 16 2015, 14:54:10)
[GCC 4.1.2 20080704 (Red Hat 4.1.2-55)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import re
>>> re.sub(u'[\U0001d300-\U0001d356]', "", "")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/fast/services/lib/python2.7/re.py", line 155, in sub
return _compile(pattern, flags).sub(repl, string, count)
File "/home/fast/services/lib/python2.7/re.py", line 251, in _compile
raise error, v # invalid expression
sre_constants.error: bad character range
Run Code Online (Sandbox Code Playgroud)
nha*_*tdh 26
该错误表明您正在使用"窄"(UCS-2)构建,它仅支持最高65535的Unicode代码点作为一个"Unicode字符" 1.代码点高于65536的字符表示为代理项对,这意味着Unicode字符串u'\U0001d300'由窄构建中的两个"Unicode字符"组成.
Python 2.7.8 (default, Jul 25 2014, 14:04:36)
[GCC 4.8.3] on cygwin
>>> import sys; sys.maxunicode
65535
>>> len(u'\U0001d300')
2
>>> [hex(ord(i)) for i in u'\U0001d300']
['0xd834', '0xdf00']
Run Code Online (Sandbox Code Playgroud)
在"宽"(UCS-4)版本中,所有1114111代码点都被识别为Unicode字符,因此Unicode字符串只u'\U0001d300'包含一个"Unicode字符"/ Unicode代码点.
Python 2.6.6 (r266:84292, May 1 2012, 13:52:17)
[GCC 4.4.6 20110731 (Red Hat 4.4.6-3)] on linux2
>>> import sys; sys.maxunicode
1114111
>>> len(u'\U0001d300')
1
>>> [hex(ord(i)) for i in u'\U0001d300']
['0x1d300']
Run Code Online (Sandbox Code Playgroud)
1我使用"Unicode字符"(引号)来表示Python Unicode字符串中的一个字符,而不是一个Unicode代码点.字符串中"Unicode字符"的数量是len()字符串的数量.在"窄"构建中,一个"Unicode字符"是UTF-16的16位代码单元,因此一个星体字符将显示为两个"Unicode字符".在"宽"构建中,一个"Unicode字符"始终对应于一个Unicode代码点.
问题中的正则表达式在"宽"构建中正确编译:
Python 2.6.6 (r266:84292, May 1 2012, 13:52:17)
[GCC 4.4.6 20110731 (Red Hat 4.4.6-3)] on linux2
>>> import re; re.compile(u'[\U0001d300-\U0001d356]', re.DEBUG)
in
range (119552, 119638)
<_sre.SRE_Pattern object at 0x7f9f110386b8>
Run Code Online (Sandbox Code Playgroud)
但是,相同的正则表达式在"窄"构建中不起作用,因为引擎不识别代理对.它只是把\ud834一个字符,然后试图从创建一个字符范围\udf00来\ud834和失败.
Python 2.7.8 (default, Jul 25 2014, 14:04:36)
[GCC 4.8.3] on cygwin
>>> [hex(ord(i)) for i in u'[\U0001d300-\U0001d356]']
['0x5b', '0xd834', '0xdf00', '0x2d', '0xd834', '0xdf56', '0x5d']
Run Code Online (Sandbox Code Playgroud)
解决方法是使用与ECMAScript中相同的方法,我们将构造正则表达式以匹配代表代码点的代理.
Python 2.7.8 (default, Jul 25 2014, 14:04:36)
[GCC 4.8.3] on cygwin
>>> import re; re.compile(u'\ud834[\udf00-\udf56]', re.DEBUG)
literal 55348
in
range (57088, 57174)
<_sre.SRE_Pattern object at 0x6ffffe52210>
>>> input = u'Sample \U0001d340. Another \U0001d305. Leave alone \U00011000'
>>> input
u'Sample \U0001d340. Another \U0001d305. Leave alone \U00011000'
>>> re.sub(u'\ud834[\udf00-\udf56]', '', input)
u'Sample . Another . Leave alone \U00011000'
Run Code Online (Sandbox Code Playgroud)
由于在Python窄版本中与星体平面字符匹配的构造与ES5相同,您可以使用regexpu,一种将ES6中的RegExp文字转换为ES5的工具,为您进行转换.
只需在ES6中粘贴等效的正则表达式(注意u标志和\u{hh...h}语法):
/[\u{1d300}-\u{1d356}]/u
Run Code Online (Sandbox Code Playgroud)
并且你得到了可以在Python narrow build和ES5中使用的正则表达式
/(?:\uD834[\uDF00-\uDF56])/
Run Code Online (Sandbox Code Playgroud)
/如果要在Python中使用正则表达式,请注意删除JavaScript RegExp文本中的分隔符.
当范围分布在多个高代理(U + D800到U + DBFF)时,该工具非常有用.例如,如果我们必须匹配字符范围
/[\u{105c0}-\u{1cb40}]/u
Run Code Online (Sandbox Code Playgroud)
Python narrow build和ES5中的等效正则表达式是
/(?:\uD801[\uDDC0-\uDFFF]|[\uD802-\uD831][\uDC00-\uDFFF]|\uD832[\uDC00-\uDF40])/
Run Code Online (Sandbox Code Playgroud)
这是相当复杂和容易出错的.
Python 3.3实现了PEP 393,它消除了窄版本和宽版本之间的区别,而Python从现在开始就像一个广泛的版本.这完全消除了问题中的问题.
虽然可以在Python窄版本中解决并匹配星体平面角色,但是最好通过使用Python宽版本来更改执行环境,或者将代码移植到Python 3.3及更高版本中使用.
用于窄版本的正则表达式代码对于普通程序员来说很难阅读和维护,并且在移植到Python 3时必须完全重写.