函数是人为组织好的,可重复使用的,用来实现单一,或相关联功能的代码。函数能提高应用的模块性,和代码的重复利用率。Python中有很多内建函数如print()
。但你也可以自己创建函数,这些由你自己创建的函数被叫做自定义函数。
定义函数
定义一个函数一般有下面的规则:
- 函数代码以
def
关键词开头,后接函数标识符名称和圆括号()
- 任何传入参数和自变量必须放在圆括号中,圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串——用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
return [表达式]
结束函数,选择性地返回一个值给调用方。不带表达式的return
相当于返回None
语法
Python定义函数使用def
关键,一般格式如下:
def 函数名(参数列表):
函数体
默认情况下,参数值和参数名称是按函数声明中定义的顺序匹配起来的。
实例
用一个函数来输出Hi,Python!
def sayHi():
print("Hi,Python!")
更复杂点的,函数中带伤参数变量:
# 计算圆的面积
def calculate(r):
pi = 3.14
return pi * r * r
# 计算长方形面积
def calcusquar(width,height):
return width * height
空函数
如果想定义一个什么事也不做的空函数,可以用pass
语句:
def nothing():
pass
实际上pass
可以用来作为占位符,比如现在还没想好怎么写这段代码,就可以先放一个pass
,让代码运行起来。pass
还可以用在其他语句里,比如:
if age >= 20:
pass
如果此处没有pass
且什么也不写,代码运行就会有语法错误。
函数调用
不管是内建函数还是自定义的函数,我们都可以通过调用该函数来达到我们想要的结果。
def sayHi():
print("Hi,Python!")
# 调用函数
sayHi()
# 输出结果
# Hi,Python!
def calcusquar(width,height):
return width * height
# 调用函数
calcusquar(2,4)
# 输出结果
# 8
# 调用函数(返回一个数的绝对值)
abs(-2)
# 输出结果
# 2
调用函数的时候,如果传入的参数数量不对,会报TypeError
的错误,并且Python会明确地告诉你:abs()
有且仅有1个参数,但给出了两个:
>>> abs(-2,1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: abs() takes exactly one argument (2 given)
>>>
如果传入的参数数量是对的,但参数类型不能被函数所接受,也会报TypeError
的错误,并且给出错误信息:str
是错误的参数类型。
>>> abs('1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'
函数中的文档
“程序在大多数情况下是给人看的,只是偶尔被机器执行”。所以,在编写函数时除了必要的注释外还需写些文档来进行说明函数的作用。所谓函数的文档,一般写在每个函数名字的下面,主要是说明这个函数的用途。
def calcusquar(width,height):
"""
This is a function about area of rectangle
"""
return width * height
如上函数,用三对引号方式包裹着这个函数的说明,即函数文档。想要查看函数的文档,可以通过调用__doc__
来进行查看:
def calcusquar(width,height):
"""
This is a function about area of rectangle
"""
return width * height
print(calcusquar.__doc__)
# 输出
# This is a function about area of rectangle
函数的属性
任何对象都具有属性,函数也是对象,那么它自然而然也有属性。
def country():
"""
This is a function about country
"""
pass
实际上,前面提到的__doc__
函数文档,就是函数的一个属性,只不过这个属性是每个函数都有的。我们还可以为函数对象增加属性。
country.name = 'China'
用以上的方式为函数对象添加属性后,可以用dir()
查看。
print(dir(country))
# 输出
'''
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'name']
'''
上面列出了所有country
这个函数对象的属性和方法,我们新增的name
属性也有在里面。至于一些用双下划线开始和结束的,称为特殊属性,所有的这些属性都可以用句点的方式调用。
函数的参数
在定义函数的时候,函数名后面的括号内如果有变量,则它们通常被称为"形参"。调用函数的时候,给函数提供的值叫作"实参"。下面来看段代码:
def add(x): # x是函数的参数,准确地说是形参
a = 10 # a是变量
return a + x # x就是那个形参作为变量
x = 2 # x是变量,只不过是在函数外的一个变量
add(x) # x是函数的参数,准确地说是实参,它是前面的变量x传递对象2的引用
位置参数
先来计写一个计算 x 2 x^2 x2的函数:
def calc(x):
return x * x
对于函数calc(x)
来说,参数x
就是一个位置参数。当我们调用calc()
函数时,必须传入有且仅有的一个参数x
:
>>> calc(2)
4
>>> calc(10)
100
但,如果我们现在想要计算
x
3
x^3
x3怎么办?那好办,可以再定义一个calc3
函数,但是如果要计算
x
4
x^4
x4、
x
5
x^5
x5…又该怎么办呢?我们不可能定义无限多个,这样显得太笨。这是我们可以这样做,把calc(x)
改为calc(x,n)
,用于计算
x
n
x^n
xn:
def calc(x,n):
ans = 1
while n > 0:
n -= 1
ans *= x
return ans
这样,修改后的calc(x,n)
函数,可以计算任意n
次方:
>>> calc(4,2)
16
>>> calc(7,2)
49
注意在调用以上函数时,要传入的参数数量必须与声明时严格一致,否则程序会报错。
默认参数
接着上面的函数calc(x,n)
讲,新定义之后,新的方式计算
x
n
x^n
xn,但如果还用旧的方式调用该函数失败了:
>>> power(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: power() missing 1 required positional argument: 'n'
这是因为,我们新实现的函数增加了一个参数,导致旧的调用方式因为缺少一个参数而无法正常调用。这个时候,默认参数就可以排上用场来解决此类问题。由于我们比较常计算
x
2
x^2
x2,所以,可以把第二个参数n
的默认值设置为2:
def power(x,n=2):
s = 1
while n > 0:
n = n - 1
s = s * n
return s
这样一来,我们调用该函数时,想要计算几次方,就将n
赋值为多少,当我们不传第二个参数时,即calc(5)
,就会触发第二个参数的默认值,相当于调用calc(5,2)
>>> power(5)
25
>>> power(5,2)
25
很明显,当n≠2
的情况,就必须明确地传入n
。虽然默认参数可以简化函数的调用,但在使用默认参数时,需要注意以下几点:
- 必选参数在前,默认参数在后,否则Python的解释器会报错。
- 当函数有多个参数时,把变化大的参数放在前面,变化小的参数放在后面,变化小的参数就可以考虑作为默认参数。
- 默认参数必须指向不变对象
可变参数
在Python中,还可以定义可变参数,即传入的参数个数是可变的,可以是1个、2个或任意个,甚至是0个。我们实现一个函数,给定的一组数字a,b,c,....
,计算
a
2
+
b
2
+
c
2
+
.
.
.
.
a^2+b^2+c^2+....
a2+b2+c2+....。首先要定义该函数,必须确定输入的参数,但由于参数个数不确定,因此我们可以把这组数字以list
或tuple
形式传进来:
def calc(nums:List[int])-> List:
sum = 0
for n in nums:
sum += n * n
return sum
但是,在调用的时候,我们必须先组装出一个list
或tuple
:
>>> calc([1,2,3])
14
>>> calc((1,3,5))
35
但如果利用可变参数,则可以简化为这样:
>>> calc(1,2,3)
14
>>> calc(1,3,5)
35
把函数的参数改为可变参数如下:
def calc(*nums):
sum = 0
for n in nums:
sum += n * n
return sum
定义可变参数和定义一个list
或tuple
参数相比,仅仅在参数前面加了一个*
号。在函数内部,参数nums
接收到的是一个tuple
,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数。
>>> calc(1,3)
10
>>> calc()
0
如果已经有一个list
或者tuple
,要调用一个可变参数怎么办?可以这样做:
>>> nums = [1,2,3]
>>> calc(nums[0],nums[1],nums[2])
14
不过这种写法显然还是太复杂了,因此Python允许你在list
或tuple
前面加一个*
号,把list
或tuple
的元素变成可变参数传进去:
>>> nums = [1,2,3]
>>> calc(*nums)
14
关键字参数
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict
。
def student(name,age,**kw):
print('name:',name,'age:',age,'other:',kw)
函数student
除了必选参数name
和age
外,还接受关键字参数kw
。在调用该函数时,可以只传入必选参数:
>>> student('Mike',18)
name:Mike age:18 other:{}
也可以传入任意个数的关键字参数:
>>> student('Jerry',20,city='SZ')
name:Jerry age:20 other:{'city':'SZ'}
>>> student('Ada',24,gener='F',job='IT')
name:Ada age:24 other:{'gender':'F','job':'IT'}
那么,关键字参数有什么用呢?它可以扩展函数的功能。例如,在student
函数里,我们保证能接收到name
和age
这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想如果你正在实现一个用户注册的功能,除了用户名和密码时必填外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。当然,和可变参数类似,也可以先组装出一个dict
,然后把该dict
转换为关键字参数传进去:
>>> dt = {'city':'SZ','job':'Engineer'}
>>> student('Eason',20,city=dt['city'],job=dt['job'])
name:Eason age:20 other:{'city':'SZ','job':'Engineer'}
当然,上面复杂的调用可以用简化的写法:
>>> dt = {'city':'SZ','job':'Engineer'}
>>> student('Eason',20,**dt)
name:Eason age:20 other:{'city':'SZ','job':'Engineer'}
**dt
表示把dt
这个dict
的所有key-value
用关键字参数传入到函数的**kw
参数,kw
将获得一个dict
,注意获得的dict
是dt
的一份拷贝,对kw
的改动不会影响到函数外的dt
。
使用关键字参数时允许函数调用时参数的顺序与声明时不一致,因为Python解释器能够用参数名匹配参数值:
>>> student(**dt,name='mike',age=20)
name: mike age: 20 other: {'city': 'SZ', 'job': 'Engineer'}
在正常使用时还是不推荐这样调用函数,最好与函数声明的参数顺序一致来调用函数以免程序出错。
命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在内部通过kw
检查。仍以student()
函数为例,我们希望检查是否有city
和job
参数:
def person(name,age,**kw):
if 'city' in kw:
# 有city参数
pass
if 'job' in kw:
# 有job参数
pass
print('name:',name,'age:',age,'other:',kw)
但是调用者仍可以传入不受限制的关键字参数:
>>> student('Tom',24,city='Guangdong',adrr='SZ',zipcode=41580)
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city
和job
作为关键字参数。这种方式定义的函数如下:
def student(name,age,*,city,job):
print(name,age,city,job)
和关键字参数**kw
不同,命名关键字参数需要一个特殊的分隔符*
,*
后面的参数被视为命名关键字参数。
调用方式如下:
>>> student('Tom',20,city='SZ',job='code')
Tom 20 SZ code
如果函数定义已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*
了:
def student(name,age,*args,city,job):
print(name,age,args,city,job)
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
>>> student('Tom',20,'SZ','code')
Exception has occurred: TypeError
student() missing 2 required keyword-only arguments: 'city' and 'job'
File "F:\VSCodeProjectForPython\python\demotest.py", line 16, in <module>
student('Tom',20,'SZ','code')
命名关键字参数可以有缺省值,从而简化调用:
def student(name,age,*,city='SZ',job):
print(name,age,city,job)
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*
作为特殊分隔符。如果缺少*
,Python解释器将无法识别位置参数和命名关键字参数:
def student(name,age,city,job):
# 缺少 * ,city和job被视为位置参数
pass
参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5中参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
比如定义一个函数,包含上述若干种参数:
def fun1(a,b,c=0,*args,**kw):
print('a=',a,'b=',b,'c=',c,'args=',args,'kw=',kw)
def fun2(a,b,c=0,*,d,**kw):
print('a=',a,'b=',b,'c=',c,'d=','kw=',kw)
在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。
>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
更有趣的是,通过一个tuple和dict,你也可以调用上述的函数:
>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
所以,对于任意函数,都可以通过类似func(*args,**kw)
的形式调用它,无论它的参数是如何定义的。但需要注意的是,虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差。