Python按ref调用使用ctypes按值调用

Python按ref调用使用ctypes按值调用,python,Python,我正试图编写一个程序,用Python向一个级别的学生演示按引用调用和按值调用之间的区别。我成功地将可变对象作为变量传递给函数,但发现我也可以使用ctypes库做同样的事情 我不太明白它是如何工作的,因为ctype库中有一个函数byref(),但在我的示例中它不起作用。然而,通过调用一个没有byref()的函数,它确实起作用了 **我的工作代码 """ Program to illustrate call by ref """ from ctypes import * #alloews c

我正试图编写一个程序,用Python向一个级别的学生演示按引用调用和按值调用之间的区别。我成功地将可变对象作为变量传递给函数,但发现我也可以使用ctypes库做同样的事情

我不太明白它是如何工作的,因为ctype库中有一个函数
byref()
,但在我的示例中它不起作用。然而,通过调用一个没有
byref()
的函数,它确实起作用了

**我的工作代码

"""
Program to illustrate call by ref
"""


from  ctypes import *  #alloews call by ref

test = c_int(56)  #Pytthon call by reference eg addres
t = 67            #Python call by value eg copy


#expects a ctypes argument
def byRefExample(x):
    x.value= x.value + 2


#expects a normal Python variable
def byValueExample(x):
    x = x + 2


if __name__ == "__main__":

    print "Before call test is",test
    byRefExample(test)                
    print "After call test is",test

    print "Before call t is",t
    byValueExample(t)
    print "After call t is",t
问题:

将普通Python变量传递给
byValueExample()
时,它会按预期工作。函数参数
t
的副本会更改,但标头中的变量
t
不会更改。然而,当我通过ctypes变量测试时,本地变量和头变量都会发生变化,因此它的行为就像一个C指针变量。虽然我的程序可以工作,但我不确定在这样使用时,
byref()
函数如何以及为什么不能工作:

x = c_int(10)
xp = pointer(x)
by_ref_in_c(xp)
print(x)

byRefExample(byref(test))

您实际使用的术语并不完全正确,而且可能会产生误导。我会在最后解释。但首先我会用你的措辞来回答


我成功地将可变对象作为变量传递给函数,但发现我也可以使用ctypes库执行同样的操作

这是因为那些
ctypes
对象是可变的对象,所以您只是在做与以前相同的事情。特别是,
ctypes.c_int
是一个包含整数值的可变对象,您可以通过设置其
成员来改变它。因此,您已经在做与不使用
ctypes
时完全相同的事情了

更详细地说,比较这些:

def by_ref_using_list(x):
    x[0] += 1
value = [10]
by_ref_using_list(value)
print(value[0])

def by_ref_using_dict(x):
    x['value'] += 1
value = {'value': 10}
by_ref_using_list(value)
print(value['value'])

class ValueHolder(object):
    def __init__(self, value):
        self.value = value
def by_ref_using_int_holder(x):
    x.value += 1
value = ValueHolder(10)
by_ref_using_list(value)
print(value.value)
你会期望所有这三个都打印出11个,因为它们只是传递不同类型的可变对象并对其进行变异的三种不同方式

这正是您使用
c_int
所做的

您可能想阅读FAQ,尽管您似乎已经知道那里的答案,只是想知道
ctypes
如何适合


那么,byref的作用是什么呢

它用于调用一个C函数,该函数通过引用C样式获取值:使用显式指针类型。例如:

void by_ref_in_c(int *x) {
    *x += 1;
}
无法将此对象传递给
c_int
对象,因为它需要指向
c_int
的指针。你不能给它传递一个未初始化的
指针(c_int)
,因为那样它就只能写入随机内存了。您需要获取指向实际
c_int
的指针。你可以这样做:

x = c_int(10)
xp = pointer(x)
by_ref_in_c(xp)
print(x)
那很好用。但这太过分了,因为您已经创建了一个额外的Python
ctypes
对象,
xp
,您实际上不需要任何东西。这就是
byref
的作用:它提供了一个指向对象的轻量级指针,该指针只能用于通过引用传递该对象:

