理解Python的传递函数参数的逐个调用样式

Understanding Python's call-by-object style of passing function arguments

我不确定我是否理解python的按对象调用的概念,即传递函数参数的风格(在这里解释http://effbot.org/zone/call by object.htm)。似乎没有足够的例子来很好地阐明这个概念(或者我的google fu可能很弱!)D)

我编写了这个小的人为的python程序来试图理解这个概念。

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
def foo( itnumber, ittuple,  itlist, itdict   ):
    itnumber +=1
    print id(itnumber) , itnumber

    print id(ittuple)  , ittuple

    itlist.append(3.4)
    print id(itlist)   , itlist

    itdict['mary']  = 2.3
    print id(itdict),    itdict



# Initialize a number, a tuple, a list and a dictionary
tnumber = 1
print id( tnumber ), tnumber

ttuple  = (1, 2, 3)
print id( ttuple ) , ttuple

tlist   = [1, 2, 3]
print id( tlist ) , tlist

tdict = tel = {'jack': 4098, 'sape': 4139}
print '-------'
# Invoke a function and test it
foo(tnumber, ttuple, tlist , tdict)

print '-------'
#Test behaviour after the function call is over
print id(tnumber) , tnumber
print id(ttuple)  , ttuple
print id(tlist)   , tlist
print id(tdict),  tdict

程序的输出是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
146739376 1
3075201660 (1, 2, 3)
3075103916 [1, 2, 3]
3075193004 {'sape': 4139, 'jack': 4098}

---------

146739364 2
3075201660 (1, 2, 3)
3075103916 [1, 2, 3, 3.4]
3075193004 {'sape': 4139, 'jack': 4098, 'mary': 2.3}

---------

146739376 1
3075201660 (1, 2, 3)
3075103916 [1, 2, 3, 3.4]
3075193004 {'sape': 4139, 'jack': 4098, 'mary': 2.3}

如您所见,除了传递的整数之外,对象ID(据我所知是指内存位置)保持不变。

所以在整数的情况下,它是(有效地)通过值传递的,其他数据结构是(有效地)通过引用传递的。我试图更改列表、数字和字典,以测试数据结构是否已更改到位。号码不在名单上字典是。

我在上面有效地使用了这个词,因为根据上面代码中传递的数据结构,参数传递的"按对象调用"样式似乎表现为两种方式。

对于更复杂的数据结构(比如numpy数组等),是否有快速的经验法则识别哪些参数将通过引用传递,哪些参数将通过值传递?


关键的区别在于,在C语言中,变量是内存中的一个框,用于放置内容。在python中,变量是一个名称。

python既不是按引用调用,也不是按值调用。这是更明智的做法!(事实上,我在学习更常见的语言之前就已经学习了Python,所以按值调用和参照调用对我来说很奇怪。)

在python中,有事物,也有名字。列表、整数、字符串和自定义对象都是。xyz是名称。写作

1
x = []

意思是"建造一个新的东西,命名为x"。写作

1
2
3
x = []
foo = lambda x: x.append(None)
foo(x)

意思是"用x的名字构造一个新的东西,用foo的名字构造一个新的函数(这是另一个东西),用x的名字调用foo"。现在foo只是把None附加到它收到的任何东西上,所以这就减少到"把None附加到空名单上"。写作

1
2
3
4
x = 0
def foo(x):
    x += 1
foo(x)

意思是"在x上构造一个新东西0,构造一个新函数foo,在x上调用foo"。在foo中,任务只是说"将x重命名为1加上它以前的样子",但这并没有改变0。


其他人已经给出了很好的答案。我认为还有一件事会有所帮助:

1
 x = expr

评估expr并将x绑定到结果。另一方面:

1
 x.operate()

x执行某些操作,因此可以更改它(导致相同的基础对象具有不同的值)。

有趣的案例包括:

1
 x += expr

可译为x = x + expr(重新绑定)或x.__iadd__(expr)(修改),有时用非常特殊的方式:

1
2
3
4
>>> x = 1
>>> x += 2
>>> x
3

(所以x是反跳的,因为整数是不可变的)

1
2
3
4
5
6
7
8
9
>>> x = ([1], 2)
>>> x
([1], 2)
>>> x[0] += [3]
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> x
([1, 3], 2)

在这里,x[0]本身是可变的,在适当的位置上发生了突变;但随后,python也尝试对x本身进行突变(与x.__iadd__一样),因为元组是不可变的,所以出错了。但是到那时,x[0]已经变异了!


python中的数字、字符串和元组是不可变的;使用增强赋值将重新绑定名称。

您的其他类型仅仅是变异的,并且保持相同的对象。