系列文章目录
文章目录
前言
随着业务层的功能越来越多,代码量也会越来越多,且很多功能是很多业务所通用的,这时候就要考虑将这些常用的功能封装在一个模块中,而函数就是一系列功能的封装,实现了模块的复用性。在本章节中,我们就来学习如何自定义函数,函数调用时的规则,以及python中有哪些内置的函数。
一、自定义函数
1. 形式
def 函数名(arg1, arg2, ...):
业务逻辑
return 表达式
def
是函数定义的标志,arg1
和arg2
分别表示形参,各形参间中间用逗号隔开,假如没有return
,返回值为None
示例:
def my_sum(x, y):
return (x+y)
print(my_sum(2, 3)) # 输出5
2. 参数
2.1 参数类型
- 位置传参:在实参中,通过与形参位置对应决定参数的传递
示例:
def greet(name, age):
print(f"Hello {name}, you are {age} years old.")
greet('Welt', 21) # 输出Hello Welt, you are 21 years old.
- 关键词传参:在实参中,通过对形参的变量名“赋值”进行参数传递(要在位置参数之后,否则会破坏位置关系)
示例:
def greet(name, age):
print(f"Hello {name}, you are {age} years old.")
greet(age=21, name='Welt') # 输出Hello Welt, you are 21 years old.
- 默认参数值:在形参中,对形参进行赋值,表示在参数传递时,可以不给该形参传递参数,不传参是取等号右方值(要在非默认值参数的后面,否则将会报错:
SyntaxError: non-default argument follows default argument
)
示例:
def greet(age, name = 'Tesra'):
print(f"Hello {name}, you are {age} years old.")
greet(21) # 输出Hello Tesra, you are 21 years old.
- 可变位置参数:在形参中,通过在变量前添加*表示不确定的附加参数,作为一个元组传入
示例:
def add(*numbers):
print(numbers)
print(sum(numbers))
add(1, 2, 3, 4) # 输出(1, 2, 3, 4)
# 10
- 可变关键词传参:在形参中,通过在变量前添加**表示不确定的附加参数,作为一个字典传入(在所有参数后面)
示例:
def my_show(name, age, **arg):
print(f"My name is {name}, {age} years old.")
# 下方输出附加信息
for key, value in arg.items():
print(f"{key}: {value}")
my_show('Welt', 21, address='Moon',gender='male')
'''
My name is Welt, 21 years old.
address: Moon
gender: male
'''
- 解包位置参数:当要传入的数据是
列表
、元组
这样的结构化数据时,可以将其解包并依次填充到各位置参数上
示例:
def greet(name, age, country):
print(f"Name: {name}, Age: {age}, Country: {country}")
# 数据封装在一个元组中
info = ("Alice", 30, "Wonderland")
# 使用 * 解包为位置参数
greet(*info) # 输出 Name: Alice, Age: 30, Country: Wonderland
- 解包关键词参数:当要传入的数据是
字典
这样的结构化数据时,可以将其解包并将传参给各关键词参数上
示例:
def show_info(name, age, country):
print(f"Name: {name}, Age: {age}, Country: {country}")
# 参数封装在字典中
info_dict = {
"name": "Bob",
"age": 25,
"country": "USA"
}
# 使用 ** 解包为关键字参数
show_info(**info_dict) # 输出Name: Bob, Age: 25, Country: USA
2.2 参数传递
无论参数为可变类型还是不可变类型,参数的传递都是传递 引用(即函数内的形参作为一个临时的引用,指向实参的内存空间或者实参所引用的空间(实参同样是一个引用的情况下)),区别在于对可变类型的引用可以修改该内存空间中的内容,而对不可变类型的引用则无法修改所指向内存空间的内容,只能重新指向其他内存空间
3. lambda表达式
是一个 表达式 ,意味着可以返回值,返回的是是一个函数对象。在逻辑较简单或者调用的次数不多的情况下可以替代函数定义
3.1 形式
lambda arg1, arg2: 表达式
由于lambda返回的是一个
函数对象
,arg1和arg2表示该函数对象传入的参数
,表达式表示的是该函数对象的返回值
3.2 示例
f = lambda x, y: x + y
print(f) # 输出<function <lambda> at 0x000001F682A35E40>
print(type(f)) # 输出<class 'function'>
print(f(2, 3)) # 输出5
替换成函数定义形式如下:
def func(x, y):
return x + y
print(func) # 输出<function func at 0x000001F68292CA40>
print(type(func)) # 输出<class 'function'>
print(func(2,3)) # 输出5
二、常见内置函数
1. all(iterable)
对
iterable
的元素进行遍历,如果其中全部元素都为True
(包括没有元素时),则返回True
,发现有任意元素为False
则返回False
示例:
list1 = []
list2 = [1, 2, 3]
list3 = [1, 2, 0]
list4 = [1, 2, None]
list5 = [1, 2, False]
list6 = [1, 2, []]
print(all(list1)) # 输出True,空列表没有元素为假
print(all(list2)) # 输出True
print(all(list3)) # 输出False,因为元素10为Flase
print(all(list4)) # 输出Fasle,因为元素None为False
print(all(list5)) # 输出False,因为有元素False
print(all(list6)) # 输出False,因为元素[]为False
2. any (iterable)
对
iterable
的元素进行遍历,如果存在任意元素为True,则返回True,当所有元素全为False时返回False(包括没有元素时)
示例:
list1 = []
list2 = [None, 0, []]
list3 = [None, 0, [], 1]
print(any(list1)) # 输出False
print(any(list2)) # 输出False
print(any(list3)) # 输出True
3. sum(iterable, /, start=0)
将
start
作为起始值,对iterable
中的元素进行进行遍历求和,start
的默认值为0
,当内部元素为同类的可迭代对象也可以进行可迭代对象进行拼接,只是要对start
赋值使得其能够适用+
运算就可以,但是str
的拼接是不可行的,会报错:TypeError: sum() can't sum strings [use ''.join(seq) instead]
,因为start
值则不允许为字符串。
示例:
list1 = [1, 3, 4]
list2 = [[1,2,3], [4,5,6]]
list3 = ['hello', 'world']
print(sum(list1)) # 输出8
print(sum(list2, start = )) # 输出[1, 2, 3, 4, 5, 6]
print(sum(list3)) # 报错TypeError
4. zip(*iterables)
在多个迭代器(来自
*iterables
)上并行迭代,从每个迭代器返回一个数据项组成元组
,而这些元组作为元素构成一个迭代器返回。返回的迭代器中元组的数量取决于iterables
元素最少的那一个。
示例:
list1 = [1, 2, 3, 4]
tuple1 = ['a', 'b', 'c']
list2 = [True, False, False]
ziped = zip(list1, tuple1, list2)
print(ziped) # 输出<zip object at 0x000001F682929AC0>,是一个迭代器
print(list(ziped)) # [(1, 'a', True), (2, 'b', False), (3, 'c', False)],其中有3个元组
三、函数递归调用
在对复杂问题的求解过程中,递归调用是一种非常有效的编程技术。递归是指函数直接或间接 调用自身 的过程,通过将大问题 分解 为相似的子问题来逐步解决,思考的过程相对简单。
1. 经典案例
1.1 斐波那契数列
首先得到问题的定义:
F e b ( n ) = { 0 n = 0 1 n = 1 F e b ( n − 1 ) + F e b ( n − 2 ) n > 1 Feb(n) = \begin{cases} 0 & n = 0 \\ 1 & n = 1 \\ Feb(n-1) + Feb(n-2) & n > 1 \end{cases} Feb(n)=⎩ ⎨ ⎧01Feb(n−1)+Feb(n−2)n=0n=1n>1
即从第三项(Feb(2)
)开始,每一项都等于前两项之和
def feb(n):
if n==0 or n==1:
return n
return feb(n-1) + feb(n-2) # 递归部分
# 调用函数
print(feb(0)) # 输出0
print(feb(1)) # 输出1
print(feb(6)) # 输出8
# print(feb(3000)) # 将导致栈溢出,程序无法正常执行
1.2 求n的阶乘:
首先得到问题的定义: n ! = { 1 n = 0 n ⋅ ( n − 1 ) ! n > 0 n! = \begin{cases} 1 & n = 0 \\ n \cdot (n - 1)! & n > 0 \end{cases} n!={1n⋅(n−1)!n=0n>0
即从n往前依次相乘,乘到1(0的阶乘)为止
def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1) # 递归部分
# 函数调用
print(factorial(0)) # 输出1
print(factorial(1)) # 输出1
print(factorial(5)) # 输出120
2. 递归和循环(迭代)的区别
由于Python是图灵完备的语言,故从理论上来说:凡是可以用递归实现的算法,都可以用 循环(迭代) 来实现,反之亦然。然而,两者在实际的实现过程中,性能上和编写上有显著的差异
- 递归的特点:
- 递归算法通常采用"分而治之"的策略,将问题分解为更小的同类子问题(例如阶乘计算:n! = n * (n-1)!)
- 递归代码通常更简洁直观,更接近数学定义形式(如斐波那契数列:fib(n) = fib(n-1) + fib(n-2))
- 每次递归调用都会在调用栈中创建新的栈帧,递归次数太多时会导致导致栈溢出
- 递归可能存在重复计算问题(如简单递归实现的斐波那契数列会有O(2^n)的时间复杂度)
- 循环(迭代)的实现特点:
- 通常需要维护额外的状态变量(如在计算阶乘时需要维护累乘变量)
- 不会产生额外的函数调用开销,空间复杂度通常是O(1)
- 代码可能比递归版本更冗长,但执行效率更高
总结
在本章中,我们学习了如何定义函数,以及如何传递参数、如何进行函数的递归调用 。在实际的应用中,应该根据结构化数据(如.json
文件)的特点来选择最佳的参数传递方式,根据问题规模选择递归还是迭代,还要在适当的情境下能够想起来有哪些内置函数是可用的。