函数的定义
1 函数strlen叫做函数名
函数名
必须由字母下划线数字组成,不能是关键字,不能是数字开头
函数名还是要有一定的意义能够简单说明函数的功能
2 def是关键字
3 ():必须写
4 函数体内尽量写注释,方便查看代码
5 函数的调用:函数名+括号
def strlen(): #函数名 ''' 计算字符串长度的函数 这里写的是注释,方便查看代码 ''' s = 'hello world' length = 0 for i in s: length += 1 return length #函数的返回值 leng = strlen() #函数的调用 print(leng)
函数调用的三种形式
register() # 语句形式 def max2(x,y): if x > y: return x else: return y # res=max2(10,20)*12 # 表达式 res=max2(max2(10,20),30) # 将函数的调用当作参数传给另外一个函数 print(res)
函数的返回值
函数返回值可以是任意的数据类型
如果有返回值:必须要用变量接收才会有效果
函数可以没有返回值,默认返回给None
函数的返回值为None有三种情况
不写返回值 只写一个return return None (几乎不用)
return除了有返回值的功能,还有结束函数执行的的功能
函数内可以有多个return,但只要执行一次,整个函数就立即结束,并且将return后的值返回
函数的返回值不为None,有返回值
返回一个值(一个变量) 返回多个值(多个变量),多个值之间用逗号区分 接收:可以用一个变量接收,以元组的形式返回 也可以用多个变量接收,返回几个就用几个变量去接收 相当于解压式接收
函数的参数
实参:函数调用的时候传入的参数
形参:函数定义的时候括号内的参数
定义阶段的形参
默认形参
在定义阶段,就已经为形参赋值,该形参称之为默认参数
特点:
1. 定义阶段就已经有值意味着调用阶段可以不用传值
2. 位置形参必须放到默认形参的前面
3. 默认形参的值在函数定义阶段就已经固定死了,定义阶段之后的改动不会影响该值
4. 默认形参的值通常应该是不可变类型
默认形参
def stu_info(name,sex = 'male'): #默认参数,有一个默认值 print('name:%s,sex:%s'%(name,sex)) stu_info('zioe') #默认参数可以不传值 stu_info('joek','male') #如果传值了,覆盖默认值
关于默认参数形成流程
s = 'male' def stu_info(name,sex=s): #默认参数,有一个默认值 print('name:%s,sex:%s'%(name,sex)) s = 'female' #不影响定义时sex的值 此时s标签被撕下来,贴在了新的值上,变的是标签不是值 print(stu_info("饭跑跑")) # 打印出来的就是male 因为开始sex=x=male,然后s是新开辟的一个空间存储female
不要设置可变数据类型为默认参数
b = [] def func(a = []): a.append(1) print(a) func().... 打印下去你会发现这个[]列表一直在追加
位置形参: 在定义阶段,按照从左到右的顺序依次定义的形参称之为位置形参
特点: 但凡时按照位置定义的形参,必须被传值,多一个不行少一个也不行
def func(x,y,z): print(x,y,z) func(1,2) func(1,2,3) func(1,2,3,4)
调用阶段的实参
位置参数:
位置实参: 在调用阶段,按照从左到右的顺序依次传入的值称之为位置实参
特点: 与形参一一对应
1 #coding:utf-8 2 3 def my_max(a,b): #定义:位置参数 4 '''返回两个值之间的最大值''' 5 print(a,b) 6 if a > b: 7 return a 8 else: 9 return b 10 print(my_max(10,20)) 11 print(my_max(30,20))
关键字参数:关键字实参: 在调用阶段,按照key=value的形式定义的实参称之为关键字实参
特点: 可以完全打乱顺序,但仍然能为指定的形参传值(总结:指名道姓地为指定的形参传值)
def func(x,y,z): print(x,y,z) func(x=1,y=2,z=3) func(1,2,3) func(z=3,y=2,x=1)
实参的形式可以是位置实参与关键字实参混合使用,但是必须遵循原则
1.位置实参必须放在关键字实参的前面
2.不能对同一个形参重复传值
可变长参数
可变长实参:指的是在调用阶段,实参值个数是不固定的,
实参无非两种形式(位置,关键字实参),对应着形参也必须有两种解决方案来分别接收溢出位置实参或者关键字实参
*--->溢出的位置实参
**--->溢出的关键字实参
def sum2(*x): #x=(1,2,3,4,5) res=0 for i in x: res+=i return res print(sum2(1,2,3,4,5))
*的用法
在形参前加*:*会将溢出的位置实参存成元组的形式,然后赋值给*后的形参名
def func(x,y,*z): #z=(3,4,5) print(x,y,z) func(1,2,3,4,5)
在实参前加*:但凡碰到实参中带*的,先将实参打散成位置实参再与形参做对应
def func(x,y,z): print(x,y,z) func(1,2,[3,4,5]) func(*[1,2,3,4,5]) #func(1,2,3,4,5) func(*[1,2,3]) #func(1,2,3) def func(x,y,*z): print(x,y,z) func(1111,2222,*[1,2,3,4,5]) #func(1111,2222,1,2,3,4,5)
**的用法
在形参前加**:**会将溢出的关键字实参存成字典的形式,然后赋值给**后的形参名
def func(x,y,**z): #z={'c':3,'b':2,'a':1} print(x,y,z) func(1,y=2,a=1,b=2,c=3)
在实参前加**:但凡碰到实参中带**的,先将实参打散成关键字实参再与形参做对应
def func(x,y,z): print(x,y,z) func(1,**{'y':2,'z':3}) #func(1,z=3,y=2) func(1,**{'a':2,'y':333,'z':3}) #func(1,a=2,y=333,z=3) # 错误 def func(x,y,**z): print(x,y,z) func(**{'y':1,'x':2,'a':1111,'b':2222}) #func(y=1,x=2,a=1111,b=2222)
形参中:*args,**kwargs
def func(x,*args): print(x) print(args) def func(x,**kwargs): print(x) print(kwargs)
*与**的应用:
def index(name,age,sex): print('index=====>',name,age,sex) 会将wrapper函数接收的参数格式原封不动地转嫁给其内部的index函数,必须要遵循的是index的参数规则 def wrapper(*args,**kwargs): #args=('egon',) kwargs={'sex':'male','age':18} # print(args) # print(kwargs) index(*args,**kwargs) #index(*('egon',),**{'sex':'male','age':18}) #index('egon',sex='male',age=18) wrapper(1,2,3,4,5,a=1,b=2,c=3) wrapper('egon',sex='male',age=18)
传参时候传递的是引用:
# 我们之前学过字符串的format方法 msg = "{0},{1}" print(msg.format("joker","11")) print(msg.format(*["joker","11"])) msg = "{name},{age}" print(msg.format(name="joker",age="11")) print(msg.format(**{"name":"joker","age":"11"})) # 传递数据类型时候,其实传递的是引用 l = [1,2,3,4] def f1(a1): a1.append(999) f1(l) print(l) # [1, 2, 3, 4, 999] # 全局变量,通常全部大写 name = [1,2,3,4] def f1(): global name # 直接修改 name = [111] return f1() print(name) # [111] name = [1,2,3,4] def f1(): name.append(5) # 局部不能给全局重新赋值 return f1() print(name) # [1, 2, 3, 4, 5]
函数默认参数的陷阱
函数参数默认值陷阱的根本原因
一句话来解释:Python函数的参数默认值,是在编译阶段就绑定的。
可见如果参数默认值是在函数编译compile阶段就已经被确定。之后所有的函数调用时,如果参数不显示的给予赋值,那么所谓的参数默认值不过是一个指向那个在compile阶段就已经存在的对象的指针。如果调用函数时,没有显示指定传入参数值得话。那么所有这种情况下的该参数都会作为编译时创建的那个对象的一种别名存在。如果参数的默认值是一个不可变(Imuttable)数值,那么在函数体内如果修改了该参数,那么参数就会重新指向另一个新的不可变值。而如果参数默认值是和本文最开始的举例一样,是一个可变对象(Muttable),那么情况就比较糟糕了。所有函数体内对于该参数的修改,实际上都是对compile阶段就已经确定的那个对象的修改。
对于这么一个陷阱在 Python官方文档中也有特别提示
Important warning: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. For example, the following function accumulates the arguments passed to it on subsequent calls:
如何避免这个陷阱带来不必要麻烦
当然最好的方式是不要使用可变对象作为函数默认值。如果非要这么用的话,下面是一种解决方案
{% highlight python %} {% raw %} def generate_new_list_with(my_list=None, element=None): if my_list is None: my_list = [] my_list.append(element) return my_list {% endraw %} {% endhighlight %}
为什么Python要这么设计这个问题的答案在 StackOverflow 上可以找到答案。这里将得票数最多的答案最重要的部分摘录如下
Actually, this is not a design flaw, and it is not because of internals, or performance.
It comes simply from the fact that functions in Python are first-class objects, and not only a piece of code.
As soon as you get to think into this way, then it completely makes sense: a function is an object being evaluated on its definition; default parameters are kind of "member data" and therefore their state may change from one call to the other - exactly as in any other object.
In any case, Effbot has a very nice explanation of the reasons for this behavior in Default Parameter Values in Python.
I found it very clear, and I really suggest reading it for a better knowledge of how function objects work.
在这个回答中,答题者认为出于Python编译器的实现方式考虑,函数是一个内部一级对象。而参数默认值是这个对象的属性。在其他任何语言中,对象属性都是在对象创建时做绑定的。因此,函数参数默认值在编译时绑定也就不足为奇了。
然而,也有其他很多一些回答者不买账,认为即使是first-class object也可以使用closure的方式在执行时绑定。
This is not a design flaw. It is a design decision; perhaps a bad one, but not an accident. The state thing is just like any other closure: a closure is not a function, and a function with mutable default argument is not a function.
好吧,这么看来,如果没有来自于Python作者的亲自陈清,这个问题的答案就一直会是一个谜了
默认值的作用域
def foo(xyz=[]): xyz.append(10) print(xyz) foo() # result: [10] foo() # result: [10, 10]
为什么第二次打印[10, 10]?
因为函数也是对象,python把函数的默认值放在了属性中
这个属性就伴随着这个函数对象的整个生命周期。即foo.__defaults__属性
def foo(xyz=[], u='abc', z=123): xyz.append(1) return xyz print(foo(), id(foo)) # [1] 44516624。id(foo)表示去foo函数对象的地址 print(foo.__defaults__) # ([1], 'abc', 123) print(foo(), id(foo)) # [1, 1] 44516624 print(foo.__defaults__) # ([1, 1], 'abc', 123) 元组中的列表是可变类型,可以修改,但不代编元组可变*
foo.__defaults__属性会将形参的缺省值放入到一个元组中
def foo(w, u='abc', z=123): u = 'ccc' z = 789 print(w, u, z) print(foo.__defaults__) # ('abc', 123) foo('test') # test ccc 789 print(foo.__defaults__) # ('abc', 123)
形参默认值处理:影子拷贝,默认值安全
def foo(lst=[], u='abc', z=123): lst = lst[:] # 影子拷贝(浅拷贝),没有改变形参默认值 lst.append(1) print(lst) print('------------------') foo() print(foo.__defaults__) foo() print(foo.__defaults__) foo([10]) print(foo.__defaults__) foo([10, 5]) print(foo.__defaults__)
形参默认值处理:默认值为None,对None进行判断
def foo(lst=None, u='abc', z=123): if lst is None: lst = [] lst.append(1) print(lst) print('------------------') foo() print(foo.__defaults__) foo() print(foo.__defaults__) foo([10]) print(foo.__defaults__) foo([10, 5]) print(foo.__defaults__) print('------------------')