python - python 字符串实际上在硬件级别上是不可变的吗?

标签 python c string ctypes

好的,听我说完;这个问题并不像您想象的那么愚蠢。

首先,一些背景知识:我最近开始使用 ctypes 模块,作为一项技术测试,我想使用 pygame 和 ctypes 编写一个 Mandelbrot 资源管理器,分别用于事件处理和访问 Mandelbrot 计算 dll。我最初的计划是通过让 Mandelbrot 函数计算和存储字符数组中整行像素的值并返回指向该数组的指针来最小化 ctypes 包装器开销:

Mandelbrot.restype = c_char_p
#...
str_location = Mandelbrot(x)
row = str_location.value

但事实证明这并没有真正起作用。 value 方法有两个缺陷:它会降低性能,因为它将 C 字符串逐字节复制到 python 字符串中,并且它不知道字符串的预期长度,因此数据中的任何零都将被视为空终止符,导致任何进一步的数据丢失。

我的第一个行动方案是拼凑一个快速 DLL,允许我反汇编一些 Python 对象。它有以下两个功能:

#define DLLINFO extern "C" __declspec(dllexport)
DLLINFO char show_char(char *p)
{
    return *p;
}
DLLINFO void mov(char *p, char payload)
{
    *p = payload;
}

我还将 show_char 函数封装在一个 Python 函数 show_object 中,该函数使用 sys.getsizeof 来打印 Python 对象的内存内容。 拆开绳子后发现一个非常简单的设计:

>>> from hack import *; import sys
>>>
>>> #string experiment
>>> a = '01234567'
>>> hex(sys.getrefcount(a))
'0x3'
>>> hex(id(type(a)))
'0x1e1d81f8'
>>> hex(len(a))
'0x8'
>>> show_object(a)
  3  2  1  0 byte

  0  0  0  4   0    #reference count (+1 temporary reference)
 1e 1d 81 f8   4    #pointer to type
  0  0  0  8   8    #length
 94  b b6 98  12    #???
  0  0  0  1  16    #???
 33 32 31 30  20    #Data '0123' (little endian)
 37 36 35 34  24    #Data '4567'
           0  28    #Null terminator
>>> #sys.getsizeof reported 29 bytes for 9 bytes of data.

(后面添加的数据注释)

我尝试用可变字节数组替换字符串,然后反汇编字节数组以查看应该将 Mandelbrot 数据写入何处:

>>> #bytearray experiment
>>> b = bytearray('01234567')
>>> hex(sys.getrefcount(b))
'0x2'
>>> hex(id(type(b)))
'0x1e1e5e20'
>>> hex(len(b))
'0x8'
>>> show_object(b)
  3  2  1  0 byte

  0  0  0  3   0    #reference count (+1 temporary reference)
 1e 1e 5e 20   4    #pointer to type
  0  0  0  8   8    #length
  0  0  0  0  12    #???
  0  0  0  9  16    #???
  2 3a 63 a0  20    #???
  2 92 93 38  24    #???
  2 91 e4 90  28    #???
           1  32    #???
>>> #sys.getsizeof reported 33 bytes for 8 bytes of data

好吧,我不知道数据在字节数组中的位置,所以没有骰子。

我的下一个计划是将字符串替换为 ctypes 内置的可变字符串,即 create_string_buffer。

>>> #buffer experiment
>>> from ctypes import *
>>> c = create_string_buffer('01234567')
>>> hex(id(type(c)))
'0x1ceb778'
>>> show_object(c)
  3  2  1  0 byte

  0  0  0  3   0    #reference count
  1 ce b7 78   4    #pointer to type
  2 38 f7 38   8    #???
  0  0  0  1  12    #Here be dragons
  0  0  0  0  16    #etc.
  0  0  0  9  20
  0  0  0  9  24
  0  0  0  0  28
  0  0  0  0  32
  0  0  0  0  36
 33 32 31 30  40    #data '0123'
 37 36 35 34  44    #data '4567'
  0  0  0  0  48
  0  0  0  0  52
  0  0  0  0  56
  0  0  0  0  60
  2 38 f8 40  64
  2 38 f7 a0  68
 ff ff ff fe  72
  0 2e  0 65  76
