函数是可重用的程序代码块,函数不仅可以实现代码的复用,更能实现代码的一致性。函数只是只是对代码实现了封装,并增加了函数调用、传递参数、 返回计算结果等内容。一个程序由一个一个的任务组成;函数就是代表一个任务或者一个功能(function)。

函数的定义与调用
定义语法:
python
def 函数名 ([参数列表]) :
'''文档字符串'''
函数体/若干语句
通过上面的定义来简单的创建一个函数:
python
def add(a, b, c):
"""
:param a: 参数a
:param b: 参数b
:param c: 参数c
:return:
"""
sum = a + b + c
print("{0},{1},{2}三个数的和是:{3}".format(a, b, c, sum))
return sum
add(10, 20, 30)
add(30, 40, 50)
def是创建函数的关键字,python中一切皆对象,当执行到def时,会创建一个函数对象,并将函数名变量指向这个对象,完成绑定。
参数列表:多个参数参数之间使用逗号隔开,定义时的形式参数不需要声明类型,也不需要指定函数返回值类型,调用时的实际参数必须与形参列表一一对应
return返回值:如果函数体中包括return语句,那么则结束函数执行并返回值;如果不包含return语句,那么在函数执行完毕之后,则会返回None值

形参与实参
形参:形参也叫形式参数,就是函数在定义时,在()内定义的参数.如上面的函数add(a, b, c),其中a,b,c就是形参。
实参:实参也叫实际参数,就是函数在调用时,给函数提供的实际的值。如上面的函数add(10, 20, 30),其中10,20,30就是实参。
python
def printMax(a, b):
if a > b:
return a
else:
return b
print(printMax(20, 15)) # 20
print(printMax(11, 10)) # 11
上面的 printMax 函数中,在定义时写的 printMax(a,b) 。 a 和 b 称为"形式参 数",简称"形参"。也就是说,形式参数是在定义函数时使用的。 形 式参数的命名要符合"标识符"命名规则。
在调用函数时,传递的参数称为"实际参数",简称"实参"。上面代码 中, printMax(10,20) , 10 和 20 就是实际参数。
文档字符串(函数的注释)
文档字符串就是对函数的解释说明,让调用者知道函数的功能,以及如何调用。文档字符串一般写在函数体的最上面,如:
python
def print_star(n):
"""
传入n,打印多个星号
:param n: 参入的数字
:return: n个星号拼接的字符串
"""
return "*" * n
print(help(print_star))
# print_star(n)
# 传入n,打印多个星号
# :param n: 参入的数字
# :return: n个星号拼接的字符串
#
# None
# print(print_star.__doc__)
# 传入n,打印多个星号
# :param
# n: 参入的数字
# :return: n个星号拼接的字符串
文档字符串可以通过:help(函数名) 和 函数名._ doc _ 获取到
return
函数的返回内容,有时候执行一个函数时,我们需要它返回一个结果就可以使用return语句
如果函数体中包含return语句且有返回值:则结束函数执行,并将值返回出去
如果函数体中不包含return语句,则函数结束时,返回None值
要返回多个值,使用列表、元组、字典、集合将多个值"存起来"即可
python
def print_star(n):
print("*" * n)
print_star(5)
def my_avg(a, b):
return (a + b) / 2
print(my_avg(20, 30)) # 25.0
def printShape(n):
s1 = "#"*n
s2 = "*"*n
return [s1,s2]
print(printShape(5)) # ['#####', '*****']
函数也是对象
前面提到过,当python执行到def时,会创建一个函数对象,并将函数名变量指向它。本质就是赋值,那么我们也可以创建另一个变量,也去指向这个对象,通过这个变量也可以去执行这个函数对象
python
def print_star(n):
print("*"*n)
print(print_star) # <function print_star at 0x0000011E412D4DC0>
print(id(print_star)) # 1631933451712
# 执行 def 定义函数后,系统就创建了相应的函数对象,函数名就是指向这个对象的变量
# 将变量c也指向这个函数对象,并运行函数内部的代码
c = print_star
print(c) # <function print_star at 0x000001F998C34DC0>
print(id(c)) # 1631933451712
# 执行c这个函数
c(5) # *****
变量的作用域
全局:声明在def和class之外的变量,其作用域是从变量的声明到模块结束。如果在函数内部想要改变外面的全局变量,需要使用global进行声明(如果不修改这个全局变量,则不需要这一步),再使用。如果想要获取到所有的全局变量,则使用globals()
局部:声明在def/class中的变量,其作用域在函数执行完毕之后就销毁。当函数内部与外部有着同名变量,则优先使用自己作用域下的变量。获取函数内部所有的局部变量:locals()
python
a = 300 # 全局变量
def f1():
global a # 如果需要修改全局变量的值,需要加上这一段
print(a)
a = 100
f1() # 300
f1() # 100
print(a) # 100
# 就近原则
a = 300 # 全局变量
def f1():
a = 100
print(a)
f1() # 100
f1() # 100
print(a) # 300
def f1(a,b,c):
print(locals()) # {'a': 1, 'b': 2, 'c': 3}
print("*******")
print(globals()) # 所有的全局变量
f1(1,2,3)
参数的传递
参数的传递:其实就是对形参赋值,python只有引用传递,没有值传递。
可变参数:列表,字典...,那么不会拷贝一个新对象,对形参进行修改本质上就是对源对象进行修改
python
a = [20,30]
def f1(m):
print(id(m))
m.append(50)
print(m,id(m)) # [20, 30, 50] 2728819248896
f1(a)
print(a,id(a)) # [20, 30, 50] 2728819248896
不可变参数:元组,字面量,在对形参修改之前,形参与实参指向的是同一个变量,如果对形参进行修改,由于不可变对象无法修改,会拷贝一个新的对象,而这个形参就指向这个新对象。
python
def f1(m):
print(id(m)) # 1919563209168
m=100 # 修改了数据,直接创建了新对象,并指向它
print(m,id(m)) # 1919563205968
f1(a)
print(a,id(a)) # 1919563209168
不可变对象内的可变对象:a(1,2,[5,6]),如果对内部的列表进行修改,其实也是作用在源对象上
python
a = (10,20,[5,6])
print("a",a,id(a)) # a (10, 20, [5, 6]) 2457480691328
def f1(m):
print("m",m,id(m)) # m (10, 20, [5, 6]) 2457480691328
m[2][0] = 888
print("m",m,id(m)) # m (10, 20, [888, 6]) 2457480691328
f1(a)
print("a",a,id(a)) # a (10, 20, [888, 6]) 2457480691328
拷贝
浅拷贝:就像上面提到的不可变对象内的可变对象(1,2,[5,6]),元组不可变,但是列表可变当对不可变对象进行修改时,则会拷贝一个新对象,但是如果对内部列表改变,则源对象也会改变,这就是浅拷贝;拷贝对象,但是不拷贝子对象的内容,只拷贝子对象引用,即源对象的子对象id = 新对象的子对象id。所以在修改子对象的时候,其源对象的子对象也会改变。
python
import copy
def testCopy():
a = [20,30,[5,6]]
b = copy.copy(a)
# 对b进行操作
b.append(40)
b[2].append(7)
print(a) # [20, 30, [5, 6, 7]]
print(b) # [20, 30, [5, 6, 7], 40]
testCopy()
深拷贝:还是拿上面为例:(1,2,[5,6]), 深拷贝就是不仅拷贝对象,连子对象的内存也全部(递归)拷贝一份,即源对象的子对象id ≠ 新对象子对象的id。修改新对象的子对象不会影响到源对象。
python
def testDeepCopy():
a = [20,30,[5,6]]
b = copy.deepcopy(a)
# 对b进行操作
b.append(40)
b[2].append(7)
print(a) # [20, 30, [5, 6]]
print(b) # [20, 30, [5, 6, 7], 40]
testDeepCopy()
参数类型
位置参数:首先形参与实参在个数上要一致,实参按照先后顺序给形参赋值,上面所使用的都是位置参数
python
# 1.位置参数:上面定义的其实就是位置参数,实参按照顺序给形参赋值,实参顺序调整可能影响整个函数的值
def print_num(a,b,c,d):
print(a,b,c,d)
print_num(1,2,3,4) # 1 2 3 4
print_num(2,1,4,3) # 2 1 4 3
# 位置参数形参和实参的个数必须一致,否则报错
# TypeError: print_num() missing 1 required positional argument: 'd'
# print_num(1,2,3)
默认参数:当有些参数是可选的,不一定要传递,但是位置参数又必须传递,可以使用默认参数,默认参数是指在如果不传递某个参数,在形参列表中去指定默认值,这样当这个参数没有传递的时候
使用的是默认值,默认参数要放在参数列表的最后。
python
# 默认参数其实就是给形参一个默认值,当没有传入该参数时使用默认值,这使得这个参数变成可选参数
# 默认参数要放在最后,也即是说默认参数不能放在非默认参数后面
def print_num(a,b,c,d=4):
print(a,b,c,d)
print_num(1,2,3,5) # 1 2 3 5
print_num(1,2,3) # 1 2 3 4
命名参数:在位置参数中,如果参数的顺序传递错误,那么可能函数返回的结果就不一致;为避免这个问题可以使用命名参数,命名参数是指在进行参数传递时,使用(形参名=实参, 形参名=实参)这样的形式,使形参与实参通过通过形参进行传递。
python
# 默认参数之能放在最后是因为:实参是按照顺序给形参赋值,当只传入3个参数时
# 如果默认参数不是最后一个,那么这个默认参数将会被赋值,第四个参数将会没有值,从而报错
# 命名参数,根据形参的名称来赋值
def print_num(a,b,c,d):
print(a,b,c,d)
print_num(a=1,b=2,c=3,d=4) # 1 2 3 4
print_num(c=3,b=2,a=1,d=4) # 1 2 3 4
可变参数:参数列表的个数不固定
*param:将多余的参数收集到列表中存储,param指向这个列表
**param:将多余的(a=1)这样类型的参数收集到字典中,param指向这个字典中
python
# *param:多余的参数将会组成元组
def print_num(a,b,*c):
print(a,b,c)
print_num(3,4) # 3 4 ()
print_num(1,2,3,4) # 1 2 (3, 4)
print_num(1,2,3,4,"a") # 1 2 (3, 4, 'a')
# **params:多余的参数将会转换为字典
def print_num(a,b,**c):
print(a,b,c)
print_num(3,4,name="hhc",age=21) # 3 4 {'name': 'hhc', 'age': 21}
强制命名参数:当param不是最后一个参数时,那么param会接收所有剩余参数,那么在*param后面的变量如果不是默认参数时,则没有值赋值给该量,那么程序将会报错。如果有这样类似的情况,就必须使用命名参数。
python
# 当*a是第一个形参时,如果后面的形参也想要赋值,则必须带参数名
def print_num(*a,b,c):
print(a,b,c)
print_num(1,2,3,b=4,c=5) # (1, 2, 3) 4 5
lambda表达式
声明一个匿名函数,生成一个函数对象,这个函数对象没有变量去指向它,需要手动指向,在lambda表达式中,只能有一条语句,该条语句的结构就是这个函数的返回值
python
a = lambda a,b,c: a+b+c
print(a) # <function <lambda> at 0x00000185416AC700>
print(a(1,2,3)) # 6
print(a(2,3,4)) # 9
g = [lambda a:a*2, lambda b:b*3, lambda c:c*4]
print(g[0](6),g[1](7),g[2](8)) # 12 21 32
g = [lambda a:a*2, lambda a:a*3, lambda a:a*4]
print(g[0](6),g[1](7),g[2](8)) # 12 21 32
eval()函数
eval(str):将str当成一条可执行的语句,去执行它
python
eval("print('123哈哈')") # 123哈哈
def sum(a,b):
c = f"{a}+{b}"
eval("print(a+b)")
sum(3,4) # 7
sum(73,54) # 127
函数的递归
递归函数指的是:自己调用自己的函数,在函数体内部直接或间接 的自己调用自己。每个递归函数必须包含两个部分:
1 终止条件 表示递归什么时候结束。一般用于返回值,不再调用自己。
2 递归步骤 把第n步的值和第n-1步相关联。
python
def my_recursion(n):
print("start:",n)
if n==1:
print("recursion over!")
else:
my_recursion(n-1)
print("end:",n)
my_recursion(3)
# 递归2
def factorial(n):
if n == 1:
return n
return n*factorial(n-1)
print(factorial(5)) # 120
# 递归3
def num_reverse(num):
str_num = str(num)
if len(str_num) == 1:
return str_num
return num_reverse(str_num[1:]) + str_num[0]
print(num_reverse(3245))
# 递归4
# 1/2 + 2/3 +... n/n+1
def test01(n):
if n == 1:
return 1/2
return n/(n+1) + test01(n-1)
print(test01(3))

嵌套函数
python
def outer():
print("outer....")
def inner():
print("inner....")
inner()
# outer....
# inner....
outer()
def printChineseName(name,familyName):
print("{0} {1}".format(familyName,name))
def printEnglishName(name,familyName):
print("{0} {1}".format(name, familyName))
def printName(isChinese,name,familyName):
def inner_print(a,b):
print("{0} {1}".format(a,b))
if isChinese:
inner_print(familyName,name) # hc h
else:
inner_print(name,familyName) # George Bush
printName(True,"hc","h")
printName(False,"George","Bush")
nonlocal关键字
nonlocal 用来在内部函数中,声明外层的局部变量
global 函数内声明全局变量,然后才使用全局变量
python
a =100
def outer():
b = 200
def inner():
nonlocal b
print("innerb:",b) # 200
b = 300
global a
print("innera:",a) # 100
a = 1000
inner()
print("outerb:",b) # 300
print("outera:",a) # 1000
outer()