x = c_int(10)
by_ref_in_c(byref(x))
print(x)

这就解释了为什么这不起作用:

byRefExample(byref(test))
该调用正在生成一个指向
test
的轻量级指针,并通过refexample将该指针传递给
。但是,
byRefExample
不需要指向
c_int
的指针,它需要一个
c_int

当然,这都是用Python编写的,而不是用C编写的,因此没有进行静态类型检查。函数调用工作得很好,您的代码不在乎它得到什么类型,只要它有一个可以递增的
成员。但是
指针
没有
成员。(它有一个
contents
成员。)因此,您会得到一个
AttributeError
试图访问
x.value


那么,你是如何做到这一点的呢

使用单个元素列表是一种众所周知的破解方法,它可以绕过这样一个事实:您需要共享一些可变的东西,但您只有一些不可变的东西。如果您使用它,经验丰富的Python程序员将知道您在做什么

也就是说,如果你认为你需要这个,你通常是错的。通常正确的答案是只返回新值。对不发生任何变异的函数进行推理更容易。你可以用任何你想要的方式把它们串在一起,用生成器和迭代器把它们从里到外转换,把它们发送到子进程以利用你CPU中的额外核心,等等。即使你不做这些事情,返回一个新值通常比就地修改一个值要快,即使在您不希望的情况下(例如,删除列表中75%的值)

通常,当您确实需要可变值时,它们已经有了一个明显的生存空间,例如类的实例属性

但有时你确实需要单元素列表,所以在你的剧目中有它是值得的;不要在你不需要的时候使用它


那么,你的术语怎么了

在某种意义上(Ruby和Lisp程序员使用的意义),Python中的所有内容都是通过引用传递的。在另一种意义上(许多Java和VB程序员使用的意义),它都是按值传递的。但实际上,最好也不要调用它。*您传递的既不是变量值的副本,也不是对变量的引用,而是对值的引用。当你调用
byValueExample(t)
函数时,你传递的不是一个新的带有
67
值的整数,而是一个与
t
绑定的整数的引用。如果你可以变异
67
(你不能,因为int是不可变的),调用者就会看到变化

第二,Python名称甚至不是您想象中的变量。在C语言中,变量是一个
左值
。它有一个类型,更重要的是,还有一个地址。因此,您可以传递对variabl的引用
x.value = x.value + 2
x = x + 2
from  ctypes import *

test = c_int(56)
t = 67

print('test id =',id(test))
print('t    id =',id(t))

#expects a ctypes argument
def byRefExample(x):
    print('ByRef x',x,id(x))
    print('ByRef x.value',x.value,id(x.value))
    x.value = x.value + 2
    print('ByRef x.value',x.value,id(x.value))
    print('ByRef x',x,id(x))

#expects a normal Python variable
def byValueExample(x):
    print('ByVal x',x,id(x))
    x = x + 2
    print('ByVal x',x,id(x))

print("Before call test is",test,id(test))
print("Before call test is",test.value,id(test.value))
byRefExample(test)                
print("After call test is",test.value,id(test.value))
print("After call test is",test,id(test))

print("Before call t is",t,id(t))
byValueExample(t)
print("After call t is",t,id(t))
test id = 80548680
t    id = 507083328
Before call test is c_long(56) 80548680
Before call test.value is 56 507082976
ByRef x c_long(56) 80548680                 # same id as test
ByRef x.value 56 507082976
ByRef x.value 58 507083040                  # x.value is new object!
ByRef x c_long(58) 80548680                 # but x is still the same.
After call test.value is 58 507083040       # test.value sees new object because...
After call test is c_long(58) 80548680      # test is same object as x.
Before call t is 67 507083328
ByVal x 67 507083328                        # same id as t
ByVal x 69 507083392                        # x is new object!
After call t is 67 507083328                # t id same old object.