按"指针"传递

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已经限制了函数的定义方式。

python函数的参数细节的更多相关文章

  1. Python函数可变参数*args及**kwargs详解

    初学Python的同学们看到代码中类似func(*args, **kwargs)这样的函数参数定义时,经常感到一头雾水. 下面通过一个简单的例子来详细解释下Python函数可变参数*args及**kw ...

  2. 【转】Python函数默认参数陷阱

    [转]Python函数默认参数陷阱 阅读目录 可变对象与不可变对象 函数默认参数陷阱 默认参数原理 避免 修饰器方法 扩展 参考 请看如下一段程序: def extend_list(v, li=[]) ...

  3. 详解python函数的参数

    详解python函数的参数 一.参数的定义 1.函数的参数在哪里定义 在python中定义函数的时候,函数名后面的括号里就是用来定义参数的,如果有多个参数的话,那么参数之间直接用逗号, 隔开 案列: ...

  4. 可变数据类型不能作为python函数的参数

    可变数据类型:列表.字典 不可变数据类型:整型.浮点型.字符串.元组 为什么可变数据类型不能作为python函数的参数?请看以下例子: def foo(a=[]): a.append(1) retur ...

  5. python 函数传递参数的多种方法

    python中函数根据是否有返回值可以分为四种:无参数无返回值,无参数有返回值,有参数无返回值,有参数有返回值. Python中函数传递参数的形式主要有以下五种,分别为位置传递,关键字传递,默认值传递 ...

  6. python函数动态参数详解

    Python的动态参数: 1,参数前一个"*":在函数中会把传的参数转成一个元组. def func (*args): print(args) func(123,1,2,'a') ...

  7. Python 函数的参数知识汇总

    函数搞不熟,后边就晕头转向了,在此重新归纳一下廖大神的函数教程,加深记忆 一.函数的参数分为: 位置参数 def power(x): # x 就是power(x)的一个位置参数,我们调用power(x ...

  8. Python学习笔记(四)Python函数的参数

    Python的函数除了正常使用的必选参数外,还可以使用默认参数.可变参数和关键字参数. 默认参数 基本使用 默认参数就是可以给特定的参数设置一个默认值,调用函数时,有默认值得参数可以不进行赋值,如: ...

  9. 使用可变对象作为python函数默认参数引发的问题

    写python的都知道,python函数或者方法可以使用默认参数,比如 1 def foo(arg=None): 2 print(arg) 3 4 foo() 5 6 foo("hello ...

随机推荐

  1. 通过shell脚本进行数据库操作

    #!/bin/bash HOSTNAME="192.168.111.84" #数据库信息 PORT=" USERNAME="root" PASSWOR ...

  2. centos7安装mariadb

    ~]# cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core) 1.官方um安装mariadb 1).准备官方yum [mariadb ...

  3. noip第27课资料

  4. IDEA安装插件提示was not installed: Cannot download解决办法

    打开settings->system settings->updata,把下面的Use secure Connetion去掉

  5. Django 信号、中间件、i18n 专题

    信号 Django中提供了“信号调度”,用于在框架执行操作时解耦.通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者. 1. Django内置信号 Model signals pr ...

  6. 28.实现 strStr() 函数

    28.实现 strStr() 函数 给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始).如果不存在, ...

  7. Javascript高级编程学习笔记(23)—— 函数表达式(1)递归

    前面的文章中,我在介绍JS中引用类型的时候提过,JS中函数有两种定义方式 第一种是声明函数,即使用function关键字来声明 第二种就是使用函数表达式,将函数以表达式的形式赋值给一个变量,这个变量就 ...

  8. Python面向对象4:类的相关函数与属性

    1 类相关函数- issubclass:检测一个类是否是另一个类的子类- isinstance:检测一个对象是否是一个类的实例- hasattr:检测一个对象是否由成员xxx- getattr: ge ...

  9. Web前端-Vue.js必备框架(二)

    Web前端-Vue.js必备框架(二) vue调式工具vue-devtools 过滤器:vue.js允许你自定义过滤器,可被用作一些常见的文本格式化. mustache插值和v-bind表达式. vu ...

  10. 应用监控CAT之cat-home源码阅读(三)

    上两章从点到点讲了,cat-client  到  cat-consumer 的请求处理过程,但是怎么样让我们监控给人看到呢?那么就需要一个展示的后台了,也就是本章要讲的 cat-home 模块 ! 带 ...