关于编程语言:如何在Python中调用工作?

How Does Calling Work In Python?

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

1
2
3
4
5
6
7
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分别是指向前一个和下一个链接的指针。

要设置链接列表,我有一个这样的安装函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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向函数传递参数的方式的理解是有缺陷的。考虑一下我写的这个测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
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

当我运行此测试时,得到以下结果。

1
2
3
4
5
6
0
0
10
None
10108
0

我不明白为什么第二次调用p.next_pair会产生与第一次调用(0不同的结果(10108)。install不返回可以覆盖传入对象的Pair对象(它返回None,而不像我传递install指针。

我对按值调用的理解是,解释器复制传递给函数的值,使调用方的变量保持不变。例如,如果我说

1
2
3
4
5
6
7
8
def foo(x):
     x = x+1
     return x

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

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

如果有人能给我指明正确的方向,我会非常感激的。


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

例如:

1
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中,所有参数传递都是按值传递的,但所有值都是对对象的引用。

这样做的一个反直觉的结果是,如果一个对象是可变的,它可以通过任何引用进行修改,并且所有引用都将"看到"更改。例如,考虑一下:

1
2
3
4
5
6
7
8
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"看到"对列表的修改。怎么会不呢?

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

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


当向函数传递变量时,python不复制任何内容。它既不是按值调用,也不是按引用调用,但在这两者中,它更类似于按引用调用。您可以将其视为"按值调用,但该值是一个引用"。

如果将可变对象传递给一个函数,那么在函数内部修改该对象将影响它出现的所有地方的对象。(如果将不可变对象传递给函数,如字符串或整数,则根据定义,根本无法修改对象。)

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

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

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

如果您重写了第一个示例,根据传递给函数的pair对象的信息(无论这是您随后修改的副本,还是您在构造时修改数据的构造函数),在函数内部显式地创建一个新的pair对象,那么您的函数将不具有修改p对象的副作用。参与进来。

编辑:顺便说一下,在python中,不应该使用0作为占位符来表示"我没有值"--使用None。同样,你也不应该用0来表示False,就像你在flag中所做的那样。但是所有的0NoneFalse在布尔表达式中都对False进行了评估,因此无论使用哪种表达式,都可以用if not flag来代替if flag == 0来表示。


我建议您忘记实现一个链接列表,只需使用一个python list的实例。如果您需要的不是默认的python list,那么您可以使用来自python模块(如collections)的内容。

跟踪链接列表中链接的python循环将以python解释器的速度运行,也就是说,速度很慢。如果您只使用内置的list类,那么您的列表操作将在Python的C代码中进行,并且您将获得速度。

如果您需要类似于列表的东西,但是插入和删除速度都很快,那么您能让dict工作吗?如果有某种ID值(字符串或整数或其他类型)可以用于对值进行排序,那么您可以将其作为键值,并快速插入和删除值。然后,如果需要按顺序提取值,可以使用dict.keys()方法函数来获取关键值列表并使用它。

但是,如果您真的需要链表,我建议您查找由其他人编写和调试的代码,并根据您的需要进行调整。谷歌搜索"python linked list recipe"或"python linked list module"。


我将加入一个稍微复杂的因素:

1
2
3
4
>>> def foo(x):
...   x *= 2
...   return x
...

对于数字、列表和字符串,使用我知道支持的方法定义稍微不同的函数。

首先,用字符串调用它:

1
2
3
4
5
6
>>> baz ="hello"
>>> y = foo(baz)
>>> y
'hellohello'
>>> baz
'hello'

接下来,用列表调用它:

1
2
3
4
5
6
7
>>> baz=[1,2,2]
>>> y = foo(baz)
>>> y
[1, 2, 2, 1, 2, 2]
>>> baz
[1, 2, 2, 1, 2, 2]
>>>

对于字符串,参数不会被修改。通过列表,可以修改参数。

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