上一节,博主从数组组织的维度探索了 小Python 的用法。随着你所掌握的技术越来越广阔、深入,你的代码量也在突飞猛进,但代码重复和可读性性差的问题,也在慢慢凸显。这时候,该有个角色帮我们适当的组织下代码了 —— 函数。
1. 何谓函数
想象如下场景,你来到了KFC之后,准备大吃一顿,于是乎你冲服务员叫道:“美女,给我来***吮指原味鸡5块+香辣鸡翅6块+醇香土豆泥1份+香甜粟米棒1份+1.25L装百事可乐1瓶***”,服务员,擦了把汗,说道:“帅哥,下次来直接说***全家桶***一份就可以了"。你眨巴眨巴眼睛,付完款悻悻的找了个位置等待用餐。
如果由程序员来解读这个场景,那么啰里吧嗦的内容罗列就是程序中的一条一条的语句,而***全家桶***则可以称之为***函数***。全家桶(函数)就是对***吮指原味鸡5块+香辣鸡翅6块+醇香土豆泥1份+香甜粟米棒1份+1.25L装百事可乐1瓶***这多个商品(语句)的封装,言简意赅。
再从专业的编程角度强化一下函数的含义及存在价值:
- 定义:指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可。
- 价值一:简化代码,将多条语句封装为一个函数,通过这个函数就可以找到对应的多条语句
- 价值二:提高代码的复用性,多次使用再也不需要重复多条语句,只需要使用函数就可以
- 价值三:代码可扩展,函数实现由变,只需要修改函数中的语句即可,添删改随你的便。
函数的定义:
函数的调用:
2. 分门别类话函数
2.1 函数的来源
- 内置函数:为了提高程序员的开发效率,Python标准库里(语言自身携带的)提供了很多函数,俗名为拿来主义式函数,只需要根据其用法描述直接使用即可,点击访问内置函数详解。
- 用户自定义函数:为了满足程序员自身的开发需求,由使用者(第三方用户)编写的函数,俗名为自产自销函数,需要根据函数的定义规范生产创造函数。
2.3 函数返回值
首先,Python中所有函数都有返回值,而这个返回值通过return
语句实现,如果你没看见这个return
语句,函数运行结束会隐含返回一个 None 作为返回值,类型是 NoneType,与 return 、return None 等效,都是返回 None。
其次,一个函数可以存在多条 return 语句,但只有一条可以被执行,如果函数执行了 return 语句,则结束函数,即:return 之后的语句都不会被执行。
2.4 参数分类
当函数中处理的数据对象因调用者不同,处理的数据对象也有所不同的话,那么调用者就需要把要处理的数据传递给函数,而用于接收这些数据的占位作用的就是参数,如同我们点菜时,不同用户口味不同,就需要传递给商家不同的口味一样。
根据函数是否需要接收参数,以及接收参数的方式,可分类如下:
2.4.1 无参函数
# =*=*=*=*=*=*=*=*=*=*=*=*无参函数*=*=*=*=*=*=*=*=*=*=*=*=
def sum():
return 10 + 20
s = sum()
print(s)
# =*=*=*=*=*=*=*=*=*=*=*=*无参函数*=*=*=*=*=*=*=*=*=*=*=*=
2.4.2 位置或关键字参数
# =*=*=*=*=*=*=*=*=*=*=*=*positional-or-keyword:位置或关键字,必传参数*=*=*=*=*=*=*=*=*=*=*=*=
def sum(first_number, second_number):
print("first_number = " + str(first_number))
print("second_number = " + str(second_number))
return first_number + second_number
s1 = sum(100, 10) # 位置对应
print(s1)
s2 = sum(second_number=100, first_number=10) # 关键字对应
print(s2)
# =*=*=*=*=*=*=*=*=*=*=*=*positional-or-keyword:位置或关键字,必传参数*=*=*=*=*=*=*=*=*=*=*=*=
2.4.3 关键字默认值参数
# =*=*=*=*=*=*=*=*=*=*=*=*keyword-default-value:关键字参数默认值,可选参数*=*=*=*=*=*=*=*=*=*=*=*=
def sum(first_number, second_number=0):
print("first_number = " + str(first_number))
print("second_number = " + str(second_number))
return first_number + second_number
s1 = sum(100)
print(s1)
s2 = sum(100, 10)
print(s2)
# =*=*=*=*=*=*=*=*=*=*=*=*keyword-default-value:关键字参数默认值,可选参数*=*=*=*=*=*=*=*=*=*=*=*=
2.4.4 仅限位置参数
# =*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*positional-only:仅限位置*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
# 仅限位置:指定一个只能按位置传入的参数。Python 中没有定义仅限位置参数的函数语法,但是一些内置函数有仅限位置形参(比如 abs())。
abs_value = abs()
print(abs_value)
abs_value = abs(-10)
print(abs_value)
# abs_value = abs(x=-10) # 不支持关键字参数传递,异常报错
# print(abs_value)
# PS:=*=*=*=*用户自定义函数不支持 仅限位置参数,支持位置和关键字*=*=*=*=
def sum(x):
return x
s1 = sum(100)
print(s1)
s2 = sum(x=100)
print(s2)
# =*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*positional-only:仅限位置*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
2.4.5 可变位置参数
# =*=*=*=*=*=*=*=*=*=*=*=*var-positional:可变数量位置参数*=*=*=*=*=*=*=*=*=*=*=*=
def sum(first_number, second_number=0, *args):
print("first_number = " + str(first_number))
print("second_number = " + str(second_number))
print("args = " + str(args))
s = first_number + second_number
for arg in args:
s += arg
return s
s1 = sum(100, 10)
print(s1)
s2 = sum(100, 10, 1000)
print(s2)
s3 = sum(100, 10, 1000, 2000, 3000, 4000) # 涉及到 打包
print(s3)
numbers = [1000, 2000, 3000, 4000] # 序列类型对象都可作为实参传递
s4 = sum(100, 10, *numbers) # 涉及到 解包 和 打包
print(s4)
# =*=*=*=*=*=*=*=*=*=*=*=*var-positional:可变数量位置参数*=*=*=*=*=*=*=*=*=*=*=*=
图解:
2.4.6 可变关键字参数
# =*=*=*=*=*=*=*=*=*=*=*=*var-keyword:可变数量关键字参数*=*=*=*=*=*=*=*=*=*=*=*=
def sum(first_number, second_number=0, **kwargs):
# 关键字是隐性的要求,但数量不确定
# 隐性的关键字如下:third_number, fourth_number, fifth_number
# kwargs = {"third_number":1000, "fourth_number":2000, "fifth_number":4000}
# kwargs.items() ====> [("third_number",1000), ("fourth_number",2000), ("fifth_number",4000)]
s = first_number + second_number
for key,value in kwargs.items():
print(key + " = " + str(value))
s += value
return s
s1 = sum(100)
print(s1)
s2 = sum(100, 10, third_number=1000) # 涉及到 打包
print(s2)
s3 = sum(100, 10, third_number=1000, fifth_number=4000) # 涉及到 打包
print(s3)
numbers = {"third_number":1000, "fourth_number":2000, "fifth_number":4000}
s4 = sum(100, 10, **numbers) # 涉及到 解包 和 打包
print(s4)
# =*=*=*=*=*=*=*=*=*=*=*=*var-keyword:可变数量关键字参数*=*=*=*=*=*=*=*=*=*=*=*=
PS:此处涉及的解包和打包同可变数量位置参数相同,不在赘图。
2.4.7 仅限关键字参数
# =*=*=*=*=*=*=*=*=*=*keyword-only:仅限关键字,关键字前面一定是可变数量的位置参数*=*=*=*=*=*=*=*=*=*=
# 四则运算:+,-,*,/
def calc(first_number, second_number=0, *, operation=None):
s = 0
if operation == "+":
s = first_number + second_number
elif operation == "-":
s = first_number - second_number
elif operation == "*":
s = first_number * second_number
elif operation == "/":
s = first_number / second_number
else:
return "Invalid operation" # 终止整个函数,函数提前结束了
return s
result1 = calc(100, 10)
print(result1)
result2 = calc(100, 10, operation="+")
print(result2)
# =*=*=*=*=*=*=*=*=*=*keyword-only:仅限关键字,关键字前面一定是可变数量的位置参数*=*=*=*=*=*=*=*=*=*=
点击访问形参分类详解
3. Lambda函数
Python 中定义函数有两种方法,一种是用常规方式def定义,函数要指定名字,第二种是用lambda定义,不需要指定名字,称为Lambda函数。
Lambda 函数又称匿名函数,匿名函数就是没有名字的函数,函数没有名字也行?当然可以啦。有些函数如果只是临时一用,而且它的业务逻辑也很简单时,就没必要非给它取个名字不可。
好比电影里面的群众演员,往往他们的戏份很少,最多是衬托主演,跑跑龙套,他们需要名字吗?不需要,因为他们仅仅只是临时出镜,下次可能就用不着了,所以犯不着费心思给他们每个人编个号取个名字,毕竟取个优雅的名字是很费劲的事情。
图解lambda函数:
4. 我的地盘,我做主
Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python 的作用域一共有4种,分别是:
- L (Local) 局部作用域:属于内层函数的范围,不属于外层
- E (Enclosing) 闭包函数外的函数中:函数内部属于本函数的作用范围,因为函数可以嵌套函数,嵌套的内层函数有自身的内层范围
- G (Global) 全局作用域:文件级别的,或者说是模块级别的,每个py文件中处于顶层的变量都是全局作用域范围内的变量
- B (Built-in) 内建作用域:预先定义好的,在
__builtins__
模块中。这些名称主要是一些关键字,例如open、range、quit等
以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。
PS:闭包函数相对比较复杂,暂时不涉及,将会在Python进阶中深入讲解。
图解***L –> G –>B***:
4.1 搜索规则
当在某个范围引用某个变量的时候,将从它所在的层次开始搜索变量是否存在,不存在则向外层继续搜索。搜索到了,则立即停止。
内层范围可以引用外层范围的变量,外层范围不包括内层范围的变量。
4.2 内建作用域
两种方式可以搜索内置作用域:一是直接导入builtins
模块,二是让 小Python 自动搜索。导入builtins
模块会让内置作用域内的变量直接置于当前文件的全局范围,自动搜索内置作用域则是最后的阶段进行搜索,如图解中的variable_1 = int(1.2)
中的int
。
一般来说无需手动导入builtins
模块,不过可以看看这个模块中包含了哪些内置变量。
# 交互式解释环境
>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', ...............
'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
4.3 变量掩盖和修改规则
如果在函数内部引用了一个和全局变量同名的变量,且不是重新定义、重新赋值(其实 小Python 中没有变量声明的概念,只有赋值的概念),那么函数内部引用的是全局变量,如图解中的variable_2
。
如果函数内部重新赋值了一个和全局变量名称相同的变量,则这个变量是本地变量,它会掩盖全局变量。注意是掩盖而非覆盖,掩盖的意思是出了函数的范围(函数退出),全局变量就会恢复。如图解中的,在函数内部看到的是本地变量variable_1=2
,在函数外部看到的是全局变量variable_1 = int(1.2)
。
如果想要在def
的内部修改全局变量,就需要使用global
关键字声明变量,global
可以声明一个或多个变量为全局变量,多个变量使用逗号隔开,也可以声明事先不存在的变量为全局变量。如图解中的,global variable_3
。
4.4 关于全局变量
- 每个
py
文件(模块)都有一个自己的全局范围 - 文件内部顶层的,不在
def
区块内部的变量,都是全局变量 def
内部声明(赋值)的变量默认是本地变量,要想让其变成全局变量,需要使用global
关键字声明def
内部如果没有声明(赋值)某变量,则引用的这个变量是全局变量
5. 场景驱动
场景描述:某班级有 N 名学员,刚经历了一次考试的洗礼,现使用 小Python 编写一个成绩清单,成绩清单中包含学号、姓名、成绩三项数据,功能如下:
- 提供录入功能
- 成绩单按照分数从高到低显示
- 可以根据学号查找出某位学员的的详细信息
- 可将补考学员的信息,插入到已排序后的成绩单中
PS:针对该场景,在上节实现中,是否发现有很多重复的代码块,相似的代码块,那能否像“全家桶”套餐那样封装重用呢?函数,封装、重用的利器,试一试吧。