对于我正在进行的项目,我正在实现一个链表数据结构,它基于一对的概念,我定义为:
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
分别是指向上一个和下一个链接的指针。
要设置链表,我有一个看起来像这样的安装功能。
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
在整个计划过程中,我正在建立这些链接列表的字典,并从一些链接中删除链接并将其添加到其他链接列表中。在修剪和重新安装之间,链接存储在中间阵列中。
在调试这个程序的过程中,我逐渐意识到我对Python向函数传递参数的方式的理解是有缺陷的。考虑一下我写的这个测试用例:
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
当我运行此测试时,我得到以下结果。
0
0
10
None
10108
0
我不明白为什么第二次调用p.next_pair
会产生与第一次调用(10108
)不同的结果(0
)。 install
不返回可以覆盖传入的Pair
对象(它返回None
),并且它不像我正在传递install
指针。 / p>
我对call-by-value的理解是解释器复制传递给函数的值,使调用者的变量保持不变。例如,如果我说
def foo(x):
x = x+1
return x
baz = 2
y = foo(baz)
print y
print baz
然后分别打印3
和2
。事实上,当我在Python解释器中测试时,就会发生这种情况。
如果有人能指出我正确的方向,我真的很感激。
答案 0 :(得分:8)
在将变量传递给函数时,Python不会复制任何内容。它既不是按值调用也不是逐个引用,但在这两个中它更类似于引用调用。您可以将其视为“按值调用,但值是参考”。
如果将可变对象传递给函数,则在函数内修改该对象将影响对象出现的任何位置。 (如果将不可变对象传递给函数,如字符串或整数,则根据定义,您根本无法修改对象。)
这在技术上不是通过引用传递的原因是你可以重新绑定一个名称,以便名称完全引用其他内容。 (对于不可变对象的名称,这是您可以对它们执行的唯一操作。)重新绑定仅存在于函数内的名称不会影响函数外部可能存在的任何名称。
在第一个包含Pair
个对象的示例中,您正在修改一个对象,因此您可以看到该函数之外的效果。
在第二个示例中,您没有修改任何对象,只是将名称重新绑定到其他对象(在本例中为其他整数)。 baz
是一个指向整数对象的名称(在Python中,一切都是对象,甚至是整数),其值为2.当您将baz
传递给foo(x)
时,名称为{ {1}}在堆栈上的x
函数内部创建,foo
设置为传递给函数的指针 - 与x
相同的指针。但是baz
和x
不是同一个东西,它们只包含指向同一个对象的指针。在baz
行,x = x+1
被反弹为指向一个值为3的整数对象,该指针是从函数返回的,用于将整数对象绑定到y。
如果你重写了第一个例子,根据传递给它的Pair对象的信息在你的函数中显式创建一个新的Pair对象(无论这是你修改的副本,还是你做了一个修改数据的构造函数在构造上)然后你的函数不会有修改传入的对象的副作用。
编辑:顺便说一下,在Python中,你不应该使用x
作为占位符来表示“我没有价值” - 使用0
。同样,您不应该使用None
来表示0
,就像您在False
中所做的那样。但是flag
,0
和None
的所有内容都在布尔表达式中评估为False
,因此无论您使用哪一个,都可以说False
之类的内容。而不是if not flag
。
答案 1 :(得分:8)
在Python中,一切都是对象。简单赋值将引用存储到指定名称中的已分配对象。因此,将Python变量视为分配给对象的名称而不是存储在命名位置的对象更为直接。
例如:
baz = 2
...在baz
中存储指向或引用的整数对象2
,存储在别处。 (由于类型int
是不可变的,因此Python实际上有一个小整数池,并且在任何地方重用相同的2
对象,但这是一个不需要太多关注的实现细节。)
当您致电foo(baz)
时,foo()
的本地变量x
也会首先指向整数对象2
。也就是说,foo()
- 本地名称x
和全局名称baz
是同一对象2
的名称。然后执行x = x + 1
。这会将x
更改为指向其他对象:3
。
了解一点非常重要:x
不是包含2
的框,而2
则会增加到3
。不,x
最初指向2
,然后指针会更改为指向3
。当然,由于我们没有更改baz
指向的对象,因此它仍然指向2
。
另一种解释方法是,在Python中,所有参数传递都是按值进行的,但所有值都是对象的引用。
反直觉的结果是,如果一个对象是可变的,它可以通过任何引用进行修改,所有引用都将“看到”该更改。例如,考虑一下:
baz = [1, 2, 3]
def foo(x):
x[0] = x[0] + 1
foo(baz)
print baz
>>> [2, 2, 3]
这个似乎与我们的第一个例子非常不同。但实际上,论证以同样的方式传递。 foo()
在名称baz
下接收指向x
的指针,然后对其执行更改它的操作(在这种情况下,列表的第一个元素指向不同的{{ 1}}对象)。不同之处在于名称int
永远不会指向新对象;它被x
修改为指向不同的对象。 x[0]
本身仍指向与x
相同的对象。 (事实上,在baz
的分配变成了方法调用:x[0]
。因此,x.__setitem__()
“看到”对列表的修改。怎么可能不是?
您没有看到整数和字符串的这种行为,因为您无法更改整数或字符串;它们是不可变类型,当你修改它们时(例如baz
),你实际上并没有修改它们,而是将变量名绑定到一个完全不同的对象。如果您将x = x + 1
更改为元组,例如baz
,你会发现baz = (1, 2, 3)
给你一个错误,因为你不能指定一个元组的元素;元组是另一种不可变类型。 “更改”元组需要创建一个新元组,然后赋值将变量指向新对象。
您定义的类的对象是可变的,因此您的foo()
实例可以通过传入的任何函数进行修改 - 也就是说,可以添加,删除或重新分配属性到其他对象。这些东西都不会重新绑定指向您对象的任何名称,因此当前指向它的所有名称都将“看到”更改。
答案 2 :(得分:2)
我建议您忘记实现链接列表,只需使用Python list
的实例。如果您需要默认Python list
以外的其他内容,也许您可以使用Python模块中的某些内容,例如collections
。
跟踪链表中链接的Python循环将以Python解释器速度运行,也就是说,缓慢运行。如果您只是使用内置的list
类,那么列表操作将在Python的C代码中进行,您将获得速度。
如果您需要类似列表但快速插入和快速删除的内容,您可以进行dict
工作吗?如果存在可用于对值进行排序的某种ID值(字符串或整数或其他),则可以将其用作键值并快速插入和删除值。然后,如果需要按顺序提取值,可以使用dict.keys()
方法函数获取键值列表并使用它。
但是如果你真的需要链表,我建议你找到其他人编写和调试的代码,并根据你的需要进行调整。 Google搜索“python链表信息”或“python链表模块”。
答案 3 :(得分:1)
我要投入一个稍微复杂的因素:
>>> def foo(x):
... x *= 2
... return x
...
使用我所知道的数字,列表和字符串支持的方法定义稍微不同的函数。
首先,用字符串调用它:
>>> baz = "hello"
>>> y = foo(baz)
>>> y
'hellohello'
>>> baz
'hello'
接下来,使用列表调用它:
>>> baz=[1,2,2]
>>> y = foo(baz)
>>> y
[1, 2, 2, 1, 2, 2]
>>> baz
[1, 2, 2, 1, 2, 2]
>>>
使用字符串时,不会修改参数。使用列表,参数将被修改。
如果是我,我会避免在方法中修改参数。