Python函数
1. 定义一个函数
可以重复执行、可以重复调用的语句块
用于封装语句块, 提高代码的重用性。
函数是面向过程编程的最小单位
1.1 定义函数:def 语句
-
语法
def 函数名(形式参数列表):语句块
-
说明
-
函数名是一个变量,不要轻易对其赋值
-
函数有自己的名字空间,在函数外部不可以访问函数内部的变量,在函数内部可以访问函数外部的变量,但不能轻易对其改变
-
函数的形参列表如果不需要传入参数,形式参数列表可以为空
-
任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
-
函数内容以冒号 : 起始,并且缩进。
-
return [表达式] 结束函数,选择性地返回一个值给调用方,不带表达式的 return 相当于返回 None。
-
-
示例
# 定义一个函数,用 say_hello 变量绑定 def say_hello():print("hello world!")print("hello tarena!")print("hello everyone!")# 定义一个函数,传入两个参数,让这个函数把最大的值打印到终端 def mymax(a, b):if a > b:print("最大值是", a)else:print("最大值是", b)
#函数创建 #无参数函数的创建 def attack():print("hello world") attack() #有参数函数的创建 def attack2(count):for i in range(count):print(i) attack2(3)
1.2 函数的调用
-
语法
函数名(实际调用传递参数)
-
说明
-
函数调用是一个表达式
-
如果函数内没有return 语句,函数执行完毕后返回 None 对象
-
-
示例
# 调用 say_hello() # 调用一次 say_hello() # 调用第二次 # 调用 mymax(100, 200) mymax(999, 1) mymax('abc', 'cba')
1.3 return 语句
-
语法
return [表达式]
注: [] 代表 内部的内容可以省略
-
作用
用于函数的内部,结束当前函数的执行,返回到调用此函数的地方,同时返回一个对象的引用关系
-
说明
-
return 语句后面的表达式可以省略,省略后相当于 return None
-
如果函数内部没有 return 语句, 则函数执行完毕后返回None, 相当于在最后一条语句后有一条return None
-
-
示例
def say_hello():print("hello aaa")print("hello bbb")return 1 + 2print("hello ccc") r = say_hello() print(r) # 3
def fun01():print("hello world")return 100#后面写条件或者循环语句可以打印print(" gh")# 这行不会打印 re=fun01() print(re)
2. 函数参数
2.1 函数的调用传参
-
位置传参
实际参数传递时,实参和形参按位置来依次对应
-
关键字传参
实际参数传递时,实参和形参 按名称依次对应
注: 位置传参要先于关键字传参
示例
def myfun1(a, b, c):print('a=', a)print('b=', b)print('c=', c) ## 位置传参 myfun1(1, 2, 3) ## 关键字传参 myfun1(c=33, a=11, b=22) ## 位置传参要先于关键字传参 myfun1(111, c=333, b=222) # 正确 # 序列实参:使用星号将序列拆分后,与形参进行对应 list01 = [7,8,9] myfun01(*list01) # 字典实参:使用双星号将字典拆分后,依次与形参对应 dict01 = {"c":3,"b":2,"a":1} myfun01(**dict01)
2.2 函数的形式参数定义方法
函数的缺省参数(默认参数)
语法
def 函数名(形参名1=默认实参1, 形参名2=默认实参2, ... ):语句块
说明
缺省参数即默认实参,必须自右向左依次存在(即,如果一个参数有缺省参数,则其右侧的所有参数都必须有缺省参数)
示例
def myadd4(a, b, c=0, d=0): print(a)print(b)print(c)print(d)return a + b + c + d print(myadd4(1, 2)) print(myadd4(1, 2, 3)) print(myadd4(1, 2, 3, 4))
错误示例
>>> def myadd(a, b=0, c): # 报错 ... pass
形参的定义
-
位置形参
-
星号元组形参(*args)
-
命名关键字形参
-
双星号字典形参(**kwargs)
1)位置形参
-
语法:
def 函数名(形参名1, 形参名2, ...):pass
2)星号元组形参
-
语法
def 函数名(*元组形参名):pass
-
作用
收集多余的位置实参
元组形参名一般命名为args
-
示例
def myfunc2(*args):print("len(args)=", len(args))print('args=', args)myfunc2() # args=() myfunc2(1, 2, 3) # args=(1, 2, 3) def myfunc3(a, b, *args):print(a, b, args) myfunc3(1, 2) # 1-->a, 2-->b, ()--->args myfunc3(1, 2, 3, 4) # # 1-->a, 2-->b, (3, 4)--->args
3)命名关键字形参
-
语法
def 函数名(*, 命名关键字形参1, 命名关键字形参2, ...):pass # 或者 def 函数名(*args, 命名关键字形参1, 命名关键字形参2, ...):pass
-
作用
强制,所有的参数都必须用关键字传参
-
示例
def myfunc4(a, b,*args, c, d):print(a, b, c, d) myfunc4(1, 2, d=4, c=3) # 正确,c,d 必须关键字传参 myfunc4(1, 2, 3, 4) # 错误
4)双星号字典形参
-
语法
def 函数名(**kwargs):pass
-
作用
收集多余的关键字传参
字典形参名最多有一个,
字典形参名 一般命名为 kwargs
-
示例
def myfunc5(**kwargs):print(kwargs) # {'name': 'tarena', 'age': 18}-->kwargs myfunc5(name='tarena', age=18)
函数的形参定义方法说明
-
位置形参,星号元组形参,命名关键字参数,双星号字典形参,缺省参数可以混合使用。
-
函数的形参定义自左至右的顺序为:位置形参,星号元组形参,命名关键字参数,双星号字典形参
示例:
def fn(a, b, *args, c, d, **kwargs):print(a)print(b)print(*args)print(c)print(d)print(kwargs) fn(100, 200, 300, 400, c='C',name='tarena',d='D')
#形式参数-形参 #默认形参-默认参数不能放在前面 def fun01(a,b="NONE",c=0):#def fun01(b="NONE",a,c=0):错误写法print(a)print(b)print(c) fun01(1) #位置形参 #星号元组形参:将位置实参合并为一个元组 #通用写法:args def fun01(*args):print(*args) #fun01(1,2,3) #命名关键字形参 #强制,所有的参数都必须用关键字传参 def fun04(*args,a,b,c):print(args)print(a)print(b)print(c) fun01(1,2,3,a=4,b=5,c=6) #双星号字典形参:收集所有的传入数据,打包成字典的形式 #通用写法**kwargs-约定俗成 def fun02(**kwargs):print(kwargs) fun02(1,2,3) def fun03(*args,**kwargs)print(args)print(kwargs)#混合使用 fun03(1,2,3,a=1,b=2)
思考:
print() # 函数是如何定义的呢: def myprint(*args, sep=' ', end='\n'):pass
#函数参数 #实际参数(调用参数)-实参 #位置参数 #序列实参 #关键字参数 #字典实参 def fun01(a,b,c):print(a)print(b)print(c) #fun01(1,2,3)#位置传参交换顺序传参也不一样 fun01(a=11,c=33,b=22)#关键字传参交换顺序也可以正确传参 fun01(1,c=4,b=5)#混合传参 #位置传参需要放到关键字传参之前 #fun01(c=2,2,3) #序列实参:用*将序列拆分后,与形参形成对应,传入函数 list01=[1,2,3] fun01(*list01) #字典实参:将字典拆分传入参数-键名对应 dict01=dict(a=1,b=2,c=3) print(dict01) fun01(**dict01) def sum4():print(a)print(b) a=float(input("")) a=float(input("")) a=float(input("")) a=float(input(""))
2.3 可变不可变
可更改(mutable)与不可更改(immutable)对象
在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。
-
不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变 a 的值,相当于新生成了 a。
-
可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。
python 函数的参数传递:
-
不可变类型:值传递: 如整数、字符串、元组。如 fun(a),传递的只是 a 的值,没有影响 a 对象本身。如果在 fun(a) 内部修改 a 的值,则是新生成一个 a 的对象。
-
可变类型:引用传递: 如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后 fun 外部的 la 也会受影响
python 中一切都是对象(后面会讲),严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。
传不可变对象实例
通过内置的 id() 函数来查看内存地址变化:
def change(a):print(id(a)) a=10 print(id(a))#1428793408 a=1 print(id(a))#1428793264 #总结:可以看见在调用函数前后,形参和实参指向的是同一个对象(对象 id 相同),在函数内部修改形参后,形参指向的是不同的id。
传可变对象实例
可变对象在函数里修改了参数,那么在调用这个函数的函数里,原始的参数也被改变了。
def changeme( mylist ):mylist.append(40)print (mylist) mylist = [10,20,30] changeme(mylist)#[10, 20, 30, 40] print (mylist)#[10, 20, 30, 40] #总结:传入函数的和在末尾添加新内容的对象用的是同一个引用
3. 返回值
函数可以使用return
语句来返回一个或多个值。
如果没有明确的return
语句,函数将默认返回None
。
#没有return def fn1():print("调用了函数") re=fn1() print(re)#None #返回一个值 def fn2(x):return x+100 re2=fn2(100) print(re2)#200 #返回多个值 def fn3(x):return x*x,x%3 re3=fn3(100) print(re3)#(10000, 1) x,y=fn3(100) print(x,y)#10000 1
4. 匿名函数
在Python中,匿名函数通常使用lambda
关键字来创建。匿名函数也被称为lambda函数,它是一种简单的、一行的函数,常用于临时需要一个小函数的地方。匿名函数的语法如下:
-
语法
lambda [函数的参数列表]: 表达式
-
作用
-
创建一个匿名函数对象
-
lambda
是关键字,表示你正在定义一个匿名函数。 -
同 def 类似,但不提供函数名
-
[函数的参数列表]
是函数的参数,可以有零个或多个参数,参数之间用逗号分隔。 -
: 表达式
是函数的返回值,通常是一个表达式,匿名函数会计算这个表达式并返回结果。
-
-
说明
lambda 表达式 的创建函数只能包含一个表达式
-
示例
def myadd(x, y):return x + y print('1 + 2 =', myadd(1, 2)) # 3 # myadd 函数可以改写成 myadd2 = lambda x, y: x + y print('3 + 4 =', myadd2(3, 4)) # 7 square = lambda x: x * x print(square(5)) # 输出: 25
#匿名函数 def myadd(x,y):return x+y re=myadd(3,4) print(re) myadd2=lambda x,y:x+y#匿名函数 re=myadd(3,4) print(re)
5. 变量作用域
5.1 什么是变量作用域
一个变量声明以后,在哪里能够被访问使用,就是这个变量"起作用"的区域:也就是这个变量的作用域
一般来说,变量的作用域,是在函数内部和外部的区域 来体现,因此常常与函数有关
5.2 局部变量和全局变量
-
局部变量
-
定义在函数内部的变量称为局部变量(函数的形参也是局部变量)
-
函数内部的变量只能在函数内部或者函数内部的函数内部访问 ,函数外部不能访问
-
局部变量在函数调用时才能够被创建,在函数调用之后会自动销毁
-
-
全局变量
-
定义在函数外部,模块内部的变量称为全局变量
-
全局变量, 所有的函数都可以直接访问(取值,但函数内部不能直接将其赋值改变)
-
-
局部变量示例
>>> def fn(a, b): ... c = 100 ... print(a, b, c) # a, b, c三个都是局部变量 >>> fn(1, 2) >>> print(a, b, c) # 报错, 因为a,b,c 在调用后就销毁了
-
全局变量示例
a = 100 # 全局变量 def fx(b, c): # b, c 局部变量d = 400 # d 局部变量print(a, b, c, d)fx(200, 300) print(a) # 100 print(b) # 报错, 因为此时 b 不存在了
-
全局变量示例2
a = 100 # 全局变量 def fx(b):a = 666 # 创建局部变量,不是改变全局变量c = 300print(a, b, c) # 优先访问局部变量fx(200) # 666 200 300 print(a) # 100
#函数变量的作用域 #全局变量-访问顺序LEGB a=100 def fun01():a=20def fun02():#局部作用域a=10print() fun02() fun01()
5.3 局部作用域修改全局变量
-
问题
## 如何用一个变量来记录一个函数调用的次数 count = 0 def hello(name):print('hello', name)count += 1 # 等同于 count = count + 1 # 如何让 此语句能改变全局变量而不是创建局部变量 hello('小张') hello('小李') print('您共调用hello函数', count, '次') # 2 次
-
作用
告诉python 的解释执行器, global 语句声明的一个或多个变量, 这些变量是全局变量
-
语法
global 变量名1, 变量名2, ....#不可变类型
-
示例
# 如何用一个变量来记录一个函数调用的次数 count = 0 def hello(name):global count # 声明 global 是全局变量print('hello', name)count += 1 # 等同于 count = count + 1 hello('小张') hello('小李') hello('小魏') print('您共调用hello函数', count, '次') # 3 次
-
global 说明
-
全局变量如果要在函数内部被赋值,则必须经过全局声明 global
-
默认全局变量在函数内部可以使用,但只能取值,不能赋值
-
不能先声明为局部变量,再用 global 声明为全局变量,此做法不符合语法规则
-
-
函数的形参已经是局部变量,不能用 global 声明为全局变量
错误示例
a = 100 b = 200 def fa(a):b = 20 # SyntaxError: name 'b' is assigned to before global declarationglobal bb = 222
5.4 局部作用域修改外部变量
在函数内部 提前用nonlocal声明 函数内部的某个变量为外部的变量
前提:必须是函数嵌套
说明:
(1)变量的查找顺序还是遵从:LEGB顺序
在 Python 中,LEGB 代表四种作用域的查找顺序:Local、Enclosing、Global 和 Built-in。
(2)局部作用域中若要修改外部函数嵌套作用域中的变量需要使用:nonlocal 语句
-
格式:nonlocal 变量名
-
作用:将局部作用域中变量声明为外部函数嵌套作用域中的变量。
LEGB顺序
Local (L):
-
本地作用域,指当前函数内部的变量。
-
当你在函数内部定义变量并尝试访问它时,Python 首先会在函数内部查找这个变量。
def outer_function():x = 10 # Local variableprint(x) # 查找顺序从 Local 开始
Enclosing (E):
-
闭包函数外的函数作用域,指嵌套函数的外部函数中定义的变量。
-
如果在当前函数内部找不到变量,Python 会查找外层(闭包)函数中的变量。
def outer_function():x = 10 # Enclosing variabledef inner_function():print(x) # 查找顺序从 Enclosing 开始inner_function()
Global (G):
-
全局作用域,指模块级别定义的变量。
-
如果在本地和闭包函数中找不到变量,Python 会查找全局作用域的变量。
x = 20 # Global variable def outer_function():print(x) # 查找顺序从 Global 开始
Built-in (B):
-
内建作用域,指 Python 预定义的变量、函数等,如
len
、sum
等。 -
如果在以上三个作用域中都找不到变量,Python 会查找内建作用域。
def outer_function():print(len) # 查找顺序从 Built-in 开始
nonlocal
"""外部嵌套作用域 """ def func01():a = 10def func02():# 内部函数,可以访问外部嵌套变量# print(a)# 内部函数,如果修改外部嵌套变量,需要使用nonlocal语句声明nonlocal aa = 20func02()print(a)func01()
#在函数内部修改全局变量 def fun03():global aa=200print(a) fun03() print(a) #修改嵌套函数的变量 def fun01():a=20def fun02():#局部作用域nonlocal aa=100 fun02()print(a)
6. 函数的内存分配
1、将函数的代码存储到代码区,函数体中的代码不执行。
2、调用函数时,在内存中开辟空间(栈帧),存储函数内部定义的变量。
3、函数调用后,栈帧立即被释放。
def func(a, b):a = 20b[0] = 20 a = 10 b = [100] func(a, b) print(a) # 10 print(b) # 20
(1) 不可变类型参数有:
-
数值型(整数,浮点数)
-
布尔值bool
-
None 空值
-
字符串str
-
元组tuple
(2) 可变类型参数有:
-
列表 list
-
字典 dict
-
集合 set
(3) 传参说明:
-
不可变类型的数据传参时,函数内部不会改变原数据的值。
-
可变类型的数据传参时,函数内部可以改变原数据。
#函数内存分配 #传入的是不可变类型,那么函数内部变量是不能直接改变的 #传入的是可变的类型,函数内部可以直接修改 def fun01(data1,data2):data1=10data2[0]=100 a=20 b=[1,2,3] fun01(a,b) print(a) print(b)#20 [100,2,3]
7. 函数自调用(递归)
-
函数直接或间接的调用自身
讲故事:
-
从前有座山,山上有座庙,庙里有个老和尚讲故事
-
从前有座山,山上有座庙,庙里有个老和尚讲故事
-
从前有座山,山上有座庙,庙里有个老和尚讲故事
-
-
示例:
# 函数直接的调用自身 def f():f() # 调用自己# 此递归达最大深度出错 # f() print("递归完成") # 函数直接的调用自身 def fa():fb() def fb():fa() fa() print("递归完成")
说明:
-
递归一定要控制递归的层数,当符合某一条件时要终止递归调用
-
几乎所有的递归都能用while循环来代替
递归的实现方法:
-
先假设此函数已经实现
递归优缺点:
-
优点:
-
递归可以把问题简单化,让思路更为清晰,代码更简洁
-
-
缺点:
-
递归因系统环境影响大,当递归深度太大时,可能会得到不可预知的结果
-
限制层数递归示例:
递归函数示例 计算阶乘 factorial 迭代实现
def factorial(n):result = 1i = 1while i <= n:result *= ii+=1return result print("factorial: ", factorial(5))
递归实现示例
# file: factorial.pydef factorial(n):if n == 1:return 1s = n * factorial(n-1)return sprint("factorial: ", factorial(5))
#递归思想 #计算阶乘 #非递归的思想 def fun01(n):result=1i=1while<=n:result*=ii+=1return result re=fun01(10) print(re) #递归的思想:递归边界 自调用 调用本身的函数 def fun01(n):if n==1:#递归边界 返回条件是什么return 1#调用自己result=n*fun01(n-1)return result re=fun02(10) print(re)