>>> #sys.getsizeof reported 80 bytes for 9 bytes of data.

嗯。至少数据在某处。不幸的是,这个对象过于冗长而不实用。此外,它不是内置类型,所以我很难让它与其他函数一起使用。 这是我决定切换回字符串并运行一些修改字符串的谨慎测试的时候:

>>> from hack import *
>>> s = "Hello, world!"
>>> show_object(s)
  3  2  1  0 byte

  0  0  0  3   0
 1e 1d 81 f8   4
  0  0  0  d   8
 8f 8d ce 9c  12
  0  0  0  0  16
 6c 6c 65 48  20
 77 20 2c 6f  24
 64 6c 72 6f  28
        0 21  32
>>> mov(id(s)+32, 63)
>>> print s
Hello, world?
>>> mov(id(s)+8,5)
>>> print s
Hello

到目前为止一切顺利。至少我这样做的几次都没有崩溃。事实上,即使将长度修改为较低的值也不会立即导致任何问题。 (虽然我不打算这样做) 那么,为什么我在布置显示字符串可变的数据后问这个问题?

首先,我知道硬件有可能将字符串标记为不可变,并且尝试修改它们可能会导致段错误或类似问题:

char good_string[80];
good_string[8] = '!'; //Everything's okay so far.
char* bad_string = "This string's made out of const chars, beware!";
bad_string[8] = '!'; //And now you've got segfault!

其次,也是更重要的一点,我对 Python 的内部工作原理了解不够,无法自信地绕过 Python 对字符串的锁定并玩弄未定义的行为。现在,我很容易说服自己 Python FAQ 中关于字符串不可变的原因是错误的(我没有改变字符串的大小,而且字符串不像整数那样是基本的。),但我不知道是否有一些隐藏的原因字符串不应该被修改,如果我尝试做我计划做的事情,某些东西会在我脸上爆炸。这是我提交这个问题的主要原因;希望有知识的大神不吝赐教。

谢谢,您阅读了整个问题。对不起,简洁不是我的强项。 :)

最佳答案

有些计算机系统可以在硬件级别将任意范围的内存标记为只读,但这不是 python 中发生的情况。正在发生的事情是,根据定义,python 防止字符串在创建的位置被更改。

是的 - 通过更改 python 代码或提供新的内置函数,编写允许字符串在某些情况下可变的代码是完全可能的,但是如果您尝试使用可变的,您将遇到真正的困难例如,字符串作为字典键,并且清楚地给出了字符串的存储方式,改变长度会很困难(如果在大多数情况下不是不可能的话 - 你需要在当前字符串之后立即释放内存以便扩展到例如)。

请记住,即使使用可能称为直接内存访问的语言(例如 C),它的字符串也仅在某些情况下可变:您可以更改特定字符,但不能任意延长一个 C 字符串,既没有为它预留内存,也没有在每次更改时更改它的标识(如果你对它有多个引用,那么你就会遇到问题)。

关于python - python 字符串实际上在硬件级别上是不可变的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28020935/

相关文章:

python - 在 csv 文件中编写混合整数和 float 的 pandas DataFrame

python - 连接中止错误 : [WinError 10053] An established connection was aborted by the software in your host machine with GeckoDriver and Firefox

C char 指针 strcpy 内存错误

php - 有人熟悉PHP源代码吗?

PHP:返回两个字符之间的字符串

java - 两个看似相等的字符串并不相等

python - For 循环似乎比 NumPy/SciPy 3D 插值更快

python - Nuke 中的 Pyside 小部件不保持值

c - struct tm中tm_isdst字段的解释

java - 为什么 char[] 比 String 表现更好?- Java