@hpp157
2016-08-15T14:21:50.000000Z
字数 2805
阅读 2061
python
第十七章介绍了Python的作用域背后的细节——即定义和查找变量名的位置。正如我们所学到的,变量名定义时的位置决定了它大部分的含义,本章继续讲解函数,学习python中参数传递的概念——即对象作为输入发送给函数的方式。arguments或者叫parameters,被赋值给一个函数中的变量名,但是它们更多的是和对象引用有关而不是变量作用域。我们也会发现python提供额外的tools,像关键字参数,默认参数,任意参数收集器,它们为参数发送给函数的方式提供了广泛的灵活性,我们将会用实例来说明它们的用法。
本书前面介绍过,参数是通过赋值来传递的(by assignment)。对于初学者来说,这有点混乱(ramification)不清晰,所以我会在这一节详尽的阐述。下面是给函数传递参数时的一些简要重点:
函数执行时,函数头部的参数名会变成一个新的,本地变量名,这个names是在函数的local scope内的。函数的参数名(argument names)和caller作用域中的变量名)之间是没有关系的
- 在函数中改变一个可变对象参数可能会对调用着产生影响
另一方面,因为参数是简单的赋值给传入对象,函数可以当场改变传入的可变对象,结果会影响caller。可变参数(mutable arguments)是可以传入函数,或者由函数输出的.我们这里所学的也可用于function arguments,尽管它对参数名的赋值是自动且隐式的。
Python的通过赋值进行传递的机制和C++的引用参数并不完全相同,但是在实际中,和c语言的参数传递模型相当相似
- 不可变参数(inmutable arguments)通过值(by value)有效传递像整型和字符串这样的对象是通过引用而不是拷贝(reference instead of by copying),但是因为你无论怎样都不可能在原处改变它们(因为它们是不可变对象),所以实际上的效果就很象创建了一份拷贝。
- 可变参数(Mutable arguments)通过指针(by pointer)有效传递。例如,list和dict,也是靠对象引用传递(by object reference)和C 语言使用指针传递数组相似——可变对象能够在函数内部原处改变,这一点和C数组很像
当然,如果你从来没有使用过C,Python的参数传递模型看起来也会比较简单:它仅仅是将对象赋值给变量名,并且无论可变或不可变对象都是这样的
为了说明参数传递属性的工作方式,思考如下代码:a是变量名
def f(a): #传入的对象赋值给a,
a =99 # 这个语句只改变本地变量a
b=88
f(b) # 刚开始的时候a和b都引用相同的对象88 相当于a=b=88
print(b) # 88 b没有被改变
在这个例子中,在使用f(b)调用函数的时候,变量a赋值给了对象88,但是a只是生存于called function中(意思是函数调用结束后,本地变量a就消失)。在函数中改变a对于调用函数的地方(a =88)没有任何影响。它(函数中改变a的值)简单的重置本地变量local a为一个完全不同的对象
这就是没有名称冲突的含义,对函数中的一个参数名的赋值(如 a=99)不会神奇的改变调用函数里的b变量(a=b=88),参数名称可能最初共享传递的对象(它们实质上是指向这些对象的指针),但只是临时的,当函数第一次调用的时候会共享。只要对参数名进行重新赋值,这种关系就结束类。
第一次调用时图示如下:
而如果后来对参数名进行赋值:a被重置为新对象,b不会受影响
至少,这是对参数名称自身赋值的情况。当参数传递像list和dict这样的可变对象的时候,我们还需要注意,对这样的对象原处修改可能在函数执行完毕后依然有效,并由此影响到调用者。
def changer(a,b):
a=2
b[0]='hunter'
x=1
l=[1,2]
changer(x,l)
print(x) #1
print(l) #['hunter',2]
在这段代码中,changer函数给参数a赋值,并给参数引用的一个对象元素赋值(b[0]='hunter'],这两个函数内赋值从语法上仅有一点不同,但是从结构上却大相径庭。
实际上,changer中的第二条赋值语句(b[0]="hunter"
)并没有修改b,我们修改的是b当前所引用的对象的一部分。这种原处修改,只有在修改的对象比函数调用生命周期更长的时候,才会影响到调用者。名称l没有变——它仍然引用同样的,修改后的对象。但是就好象l在调用后变化了一样,因为它引用的值已经在函数中修改过了
调用前图示
调用后图示
因为参数是通过引用传递的,函数中的参数名可以在调用时通过变量实现共享对象。因此函数中对可变对象参数的原处修改能够影响调用者。这里,函数中的a和b在函数一开始调用时最初通过变量x和l进行了对象引用。通过变量b对列表的修改在函数调用结束后,l也会发生改变。
我们已经讨论了return语句并在一些例子中使用过了它,这儿有关于return语句的另一种使用方法:由于return能返回任意类型的对象,因此它能通过把值打包成一个tuple或其他集合类型的方式返回多值。实际上,尽管python不支持一些其他语言所谓的'通过引用进行调用‘的参数传递。我们常常通过返回元组并将结果赋值给最初的调用者的的argument names
def multiple(x,y):
x = 2 #只改变本地变量名
y=[3,4]
return x, y #在tuple中返回多值
a=1
b=[1,2]
a, b =multiple(a,b)#赋值给caller’s names
print(a,b) #结果为(2,[3,4])
看起来代码好像返回类两个值,但是实际上只有一个值 :一个包含2个元素的tuple,它的园括号是可选的,这里省略了。在调用返回之后我们可以使用元组赋值去解压缩这个返回的元组组成部分。如果你忘记怎么去做了,阅读第四章tuple一节,第九章以及第11章的赋值语句一节。