6

对于我正在进行的一个项目,我正在实现一个链表数据结构,它基于一对的想法,我将其定义为:

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_pairself.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一个指针。

我对按值调用的理解是解释器将传递的值复制到函数中,而调用者的变量保持不变。例如,如果我说

def foo(x):
     x = x+1
     return x

baz = 2
y = foo(baz)
print y
print baz

然后32应该分别打印。事实上,当我在 Python 解释器中测试它时,就会发生这种情况。

如果有人能在这里指出正确的方向,我将不胜感激。

4

4 回答 4

8

将变量传递给函数时,Python 不会复制任何内容。它既不是按值调用也不是按引用调用,但在这两者中,它更类似于按引用调用。您可以将其视为“按值调用,但值是参考”。

如果将可变对象传递给函数,则在函数内部修改该对象将影响该对象出现的任何位置。(如果您将不可变对象传递给函数,例如字符串或整数,那么根据定义,您根本无法修改该对象。)

这在技术上不是按引用传递的原因是您可以重新绑定名称,以便该名称完全引用其他内容。(对于不可变对象的名称,这是您唯一可以对它们执行的操作。)重新绑定仅存在于函数内部的名称不会影响可能存在于函数外部的任何名称。

在您使用Pair对象的第一个示例中,您正在修改一个对象,因此您可以看到函数之外的效果。

在您的第二个示例中,您没有修改任何对象,您只是将名称重新绑定到其他对象(在这种情况下为其他整数)。 baz是一个名称,它指向一个值为 2 的整数对象(在 Python 中,一切都是对象,甚至是整数)。当您传递baz到 时foo(x),该名称在堆栈上x的函数内部本地创建,并设置为指针它被传递到函数中——与 . 相同的指针。但是和不是一回事,它们只包含指向同一个对象的指针。上线,被反弹指向一个值为 3 的整数对象,并且该指针是从函数返回并用于将整数对象绑定到 y 的指针。fooxbazxbazx = x+1x

如果您重写第一个示例以根据传递给它的 Pair 对象的信息在函数内显式创建一个新的 Pair 对象(无论这是您随后修改的副本,还是创建一个修改构造数据的构造函数)那么你的函数不会有修改传入的对象的副作用。

编辑:顺便说一句,在 Python 中,您不应该将0其用作占位符来表示“我没有价值”——使用None. 同样,您不应该使用0to 来表示False,就像您似乎在做的那样flag。但是所有的0,None并在布尔表达式中FalseFalse值,所以无论你使用哪一个,你都可以说if not flag而不是if flag == 0.

于 2012-05-22T01:35:17.330 回答
8

在 Python 中,一切都是对象。简单分配将对分配对象的引用存储在分配对象名称中。因此,更直接地将 Python 变量视为分配给对象的名称,而不是存储在命名位置的对象。

例如:

baz = 2

...存储在指向存储在其他地方baz的整数对象的指针或引用中2。(由于类型int是不可变的,Python 实际上有一个小整数池,并且在2任何地方都重用了同一个对象,但这是一个实现细节,我们不需要过多关注。)

当您调用 时foo(baz)foo()的局部变量首先x也指向整数对象2。也就是说,foo()-local 名称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,然后对其执行更改它的操作(在这种情况下,列表的第一个元素指向不同的int对象)。不同之处在于名称x永远不会指向新对象。它x[0]被修改为指向不同的对象。x本身仍然指向与 相同的对象baz。(事实上​​,在幕后赋值x[0]变成了一个方法调用:x.__setitem__()。)因此baz“看到”对列表的修改。怎么可能没有?

你看不到整数和字符串的这种行为,因为你不能改变整数或字符串;它们是不可变类型,当您修改它们时(例如x = x + 1),您实际上并没有修改它们,而是将您的变量名绑定到一个完全不同的对象。如果您更改baz为元组,例如baz = (1, 2, 3),您会发现这foo()会给您一个错误,因为您无法分配给元组的元素;元组是另一种不可变类型。“更改”一个元组需要创建一个新元组,然后赋值将变量指向新对象。

你定义的类的对象是可变的,所以你的Pair实例可以被它传入的任何函数修改——也就是说,属性可以被添加、删除或重新分配给其他对象。这些东西都不会重新绑定指向您的对象的任何名称,因此当前指向它的所有名称都将“看到”更改。

于 2012-05-22T01:45:18.763 回答
2

我建议您忘记实现链表,而只需使用 Python 的实例list。如果您需要默认 Python 以外的其他list内容,也许您可​​以使用 Python 模块中的某些内容,例如collections.

跟随链表中的链接的 Python 循环将以 Python 解释器的速度运行,也就是说,速度很慢。如果你简单地使用内置list类,你的列表操作将发生在 Python 的 C 代码中,你将获得速度。

如果你需要一个类似列表但快速插入和快速删除的东西,你能做一个dict工作吗?如果有某种 ID 值(字符串或整数或其他)可用于对您的值进行排序,您可以将其用作键值并获得闪电般的快速插入和删除值。然后,如果您需要按顺序提取值,您可以使用dict.keys()方法函数获取键值列表并使用它。

但是如果你真的需要链表,我建议你找别人编写和调试的代码,并根据你的需要进行调整。谷歌搜索“python 链表配方”或“python 链表模块”。

于 2012-05-22T01:45:54.697 回答
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]
>>> 

对于字符串,参数不会被修改。使用列表,参数会被修改。

如果是我,我会避免修改方法中的参数。

于 2012-05-22T01:22:50.170 回答