Python3将Unicode字符串转换为整数表示

17

众所周知,计算机使用数字进行运算。我正在输入这段文字,服务器会将其转化为数字,当您想要阅读它时,您会从服务器获取文本。

那么如何在自己的计算机上实现这一过程呢?

我想用自己的算法加密一些内容,我的算法在使用整数时工作正常,但现在我想加密一个字符串,我不知道如何将Unicode字符串转换为整数以及如何相反操作。

我正在使用Python 3。有没有人知道一个优雅的解决方案来解决我的问题?


3
"我想用自己的算法加密一些东西" - 为什么? - Amber
你对计算机的工作原理的理解似乎浅薄且不准确。计算机不是“使用数字”工作的,而是使用二进制数组;如何解释和处理它取决于程序。实际上,许多处理器指令将我们称为“字”(或“双字”)的32位二进制数组视为整数数字的某种二进制表示,通常是2的补码,或者像IEEE 754那样的一些分数数字表示。但该数组意味着什么的最终表示取决于程序员和语言的抽象。 - lvella
1
@LennartRegebro 229 和 -27 是两个不同的数字,它们可以被计算机表示为相同的二进制数组。在对其进行操作时,计算机不关心它们的“数字值”,只关心二进制字符串。 - lvella
1
@lvella:不,它们不是图灵机实现。你又错了。它们是图灵完备的,这意味着你可以在它们中实现图灵机。这是完全不同的事情。 - Lennart Regebro
这个问题在 meta 上进行了讨论。 - Travis J
显示剩余8条评论
5个回答

21
你正在寻找ord()函数,我认为是这个:
>>> ord('a')
97
>>> ord('\u00c2')
192

这会给你Unicode码点的整数表示。
要转换一整组字符,请使用列表推导式:
>>> [ord(c) for c in 'Hello World!']
[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]

它的反函数是chr()函数
>>> chr(97)
'a'
>>> chr(193)
'Á'

请注意,当您加密和解密文本时,通常会使用字符编码将文本编码为二进制表示。 Unicode文本可以使用不同的编码进行编码,具有不同的优缺点。如今,Unicode文本最常用的编码是UTF-8,但也存在其他编码。
在Python 3中,二进制数据以bytes对象表示,您可以使用str.encode()方法将文本编码为字节,并通过使用bytes.decode()方法返回。
>>> 'Hello World!'.encode('utf8')
b'Hello World!'
>>> b'Hello World!'.decode('utf8')
'Hello World!'

"bytes值实际上只是由0-255的整数数字组成的序列,类似于列表、元组和字符串:"
>>> list('Hello World!'.encode('utf8'))
[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]

个人而言,在加密时,您需要对生成的字节进行编码和加密。

如果所有这些似乎令人不知所措或难以理解,也许这些有关Unicode和字符编码的文章可以帮助:


我能用整个字符串做到这一点吗?(一个字符串->一个整数) - user1703918
@Emerald:一个整数将代表什么?您可以将每个“字符”转换为整数;我已更新我的答案以展示如何处理整个字符串。将该字符串转换为“一个”数字没有太多意义。 - Martijn Pieters
我原本认为将一个整数发送到我的算法中比发送数百个整数更快,但我认为你的解决方案是最好的。非常感谢! - user1703918
@Emerald:这将是一个非常大的数字。超过处理器本地处理能力。因此,它将被分成许多数字,所以你不会得到任何好处。 - Lennart Regebro

12

将Unicode字符串转换为数字的通常方法是将其转换为字节序列。 Unicode字符是纯抽象的,每个字符都有自己的编号;然而,有多种方法可以将这些编号转换为字节流。可能最通用的方法是对字符串进行UTF-8编码。你可以选择许多方法从中获得整数。这里是其中一种方法(我借用了Ivella的漂亮字符串——希望里面没有脏话 :):

