您实际上使用的术语并不完全正确,并且可能非常具有误导性。我会在最后解释。但首先我会根据你的措辞来回答。
我成功地将可变对象作为变量传递给函数,但发现我也可以使用 ctypes 库来做同样的事情。
那是因为这些ctypes
对象是可变对象,所以你只是在做你已经做过的事情。特别是,actypes.c_int
是一个持有整数值的可变对象,您可以通过设置它的value
成员来改变它。因此,您已经在做与没有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
。
您可能想阅读常见问题解答如何编写带有输出参数的函数(通过引用调用)?,尽管您似乎已经知道那里的答案,只是想知道如何ctypes
适应……</p>
那么,byref
甚至是为了什么呢?
它用于调用通过引用 C 风格获取值的 C 函数:通过使用显式指针类型。例如:
void by_ref_in_c(int *x) {
*x += 1;
}
您不能将此c_int
对象传递给它,因为它需要一个指向c_int
. 而且您不能将它传递给 uninitialized POINTER(c_int)
,因为那样它只会写入随机存储器。您需要获取指向实际c_int
. 你可以这样做:
x = c_int(10)
xp = pointer(x)
by_ref_in_c(xp)
print(x)
这工作得很好。但这太过分了,因为您已经创建了一个额外的 Pythonctypes
对象,xp
您实际上并不需要它。这就是它的byref
用途:它为您提供了一个指向对象的轻量级指针,该指针只能用于通过引用传递该对象:
x = c_int(10)
by_ref_in_c(byref(x))
print(x)
这就解释了为什么这不起作用:
byRefExample(byref(test))
该调用正在创建一个指向 的轻量级指针test
,并将该指针传递给byRefExample
。但byRefExample
不想要指向 a 的指针c_int
,它想要 a c_int
。
当然,这都是在 Python 中,而不是 C,所以没有进行静态类型检查。函数调用工作得很好,你的代码并不关心它得到什么类型,只要它有一个value
你可以递增的成员。但是 aPOINTER
没有value
成员。(它有一个contents
成员。)因此,您AttributeError
尝试访问x.value
.
那么,你是怎么做这种事情的呢?
好吧,使用单元素列表是一种众所周知的技巧,可以解决您需要共享可变内容但您只有不可变内容的事实。如果你使用它,有经验的 Python 程序员会知道你在做什么。
话虽如此,如果你认为你需要这个,你通常是错的。通常正确的答案是只返回新值。更容易推理不会改变任何东西的函数。你可以用任何你想要的方式将它们串在一起,用生成器和迭代器将它们从里到外,将它们发送到子进程以利用 CPU 中的这些额外内核等。即使你不做任何那些东西,返回一个新值通常比就地修改一个值更快,即使在你不期望的情况下(例如,删除列表中 75% 的值)。
通常,当您确实需要可变值时,已经有一个明显的地方可以让它们存在,例如类的实例属性。
但有时你确实需要单元素列表技巧,所以值得在你的曲目中使用;只是在不需要时不要使用它。
那么,您的术语有什么问题?
在某种意义上(Ruby 和 Lisp 程序员使用的意义上), Python 中的一切都是通过引用传递的。在另一种意义上(许多 Java 和 VB 程序员使用的意义上),它都是按值传递的。但实际上,最好不要调用它。* 您传递的既不是变量值的副本,也不是对变量的引用,而是对值的引用。当您调用该byValueExample(t)
函数时,您不会67
像在 C 中那样传递具有值的新整数,而是传递对绑定到 name的同一整数的引用。如果你可以改变(你不能,因为 int 是不可变的),调用者会看到改变。67
t
67
其次,Python 名称甚至不是您所想的意义上的变量。在 C 中,变量是lvalue
. 它有一个类型,更重要的是,有一个地址。因此,您可以传递对变量本身的引用,而不是其值。在 Python 中,名称只是一个名称(通常是模块、本地或对象字典中的键)。它没有类型或地址。这不是你可以传递的东西。所以,没有办法x
通过引用传递变量。**
最后,=
在 Python 中不是将值复制到变量的赋值运算符。它是一个绑定运算符,为值提供名称。因此,在 C 中,当您编写时,会将x = x + 1
值复制x + 1
到变量的位置x
,但在 Python 中,当您编写时x = x + 1
,它只是重新绑定局部变量x
以引用新值x + 1
。这不会对x
曾经绑定的任何值产生任何影响。(好吧,如果它是对该值的唯一引用,垃圾收集器可能会清理它……但就是这样。)
如果您来自 C++,这实际上更容易理解,这确实迫使您理解右值和左值以及不同类型的引用以及复制构造与复制分配等等……在 C 中,这一切都看似简单,这很难意识到它与同样简单的 Python 有多么不同。
* Python 社区中有些人喜欢称其为“传递共享”。一些研究人员称其为“通过对象”。其他人选择首先区分值语义和引用语义,然后再描述调用样式,因此您可以将此称为“引用语义传递复制”。但是,虽然至少这些名字不是模棱两可的,但它们也不是很知名,所以它们不太可能帮助任何人。我认为描述它比试图为它找出最好的名字更好......</p>
** 当然,因为 Python 是完全反射的,所以你总是可以x
直接或间接地传递字符串和找到它的上下文……如果你byRefExample
这样做了globals()['x'] = x + 2
,那将影响全局x
. 但是……不要那样做。