函数的定义
所谓函数,就是把 具有独立功能的代码块 组织为一个小模块,在需要的时候 调用
函数的格式如下:
def 函数名():
函数封装的代码
……
def
是英文define
的缩写- 函数名称 应该能够表达 函数封装代码 的功能,方便后续的调用
- 函数名称 的命名应该 符合 标识符的命名规则
- 可以由 字母、下划线 和 数字 组成
- 不能以数字开头
- 不能与关键字重名
函数调用
通过 函数名()
调用函数
- 定义好函数之后,只表示这个函数封装了一段代码而已,如果不主动调用函数,函数是不会主动执行的
# 定义函数
def info_print():
print('hello world')
# 调用函数
info_print()
函数执行流程
1. 函数先定义后调用,如果先调用会报错
提示
NameError: name 'say_hello' is not defined
(名称错误:say_hello 这个名字没有被定义)
2.当调用函数的时候,解释器回到定义函数的地方去执行下方缩进的代码,当这些代码执行完,回到调用函数的地方继续向下执行(定义函数的时候,函数体内部缩进的代码并没有执行)
函数的说明文档
- 在开发中,如果希望给函数添加注释,应该在 定义函数 的下方,使用 连续的三对引号,在 连续的三对引号 之间编写对函数的说明文字
- 在 函数调用 位置,使用快捷键
CTRL + Q
可以查看函数的说明信息 - 使用help函数可以查看说明文档的信息
注:因为 函数体相对比较独立,函数定义的上方,应该和其他代码(包括注释)保留 两个空行
def my_max(m, n, *args):
#下面形式的说明文档,先打出3个引号,再将鼠标放中间,敲回车,就可以出现
"""
返回所有参数的最大值
:param m:
:param n:
:param args:
:return:
"""
if m < n:
m = n
for i in args:
if m < i:
m = i
return m
PyCharm 的调试工具
- F8 Step Over 可以单步执行代码,会把函数调用看作是一行代码直接执行
- F7 Step Into 可以单步执行代码,如果是函数,会进入函数内部
函数参数的使用
- 在函数名的后面的小括号内部填写 参数,多个参数之间使用
,
分隔
形参和实参
- 形参:定义 函数时,小括号中的参数,是用来接收参数用的,在函数内部 作为变量使用
- 实参:调用 函数时,小括号中的参数,是用来把数据传递到 函数内部 用的
# # 函数定义的位置的参数叫形式参数
def say(name):
print('hello %s'%name)
# 实际调用的时候传入的参数叫实际参数
say('tom')
给形参设置默认值 (缺省参数、默认参数)
# 在定义函数的时候给形参设置默认值,此时设置过默认值的参数可以不传
# 可以设置多个默认值
# 要把没有默认值得参数放在前面,有默认值的参数放在后面
def say(name, age, weight=80):
print('hello %s'%name)
print('你今年%d岁了' % age)
print('你体重%.1f公斤!' % weight)
return #作用是使函数返回到函数调用的位置
# 在不传参数的时候取默认值
say('jack', 22)
# 在传参的时候,优先取值实际传入的值
say('jack1',22,55)
调用函数的时候传参的两种方式
位置参数
也叫非关键字参数,按照函数定义的时候形式参数的位置来传参的
def say(name, age, weight=80):
print('hello %s'%name)
print('你今年%d岁了' % age)
print('你体重%.1f公斤!' % weight)
say('tom', 22, 82.8)
关键字参数
- 函数调⽤,通过“键=值”形式加以指定
- 可以不按照形参的位置的顺序传参
def say(name, age, weight=80):
print('hello %s'%name)
print('你今年%d岁了' % age)
print('你体重%.1f公斤!' % weight)
say(name='tom', age=22, weight=82.8)
同时使用两种传递参数的方式
位置参数必须写在关键字参数的前面
def say(name, age, weight=80):
print('hello %s'%name)
print('你今年%d岁了' % age)
print('你体重%.1f公斤!' % weight)
say('tom', age=22, weight=82.8)
典型使用情况,中间的参数使用默认值,最后一个不使用默认值
def say(name, age, weight=80):
print('hello %s'%name)
print('你今年%d岁了' % age)
print('你体重%.1f公斤!' % weight)
say('tom', weight=82.8)
不可变和可变的参数
不可变参数
在函数内部,针对参数使用 赋值语句,不会影响调用函数时传递的 实参变量
无论传递的参数是 可变 还是 不可变;只要 针对参数 使用 赋值语句,会在 函数内部 修改 局部变量的引用,不会影响到 外部变量的引用
def demo(num, num_list):
print("函数内部")
# 赋值语句
num = 200
num_list = [1, 2, 3]
print(num)
print(num_list)
print("函数代码完成")
gl_num = 99
gl_list = [4, 5, 6]
demo(gl_num, gl_list)
print(gl_num)
print(gl_list)
可变参数
如果传递的参数是 可变类型,在函数内部,使用 方法 修改了数据的内容,会影响到外部的数据
def mutable(num_list):
# num_list = [1, 2, 3]
num_list.extend([1, 2, 3])
print(num_list)
gl_list = [6, 7, 8]
mutable(gl_list)
print(gl_list)
不定长参数(多值参数)
不定长参数(多值参数),⽤于不确定调⽤的时候会传递多少个参数(不传参也可以)的场景,Python
中有 两种 多值参数
包裹(packing)位置参数
定义函数时,*参数名 接受任意多个位置参数
一般将参数名写成*args,args
是arguments
的缩写,有变量的含义参数的类型是元组类型
def foo(*args):
print(args)
# 不传参,返回空元组
foo()
# 可以传一个参数
foo(1)
# 可以传多个参数,类型也不限制
foo(1, 3, 22, 'hello')
# 也可以在前边添加位置参数,表示有一个必传参数
# 位置参数必须放在前面
def foo1(n,*args):
print(args)
foo1(1, 3, 4)
包裹关键字参数
定义函数时,**参数名 接收任意多个关键字参数
默认写成**kwargs,kw
是keyword
的缩写,参数的类型为字典,参数名为字典的键,参数值为字典的值
def user_info(**kwargs):
print(kwargs)
# {'name': 'TOM', 'age': 18, 'id': 110}
user_info(name='TOM', age=18, id=110)
综上:⽆论是包裹位置传递还是包裹关键字传递,都是⼀个组包的过程(传入零散参数,返回一个完整的元组,字典)。
实际参数的拆包(元组和字典的拆包)
- 在调用带有多值参数的函数时,如果希望:
- 将一个 元组变量,直接传递给
args
- 将一个 字典变量,直接传递给
kwargs
- 就可以使用 拆包,简化参数的传递,拆包 的方式是:
- 在 元组变量前,增加 一个
*
- 在 字典变量前,增加 两个
*
# 实际参数的拆包
def say(name = 'tom', age = 22, weight=80):
print('hello %s'%name)
print('你今年%d岁了' % age)
print('你体重%.1f公斤!' % weight)
# 在实际参数前面加*,表示将实际参数拆成位置参数的形式传进去
# 序列类型的参数都可以拆
# 要求序列中为元素与函数定义时所需要的参数数量和类型一致
a = ['tom', 23, 66.5]
# say(a[0], a[1], a[2])
say(*a)
# 可以将字典拆成关键字参数的类型
# 在字典的名字前面加上**
b = {'name': 'tom', 'weight': 66.5, 'age': 23}
# say(name='tom', age=23,weight=66.5)
say(**b)
def demo(*args, **kwargs):
print(args)
print(kwargs)
# 需要将一个元组变量/字典变量传递给函数对应的参数
gl_nums = (1, 2, 3)
gl_xiaoming = {"name": "小明", "age": 18}
# 会把 num_tuple 和 xiaoming 作为元组传递个 args
# demo(gl_nums, gl_xiaoming)
demo(*gl_nums, **gl_xiaoming)
函数的返回值
- 返回值 是函数 完成工作后,最后 给调用者的 一个结果
- 在函数中使用
return
关键字可以返回结果 - 调用函数一方,可以 使用变量 来 接收 函数的返回结果
返回一个值
def sum_2_num(num1, num2):
"""对两个数字的求和"""
return num1 + num2
# 调用函数,并使用 result 变量接收计算结果
result = sum_2_num(10, 20)
print("计算结果是 %d" % result)
无返回值
注:如果在 return
后没有跟任何内容,只是表示该函数执行到此就不再执行后续的代码,只是使函数返回到函数调用的位置
def say(name, age, weight):
print('hello %s'%name)
print('你今年%d岁了' % age)
print('你体重%.1f公斤!' % weight)
return #作用是使函数返回到函数调用的位置
ret = say('tom', 22,88.8)
# # 函数没有返回值得时候打印出none,有返回值打印返回值
print(ret)
返回多个值
1. return a, b 写法,返回多个数据的时候,默认是元组类型
2. return后⾯可以连接列表、元组或字典,以返回多个值
注:如果函数返回的类型是元组,小括号可以省略
def measure():
"""测量温度和湿度"""
print("测量开始...")
temp = 39
wetness = 50
print("测量结束...")
# 元组-可以包含多个数据,因此可以使用元组让函数一次返回多个值
# 如果函数返回的类型是元组,小括号可以省略
# return (temp, wetness)
return temp, wetness
# 元组
result = measure()
print(result)
单独处理数据
# 需要单独的处理温度或者湿度 - 不方便
print(result[0])
print(result[1])
返回值拆包
元组拆包
- 如果函数返回的类型是元组,可以使用多个变量,一次接收函数的返回结果
- 注:使用多个变量接收结果时,变量的个数应该和元组中元素的个数保持一致
gl_temp, gl_wetness = measure()
print(gl_temp)
print(gl_wetness)
def return_num():
return 100, 200
num1, num2 = return_num()
print(num1) # 100
print(num2) # 200
字典拆包
对字典进⾏拆包,取出来的是字典的key
dict1 = {'name': 'TOM', 'age': 18}
a, b = dict1
print(a) # name
print(b) # age
print(dict1[a]) # TOM
print(dict1[b]) # 18
函数的嵌套调用
一个函数里面 又调用 了 另外一个函数,这就是 函数嵌套调用
def print_line(char, times):
"""打印单行分隔线
:param char: 分隔字符
:param times: 重复次数
"""
print(char * times)
def print_lines(char, times):
"""打印多行分隔线
:param char: 分隔线使用的分隔字符
:param times: 分隔线重复的次数
"""
row = 0
while row < 5:
print_line(char, times)
row += 1
print_lines("-", 20)
使用模块中的函数
- 模块 就好比是 工具包,要想使用这个工具包中的工具,就需要 导入 import 这个模块,导入之后,就可以使用
模块名.变量
/模块名.函数
的方式,使用这个模块中定义的变量或者函数 - 每一个以扩展名
py
结尾的Python
源代码文件都是一个 模块 - 在模块中定义的 全局变量 、 函数 都是模块能够提供给外界直接使用的工具
注:如果在给 Python 文件起名时,以数字开头 是无法在 PyCharm
中通过导入这个模块的
示例:
新建 分隔线模块.py
def print_line(char, times):
"""打印单行分隔线
:param char: 分隔字符
:param times: 重复次数
"""
print(char * times)
def print_lines(char, times):
"""打印多行分隔线
:param char: 分隔线使用的分隔字符
:param times: 分隔线重复的次数
"""
row = 0
while row < 5:
print_line(char, times)
row += 1
name = "tom"
新建 hm_10_体验模块.py
文件,并且编写以下代码:
import hm_10_分隔线模块
分隔线模块.print_line("-", 80)
print(分隔线模块.name)
函数的递归
一个函数 内部 调用自自己叫递归
代码特点
- 函数内部的 代码 是相同的(自己调用自己),只是针对 参数 不同,处理的结果不同
- 当 参数满足一个条件 时,函数不再执行
- 这个非常重要,通常被称为递归的出口,否则 会出现死循环!
# 3 + 2 + 1
def sum_numbers(num):
# 1.如果是1,直接返回1 -- 出⼝
# 递归的出口很重要,否则会出现死循环
if num == 1:
return 1
# 2.如果不是1,重复执⾏累加:
result = num + sum_numbers(num-1)
# 3.返回累加结果
return result
sum_result = sum_numbers(3)
# 输出结果为6
print(sum_result)
变量的作用域
局部变量
局部变量是在函数内部 定义的变量,只能在函数内部使用;不同的函数,可以定义相同的名字的局部变量,但是彼此之间不会产生影响
def demo1():
num = 10
print(num)
num = 20
print("修改后 %d" % num)
def demo2():
num = 100
print(num)
demo1()
demo2()
print("over")
局部变量的生命周期:
- 所谓生命周期就是变量从被创建到被系统回收的过程;
- 局部变量 在 函数执行时 才会被创建;
- 函数执行结束后 局部变量 被系统回
局部变量的作用:
- 在函数内部使用,临时保存函数内部需要使用的数据
全局变量
全局变量是在函数外部定义 的变量,所有函数内部都可以使用这个变量
注:
函数执行时,需要处理变量时会:
- 首先 查找 函数内部 是否存在 指定名称 的局部变量,如果有,直接使用
- 如果没有,查找 函数外部 是否存在 指定名称 的全局变量,如果有,直接使用
- 如果还没有,程序报错!
不允许直接修改全局变量的引用:
- 使用赋值语句修改全局变量的值,只是定义了一个局部变量,不会修改到全局变量,只是变量名相同而已
- 如果在函数中需要修改全局变量,需要使用
global
进行声明全局变量定义的位置:
- 为了保证所有的函数都能够正确使用到全局变量,应该 将全局变量定义在其他函数的上方
# 全局作用域,定义的变量叫‘全局变量’
# 在任何地方都可以访问全局变量
n = 10
# 函数的入参属于局部变量
def foo(a):
# 函数内部是一个局部作用域,定义的变量为‘局部变量’
# 局部变量只能在当前函数内部访问,即在当前局部作用域里面访问
# 在其他函数内部也不能访问
m = 11
# 此时相当于定义了一个局部变量n,并不是对全局变量重新赋值
# 局部变量中如果有一个和全据变量重名的局部变量,优先使用局部变量,会把全局变量屏蔽掉
# 在函数内部给全局变量重新赋值,需要声明变量
# global n
n = 20
print('in foo:n=', n)
print('in foo:m=', m)
# 返回当前作用域所有的局部变量,不需要传参,返回值是一个字典
print(locals())
print('n =',n)
# 在函数外不能访问函数里面的变量
# print('m=',m)
foo(3)
多函数程序执⾏流程
共⽤全局变量
# 1. 定义全局变量
glo_num = 0
def test1():
global glo_num
# 修改全局变量
glo_num = 100
def test2():
# 调⽤test1函数中修改后的全局变量
print(glo_num)
# 2. 调⽤test1函数,执⾏函数内部代码:声明和修改全局变量
test1()
# 3. 调⽤test2函数,执⾏函数内部代码:打印
test2() # 100
返回值作为参数传递
def test1():
return 50
def test2(num):
print(num)
# 1. 保存函数test1的返回值
result = test1()
# 2.将函数返回值所在变量作为参数传递到test2函数
test2(result) # 50