模块化编程
指把程序进行封装(函数封装,面向对象,文件...)
10. 函数
- 定义
def 函数名([参数列表]):
当前函数的具体功能代码... - 调用
函数名([参数列表]):
系统将函数调用的参数和局部变量存放在内存的哪一部分:stack
def func(x):
if not instance(x,(int,float)):
raise TypeError('bad operand type')
if x >= 0:
return x
else:
return -x
10.1 函数的参数类型
普通参数,默认参数,收集参数,命名关键字参数
普通参数
就是位置参数,也叫顺序参数,也是必须传递的参数 ——可把普通函数的普通参数按照关键字参数进行传递
def a(age, name):
print(age)
print(name)
a(name='dsgs',age=3)
默认参数
比如要求有三个参数,x,y,i,至少要传递两个
这是最后一个参数就是默认参数,但是默认参数,需要在定义形参时,指定默认值
def func(x,y,i=100):
print(x,y,i)
调用传递了默认的参数,那么里面使用的就是传递的实参
func(1,2,3)
调用没有传递默认的参数,使用默认值
func(1,2)
默认参数必须指向不变对象!
def add_end(L=[]):
L.append('END')
return L
# 修改后
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
>>> add_end()
>>> add_end()
收集参数
——定义一个形参,专门收集多余的实参,又叫 可变参数
——或者理解为,不确定要传递多少个实参,直接用一个形参来接收
——定义函数时,在形参前面加一个 * 号,如:*args,*m
——接收的多余参数,会存储在args/m中,形成一个元组
def func(a,x="+",*args):
if x == "+":
print('add',args)
else:
print('minus',args)
func(1.2,"-",2,3); minus (2, 3)
命名关键字参数
——关键字参数定义在 收集参数 后面
——关键字参数必须通过形参的名字来进行传递
def lo(a,b,c=3,*args,name):
print(a,b,c)
print(args)
print(name)
lo(1,2,4,5,6,7,name="admin")
关键字参数收集
——普通收集参数,会把多余的参数收集为 元组
——#关键字参数收集,会把多余的关键字参数收集为 字典
def lo(a,b,c=3,*args,name,age,**kw):
print(a,b,c)
print(args)
print(kw)
lo(1,2,6,112,1133,name='lim',age=44,sex='fel', wo='ha')
- 注意形参声明的位置:
- 普通参数,默认参数,收集参数,关键字参数,关键字收集参数!!
- print(a=11,b=22,c=4,55,name='hi',age=20,sex=1) 这样是错误的!!
- 如果单独出现星号 *,则星号 * 后的参数必须用关键字传入:
def one(a,b,*,c): return a+b+c one(1,2,3) #error one(1,2,c=3)
* 强制位置参数
Python3.8 新增了一个函数形参语法 / 用来指明函数形参必须使用指定位置参数,不能使用关键字参数的形式。
# 形参 a 和 b 必须使用指定位置参数,
# c 或 d 可以是位置形参或关键字形参,
# 而 e 和 f 要求为关键字形参:
def f(a, b, /, c, d, *, e, f):
print(a, b, c, d, e, f)
f(10, 20, 30, d=40, e=50, f=60)
f(10, b=20, c=30, d=40, e=50, f=60) # b 不能使用关键字参数的形式
f(10, 20, 30, 40, 50, f=60) # e 必须使用关键字参数的形式
10.2 函数返回值
调用带有返回值的函数时,函数中的返回值会返回到函数调用处
——函数可使用return进行数据的返回
——return可返回任意内容或数据
——return意味着函数的结束,return之后代码不再执行
——若没有使用return或return之后无内容,默认返回None
def lo(a,b):
res = f'{a},i love {b}'
##print(11)
return res
lo("a",'bb');并不会输出结果
print(lo("any",'banana'))
r=lo('a','b')
print(r)
def one(a,b):
print('niceeee')
return
练习:设计函数可接收一个或多个数并计算乘积:
def func(x,*y):
for i in y:
print(i)
x = x * i
return x
def f(): pass
print(type(f()))
以上代码输出结果为?
<class 'function'>
<class 'tuple'>
<class 'NoneType'>
<class 'str'>
<class 'type'>
## type() 的参数是函数的返回值,该返回值为 None
10.3 变量的作用域
++ 函数内可以获取函数外部的变量-全局变量, 但不能更改
++ 函数内定义的变量-局部变量,函数外不能使用
++ 变量分两种:
- 可变数据类型:函数外定义的变量,函数内可以使用:列表和字典
- 不可变数据类型:函数外定义的,函数内只能访问,不能更改
++函数内部使用global关键字定义的变量就是全局变量
++函数外定义的变量,在函数内使用global关键字进行声明,也是全局变量
- globals():获取全局数据,在任意位置都能获取
- locals():只获取当前作用域的数据
- nonlocal关键字
变量和函数都有作用域
在内函数中如何使用上一层函数中的局部变量
def outer():
n = 10
def inner():
#nonlocal n
n += 1
print(n)
inner()
outer()
//inner()错误的
- 魔术变量
__name__ 当前脚本若作为主程序,那么值是__main__,若是当做一个模块,在另一个脚本中引用,那么值是当前文件档
__doc__ 当前脚本的文档说明,在当前脚本中第一个 三引号注释,就是当前脚本的说明文档
函数的说明文档 - 函数中第一个三引号注释 def outer():
'''
这里就是写当前函数的文档说明的
需要说明当前函数的作用
若还有形参,也要一一说明
name:
age:表示当前的一个年龄
:return:
'''
n = 10
——print(outer.__doc__);获取当前函数的说明文档
函数封装的练习题
- 打印九九乘法表,矩形,十二生肖
——九九,正方向
def NineM(n=0):
'''
九九乘法表
n=0时输出正向,默认的,1为反向
:param n:
:return:
'''
if n = 0:
rs = range(1,10)
else:
rs = range(9,0,-1)
for a in rs:
for b in range(1, a+1):
print(f'{a}x{b}={a*b}', end=' ')
print()
NineM()
——矩形
def sq(n,x,y):
print('# '*x)
for t in range(1,y-1):
if n == 1:
print('# '*x)
elif n == 0:
print('# ',' '*2*(x-3),'#')
# print(f'# {bb}#')
# bb=' '*2*(x-3)
print('# '*x)
sq(0,3,4)
练习题: def greetPerson(*name): print('Hello', name) greetPerson('Runoob', 'Google') 以上代码输出结果为? Hello Runoob Hello Google Hello ('Runoob', 'Google') Hello Runoob 错误!函数只能接收一个参数。
加了星号 * 的参数会以元组(tuple)的形式导入,存放所有未命名的变量参数。third
10.4 递归函数
- 定义了一个函数,函数内自己调用自己
- 递归函数必须要有结束,否则会一直调用下去,直到栈溢出
- 递归函数是一层一层的调用,再一层一层的返回
就像坐在最后一排想要第一排妹子的电话,一个一个拍过去问,直到拍到第一排要到电话,最后一层层返回
def di(n):
print(n)
if n > 0:
di(n-1)
print(n)
di(3)
di(3)==> 3
di(3-1)==> 2
di(2-1)==> 1
di(1-1)==> 0
di(0)==> 0
di(1)==> 1
di(2)==> 2
di(3)==> 3
练习题-斐波那契数列
def feibo(x):
if x == 1 or x == 2:
return 1
else:
print(x-1,x-2)
s = feibo(x - 1) + feibo(x - 2)
return s
print(feibo(1))
print(feibo(6))
练习题-阶乘
def jie(n):
if n == 1:
return 1
else:
return n*jie(n-1)
s = jie(9)
print(s)
- 递归函数效率并不高,能不用就不用
- 一个函数调用后若没有结束,在栈空间就一直存在,直到函数运算结束才销毁
练习:
汉诺塔问题 : 由一根针上移到另一根针上,并且始终保持上小下大的顺序。
编写move(n, a, b, c)函数,它接收参数n,表示3个柱子A、B、C中第1个柱子A的盘子数量,然后打印出把所有盘子从A借助B移动到C的方法
# tip:每次分成n-1和1两份
def move(n, a, b, c):
if n == 1:
print('move', a, '-->', c)
else:
move(n-1, a, c, b) #游戏要求移动过程中必须大盘在下,要把N-1个盘移动到b柱上必须得借助c柱,不可能一下子搬过去
move(1, a, b, c) #直接从a上把最大盘移动到c柱上了
move(n-1, b, a, c) #把b柱上N-1个盘再通过a柱,一个一个的移动到c柱上
move(4, 'A', 'B', 'C')
10.5 高阶函数
回调函数,闭包函数,匿名函数,迭代器,生成器
10.5.1 回调函数
函数调用的参数为函数
//如果将函数指针作为参数被传递到函数中,当这个函数指针被用于调用其指向的函数时,我们就称这个函数为回调函数
回调函数的主要优势是解耦,即调用者和被调用者被分开,调用者无需了解谁是被调用者,它只需知道如果满足某条件之后,函数将被调用
def func(x, y, f):
'''
当前函数接受两个数值,并把这两个数值传递给第三个参数进行运算
x,y, int ; f, function
:param x:
:param y:
:param f:
:return:
'''
#print(f([x,y]))
print(f(x, y))
func(1,2,sum) #sum计算一个容器
func(2,2,pow) 幂函数
10.5.2 闭包函数
函数内返回一个内函数,且内函数中使用了外函数的局部变量
特点
- 在外函数中定义局部变量,内函数总是用了这个局部变量
- 外函数中返回内函数,返回的内函数就是闭包函数
- 主要在于保护了外函数中的局部变量,既可以被使用,又不会被破坏
def person():
money = 0
def work():
nonlocal money
money += 100
print(money)
#在外函数中返回内函数,闭包
return work
res = person()
res() # = work()
res()
res()
- work()中是print不是return!!
- 此时不能在全局中对money进行任何此操作!
如何检测是否为闭包函数:
函数名.__closure__如果是闭包函数返回cell
print(res.closure)
10.5.3 匿名函数 lambda表达式
可以不使用def定义,且函数没有名字;
注意:lambda表达式仅仅是一个一行代码,不是代码块,又称为一行代码的函数;
不能访问除了自己形参外的数据包含全局变量
lambda [arg1 [,arg2,.....argn]]:expression
#lambda 参数列表:返回值
def add(x, y):
return x+y
print(add(3,4))
res = lambda a,b:a+b
print(res(4,4))
# lambda是一个表达式,不能写太复杂的逻辑,功能相对单一
# 可以使用分支结构
def func(sex):
if sex == 'male':
return 'handsome'
else:
return 'beauty'
res = func('male')
print(res)
# lambda 参数列表:真区间 if 表达式判断 else 假区间
ret = lambda sex:'nicce'if sex == 'male'else'good'
print(ret('man'))
# 可以将匿名函数封装在一个函数内,这样可以使用同样的代码来创建多个匿名函数
def test(n):
return lambda a : a * n
doubler = test(2)
tripler = test(3)
print(doubler(11))
print(tripler(11))
10.5.4 迭代器
python中最具特色的功能之一,是访问集合元素的一种方式
迭代器是一个可以记住访问遍历的位置的对象
从集合第一个元素开始到左右元素被访问完毕
只能从前往后一个一个的遍历,不能后退
能被next()函数调用,并不断放回下一个值的对象称为iterator
迭代器:
range(10,3,-1) #返回一个可迭代对象
for i in range(10,2,-1):
print(i)
arr = [99,8,'yy',1]
for i in arr:
print(i)
iter()
功能:把可迭代的对象,转为一个迭代器对象
参数:可迭代的对象(str,list,tuple,dict,set,range...)
返回值:迭代器对象
注意:迭代器一定是一个可迭代的对象,但是可迭代对象不一定是迭代器
f4 = ['one','two','three','four']
# 可以使用for循环来遍历数据、
# 可以把可迭代对象转为迭代器使用
res = iter(f4)
print(res,type(res))
迭代器的取值方案:
- next()调用一次获取一次,直到数据被取完
- list()直接取出所有的数据
- for 使用for循环遍历迭代器的数据
# 1. 使用next()函数去调用迭代器对象
r = next(res)
print(r)
print(list(res)) #2.list()
r = next(res) #超出可迭代的范围 StopIteration
print(r)
# print(next(res))
# print(next(res))
# print(next(res)) #超出可迭代的范围 StopIteration
# 3. for 循环
for i in res:
print(i)
# print(next(res)) #超出可迭代的范围 StopIteration
迭代器取值特点:
- 取出一个少一个,直到取完
检测迭代器和可迭代对象的方法
- from collections.abc import Iterator,Iterable
from collections.abc import Iterator,Iterable
varstr = '12345'
res = iter(varstr)
# type()函数返回当前数据的类型
# isinstance()检测一个数据是不是一个指定的类型
r1 = isinstance(varstr, Iterable)
r2 = isinstance(varstr, Iterator)
r3 = isinstance(res, Iterable)
r4 = isinstance(res, Iterator)
print(r1,r2,r3,r4)
#迭代器一定是一个可迭代对象,可迭代对象不一定是一个迭代器
2.next()函数检测
next(varstr) #TypeError: 'str' object is not an iterator
10.5.5 生成器
在 Python 中,使用了 yield 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象
StopIteration
StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 __next__() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。