按 "指针" 传递
python 中变量赋值, 参数传递都是通过 "指针" 拷贝的方式进行的. 除了按 "指针" 拷贝, 还有一种按值拷贝的方式, 关于按值, 按指针拷贝的细节, 参见按值传递 vs. 按指针传递.
所以在 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 也指向这个列表.
为了避免这种情况, 可以新创建一份列表的副本, 然后传递给函数参数.
- L1=[11,22,33,44]
- def f(x):
- x[0] += 1
- f(L1[:])
- print(L1) # 输出:[11, 22, 33, 44]
上面传递给函数参数 x 的是 L1[:], 它会在内存中创建一个新的列表副本, 所以 x 指向的是这个新的副本列表, 修改它不会影响原始的列表 L1.
函数参数
Python 的函数对参数和返回值方面非常宽松, 参数变量可以是任意数据类型, 返回值也一样, 只需使用变量名代替它们即可.
例如, 下面的参数 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.
按关键字 key/value 方式传值
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 参数形式
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))
函数注解(annotations)
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 已经限制了函数的定义方式.
来源: https://www.cnblogs.com/f-ck-need-u/p/9937828.html