Python 中的调用是如何工作的? [英] How Does Calling Work In Python?
问题描述
对于我正在进行的一个项目,我正在实现一个链表数据结构,它基于对的概念,我将其定义为:
For a project I'm working on, I'm implementing a linked-list data-structure, which is based on the idea of a pair, which I define as:
class Pair:
def __init__(self, name, prefs, score):
self.name = name
self.score = score
self.preferences = prefs
self.next_pair = 0
self.prev_pair = 0
其中 self.next_pair
和 self.prev_pair
分别是指向上一个和下一个链接的指针.
where self.next_pair
and self.prev_pair
are pointers to the previous and next links, respectively.
为了设置链表,我有一个看起来像这样的安装函数.
To set up the linked-list, I have an install function that looks like this.
def install(i, pair):
flag = 0
try:
old_pair = pair_array[i]
while old_pair.next_pair != 0:
if old_pair == pair:
#if pair in remainders: remainders.remove(pair)
return 0
if old_pair.score < pair.score:
flag = 1
if old_pair.prev_pair == 0: # we are at the beginning
old_pair.prev_pair = pair
pair.next_pair = old_pair
pair_array[i] = pair
break
else: # we are not at the beginning
pair.prev_pair = old_pair.prev_pair
pair.next_pair = old_pair
old_pair.prev_pair = pair
pair.prev_pair.next_pair = pair
break
else:
old_pair = old_pair.next_pair
if flag==0:
if old_pair == pair:
#if pair in remainders: remainders.remove(pair)
return 0
if old_pair.score < pair.score:
if old_pair.prev_pair==0:
old_pair.prev_pair = pair
pair.next_pair = old_pair
pair_array[i] = pair
else:
pair.prev_pair = old_pair.prev_pair
pair.next_pair = old_pair
old_pair.prev_pair = pair
pair.prev_pair.next_pair = pair
else:
old_pair.next_pair = pair
pair.prev_pair = old_pair
except KeyError:
pair_array[i] = pair
pair.prev_pair = 0
pair.next_pair = 0
在该程序的过程中,我正在构建这些链接列表的字典,并从一些链接中删除链接并将它们添加到其他列表中.在修剪和重新安装之间,链接存储在一个中间数组中.
Over the course of the program, I am building up a dictionary of these linked-lists, and taking links off of some and adding them in others. Between being pruned and re-installed, the links are stored in an intermediate array.
在调试这个程序的过程中,我开始意识到我对 Python 将参数传递给函数的方式的理解是有缺陷的.考虑一下我写的这个测试用例:
Over the course of debugging this program, I have come to realize that my understanding of the way Python passes arguments to functions is flawed. Consider this test case I wrote:
def test_install():
p = Pair(20000, [3, 1, 2, 50], 45)
print p.next_pair
print p.prev_pair
parse_and_get(g)
first_run()
rat = len(juggler_array)/len(circuit_array)
pref_size = get_pref_size()
print pref_size
print install(3, p)
print p.next_pair.name
print p.prev_pair
当我运行这个测试时,我得到以下结果.
When I run this test, I get the following result.
0
0
10
None
10108
0
我不明白为什么第二次调用 p.next_pair
会产生与第一次调用(0
)不同的结果(10108
)代码>).install
不返回可以覆盖传入的对象的 Pair
对象(它返回 None
),并且它不像我正在通过安装
一个指针.
What I don't understand is why the second call to p.next_pair
produces a different result (10108
) than the first call (0
). install
does not return a Pair
object that can overwrite the one passed in (it returns None
), and it's not as though I'm passing install
a pointer.
我对 call-by-value 的理解是解释器复制传递给函数的值,保持调用者的变量不变.例如,如果我说
My understanding of call-by-value is that the interpreter copies the values passed into a function, leaving the caller's variables unchanged. For example, if I say
def foo(x):
x = x+1
return x
baz = 2
y = foo(baz)
print y
print baz
然后 3
和 2
应该被分别打印出来.事实上,当我在 Python 解释器中测试它时,就会发生这种情况.
Then 3
and 2
should be printed, respectively. And indeed, when I test that out in the Python interpreter, that's what happens.
如果有人能在这里指出正确的方向,我将不胜感激.
I'd really appreciate it if anyone can point me in the right direction here.
推荐答案
在 Python 中,一切都是对象.简单赋值在赋值对象名称中存储对赋值对象的引用.因此,可以更直接地将 Python 变量视为分配给对象的名称,而不是存储在命名位置的对象.
In Python, everything is an object. Simple assignment stores a reference to the assigned object in the assigned-to name. As a result, it is more straightforward to think of Python variables as names that are assigned to objects, rather than objects that are stored in named locations.
例如:
baz = 2
... 在 baz
中存储一个指向存储在别处的整数对象 2
的指针或引用.(由于 int
类型是不可变的,Python 实际上有一个小整数池,并在任何地方重用相同的 2
对象,但这是一个我们不需要太关心的实现细节.)
... stores in baz
a pointer, or reference, to the integer object 2
which is stored elsewhere. (Since the type int
is immutable, Python actually has a pool of small integers and reuses the same 2
object everywhere, but this is an implementation detail that need not concern us much.)
当你调用foo(baz)
时,foo()
的局部变量x
也指向整数对象2
首先.也就是说,foo()
-local name x
和 global name baz
是同一个对象的名字,2代码>.然后
x = x + 1
被执行.这会将 x
更改为指向不同的对象:3
.
When you call foo(baz)
, foo()
's local variable x
also points to the integer object 2
at first. That is, the foo()
-local name x
and the global name baz
are names for the same object, 2
. Then x = x + 1
is executed. This changes x
to point to a different object: 3
.
理解这一点很重要:x
不是一个包含 2
的盒子,2
然后增加到 3代码>.不,
x
最初指向 2
,然后该指针更改为指向 3
.自然,由于我们没有改变baz
指向的对象,它仍然指向2
.
It is important to understand: x
is not a box that holds 2
, and 2
is then incremented to 3
. No, x
initially points to 2
and that pointer is then changed to point to 3
. Naturally, since we did not change what object baz
points to, it still points to 2
.
另一种解释方式是,在 Python 中,所有参数传递都是按值传递,但所有值都是对对象的引用.
Another way to explain it is that in Python, all argument passing is by value, but all values are references to objects.
这样做的一个违反直觉的结果是,如果一个对象是可变的,则可以通过任何引用对其进行修改,并且所有引用都将看到"更改.例如,考虑这个:
A counter-intuitive result of this is that if an object is mutable, it can be modified through any reference and all references will "see" the change. For example, consider this:
baz = [1, 2, 3]
def foo(x):
x[0] = x[0] + 1
foo(baz)
print baz
>>> [2, 2, 3]
这看起来与我们的第一个示例非常不同.但实际上,该参数以相同的方式传递.foo()
在名称 x
下接收一个指向 baz
的指针,然后对其执行一个改变它的操作(在这种情况下,第一个列表的元素指向不同的 int
对象).不同之处在于名称 x
从不指向新对象;x[0]
被修改为指向不同的对象.x
本身仍然指向与 baz
相同的对象.(实际上,在底层,对 x[0]
的赋值变成了一个方法调用:x.__setitem__()
.)因此 baz
看到"对列表的修改.怎么可能不行?
This seems very different from our first example. But in reality, the argument is passed the same way. foo()
receives a pointer to baz
under the name x
and then performs an operation on it that changes it (in this case, the first element of the list is pointed to a different int
object). The difference is that the name x
is never pointed to a new object; it is x[0]
that is modified to point to a different object. x
itself still points to the same object as baz
. (In fact, under the hood the assignment to x[0]
becomes a method call: x.__setitem__()
.) Therefore baz
"sees" the modification to the list. How could it not?
您不会在整数和字符串中看到这种行为,因为您无法更改整数或字符串;它们是不可变类型,当您修改它们(例如 x = x + 1
)时,您实际上并不是在修改它们,而是将您的变量名绑定到一个完全不同的对象.如果您将 baz
更改为元组,例如baz = (1, 2, 3)
,你会发现foo()
给你一个错误,因为你不能给元组的元素赋值;元组是另一种不可变类型.更改"元组需要创建一个新元组,然后赋值然后将该变量指向新对象.
You don't see this behavior with integers and strings because you can't change integers or strings; they are immutable types, and when you modify them (e.g. x = x + 1
) you are not actually modifying them but binding your variable name to a completely different object. If you change baz
to a tuple, e.g. baz = (1, 2, 3)
, you will find that foo()
gives you an error because you can`t assign to elements of a tuple; tuples are another immutable type. "Changing" a tuple requires creating a new one, and assignment then points the variable to the new object.
您定义的类的对象是可变的,因此您的 Pair
实例可以被它传入的任何函数修改——也就是说,属性可以添加、删除或重新分配给其他对象.这些东西都不会重新绑定指向您的对象的任何名称,因此当前指向它的所有名称都将看到"更改.
Objects of classes you define are mutable and so your Pair
instance can be modified by any function it is passed into -- that is, attributes may be added, deleted, or reassigned to other objects. None of these things will re-bind any of the names pointing to your object, so all the names that currently point to it will "see" the changes.
这篇关于Python 中的调用是如何工作的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!