[英]How to encode UTF-8 strings with only “A-Z”,“a-z”,“0-9”, and “_” in Python
我需要构建一个python编码器,以便可以重新格式化如下字符串:
import codecs
codecs.encode("Random 🐍 UTF-8 String ☑⚠⚡", 'name_of_my_encoder')
我什至在问堆栈溢出的原因是,编码的字符串需要通过此验证函数。 这是一个严格的约束,对此没有灵活性,这是因为必须存储字符串。
from string import ascii_letters
from string import digits
valid_characters = set(ascii_letters + digits + ['_'])
def validation_function(characters):
for char in characters:
if char not in valid_characters:
raise Exception
使编码器看起来很容易,但是我不确定这种编码器是否会使构建解码器变得更加困难。 这是我编写的编码器。
from codecs import encode
from string import ascii_letters
from string import digits
ALPHANUMERIC_SET = set(ascii_letters + digits)
def underscore_encode(chars_in):
chars_out = list()
for char in chars_in:
if char not in ALPHANUMERIC_SET:
chars_out.append('_{}_'.format(encode(char.encode(), 'hex').decode('ascii')))
else:
chars_out.append(char)
return ''.join(chars_out)
这是我写的编码器。 我仅出于示例目的将其包括在内,可能有一种更好的方法可以做到这一点。
编辑1-有人明智地指出只在整个字符串上使用base32,我肯定可以使用。 但是,最好具有“某种程度的可读性”,以便使用转义系统,例如https://en.wikipedia.org/wiki/Quoted-printable或https://en.wikipedia.org/wiki/Percent -encoding是首选。
编辑2-建议的解决方案必须在Python 3.4或更高版本上工作,在Python 2.7上也很好,但不是必需的。 我添加了python-3.x标签,以帮助澄清这一点。
使用base32! 它仅使用26个字母和0-9。 您不能使用base64,因为它使用=字符,该字符不会通过您的验证程序。
>>> import base64
>>>
>>> print base64.b32encode('Random 🐍 UTF-8 String ☑⚠⚡"')
KJQW4ZDPNUQPBH4QRUQFKVCGFU4CAU3UOJUW4ZZA4KMJDYU2UDRJVIJC
>>>
>>> print base64.b32decode('KJQW4ZDPNUQPBH4QRUQFKVCGFU4CAU3UOJUW4ZZA4KMJDYU2UDRJVIJC')
Random 🐍 UTF-8 String ☑⚠⚡"
>>>
这似乎可以解决问题。 基本上,字母数字字母是单独保留的。 ASCII集中的任何非字母数字字符都被编码为\\xXX
转义码。 所有其他unicode字符均使用\\uXXXX
转义码进行编码。 但是,您说过不能使用\\
,但是可以使用_
,因此所有转义序列都转换为以_
开头。 这使得解码非常简单。 只需将_
替换为\\
,然后使用unicode-escape
编解码器即可。 由于unicode-escape
编解码器仅保留ASCII字符,因此编码会稍微困难一些。 因此,首先必须转义相关的ASCII字符,然后通过unicode-escape
编解码器运行字符串,最后将所有\\
转换为_
。
码:
from string import ascii_letters, digits
# non-translating characters
ALPHANUMERIC_SET = set(ascii_letters + digits)
# mapping all bytes to themselves, except '_' maps to '\'
ESCAPE_CHAR_DECODE_TABLE = bytes(bytearray(range(256)).replace(b"_", b"\\"))
# reverse mapping -- maps `\` back to `_`
ESCAPE_CHAR_ENCODE_TABLE = bytes(bytearray(range(256)).replace(b"\\", b"_"))
# encoding table for ASCII characters not in ALPHANUMERIC_SET
ASCII_ENCODE_TABLE = {i: u"_x{:x}".format(i) for i in set(range(128)) ^ set(map(ord, ALPHANUMERIC_SET))}
def encode(s):
s = s.translate(ASCII_ENCODE_TABLE) # translate ascii chars not in your set
bytes_ = s.encode("unicode-escape")
bytes_ = bytes_.translate(ESCAPE_CHAR_ENCODE_TABLE)
return bytes_
def decode(s):
s = s.translate(ESCAPE_CHAR_DECODE_TABLE)
return s.decode("unicode-escape")
s = u"Random UTF-8 String ☑⚠⚡"
#s = '北亰'
print(s)
b = encode(s)
print(b)
new_s = decode(b)
print(new_s)
哪个输出:
Random UTF-8 String ☑⚠⚡
b'Random_x20UTF_x2d8_x20String_x20_u2611_u26a0_u26a1'
Random UTF-8 String ☑⚠⚡
这适用于python 3.4和python 2.7,这就是为什么ESCAPE_CHAR_{DE,EN}CODE_TABLE
在python 2.7上有点乱bytes
是str
的别名,这与python 3.4上的bytes
不同。 这就是为什么使用bytearray
构造表的原因。 对于python 2.7, encode
方法需要一个unicode
对象,而不是str
。
您可能会滥用url引号 ,以使其通过验证功能的其他语言格式既可读又易于解码:
#!/usr/bin/env python3
import urllib.parse
def alnum_encode(text):
return urllib.parse.quote(text, safe='')\
.replace('-', '%2d').replace('.', '%2e').replace('_', '%5f')\
.replace('%', '_')
def alnum_decode(underscore_encoded):
return urllib.parse.unquote(underscore_encoded.replace('_','%'), errors='strict')
s = alnum_encode("Random 🐍 UTF-8 String ☑⚠⚡")
print(s)
print(alnum_decode(s))
Random_20_F0_9F_90_8D_20UTF_2d8_20String_20_E2_98_91_E2_9A_A0_E2_9A_A1
Random 🐍 UTF-8 String ☑⚠⚡
这是一个使用bytearray()
的实现bytearray()
如有必要,可稍后将其移至C):
#!/usr/bin/env python3.5
from string import ascii_letters, digits
def alnum_encode(text, alnum=bytearray(ascii_letters+digits, 'ascii')):
result = bytearray()
for byte in bytearray(text, 'utf-8'):
if byte in alnum:
result.append(byte)
else:
result += b'_%02x' % byte
return result.decode('ascii')
如果要将Unicode音译为ASCII(例如ç- > c),请签出Unidecode软件包。 这是他们的例子:
>>> from unidecode import unidecode
>>> unidecode(u'ko\u017eu\u0161\u010dek')
'kozuscek'
>>> unidecode(u'30 \U0001d5c4\U0001d5c6/\U0001d5c1')
'30 km/h'
>>> unidecode(u"\u5317\u4EB0")
'Bei Jing '
这是我的示例:
# -*- coding: utf-8 -*-
from unidecode import unidecode
print unidecode(u'快樂星期天')
作为输出*
Kuai Le Xing Qi Tian
*可能是废话,但至少是ASCII
要删除标点符号,请参见此答案 。
尽管有几个好的答案。 我最终得到了一个看起来更干净,更容易理解的解决方案。 因此,我将发布最终解决方案的代码来回答我自己的问题。
from string import ascii_letters
from string import digits
from base64 import b16decode
from base64 import b16encode
ALPHANUMERIC_SET = set(ascii_letters + digits)
def utf8_string_to_hex_string(s):
return ''.join(chr(i) for i in b16encode(s.encode('utf-8')))
def hex_string_to_utf8_string(s):
return b16decode(bytes(list((ord(i) for i in s)))).decode('utf-8')
def underscore_encode(chars_in):
chars_out = list()
for char in chars_in:
if char not in ALPHANUMERIC_SET:
chars_out.append('_{}_'.format(utf8_string_to_hex_string(char)))
else:
chars_out.append(char)
return ''.join(chars_out)
def underscore_decode(chars_in):
chars_out = list()
decoding = False
for char in chars_in:
if char == '_':
if not decoding:
hex_chars = list()
decoding = True
elif decoding:
decoding = False
chars_out.append(hex_string_to_utf8_string(hex_chars))
else:
if not decoding:
chars_out.append(char)
elif decoding:
hex_chars.append(char)
return ''.join(chars_out)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.