函数
定义格式
"""
def 函数名 ():
# some codes
...
"""
# 求一个数的绝对值
def my_abs(x):
if x > 0:
return x
else:
return -x
空函数
# 定义一个什么事都不做的函数,pass仅是一个占位符,保证编译通过
def fn():
pass
返回值
上面的示例中求绝对值的函数是返回的一个值,在Python中还可以返回多个值,但其实最终返回结果就是一个tuple。下面是一个求一元二次方程的函数
# 导入第三方模块的API,相当于Java中的导包
import math
def test(a, b, c):
# b ** 2 表示b的平方 b ** 3 表示b的立方,依此在推
delta = b ** 2 - 4 * a * c
if delta > 0:
ret1 = (-b + math.sqrt(delta)) / (2 * a)
ret2 = (-b - math.sqrt(delta)) / (2 * a)
return ret1, ret2
else:
return "无解"
print(test(2, 3, 1)) # (-0.5, -1.0)
判断参数类型
在上面的my_abs函数中,我们是对一个整数求绝对值,但若调用者传递过来的不是个整数怎么办?所以我们需要对参数进行判断。我们可以使用isinstance()函数来判断
def my_abs(x):
# isinstance译为是实例,加了一个not就表示不是实例(相当于java中的"!"符号)
if not isinstance(x, (int, float)):
# 当参数不合法时我们可以在方法抛出类型错误(相当于Java中的异常)
raise TypeError("arguments is not correct")
if x >= 0:
return x
else:
return -x
函数参数类型分类
-
位置参数
# 计算一个数的平方 def test(x): return x ** 2
x在test函数中就是一个位置参数,被调用时是必须要传的。假如要计算一个数的立方,那么这个函数就不符合需求了,不过我们可以将计算几次方让用户传进来。
def test(x, n): return x ** n
-
默认参数
不过,我们经常计算一个数的平方,所以我们可以设置一个默认参数,若调用者没有传第二个参数,那就使用默认参数,若传了,就使用调用者的传过来的参数
def test(x, n=2): return x ** n
默认参数使用注意
def add_end(list=[]): list.append("end") return list # 第一次 print(add_end()) # ['end'] # 第二次 print(add_end()) # ['end', 'end']
这里有一个问题:默认参数是
[]
,但是函数似乎每次都“记住了”上次添加了'end'
后的list?第一次调用后,list的值由
[]
变为了'end'
,由于参数list是一个变量,如果改变了list的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]
了。所以,定义默认参数时必须指向不变对象
上面的代码可以这样写
def add_end(list=None): if list is None: list = [] list.append("end") return list # 第一次 print(add_end()) # ['end'] # 第二次 print(add_end()) # ['end']
对于str、None这样的不可变对象,当它们被创建时,对象的内部数据是不能被修改的。这样做的好处就是减少了因修改数据导致的错误,并且,在多任务的环境下同时读取对象不需要加锁。所以,如果可以设计一个不变对象,那就尽量设计成不变对象。
-
可变参数
可变参数就是指参数的个数是可以变的。
# 比如给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……。 def calc(*num): ret = 0 print(num) # (1, 2, 3, 4) for n in num: ret += n * n return ret print(calc(1, 2, 3, 4)) # 30 nums = [3, 4, 5, 6] # 假若传的参数是list或tuple,我们可以这样传 print(calc(*nums))
当calc被调用时,虽然传过来的是1,2,3,4,但是函数内部会先把它转换成一个tuple,然后执行for循环
-
关键字参数
关键字参数用来对函数进行扩展
# 比如在注册时,除了名字是必填外,年龄、性别是可选项 def register(name, **kw): print("name:", name, "other:", kw) register("Blain", sex="male", age=18) # name: Blain other: {'sex': 'male', 'age': 18} info = {"city": "HuNan", "job": "Engineer"} # 假若传的参数是dict,我们可以这样传 register("Blain", **info) # name: Blain other: {'job': 'Engineer', 'city': 'HuNan'}
与可变参数类似,会先把传过来的参数转换成一个dict,然后执行下面的代码
-
命名关键字参数
命名关键字参数用来限制关键字参数的名字
def register(name, *, city_name, job_name): print(name, city_name, job_name) register("Blain", city_name="SZ", job_name="Engineer") register("Blain", city="GZ", job_name="Salesman")
打印结果如下:
Traceback (most recent call last): Blain SZ Engineer File "...", line 91, in <module> register("Blain", city="GZ", job_name="Salesman") TypeError: register() got an unexpected keyword argument 'city'
很显然,第二次调用时我们传的是city,与命名参数不符,报一个TypeError的错误
如果函数的参数有一个可变参数时,后面的命名参数就不需要
*
了def register(name, age, *args, city, job): print(name, age, args, city, job)
-
参数组合
在Python函数中,参数的定义顺序为:位置参数,默认参数,可变参数,命名关键字参数,关键字参数
def func1(a, b, c=0, *args, **kw): print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw) def func2(a, b, c=0, *, d, **kw): print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw) func1(1, 2) # a = 1 b = 2 c = 0 args = () kw = {} func1(1, 2, c=3) # a = 1 b = 2 c = 3 args = () kw = {} func1(1, 2, 3, "a", "b") # a = 1 b = 2 c = 3 args = ('a', 'b') kw = {} func1(1, 2, 3, "a", "b", x=10) # a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 10} func2(1, 2, d=15, y=15) # a = 1 b = 2 c = 0 d = 15 kw = {'y': 15}
总结
*args是可变参数,args接收的是一个tuple;
**kw是关键字参数,kw接收的是一个dict;
可变参数既可以直接传入:
func(1, 2, 3)
,又可以先组装list或tuple,再通过*args
传入:func(*(1, 2, 3))
;关键字参数既可以直接传入:
func(a=1, b=2)
,又可以先组装dict,再通过**kw
传入:func(**{'a': 1, 'b': 2})
;使用
*args
和**kw
是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符
*
,否则定义的将是位置参数。