为什么可以在 python 3 中使用 ctypes 修改不可变字节对象?

Why it is possible to modify immutable bytes object using ctypes in python 3?

bytes 对象是 immutable。它不支持项目分配:

>>> bar = b"bar"
>>> bar[0] = b"#"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment

str 对象也是不可变的:

>>> bar = "bar"
>>> bar[0] = "#"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

可以用 ctypes 修改 bytes 对象,但不能用 str 对象做同样的事情。你能解释一下为什么吗?请看下面的例子。

c码

char* foo(char *bar) {
    bar[0] = '#';
    return bar;
}

c代码编译

gcc -shared -o clib.so -fPIC clib.c

字节尝试

python代码

import ctypes

clib = ctypes.CDLL('./clib.so')

bar = b"bar"
print("Before:", bar, id(bar))

clib.foo(bar)
print("After: ", bar, id(bar))

python代码输出

Before: b'bar' 140451244811328
After:  b'#ar' 140451244811328

str 尝试

str 对象在 Python 3 中也是不可变的,但与 bytes 对象不同,它不能用 ctypes 修改它。

python代码

import ctypes

clib = ctypes.CDLL('./clib.so')

bar = "bar"
print("Before:", bar, id(bar))

clib.foo(bar)
print("After: ", bar, id(bar))

python代码输出

Before: bar 140385853714080
After:  bar 140385853714080
Python 3 中的

str 被抽象为 Unicode,并且可以存储为每个字符串 1、2 或 4 字节,具体取决于字符串中使用的最高 Unicode 字符。要将字符串传递给 C 函数,必须将其转换为特定的表示形式。 ctypes 在这种情况下将转换后的临时缓冲区传递给 C 而不是原始缓冲区。 ctypes 可能会崩溃并损坏 Python 如果您的函数原型不正确或将不可变对象发送到会改变内容的函数,在这些情况下用户要小心。

bytes 的情况下 ctypes 传递一个指向其内部字节缓冲区的指针,但不希望它被修改。考虑:

a = b'123'
b = b'123'

由于 bytes 是不可变的,Python 可以自由地在 ab 中存储相同的引用。如果你将 b 传递给一个 ctypes-wrapped 函数并且它修改了它,它也可能会破坏 a

直接来自 ctypes documentation:

You should be careful, however, not to pass [immutable objects] to functions expecting pointers to mutable memory. If you need mutable memory blocks, ctypes has a create_string_buffer() function which creates these in various ways....