簡體   English   中英

如何在Python中僅用“ AZ”,“ az”,“ 0-9”和“ _”編碼UTF-8字符串

[英]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-printablehttps://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上有點亂bytesstr的別名,這與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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM