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

smi*_*dha 13 python function

我不确定我是否通过传递函数参数的对象样式理解Python调用的概念(在此处解释http://effbot.org/zone/call-by-object.htm).似乎没有足够的例子来澄清这个概念(或者我的google-fu可能很弱!:D)

我写了这个有点人为的Python程序,试图理解这个概念

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
Run Code Online (Sandbox Code Playgroud)

该程序的输出是

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}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,除了传递的整数之外,对象id(我理解的是指记忆位置)保持不变.

因此,在整数的情况下,它(有效地)通过值传递,而其他数据结构(有效地)通过引用传递.我尝试更改列表,数字和字典,以测试数据结构是否已更改.这个号码不在列表中,字典也是.

我在上面有效地使用了这个词,因为参数传递的'call-by-object'样式似乎表现出两种方式,这取决于上面代码中传递的数据结构

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

Kat*_*iel 13

关键的区别在于,在C风格的语言中,变量是内存中放置东西的框.在Python中,变量是一个名称.

Python既不是引用调用也不是按值调用.这是更明智的事情!(事实上​​,在我学习了更常见的语言之前,我学习了Python,因此按值调用和引用调用对我来说似乎很奇怪.)

在Python中,有些东西都有名称.所有内容都是列表,整数,字符串和自定义对象.x,yz是名字.写作

x = []
Run Code Online (Sandbox Code Playgroud)

意思是"构建一个新的东西,[]并给它起名字x".写作

x = []
foo = lambda x: x.append(None)
foo(x)
Run Code Online (Sandbox Code Playgroud)

意思是" []用名称构造一个新东西,用名称x构造一个新函数(这是另一个东西)foo,并调用foo名称为x" 的东西.现在foo只是附加None到它收到的任何内容,所以这减少为"附加None到空列表".写作

x = 0
def foo(x):
    x += 1
foo(x)
Run Code Online (Sandbox Code Playgroud)

意思是"建设一个新事物0与名称x,构造一个新的功能foo,并呼吁foox".在里面foo,作业只是说"重命名x为1加上以前的样子",但这不会改变事物 0.

  • 我喜欢这个答案,除了你的_last_例子。问题是“+=”对于可变对象和不可变对象的工作方式应该不同。一个可变对象很可能在“foo”之外有可见的变化。 (2认同)
  • 关于`+ =`:`a + = b`的重要观点实际上可能最终调用`a .__ iadd __(b)`,这与调用`a`的任何其他方法相同,它不会进行任何重新分配.如果`a`没有`__iadd__`函数那么`a + = b`减少为'a = a + b`,最终减少到'a = a .__ add __(b)`或`a = b .__ RADD __(一)` (2认同)

tor*_*rek 9

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

 x = expr
Run Code Online (Sandbox Code Playgroud)

评估expr并绑定x结果.另一方面:

 x.operate()
Run Code Online (Sandbox Code Playgroud)

做一些事情 x,并因此可以改变它(导致具有不同的值相同的底层对象).

有趣的案例有:

 x += expr
Run Code Online (Sandbox Code Playgroud)

其转化为任一 x = x + expr(再结合),或x.__iadd__(expr)(改性),有时在非常特殊的方法:

>>> x = 1
>>> x += 2
>>> x
3
Run Code Online (Sandbox Code Playgroud)

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

>>> 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)
Run Code Online (Sandbox Code Playgroud)

在这里x[0],它本身是可变的,就地突变; 但随后Python也试图改变x自己(与之一样x.__iadd__),因为元组是不可变的,所以它会出错.但到那时x[0]已经发生了变异!

  • @Arn:这只是操作顺序的问题:“+=”运算符首先调用“x[0].__iadd__”。然后,“+=”的语义表明 Python 应该重新分配左侧的对象(请参阅 https://docs.python.org/3/reference/simple_stmts.html#augmented-assignment-statements)。如果使用附加标志“意图分配”来执行“评估左侧”操作,则可以防止该问题:然后,Python 可以在调用“__iadd__”之前捕获错误。但事实并非如此,也并非如此。 (2认同)

Ign*_*ams 7

Python中的数字,字符串和元组是不可变的; 使用扩充分配将重新绑定名称.

您的其他类型只是变异,并保持相同的对象.