我有一个20字节的字符串,我想将它转换为ctypes.c_ubyte
数组以进行位字段操作。
import ctypes
str_bytes = '01234567890123456789'
byte_arr = bytearray(str_bytes)
raw_bytes = (ctypes.c_ubyte*20)(*(byte_arr))
有没有办法避免为了演员而从str到bytearray的深拷贝?
或者,是否可以在没有深层复制的情况下将字符串转换为bytearray? (使用memoryview等技术?)
我正在使用Python 2.7。
效果结果:
使用eryksun和Brian Larsen的建议,以下是使用Ubuntu 12.04和Python 2.7的vbox VM下的基准测试。
结果:
代码:
import ctypes
import time
import numpy
str_bytes = '01234567890123456789'
def method1():
result = ''
t0 = time.clock()
for x in xrange(0,1000000):
byte_arr = bytearray(str_bytes)
result = (ctypes.c_ubyte*20)(*(byte_arr))
t1 = time.clock()
print(t1-t0)
return result
def method2():
result = ''
t0 = time.clock()
for x in xrange(0,1000000):
result = (ctypes.c_ubyte * 20).from_buffer_copy(str_bytes)
t1 = time.clock()
print(t1-t0)
return result
def method3():
result = ''
t0 = time.clock()
for x in xrange(0,1000000):
result = ctypes.cast(str_bytes, ctypes.POINTER(ctypes.c_ubyte * 20))[0]
t1 = time.clock()
print(t1-t0)
return result
def method4():
result = ''
t0 = time.clock()
for x in xrange(0,1000000):
arr = numpy.asarray(str_bytes)
result = arr.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte*len(str_bytes)))
t1 = time.clock()
print(t1-t0)
return result
print(method1())
print(method2())
print(method3())
print(method4())
答案 0 :(得分:8)
我不这样做你的想法。 bytearray
创建字符串的副本。然后解释器将bytearray
序列解压缩到starargs
tuple
并将其合并到另一个具有其他args的新tuple
中(即使在这种情况下没有)。最后,c_ubyte
数组初始化程序遍历args tuple
以设置c_ubyte
数组的元素。这需要大量的工作和大量的复制才能初始化阵列。
相反,你可以使用from_buffer_copy
方法,假设字符串是带缓冲区接口的字节串(不是unicode):
import ctypes
str_bytes = '01234567890123456789'
raw_bytes = (ctypes.c_ubyte * 20).from_buffer_copy(str_bytes)
仍然需要复制字符串,但它只执行一次,效率更高。正如评论中所述,Python字符串是不可变的,可以实现或用作dict键。它的不变性应该得到尊重,即使ctypes允许你在实践中违反这一点:
>>> from ctypes import *
>>> s = '01234567890123456789'
>>> b = cast(s, POINTER(c_ubyte * 20))[0]
>>> b[0] = 97
>>> s
'a1234567890123456789'
修改强>
我需要强调的是,我不建议使用ctypes来修改不可变的CPython字符串。如果必须,请至少事先检查sys.getrefcount
以确保引用计数为2或更少(呼叫加1)。否则,您最终会对名称(例如"sys"
)和代码对象常量的字符串实习感到惊讶。 Python可以自由地重用不可变对象。如果你走出语言来改变一个“不可变”的对象,你就违反了合同。
例如,如果修改已经散列的字符串,则缓存的散列不再适用于内容。这打破了它作为dict键使用。具有新内容的另一个字符串或具有原始内容的字符串都不会与字典中的键匹配。前者具有不同的哈希值,后者具有不同的值。然后,获取dict项的唯一方法是使用具有错误哈希的变异字符串。继续前一个例子:
>>> s
'a1234567890123456789'
>>> d = {s: 1}
>>> d[s]
1
>>> d['a1234567890123456789']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'a1234567890123456789'
>>> d['01234567890123456789']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: '01234567890123456789'
如果密钥是一个在几十个地方重用的实习字符串,现在考虑一下这个混乱。
对于性能分析,通常使用timeit模块。在3.3之前,timeit.default_timer
因平台而异。在POSIX系统上,它是time.time
,在Windows上是time.clock
。
import timeit
setup = r'''
import ctypes, numpy
str_bytes = '01234567890123456789'
arr_t = ctypes.c_ubyte * 20
'''
methods = [
'arr_t(*bytearray(str_bytes))',
'arr_t.from_buffer_copy(str_bytes)',
'ctypes.cast(str_bytes, ctypes.POINTER(arr_t))[0]',
'numpy.asarray(str_bytes).ctypes.data_as('
'ctypes.POINTER(arr_t))[0]',
]
test = lambda m: min(timeit.repeat(m, setup))
>>> tabs = [test(m) for m in methods]
>>> trel = [t / tabs[0] for t in tabs]
>>> trel
[1.0, 0.060573711879182784, 0.261847116395079, 1.5389279092185282]
答案 1 :(得分:1)
作为另一种解决方案供您进行基准测试(我对结果非常感兴趣)。
使用numpy可能会增加一些简单性,具体取决于整个代码的样子。
import numpy as np
import ctypes
str_bytes = '01234567890123456789'
arr = np.asarray(str_bytes)
aa = arr.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte*len(str_bytes)))
for v in aa.contents: print v
48
49
50
51
52
53
54
55
56
57
48
49
50
51
52
53
54
55
56
57