Python基础
-5-函数
一、概念
写了一段代码实现了某个小功能;然后把这些代码集中到一起,起了一个名字;下一次就可以根据这个名字再次使用这个代码,这就是函数。
作用:(1)方便代码重用,(2)分解任务,简化程序逻辑,
二、定义
def 函数名():
函数体
函数的定义相当于开辟一块内存空间,定义一个与函数名相同的变量,指向这个函数
三、函数的参数、调用、返回值
形参:函数定义中的“参数名称”
实参:调用函数时传递的“真实数据(参数值)”
单个参数:
def 函数名(参数名称):
函数体
多个参数
def 函数名(参数名称1,参数名称2,……):
函数体
不定长参数(1)以元组的方式接收不定长参数,传入的参数以元组的形式保存在args中
def 函数名(*args):
函数体(2)以字典的方式接收不定长参数,传入的参数以字典的形式保存在kwargs中
def 函数名(**kwargs):
函数体(3)拆包与装包
装包->把传递的参数包装成一个元组或字典,这个过程就称之为装包
xx不定长参数函数(参数1,参数2....)
拆包->把元组、字典里面的参数,再次分解成单个的个体,称为拆包
xx函数(*args)#累加功能 def add(*args): num = 0 for i in args: num += i return num #累乘功能 def multiply(*args): num = 1 for i in args: num *= i return num #计算功能函数,一个参数接受一个运算符+或* #实现累加或累乘功能 def appliction(app,*args): if app == "+": #拆包 print(add(*args)) elif app == "*": #拆包 print(multiply(*args)) else: print("请正确输入".center(20,"-")) #装包 appliction("+", 1, 5, 8, 9, 10) appliction("*", 1, 5, 8, 9, 10) appliction("-", 1, 5, 8, 9, 10)
#运行结果 33 3600 -------请正确输入--------
缺省参数:包含其他参数时,缺省参数放最后
使用函数时,当不填写参数时,使用默认值
def 函数名(变量名1 = 默认值1 , 变量名2 = 默认值2):
函数体
调用
不带参数函数
函数名()带参数函数
函数名(参数1,参数2......) -这种方式实参和形参必须一一对应
函数名(参数名=参数1,参数名=参数2......)-这种方式实参和形参不必一一对应
Python参数传递:引用传递(地址传递)
但是修改一个不可变类型时,其实是让其指向一个新的变量
def test1(num):
print(id(num))
a = 10
print(id(a))
test1(a)
print("测试分割线".center(20, "-"))
def test2(num):
num = 20
print(id(num))
a = 10
print(id(a))
test2(a)
print("测试分割线".center(20, "-"))
def test3(num):
num.append(20)
print(id(num))
a = [5, 8]
print(id(a))
test3(a)
#运行结果
1588555520
1588555520
-------测试分割线--------
1588555520
1588555840
-------测试分割线--------
1460288630664
1460288630664
返回值
作用:当我们通过某个函数,处理好数据以后,想要拿到处理结果
注:(1)return后续代码不会被执行
(2)只能返回一次
(3)如果想要返回多个数据,可以把多个数据包装成一个“集合“,整体返回
四、函数的描述
作用:当我们编写三方函数,为了方便他人使用,就需要描述清楚我们所写的函数功能以及使用方式等信息
定义格式:
def 函数名():
"""这里写帮助信息"""
函数体一般函数的描述,需要说明以下几个信息:
(1)函数的功能
(2)参数: 含义、类型、是否可省略、默认值
(3)返回值:含义、类型
查看函数使用文档:help(函数名)
五、函数的高级使用
(1)偏函数:当我们写一个参数比较多的函数时,如果有些参数,大部分情况下都是某一个固定值;那么为了简化使用,就可以创建一个新函数,指定我们要使用的函数的某个参数为某个固定值;这个新函数就是“偏函数”
(2)高阶函数:当一个函数A的参数,接收的又是另一个函数时,这是把这个函数A称为高阶函数
(3)返回函数:是指一个函数内部,它返回的数据是另外一个函数;这样的操作称为“返回函数”
(4)匿名函数:也称为“lambda函数”
语法:
lambda 参数1 参数2 ... :表达式
表达式的结果就是返回值,只适用于一些简单的操作处理
(5)闭包
在函数嵌套的前提下,内层函数引用了外层函数的变量(包括参数),外层函数,又把‘内层函数’当做返回值进行返回。
这个内层函数+所引用的外层变量,称为“闭包”
标准格式:def test(a): b = 10 #定义了一个内部函数 def inner(): #内部函数引用了外层变量a、b c = 20 print(a*b*c) #把内部函数返回 return inner
注意事项:
(1)闭包中,如果要修改引用的外层变量
需要使用 nonlocal 变量声明,否则当做是闭包内新定义的变量(2)当闭包内,引用了一个后期会发生变化的变量时,一定要注意,当函数被调用时,才会真正确定对应值;之前都是以普通的变量标识存在。
(6)装饰器
作用:在函数名以及函数体不改变的前提下,给一个函数附加一些额外代码
原理:使用闭包
#定义一个xx装饰器,接收一个函数 def zsq(func): def inner(): """添加新的功能""" print("添加的新功能") func() return inner def test1(): """一些功能""" print("test1的功能") #把test变量指向装饰器返回的函数inner #inner里面又调用了test函数 #这样就看起来给test函数增加了新功能 test1 = zsq(test1) #语法糖写法 #@zsq 等价于 test2 = zsq(test2) @zsq def test2(): """一些功能""" print("test2的功能") test1() print("测试分割线".center(20,"-")) test2()
#运行结果 添加的新功能 test1的功能 -------测试分割线-------- 添加的新功能 test2的功能
注:装饰器执行时间,立即执行
装饰器进阶使用:
(1)装饰器叠加
从上到下“装饰“(添加新增代码)
从下到上执行(执行装饰器)
def zsq_1(func):
def inner():
print("-"*20)
func()
print("zsq1已经装饰完毕")
return inner
def zsq_2(func):
def inner():
print("*"*20)
func()
print("zsq2已经装饰完毕")
return inner
@zsq_1
@zsq_2
def test():
print("吾所成之事 ,不可逆也")
print("测试分割线".center(20, "-"))
test()
#运行结果
zsq2已经装饰完毕
zsq1已经装饰完毕
-------测试分割线--------
--------------------
********************
吾所成之事 ,不可逆也
(2)对有参函数进行装饰
无论装饰什么函数,保证函数调用参数个数一致
为了通用,可以使用不定长,结合拆包操作进行处理
def zsq(func):
def inner(*args):
print("-"*20)
func(*args)
return inner
@zsq
def test1(a, b):
print("两个数之和为:%d" % (a + b))
test1(10, 20)
@zsq
def test2(a, b, c):
print("三个数之和为:%d" % (a + b +c))
test2(10, 20, 30)
#运行结果
--------------------
两个数之和为:30
--------------------
三个数之和为:60
(3)对有返回值的函数进行装饰
直接返回函数调用
def zsq(func):
def inner(*args):
print("-"*20)
return func(*args)
return inner
@zsq
def test1(a, b):
return "两个数之和为:%d" % (a + b)
t1 = test1(10, 20)
@zsq
def test2(a, b, c):
return "三个数之和为:%d" % (a + b +c)
t2 = test2(10, 20, 30)
print(t1, t2, sep="\n")
#运行结果
--------------------
--------------------
两个数之和为:30
三个数之和为:60
(4)带有参数的装饰器
为了保证能使用语法糖写法:通过一个函数传递参数后,返回一个装饰器
def get_zsq(char):
def zsq(func):
def inner(*args):
print("分割线".center(40, char))
func(*args)
return inner
return zsq
@get_zsq("-")
def test1(a, b):
print("两个数之和为:%d" % (a + b))
@get_zsq("&")
def test2(a, b, c):
print("三个数之和为:%d" % (a + b + c))
test1(10, 20)
test2(20, 30, 52)
#运行结果
------------------分割线-------------------
两个数之和为:30
&&&&&&&&&&&&&&&&&&分割线&&&&&&&&&&&&&&&&&&&
三个数之和为:102
(7)生成器(不是函数)
当函数里面包含yeild语句,函数就变成了生成器
概念:是一个特殊的迭代器(迭代器的抽象层次更高),所以,拥有迭代器的特性惰性计算数据,节省空间,数据要使用时,才取,并且一个一个取能够记录状态,并通过next()函数,访问下一个状态,具备可迭代性
创建方式:
方式一:生成器推导式 - - 把列表推导式的[ ]修改成( )
方式二:函数中包含yield语句,这个函数的执行结果就是“生成器"
yeild语句作用:可以阻断当前的函数执行;当使用next()函数或者_ _next()_ _方法都会让函数继续执行,然后当执行到下一个yield语句时,又会被暂停,并返回yield语句后面的表示的值;gen = (a for a in range(10) if a%2==0) print(type(gen)) print(next(gen)) print(gen.__next__()) for a in gen: print(a) print("测试分割线".center(20, "-")) #斐波拉契数列生成器 def gen(): count = 1 a, b = 0, 1 while True: print("--第%d次--"%count) count += 1 yield a a, b = b, a + b g = gen() type(g) print(next(g)) for i in range(10): print(next(g))
#运行结果 <class 'generator'> 0 2 4 6 8 -------测试分割线-------- --第1次-- 0 --第2次-- 1 --第3次-- 1 --第4次-- 2 --第5次-- 3 --第6次-- 5 --第7次-- 8 --第8次-- 13 --第9次-- 21 --第10次-- 34 --第11次-- 55
获取数据方式:
使用next()函数
使用生成器对象__next__()方法for in 循环
生成器对象send()方法:send方法可接收一个参数,指定的是上一次被挂起的yield语句的返回值(注意第一次调用:g.send(None))
def gen(): recv1 = yield "第一次" print(recv1) recv2 = yield "第二次" print(recv2) recv3 = yield "第三次" print(recv3) recv4 = yield "第四次" print(recv4) g = gen() print(g.send(None)) print(g.send("枫叶")) print(g.send("银杏"))
第一次 枫叶 第二次 银杏 第三次
关闭生成器:
生成器对象close()方法,后续如果继续使用会抛出StopIteration异常
注意:(1)如果碰到return,会直接终止,抛出StopIteration异常(2)生成器只会遍历一次
(8)递归函数
函数A的内部,继续调用函数A
注:有传递就有回归,不然会陷入死循环
#求一个数字的阶乘
def jc(n):
if n == 1:
return 1
return n*jc(n-1)
print(jc(8))
print("测试分割线".center(20, "-"))
#求斐波拉契数列第n项的值,n大于等于2
def fblq(n):
if n == 1:
return 1
if n == 0:
return 0
return fblq(n-1) + fblq(n-2)
print(fblq(10))
#运行结果
40320
-------测试分割线--------
55
import time
#倒计时函数
def r_time(n):
print("倒计时:%d s" % n)
time.sleep(1)
n -= 1
if n == 0:
print("游戏结束")
return "结束"
r_time(n)
r_time(5)
#运行结果
倒计时:5 s
倒计时:4 s
倒计时:3 s
倒计时:2 s
倒计时:1 s
游戏结束
六、函数作用域
变量作用域:变量的作用范围,即可操作范围,Python是静态作用域,也就是说在Python中,变量的作用域源于它在代码中的位置;在不同的位置,可能有不同的命名空间
命名空间:是作用域的体现形式,不同的具体操作范围
Python-LEGB:L - local: 函数内的命名空间,作用范围:当前整个函数体范围
E - Enclosing function locals:外部嵌套函数命名空间, 作用范围:闭包函数
G - Global:全局命名空间,作用范围:当前模块文件
B - Builtin:内建模块命名空间,作用范围:所有模块(文件)
注意: Python没有块级作用域,块级作用域,代码块中,比如 if ,while ,for后的代码块中
LEGB访问规则:按照 L -> E -> G -> B的顺序进行查找
基于命名空间的常见变量类型:
局部变量:在一个函数内部定义的变量,作用域为函数内部查看局部变量: locals()
全局变量:在函数外部,文件最外层定义的变量,作用域为整个文件内部
查看全局变量:globals()
注:
(1)全局变量和局部变量重名,获取,就近原则 。
(2)函数内修改全局变量-声明 global 全局变量
num = 10
def test():
#函数内声明num使用的是全局变量num
global num
num = 100
test()
print(num)
#运行结果
100
(3)命名:尽可能使用 g_xxx 形式命名全局变量