Python 3.2.1 (default, Jul 10 2011, 20:02:51) [MSC v.1500 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> s = "Hello, World, عالَم, ދުނިޔެ, जगत, 世界"
>>> b = s.encode('utf-8')
>>> b
b'Hello, World, \xd8\xb9\xd8\xa7\xd9\x84\xd9\x8e\xd9\x85, \xde\x8b\xde\xaa\xde\x82\xde\xa8\xde\x94\xde\xac, \xe0\xa4\x9c\xe0\xa4\x97\xe0\xa4\xa4, \xe4\xb8\x96\xe7\x95\x8c'

现在我们有一系列字节,其中数字从128到255的字节以十六进制编码的转义序列显示。让我们将所有字节转换为它们的十六进制代码作为一个字节串。

>>> import binascii
>>> h = binascii.hexlify(b)
>>> h
b'48656c6c6f2c20576f726c642c20d8b9d8a7d984d98ed9852c20de8bdeaade82dea8de94deac2c20e0a49ce0a497e0a4a42c20e4b896e7958c'

我们可以将其看作是以十六进制符号表示的一个大数。使用 int 可以将其转换为抽象数字,当打印时通常会将其转换为十进制符号。

>>> i = int(h, 16)
>>> i
52620351230730152682202055464811384749235956796562762198329268116226267262806875102376740945811764490696968801603738907493997296927348108

现在,您可以将其存储为数字、加密它(虽然通常是加密先前的字节序列),然后稍后将其转换回整数。请注意,没有多少语言(可能也没有数据库)能够处理如此大的整数。

现在让我们回到原始字符串。首先将其转换为十六进制表示形式(字符串)。

>>> h2 = hex(i)
>>> h2
'0x48656c6c6f2c20576f726c642c20d8b9d8a7d984d98ed9852c20de8bdeaade82dea8de94deac2c20e0a49ce0a497e0a4a42c20e4b896e7958c'
>>> h3 = h2[2:]   # remove the 0x from the beginning
>>> h3
'48656c6c6f2c20576f726c642c20d8b9d8a7d984d98ed9852c20de8bdeaade82dea8de94deac2c20e0a49ce0a497e0a4a42c20e4b896e7958c'
>>> type(h3)
<class 'str'>

我们必须删除0x,因为它仅表示其余数字是代表该数字的十六进制字符。请注意,h3str 类型。由于我们在 Python 3 中(请见顶部),str 表示 Unicode 字符串。下一步是将十六进制数对转换回字节。让我们尝试使用unhexlify()

>>> binascii.unhexlify(h3)
Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    binascii.unhexlify(h3)
TypeError: 'str' does not support the buffer interface

糟糕!它只接受字节串。然后,将Unicode中的每个十六进制数字编码为字节串中的十六进制数字。解决方法是进行编码; 但是,将其编码为ASCII很容易。

>>> b2 = h3.encode('ascii')  # character by character; subset of ascii only
>>> b2
b'48656c6c6f2c20576f726c642c20d8b9d8a7d984d98ed9852c20de8bdeaade82dea8de94deac2c20e0a49ce0a497e0a4a42c20e4b896e7958c'
>>> b3 = binascii.unhexlify(b2)
>>> b3
b'Hello, World, \xd8\xb9\xd8\xa7\xd9\x84\xd9\x8e\xd9\x85, \xde\x8b\xde\xaa\xde\x82\xde\xa8\xde\x94\xde\xac, \xe0\xa4\x9c\xe0\xa4\x97\xe0\xa4\xa4, \xe4\xb8\x96\xe7\x95\x8c'

现在我们有了与第一个 .encode('utf-8') 得到的类似的字节串。让我们使用相反的操作--从 UTF-8 解码。我们应该得到与开始时相同的 Unicode 字符串。

>>> s2 = b3.decode('utf-8')
>>> s2
'Hello, World, عالَم, ދުނިޔެ, जगत, 世界'
>>> s == s2   # is the original equal to the result?
True

:)


9

根据Python文档:

binascii模块包含多种方法,用于在二进制和各种ASCII编码的二进制表示之间进行转换。

例如,您可以使用binascii.hexlify来获取“LOL”二进制字符串的十六进制表示,并通过int内置函数将其转换为整数:

>>> binascii.hexlify(b"LOL")
b'4c4f4c'
>>> int(binascii.hexlify(b"LOL"), 16)
5001036

由于您需要将此应用于Unicode字符串,因此您首先需要将它们编码为二进制字符串。您可以使用方法str.encode来实现:

>>> int(binascii.hexlify("fiŝaĵo".encode("utf-8")), 16)
7379646744164087151

就是这样。

如果要相反地操作,您需要逆转每个步骤。首先将整数转换为十六进制表示作为二进制字符串(您可以使用format(int, "x")并进行编码),使用binascii.unhexlify将十六进制转换为ascii,最后解码为utf-8:

>>> binascii.unhexlify(format(7379646744164087151, "x").encode("utf-8")).decode("utf-8")
'fiŝaĵo'

这是一步一步的解释,如果您真的会使用这些功能,最好将它们按照函数的形式排列。


我知道我有点晚了,但这实际上是最好的答案。 - WhiteWood

4

在Martijn Pieters提供的解决方案基础上,您可以将字符串转换为一个巨大的数字,Python 3可以很好地处理,因为它的int类型是任意大的(这不是“计算机工作原理”,请参见我对您问题的评论)。

给定字符编码列表:

>>> a = [ord(c) for c in 'Hello World!']
>>> print(a)
[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]

根据维基百科关于Unicode页面的说明,最大的Unicode字符编码是10FFFF(十六进制),您可以执行以下操作:

def numfy(s):
    number = 0
    for e in [ord(c) for c in s]:
        number = (number * 0x110000) + e
    return number

def denumfy(number):
    l = []
    while(number != 0):
        l.append(chr(number % 0x110000))
        number = number // 0x110000
    return ''.join(reversed(l))

因此:
>>> a = numfy("Hello, World, عالَم, ދުނިޔެ, जगत, 世界")
>>> a
31611336900126021[...]08666956
>>> denumfy(a)
'Hello, World, عالَم, ދުނިޔެ, जगत, 世界'

在这里,0x110000(从10FFFF + 1)是预见的Unicode字符数量(1114112,以十进制表示)。如果您确定只使用英文字母,则可以在此处使用128,如果您使用带有重音的某些拉丁语言,则可以安全地使用256。无论哪种方式,您的数字都会小得多,但它将无法表示每个Unicode字符。


-1

这将每个字符转换为数字...

s="hello \u2020"
print [ord(c) for c in s]

我能用整个字符串来完成这个操作吗?(一个字符串 -> 一个整数) - user1703918
1
正如Martijn在他的回答中所说,将整个字符串转换并没有太多意义;加密例程应该可以很好地处理数字流——例如,请参见hashlib.update - spiralx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接