【问题标题】:What is the replacement of deprecated array of unicode chars?什么是弃用的 unicode 字符数组的替换?
【发布时间】:2017-10-29 06:15:42
【问题描述】:

unicode 字符数组可以用作可变字符串:

import array

ins = "Aéí"
ms = array.array('u', ins)
ms[0] = "ä"
outs = ms.tounicode()
# äéí

但类型 'u' 自 Python 3.3 起已弃用。什么是现代替代品?

我能做到:

ms = list(ins)
# mutate
outs = ''.join(ms)

但我发现一个字符列表与数组相比内存效率非常低。

或者:

ms = array.array('L', (ord(ch) for ch in ins))
ms[0] = ord("ä")
outs = "".join(chr(ch) for ch in ms)

但它的可读性远不如已弃用的原版。

【问题讨论】:

  • numpy 接受 Unicode 字符串,分配 4 个字节的 ped 字符,每个元素的常量 n 字符。有了所有这些填充,它的内存效率可能不如列表。
  • @hpaulj 每个字符 4 个字节就可以了,就像 C 语言中的 UTF-32 或 wchar_t。但是sys.getsizeof('x') 在我的 PC 上是 50,getsizeof('ä') 是 74。每个列表项需要 这么多 个字节,因为列表中的每个字符实际上都是一个完整的字符串对象。
  • 它已被弃用,因为 Python unicode 字符串不再是固定大小。它们的宽度取决于现在字符串中最高的 Unicode 代码点(每个代码点 1、2 或 4 个字节)。
  • 为什么需要这个array?为什么不改用bytearray 对象,并将数据编码为字节?
  • @MartijnPieters 我会怀念mutable_string[position] = ch 的简单性。而且“弃用”通常意味着将被更好的东西取代。我不知道那是什么新东西。

标签: python-3.x unicode


【解决方案1】:

这与您的上一个示例类似,但以更易读和更有效的方式初始化数组。您必须选择一个大小为四个字节的数组大小。 Iunsigned int 的代码,在大多数操作系统上是四个字节。为了可移植性,您可能希望以编程方式选择此值。

#!coding:utf8
import array
import sys

# Verifying the item size.
assert array.array('I').itemsize == 4

# Choose encoding base on native endianness:
encoding = 'utf-32le' if sys.byteorder == 'little' else 'utf-32be'

ins = "Aéí"
ms = array.array('I',ins.encode(encoding))
ms[0] = ord('ä')
print(ms.tobytes().decode(encoding))

输出:

äéí

1000 个元素的字符串的计时表明它要快得多:

In [7]: s = ''.join(chr(x) for x in range(1000))

In [8]: %timeit ms = array.array('I',s.encode('utf-32le'))
1.77 µs ± 14.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [9]: %timeit ms = array.array('I',(ord(x) for x in s))
167 µs ± 5.35 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [21]: %timeit outs = "".join(chr(x) for x in ms)
194 µs ± 4.98 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [23]: %timeit outs = ms.tobytes().decode('utf-32le')
3.92 µs ± 97 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

但你可能想多了。我不知道您要处理的字符串大小,但仅使用 list(data) 会更快,如果内存效率较低,而且还不错。以下是非 BMP 字符 (~1M) 的列表,以及不可变字符串切片、改变数组和改变列表的时间:

In [67]: data = ''.join(chr(x) for x in range(0x10000,0x110000))

In [68]: ms = array.array('I',data.encode('utf-32le'))

In [69]: %%timeit global data
    ...: data = data[:500] + 'a' + data[501:]
    ...:
3.33 ms ± 235 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [70]: %timeit ms[500] = ord('a')
73.6 ns ± 0.433 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [71]: %%timeit v = list(data)
    ...: v[500] = 'a'
    ...:
28.7 ns ± 0.144 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [72]: sys.getsizeof(data)
Out[72]: 4194380

In [73]: sys.getsizeof(ms)
Out[73]: 4456524

In [74]: sys.getsizeof(list(data))
Out[74]: 9437296

改变一个列表很简单,比改变数组快 3 倍,并且只使用 2 倍多一点的内存。

【讨论】:

  • 是的,这确实是一项重大改进。感谢您的有益回答。
  • 在 python 3.6 array('L', s.encode('utf-32le')) 将提高 ValueError 字节长度而不是项目大小的倍数。以及为什么使用L 而不是B 来存储encode() 结果,一个字节序列?
  • @Jacky 是对数组中的 32 位值进行索引,并且项目是 4 字节的倍数,所以我不知道你做了什么。
  • @Mark 和你写的代码一样,我在python.org在线控制台试了一下。顺便问一下你用的是哪个版本?
  • @Jacky python.org 在线控制台使用 Linux,unsigned long 是 8 个字节,而我在 Windows 上,unsigned long 是 4 个字节。除非您有 16 位编译器,否则使用类型 I 应该在两个系统上都可以工作 4 个字节:^)
【解决方案2】:

Python3 中的替换是 str,内置类型。字符串是 Unicode 代码点的不可变序列。

这样就不用array('u', ...)了,str就够了。

顺便说一句,删除'u'(内部使用PyUnicode*)的原因是Py_UNICODE*表示已被弃用且效率低下;在性能或内存敏感的情况下应避免使用它。

【讨论】:

  • 能否请您显示问题中发布的代码的等效代码?
  • 由于str是不可变的,所以我们需要通过outs = 'ä' + ins[1:]创建一个新的字符串对象
【解决方案3】:

Python 3.3+ 已单独弃用 Unicode。因为PEP393,所以一切都可以作为str来表达和使用。

Here 是一篇关于 Python 3.3 后解释器中 Unicode 处理的好方法。根据定义 -

Unicode 标准描述了字符如何由代码表示 点。代码点是一个整数值,通常以 16 为基数表示。 在标准中,使用符号 U+12CA 编写代码点 表示值为 0x12ca(十进制 4,810)的字符。统一码 标准包含许多列出字符及其的表格 对应的代码点:

最后,我不确定您想要实现什么,但以下是否有效?

ins = "Aéí"
m = "ä" + ins[1:]
print(m)

在 Python 3.5.2 版本中,它给了我这个输出 -

äéí

如果有帮助请告诉我


最后一点,如果您想知道+ 运算符,那么here 是一个不错的SO 帖子。投票最多的答案的要点是:如果您使用的是 Cpython,则最好使用 + 而不是 append join 等。


如果你想使用你的方法,还有整个数组模块available,其中包含运行我们的代码所需的所有东西。我在python3.5中测试过,效果很好。

还有这种方式-

a = "ä"
p = b"".join([a.encode('utf-8'), ins[1:].encode('utf-8')])
print(p.decode('utf-8'))  

【讨论】:

  • 我做了一些测试,列表需要大约 20 倍的内存和 70 倍的时间(在我的 PC 上,100_000 个字符,YMMV)。这显然比现在已弃用的方法退了一步。
猜你喜欢
  • 2016-04-10
  • 2018-12-26
  • 2018-04-19
  • 2011-04-26
  • 1970-01-01
  • 1970-01-01
  • 2011-01-14
  • 2018-12-16
  • 2013-02-17
相关资源
最近更新 更多