Python ctypes:复制结构体的内容

9

我希望使用ctypes在Python中模仿一段C代码,该代码大致如下:

typedef struct {
  int x;
  int y;
} point;

void copy_point(point *a, point *b) {
  *a = *b;
}

在ctypes中无法执行以下操作:

from ctypes import *

class Point(Structure):
  _fields_ = [("x", c_int),("y", c_int)]

def copy_point(a, b):
  a.contents = b.contents

p0 = pointer(Point())
p1 = pointer(Point())
copy_point(p0,p1)

作为参考,contents仍然是Python ctypes结构体对象,它本身作为引用进行管理。
一个明显的解决方法是手动复制每个字段(表示为不可变的Python int),但这在处理更复杂的结构时不具有可扩展性。此外,对于不是基本的,而是结构化类型的字段,需要进行递归处理。
我的另一个选择是使用memmove并将对象复制为缓冲区,但这似乎非常容易出错(由于Python是动态类型的,因此很容易将其与不同类型和大小的对象一起使用,导致内存损坏或分段错误)...
有什么建议吗?
编辑:
我也可以使用全新的结构体副本,所以这可能会有用:
import copy
p0 = Point()
p1 = copy.deepcopy(p0) #or just a shallow copy for this example

但我不确定是否存在一些奇怪的行为,将ctypes代理复制为常规Python对象...


1
很不幸,如果ctypes结构包含指针,则deepcopy会失败:ValueError: ctypes objects containing pointers cannot be pickled - 101
5个回答

6
您可以使用序列赋值来复制指向的对象(而不是分配给p.contents,这会改变指针值):
def copy(dst, src):
    """Copies the contents of src to dst"""
    pointer(dst)[0] = src

# alternately
def new_copy(src):
    """Returns a new ctypes object which is a bitwise copy of an existing one"""
    dst = type(src)()
    pointer(dst)[0] = src
    return dst

# or if using pointers
def ptr_copy(dst_ptr, src_ptr):
    dst_ptr[0] = src_ptr[0]

ctypes会为您进行类型检查(虽然不是绝对可靠,但总比没有好)。

以下是使用示例,可以验证它确实有效 ;):

>>> o1 = Point(1, 1)
>>> o2 = Point(2, 2)
>>> print (o1.x, o1.y, addressof(o1)), (o2.x, o2.y, addressof(o2))
(1, 1, 6474004) (2, 2, 6473524)
>>> copy(o2, o1)
>>> pprint (o1.x, o1.y, addressof(o1)), (o2.x, o2.y, addressof(o2))
(1, 1, 6474004) (1, 1, 6473524)

>>> o1 = Point(1, 1), o2 = Point(2, 2)
>>> print (o1.x, o1.y, addressof(o1)), (o2.x, o2.y, addressof(o2))
(1, 1, 6473844) (2, 2, 6473684)
>>> p1, p2 = pointer(o1), pointer(o2)
>>> addressof(p1.contents), addressof(p2.contents)
(6473844, 6473684)
>>> ptr_copy(p1, p2)
>>> print (o1.x, o1.y, addressof(o1)), (o2.x, o2.y, addressof(o2))
(2, 2, 6473844) (2, 2, 6473684)
>>> addressof(p1.contents), addressof(p2.contents)
(6473844, 6473684)

看起来很有前途,但它只是改变了指针 :-s 分配后打印src和dst.contents的地址以进行检查。 - fortran
这些函数不应该传递指针,而应该传递 ctypes 结构对象。如果你想要一个类似于 C 语言中的 copy_point 函数,可以使用 dst[0] = src[0] - Miles
哦,我不明白为什么行为从 dst = pointer(a); dst[0] = src; 变成了 pointer(a)[0] = src :-| - fortran
这不应该发生;但在交互式会话中犯错误或意外使用旧变量是很容易的。 - Miles

6

memmove 是这里正确的操作。通过设置你的 CopyPoint 函数的 argtypes,你可以轻松地强制执行类型安全。

from ctypes import *

class Point(Structure):
    _fields_ = [("x", c_int), ("y", c_int)]
    def __str__(self):
        return "<Point: x=%d, y=%d, addr=%ld>" % (self.x, self.y, addressof(self))

def CopyPoint(a, b):
    memmove(a, b, sizeof(Point))
CopyPoint.argtypes = [POINTER(Point), POINTER(Point)]

pt0 = Point(x=0, y=10)
pt1 = Point(x=5, y=7)

print pt0, pt1

CopyPoint(byref(pt0), byref(pt1))
print pt0, pt1    

try:
    CopyPoint(byref(pt0), Point(x=2, y=3))
except ArgumentError as e:
    print "Could not copy!", e

输出:

$ python ct.py 
<Point: x=0, y=10, addr=3083711192> <Point: x=5, y=7, addr=3083711120>
<Point: x=5, y=7, addr=3083711192> <Point: x=5, y=7, addr=3083711120>
Could not copy! argument 2: <type 'exceptions.TypeError'>: wrong type

请注意,如果您需要泛化,可以轻松地创建一个工厂来在运行时基于特定类型生成此类函数:
def CopierFactory(typ):
    def f(a,b):
        memmove(a,b, sizeof(typ))
    f.argtypes = [POINTER(typ), POINTER(typ)]

    return f

copy_point = CopierFactory(Point)

a = Point(x=1, y=2)
b = Point(x=-1, y=-1)
print a, b
copy_point(byref(a), byref(b))
print a, b

输出:

<Point: x=1, y=2, addr=3085088024> <Point: x=-1, y=-1, addr=3085087952>
<Point: x=-1, y=-1, addr=3085088024> <Point: x=-1, y=-1, addr=3085087952>

1

我现在也在考虑定义一个类似的方法:

def safe_copy(dst, src):
  if type(src) != type(dst) or not isinstance(src, Structure):
    raise Exception("wrong types")
  memmove(addressof(dst), addressof(src), sizeof(src))

但可能还有更好的选择...


0
在Python 3.x中,您的代码可以正确运行。如下所示:
>>> from ctypes import *
>>> class Point(Structure):
...   _fields_ = [("x", c_int),("y", c_int)]
>>> def copy_point(a, b):
...   a.contents = b.contents
>>> p0 = pointer(Point())
>>> p1 = pointer(Point(1,2))
>>> p0.contents.x
0
>>> copy_point(p0,p1)
>>> p0.contents.x
1

0
指针操作通常不太安全,建议为每个感兴趣的结构数据类型创建包装类,并让它们处理指针复制操作,就像您在这里所做的一样。可以使用递归地作为语法糖的 lambda 和 map 函数。

大声思考的代价是......有元类可以用作良好的映射机制。http://code.activestate.com/recipes/576666/ - whatnick

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