函数
一.引言
a)概念:函数是完成指定或者特定任务的一组代码。在面向对象编程的类中函数通常被称为方法。不同的函数在程序中会扮演不同的角色完成不同的功能。
b)作用:
i.函数的使用可以提高代码的复用性,省去重复代码的编写,提升程序代码重复利用率。
ii.函数能封装内部的实现,保护内部的数据。通常,我们可以将函数看做成一个“黑盒子”。往往函数的使用者并不是函数的编写者,函数的使用者并不需要对函数内部实现和行为进行考虑,可以将精力更多投入到自身业务逻辑的设计中。只有函数的编写者才需要考虑函数内部的实现细节,如何暴露对外的接口,返回什么样的数据值,也就是API的设计。
iii.提高程序的可读性。使得程序模块化。从一盘散散沙变成整齐队列。
iv.提高程序的可维护性。案例:打印一个不同字符组成的分隔符。
二.函数基础
a)可以将函数抽象成现实生活中的工具。工具需要先制作出来才可以使用。函数也是一样的需要先进行函数的定义,然后方可进行函数调用。
b)函数定义语法结构:
def函数名(参数):
#内部代码
return表达式
c)Return:return不是必须要写的,如果需要则写,不需要可不写。Return后面什么都不跟表示returnNone。一旦函数执行过程中遇到了return语句,那么函数中return后面的所有语句将不会执行,函数执行结束。
d)函数调用:
i.语法结构:函数名(参数值)
ii.注意事项:
1.参数之前使用逗号隔开
2.由于python动态语言的特点,函数调用时填写的参数,python不会对参数类型进行检查,如果函数调用中的参数不符合函数内部运行机制的话,会报错。
e)参数:现实函数和调用者之间的交互
i.函数-》工具:使用ATM取钱时需要传入密码数据。
ii.实参和形参的概念。
iii.函数调用时,实参会传值给形参
1.注意:
a)Python的函数参数传递实际上传递的是实参的地址
b)Python中的参数类型分为可变数据类型和不可变数据类型。
2.Test:使用可变数据类型和不可变数据类型的数据作为参数
a=1
deffunc(a):
print("在函数内部修改之前,变量a的内存地址为:%s"%id(a))
a=2
print("在函数内部修改之后,变量a的内存地址为:%s"%id(a))
print("函数内部的a为:%s"%a)
print("调用函数之前,变量a的内存地址为:%s"%id(a))
func(a)
print("函数外部的a为:%s"%a)
打印结果为:
调用函数之前,变量a的内存地址为:1401140288
在函数内部修改之前,变量a的内存地址为:1401140288
在函数内部修改之后,变量a的内存地址为:1401140320
函数内部的a为:2
函数外部的a为:1
解释:作为参数,a被传入函数时,将数字对象1的地址传递给了函数内部的a。执行第一句内部代码时,此时内部的a和外面的a其实是一个东西,因此打印出了同样的内存地址。而当a=2被执行后,由于整数是不可变的数据类型,所以创建了一个新的内部变量a,并赋值2,将数字对象2的内存地址赋给变量a。我们知道,首先,赋值语句具有创建新变量的功能。
刚才说的是不可变类型参数,如果是可变类型的,比如列表呢?
a=[1,2,3]
deffunc(b):
print("在函数内部修改之前,变量b的内存地址为:%s"%id(b))
b.append(4)
print("在函数内部修改之后,变量b的内存地址为:%s"%id(b))
print("函数内部的b为:%s"%b)
print("调用函数之前,变量a的内存地址为:%s"%id(a))
func(a)
print("函数外部的a为:%s"%a)
执行结果是:
调用函数之前,变量a的内存地址为:34875720
在函数内部修改之前,变量b的内存地址为:34875720
在函数内部修改之后,变量b的内存地址为:34875720
函数内部的b为:[1,2,3,4]
函数外部的a为:[1,2,3,4]
三.参数类型:python函数的参数定义灵活度很大,可以定义位置参数,默认参数,动态参数,关键字参数等。
a)位置参数
i.概念:也叫做必传参数,在函数调用时必须明确提供的参数,切参数顺序个数必须和形参保持一致。但是如果在函数调用的时候给实参指定参数名,那么位置参数的顺序可以不同。
b)默认参数:如果给某个参数提供一个默认值,该参数就是默认参数。在函数调用时,可以给默认参数传递一个值,也可以使用默认值。
i.Test:
defpower(x,n=2):
returnx**n
ret1=power(10)#使用默认的参数值n=2
ret2=power(10,4)#将4传给n,实际计算10**4的值
ii.使用默认参数的注意事项:
1.默认参数必须在顺序参数后面
2.默认参数尽量指向不变的对象
a)Test:
下面是国内某上市互联网公司Python面试真题:
deffunc(a=[]):
a.append("A")
returna
print(func())
print(func())
print(func())
不要上机测试,仅凭代码,你能说出打印的结果吗?
很多同学可能会说,这还不简单,肯定是下面的结果啊:
['A']
['A']
['A']
真的是这样吗?错了!真正的结果是:
['A']
['A','A']
['A','A','A']
Why?为什么会这样?
因为Python函数体在被读入内存的时候,默认参数a指向的空列表对象就会被创建,并放在内存里了。因为默认参数a本身也是一个变量,保存了指向对象[]的地址。每次调用该函数,往a指向的列表里添加一个A。a没有变,始终保存的是指向列表的地址,变的是列表内的数据!我们可以测试一下:
deffunc(a=[]):
print("函数内部a的地址为:%s"%id(a))
a.append("A")
returna
b=func()
print('此时b的值为:%s'%b)
print("函数外部b的地址为:%s"%id(b))
print("-------------")
c=func()
print('此时c的值为:%s'%c)
print("函数外部c的地址为:%s"%id(c))
print("-------------")
d=func()
print('此时d的值为:%s'%d)
print("函数外部d的地址为:%s"%id(d))
打印结果是:
函数内部a的地址为:39287880
此时b的值为:['A']
函数外部b的地址为:39287880
-------------
函数内部a的地址为:39287880
此时c的值为:['A','A']
函数外部c的地址为:39287880
-------------
函数内部a的地址为:39287880
此时d的值为:['A','A','A']
函数外部d的地址为:39287880
那么如何避免这个问题呢?
使用不可变的数据类型作为默认值!
deffunc(a=None):
#注意下面的if语句
ifaisNone:
a=[]
a.append("A")
returna
print(func())
print(func())
print(func())
将默认参数a设置为一个类似None,数字或字符串之类的不可变对象。在函数内部,将它转换为可变的类型,比如空列表。这样一来,不管调用多少次,运行结果都是['A']了。
iii.动态参数
1.概念:函数调用时传入的参数可以是动态数量的,可以为任意个,甚至是0个。
2.分类:重点是*的个数,*后的名字任意。动态参数必须放在所有位置参数和动态参数后面。
a)*args:可以接受任意个参数。调用时,会将实参打包成一个元组传给形参。如果参数是一个列表,则会将整个列表当做一个参数传入。
i.Test
deffunc(*args):
forarginargs:
print(arg)
func('a','b','c')
li=[1,2,3]
func(li)
ii.上述案例中,我们本意是将li这个列表中的元素作为参数进行传值,但是实际却是将列表本身作为一个整体进行了传递。如果想要将一个序列类型的对象(元组,列表,字符串,字典)的元素依次作为参数进行传递,则可以在序列对象前加一个*即可。如果参数是字典,则会将字典所有的key逐一传递进去。
1.Test:
deffunc(*args):
forarginargs:
print(arg)
li=[1,2,3]
func(*li)
b)**kwargs:表示接受键值对的动态参数,数量任意。调用时,会将参数打包成一个字典。
i.Test:
deffunc(**kwargs):
forkwginkwargs:
print(kwg,kwargs[kwg])
print(type(kwg))
func(k1='v1',k2=[0,1,2])
运行结果是:
k1v1
k2[0,1,2]
ii.而如果我们这样传递一个字典dic呢?我们希望字典内的键值对能够像上面一样被逐一传入。
1.Test:
deffunc(**kwargs):
forkwginkwargs:
print(kwg,kwargs[kwg])
dic={
'k1':'v1',
'k2':'v2'
}
func(**dic)
iv.关键字参数:关键字参数前面需要一个特殊分隔符*和位置参数及默认参数分隔开来。*后面的参数被视为关键字参数。在函数调用时,关键字参数必须传入参数名。
1.Test:
defstudent(name,age,*,sex):
pass
student(name="jack",age=18,sex='male')
Test2:如果函数定义中已经有了一个*args参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了。
defstudent(name,age=10,*args,sex,classroom,**kwargs):
pass
student(name="jack",age=18,sex='male',classroom="202",k1="v1")
总结:参数定义顺序:位置参数,动态参数,关键字参数,动态参数