python-自定义函数
函数定义
def关键字定义一个函数,def后必须后跟函数名称和带括号的形式参数列表。构成函数体的语句从下一行开始,并且必须缩进。
'''定义一个函数:1+2+3...+num'''
def cumsum(num):
i = 0
for each in range(1,num + 1):
i += each
return i
'''或者也可以用递归写'''
def cumsum(num):
if num > 1:
return num + cumsum(num - 1)
else:
return num
cumsum(10) #调用函数,传入参数10
#out: 55
设置默认参数值
def cumsum(end=10, start=1):
i = 0
for each in range(start,end + 1):
i += each
return i
cumsum()
#out: 55 函数参数调用默认值
'''我们也可以参数传入其他值,如果传入的参数没有指定参数名称,需要按照函数定义参数的顺序传入,这里end=5,start=2'''
cumsum(5,2)
#out: 14
'''如果指定参数名称赋值,可以不按照函数定义参数顺序传入,这里使用参数名称赋值,把start参数放在了开头。'''
cumsum(start=2, end=5)
#out: 14
可变参数
*args,加“*”号为若干个参数,返回元组,*args放在普通参数后。
def cumsum(num,*args):
for each in args: #args是一个元组
num += each
return num
cumsum(1,5,6,7)
#out:19 num后面参数5,6,7以元组args传入函数内。
关键字参数
虽然一个参数可以接受多个实参,但是这些实参都被捆绑为元组了,而且无法将具体的实参指定给具体的形参。关键字参数,可以把带参数名的参数值组装到一个字典中,键就是具体的实参名,值就是传入的参数值。**kwargs:注意关键字参数定义时,放在最后面,如果存在可变参数,就放在可变参数定义后面。
def update_dic(name,age,**kwargs):
person_info = {}
person_info['name'] = name
person_info['age'] = age
person_info.update(kwargs)
return person_info
update_dic('张三',18,weight='70kg', height='178cm')
#out: {'name': '张三', 'age': 18, 'weight': '70kg', 'height': '178cm'} weight='70kg', height='178cm'以字典kwargs传入函数内
解包参数列表
函数定义时,*args和**kwargs会将参数以元组和字段返回到函数内调用,相反地,在函数调用时,传参前加上*和**表示解包,将元组列表拆解为元素,下面来做个示例:
def cumsum(num,*args):
for each in args: #args是一个元组
num += each
return num
cumsum(1,2,3,4)
#out: 10 正常情况下,我们在num参数后,逐个输出arg参数
'''我们也传入*列表代表args'''
ls = [2,3,4]
cumsum(1,*ls)
#out: 10 传入*列表表示解包列表为单个元素,*ls就相当于2,3,4。
'''关键字参数也是一样的,我们暂且还是借用上面的函数'''
def update_dic(name,age,**kwargs):
person_info = {}
person_info['name'] = name
person_info['age'] = age
person_info.update(kwargs)
return person_info
dic = {'weight': '70kg', 'height': '178cm'}
'''我们直接传入字典,这里**dic跟直接传入weight='70kg', height='178cm'是一样的。'''
update_dic('张三',18,**dic)
#out: out: {'name': '张三', 'age': 18, 'weight': '70kg', 'height': '178cm'}
我们或可以这么理解,对于函数定义时使用了可变参数:*args和关键字参数:**kwargs,函数调用时,对应的参数是元素,以元组,字典返回到函数内部调用。如果调用函数参数使用 *列表或元组 | **字典会将列表或元组|字典解包成元素传参,到函数内部时,依旧是元组|字典,只是传参的形式不一样罢了。
匿名函数:lambda
使用lambda关键字定义函数,lambda var :exp(var)
fun1 = lambda x : x + 1
def fun2(x):
return x + 1
fun1(1)
#out: 2
fun2(1)
#out: 2
'''这里的fun1函数和fun2函数是一样的,如果函数设计比较简单,我们完全可以使用lambda方式定义函数,书写简洁。'''
关于局部变量与全局变量**
函数内部的变量如果没有声明,只在函数内部生效,全局变量需要使用global关键字声明,来个简单的栗子:
a = 1
def about_global()
a = 10
return a
'''a=10,在函数内部有效,正常返回'''
about_global()
#out: 10
'''函数内部的赋值,a=10,函数外无效,仍然返回1'''
print(a)
#out: 1
def about_global()
'''声明a为全局变量'''
global a
a = 10
return a
'''a=10,函数内部始终有效'''
about_global()
#out: 10
'''函数内部定义a为全局变量,函数外,a变量也随着函数内修改而修改,返回10'''
print(a)
#out: 10
偏函数的使用
functools.partial可以帮助我们创建一个偏函数,它把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
import functools
def cumsum(end=10, start=1):
i = 0
for each in range(start,end + 1):
i += each
return i
cumsum()
#out: 55
'''创建一个cumsum偏函数'''
cumsum_patrial = functools.partial(cumsum,end=5,start=1)
cumsum_partial()
#out: 15 函数的默认参数修改了。
其他
函数参数里可以传入类,也可以传入函数方法名称
class TT:
@classmethod # 类方法,无需实例,类可以直接调用
def pt(cls):
print("执行了类TT的方法:pt")
def test(cls):
cls.pt()
test(TT)
#out: 执行了类TT的方法:pt
装饰器
在不改变原函数代码调用方式的前提下,为原函数添加一些扩展功能,比如下面的计算函数耗时(通过outer传参控制装饰函数是否生效)。
这里我们定义了一个outer装饰器,函数定义上方带有“@装饰器名”,在实际调用该函数时,实际调用的是装饰器函数内最终的return函数:
import time
def outer(flag):
def timer(fun):
def inner(*args,**kwargs): # 不定长参数
if flag:
start = time.time()
fun(*args,**kwargs) # 这里的args是元组,kwargs是字段,前面加‘*’号解包
if flag:
print('函数名:"{}";运行耗时:{:.2f}'.format(fun.__name__,time.time()-start),fun.__doc__)
return inner
return timer
'''装饰函数传入‘False’参数,装饰效果不生效'''
@outer(False)
def fun1(num):
'''
这里是函数说明文档:
param num:整数
return:返回0+1+2+3+...+(num-1)的累加和
'''
i = 0
for each in range(num):
i += each
print(i)
fun1(10000000)
'''这里fun1函数我们去掉@outer(False)装饰,下面的方法是等价于函数fun1上添加@outer(True)装饰'''
outer(True)(fun1)(10000000)
# outer(True)返回timer函数对象引用;
# outer(True)(fun1)返回inner函数对象引用;
# outer(True)(fun1)(10000000):inner函数传参:10000000,执行inner函数
'''
使用装饰器@outer(False)调用fun1(10000000)
out:49999995000000
不使用装饰器执行outer(True)(fun1)(10000000)
out:49999995000000
out:函数:"fun1";运行耗时:0.54
out:这里是函数说明文档:
out:param num:整数
out:return:返回0+1+2+3+...+(num-1)的累加和
'''
'''不带参数的装饰器'''
def timer(func):
def inner(*args,**kwargs):
start_time = time.time()
func(*args,**kwargs)
end_time = time.time()
print('{}函数已执行完毕,耗时{:.2f}s'.format(func.__name__,end_time-start_time)
@timer
def func1():
pass
函数传值还是传引用的问题
传值:函数内参数变量修改,被传外部变量不会修改;
传引用:通常是可变对象,函数内参数变量修改,被传外部变量会修改;函数内变量重新赋值指向另一块内存,不再影响外部变量;;
函数参数在传递的过程中将整个对象传入,对可变对象的修改在函数外部以及内部都可见,调用者和被调用者之间共享这个对象,而对于不可变对象,对象并不能真正被修改,修改往往是通过生成一个新对象然后赋值来实现的。
ls = [1]
def ls_append(var_ls):
var_ls.append(2)
print('var_ls','id_var_ls',id(var_ls))
var_ls = [2,3,4] # val_ls指向了另一个内存地址,id发生变化
print(var_ls,'id_new_var_ls',id(var_ls))
ls_append(ls)
print('ls',ls,'id_ls',id(ls))
'''out
我们发现,函数的引用valA_ls和外部引用ls指向同一个对象:id是一样的,列表是可变对象
var_ls id_var_ls 1868243761736
[2, 3, 4] id_new_var_ls 1868243965192
ls [1, 2] id_ls 1868243761736
'''
s = 'a'
def str_add(val_s):
print(val_s,'id_val_s:',id(val_s))
val_s = 'b'
print(val_s,'id_val_s_b:',id(val_s))
str_add(s)
print(s,'id_s:',id(s))
'''out
val_s未被赋值前,id和局外变量s是一样的,被赋值,id不一样
a id_val_s: 1867309509808
b id_val_s_b: 1867309494128
a id_s: 1867309509808
'''
默认参数的问题
def在Python中是一个可执行的语句,解释器执行def的时候,默认参数会被计算,存在函数的func._defaults__
属性中。由于Python中函数参数传递的是对象,可变对象在调用者和被调用者之间共享;
def add_one(num=[]):
num.append(1)
print(num)
add_one()
add_one()
'''out
[1]
[1, 1]
'''
print(add_one.__defaults__)
如果不想让默认参数所指向的对象在所有的函数调用中被共享,而是在函数调用的过程中动态生成,可以在定义的时候使用None对象作为占位符
变长参数使用问题
如果一个函数定义了普通变数、默认参数、可变长参数,传参会变得很灵活,这种灵活也往往使得可读性比价差,能使用可迭代对象参数,尽量不要使用args;
def func(a,b,va=1,vb=2,*args,**kwargs):
print(a,b,va,vb,args,kwargs)
'''以下几种传参都是可以被允许的'''
func(1,2,3)
func(1,2,3,4)
func(1,2,kw1=5)
func(1,2,23,4,5,6,kw2=100)
'''out
1 2 3 2 () {}
1 2 3 4 () {}
1 2 1 2 () {'kw1': 5}
1 2 23 4 (5, 6) {'kw2': 100}
'''
函数的参数传递:除关键字参数kwargs(要定义写在最后面),其他从左往右,先传给普通参数,再传给默认参数,其他再如果还有传参传给args参数,最后再是关键字参数。