python中变量赋值、参数传递都是经过"指针"拷贝的方式进行的。除了按"指针"拷贝,还有一种按值拷贝的方式,关于按值、按指针拷贝的细节,参见按值传递 vs. 按指针传递。html
因此在python中,变量赋值、参数传递,都只是拷贝了源数据的一个地址,而不会拷贝内存中完整的数据对象副本。因此,若是在函数内部修改变量指向的数据对象,会影响函数外部的数据。python
例如:函数
def f(x): print(x+3) a=4 f(a)
在将a
赋值给本地变量x的时候,只是拷贝了a目前保存的地址给x,使得x也保存了内存中数据对象4的地址。工具
若是传递的数据对象是可变数据对象(例如列表),那么在函数内部修改它,会影响函数外部的原始数据对象:指针
L1=[11,22,33,44] def f(x): x[0] += 1 f(L1) print(L1) # 输出:[12, 22, 33, 44]
显然,在函数内部修改x[0]
的值,函数外部原始的L1也发生了改变。由于L1赋值给x的时候,只是拷贝了一份L1所指向列表的地址给x,使得x也指向这个列表。code
为了不这种状况,能够新建立一份列表的副本,而后传递给函数参数。htm
L1=[11,22,33,44] def f(x): x[0] += 1 f(L1[:]) print(L1) # 输出:[11, 22, 33, 44]
上面传递给函数参数x的是L1[:]
,它会在内存中建立一个新的列表副本,因此x指向的是这个新的副本列表,修改它不会影响原始的列表L1。对象
Python的函数对参数和返回值方面很是宽松,参数变量能够是任意数据类型,返回值也同样,只需使用变量名代替它们便可。blog
例如,下面的参数x能够是任意类型的结构,能够是数值、字符串、列表、字典等等类型。返回值语句return同理。内存
def f(x): print(x) return x f(2) f("haha")
实际上,上面调用函数时是按照参数位置进行传参对本地变量x进行赋值的。除此以外,还能够指定为key=value
的方式进行传参。例如:
f(x=2) f(x="haha")
若是是多个参数,则按从左到右的顺序进行参数变量的赋值:
def f(x,y,z): print(x) print(y) print(z) f(2,3,4)
调用f(2,3,4)
的时候,会按照从左向右的位置方式对本地变量x、y、z赋值:x=2,y=3,z=4
。
python还支持key=value
的方式设置函数调用时的参数,使用key=value
的方式赋值时,顺序不重要。这种函数调用时的传值方式称为"关键字传值"。
例如:
def f(x,y,z): print(x) print(y) print(z) f(x=3,y="haha",z=4)
也能够打乱顺序:
f(x=3,z=4,y="haha")
还能够将key=value
和位置传参的方式进行混合:
f(3,"haha",z=4)
但混合按位置传参方式的时候,位置参数必须在其它传参方式的前面,不只此处结合key=value
时如此,后文中位置参数结合其它方式传参也都如此:位置参数必须在最前面。
例如,下面的传参方式是错的:
f(z=4,3,"haha")
在def或lambda声明函数的时候,能够经过var=default
的方式指定参数的默认值。
例如:
def f(x=3): print(x) f(4) f("haha") f()
上面的f(4)
和f("haha")
都对函数f()的本地变量x进行了赋值。可是最后一个调用语句f()
未赋值,而是使用参数的默认值3。
设置参数默认值时,若是函数有多个参数,则带默认值参数后面必须放在最后面。例如:
# 正确 def f(x,y,z=4) def f(x,y=1,z=4) # 错误 def f(x,y=4,z)
只要为参数设置了默认值,那么调用函数的时候,这个参数就是可选的,无关紧要的,若是没有,则采用默认值。
def f(x,y=2,z=4): print(x) print(y) print(z) # 不采用任何默认值 f(2,3,4) # 采用z的默认值 f(2,3) # 采用y的默认值 # 此时z必须按key=value的方式传值 f(2,z=5) # y、z都采用默认值 f(2)
对于任意长度的参数,能够在def
声明的函数中使用*
将各位置参数收集到一个元组中。例如:
def f(*args): print(args) f(1,2,3,4)
上面调用f(1,2,3,4)
的时候,将全部参数都收集到了一个名为args的元组中。因此上面的函数将输出:
(1, 2, 3, 4)
既然是元组,就能够对参数进行迭代遍历:
def f(*args): for arg in args: print(arg) f(1,2,3,4)
必须注意,*
是按位置收集参数的。
def f(x,y,*args): print(x) print(y) for arg in args: print(arg) f(1,2,3,4)
按照从左向右的传参规则,首先将1赋值给x,将2赋值给y,而后将剩余全部的位置参数收集到args元组中,因此args=(3,4)
。
若是*
后面还有参数,则调用函数的时候,后面的参数必须使用key=value
的方式传递,不然会收集到元组中,从而致使参数缺乏的问题:
def f(x,*args,y): print(x) print(y) for arg in args: print(arg) # 正确 f(1,3,4,y=2) # 错误 f(1,2,3,4)
上面调用f(1,3,4,y=2)
的时候,会按照位置参数对x赋值为1,而后将全部位置参数收集到元组args中,由于y=2
是非位置参数传值方式,因此args=(3,4)
。
若是为上面的y设置默认值:
def f(x,*args,y=2)
那么f(1,2,3,4)
会将(2,3,4)
都收集到元组args中,而后y采用默认值2。
除了能够使用*
将位置参数收集到元组中,还能够使用**
将key=value
格式的参数收集到字典中。
例如:
def f(x,**args): print(x) print(args) f(1,a=11,b=22,c=33,d=44)
上面首先按位置传参的方式赋值x=1
,而后将剩余的全部key=value
参数收集到名为args的字典中。因此,args字典的内容为:
{'a': 11, 'b': 22, 'c': 33, 'd': 44}
既然是将参数收集到字典中,就能够使用字典类的工具操做这个字典。例如,遍历字典。
在**
的后面不能出现任何其它类型的参数。例如,下面的都是错误的def定义方式:
def f(x,**args,y) def f(x,**args,y=3) def f(x,**args,*t)
只能将位置参数或者*
的收集放在**
的前面。
def f(x,y,**args) def f(x,*args1,**args2)
除了在def定义函数时,参数中能够使用*
或**
收集参数,在函数调用的时候也能够使用*
或**
分别解包元组(列表或其它对象)、字典。必定要注意区分函数定义和函数调用时的*
、**
,它们的用法是不通用的。
例如,解包元组:
def f(a,b,c,d): print(a) print(b) print(c) print(d) T=(1,2,3,4) f(*T)
*
除了能够解包元组,还能够解包其它可迭代对象,例如列表。甚至是字典也能解包,只不过*
解包的字典获得的是key组成的参数列表,和value无关:
D=dict(a=11,b=22,c=33,d=44) f(*D) # 输出: a b c d
而**
解包的字典则是key=value
组成的参数列表。如下是函数调用时使用**
进行解包,字典D中的key名称必须和def中定义的参数名称相同:
def f(a,b,c,d): print(a) print(b) print(c) print(d) D=dict(a=11,b=22,c=33,d=44) f(**D) # 输出: 11 22 33 44
在函数调用时,能够混合位置参数、关键字参数、*
解包参数、**
解包参数。用法很是的灵活:
def f(a,b,c,d): print(a) print(b) print(c) print(d) f(*(1,2),**{'d':4,'c':3}) f(1,*(2,3),**{'d':4}) f(1,c=3,*(2,),**{'d':4}) f(1,*(2,3),d=4) f(1,*(2,),c=3,**{'d':4})
上面调用函数时的效果都等同于f(1,2,3,4)
。
keyword-only的参数传值方式表示def中若是使用了*
,那么在调用函数时,它后面的参数必须只能使用关键字传值。其实在前面的内容中已经出现过几回与之相关的说明。
另外注意,*
才是keyword-only开关,**
不是,虽然**
也有本身的一些语法限制:任意类型的参数定义都必须在**
以前,包括keyword-only类型的参数。这个前面已经解释过了。
例如:
def f(a,*b,c): print(a,b,c)
按照keyword-only的规则,被*b
收集的位置参数不包括c,这个c必须只能使用关键字的方式传值,不然就被看成位置参数被收集到元组b中。
# 正确 f(1,2,3,c=4) # 错误 f(1,2,3,4) # 错误 f(1,c=4,2,3)
其中最后一个错误和如何def的定义无关,而是函数调用时的语法错误,前面已经解释过:位置参数必须放在最前面。
还能够直接使用*
而非*args
的方式,这表示不收集任何参数,但却要求它后面的参数必须按照关键字传值的方式。
def f(a,*,b,c): print(a,b,c)
如下是正确和错误的调用方式示例:
# 正确 f(1,b=2,c=3) f(1,c=3,b=2) f(b=2,c=3,a=1) # 错误 f(1,2,3) f(1,2,c=3) f(1,b=2,3)
不过,keyword-only后面的参数能够使用参数默认值。
def f(a,*,b,c=3)
那么c是可选的,但若是给定,则必须按关键字方式传值。
对于函数定义中的参数,有3种方式:普通位置参数、*
开启的keyword-only参数、**args
收集参数。它们之间的规则是:
**args
必须在最后面*
或*args
后面能够是普通参数,可是函数调用传值时,它后面的参数必须按照关键字的方式指定因此,函数定义时参数的通用形式为:其中c和d必须使用关键字传值方式
def f(a,b, *,c,d, **dicts) def f(a,b, *args,c,d, **dicts)
对于函数调用中的参数,有:普通位置参数、关键字参数、*
解包参数、**
解包参数。它们之间的规则时:
**
解包必须在最后面*
解包参数只要求在上述两种参数形式中间,顺序能够随意因此,函数调用时的传参形式为:
f(a,b,c, *(d,e,f),g=1,h=2, **dict(j=3,k=4)) f(a,b,c, d=1,e=2,*(f,g,h), **dict(j=3,k=4))
例如:
def f(a,*b,c,**d): print(a,b,c,d) f(1, 2,3, c=4, x=5,y=6) f(1, c=4,*(2,3), **dict(x=5,y=6)) f(1, *(2,3),c=4, **dict(x=5,y=6)) f(1, *(2,3), **dict(c=4,x=5,y=6)) f(1, 2,3, **dict(c=4,x=5,y=6))
python函数有一个名为__annotations__
的属性(能够使用dir(Func_Name)来查看)。它表示函数的注解。
函数的注解使得参数变得更规范、更通用,它有点相似于强调数据类型。但它们仅仅只是注解,只是给人看,用来起提示做用的,不会对实际的调用有任何影响。
例如,下面是没有给注解的函数参数,也就是平时见到的参数方式:
def myfunc(a,b,c): return a+b+c myfunc(1,2,3)
函数的注解分两种:参数注解和返回值注解。
->
分隔例如:
def myfunc(a:'string',b:[1,5],c:int)->int: return a+b+c print( myfunc(1,2,3) ) print( myfunc("a","b","c") )
虽然上面的函数注解提示了参数a是一个字符串,b是一个列表,c是一个int类型的数据,以及返回值是一个int类型的值,但在函数调用的时候,这些"强调"并无发生影响,只不过在使用该函数的时候,若是使用IDE编写代码,会有代码提示。
能够经过函数的__annotations__
属性查看函数的注解:
print(myfunc.__annotations__)
输出:
{'a': 'string', 'b': [1, 5], 'c': <class 'int'>, 'return': <class 'int'>}
能够只对其中一个或多个参数进行注解。
若是使用了注解,还要设置参数的默认值,则默认值须要在注解的后面。例如:
def f(a:'string'=4):
函数注解只对def语句有效,对lambda无效,由于lambda已经限制了函数的定义方式。