-
定义函数
-
函数参数
-
名称空间
-
作用域
-
函数对象
-
闭包
-
装饰器
-
迭代器
-
生成器
-
递归
-
匿名函数
-
内置函数
1、定义函数
def 函数名(参数1,参数2,...):
"""文档描述"""
函数体
return 值
"""
def: 定义函数的关键字;
函数名:函数名指向函数内存地址,是对函数体代码的引用。函数的命名应该反映出函数的功能;
括号:括号内定义参数,参数是可有可无的,且无需指定参数的类型;
冒号:括号后要加冒号,然后在下一行开始缩进编写函数体的代码;
"""文档描述""": 描述函数功能,参数介绍等信息的文档,非必要,但是建议加上,从而增强函数的可读性;
函数体:由语句和表达式组成;
return 值:定义函数的返回值,return是可有可无的。
"""
2、函数参数
函数的参数:形式参数、实际参数,简称形参和实参。
形参:在定义函数时,括号内声明的参数。形参本质就是一个变量名,用来接收外部传来的值。
实参:在调用函数时,括号内传入的值,值可以是常量、变量、表达式或三者的组合。
在调用有参函数时,实参(值)会赋值给形参(变量名)。
2.1 位置参数
位置即顺序,位置参数指的是按顺序定义的参数。
按照从左到右的顺序依次定义形参,称为位置形参,凡是按照这种形式定义的形参都必须被传值
在调用函数时,按照从左到右的顺序依次定义实参,称为位置实参,凡是按照这种形式定义的实参会按照从左到右的顺序与形参一一对应
>>> def func(a,b,c): # a,b,c 位置形参
print(a,b,c)
>>> func(1,2,3) # 1,2,3 位置实参,必须按顺序,不能多也不能少,不然会报错。
1 2 3
2.2 关键字参数
在调用函数时,实参可以是key=value的形式,称为关键字参数,凡是按照这种形式定义的实参,可以完全不按照从左到右的顺序定义,但仍能为指定的形参赋值
需要注意在调用函数时,实参也可以是按位置或按关键字的混合使用,但必须保证关键字参数在位置参数后面,且不可以对一个形参重复赋值
>>> def func(a,b,c):
print(a,b,c)
>>> func(1,c=2,b=3) # 关键字参数不能在位置参数的前面,可以不按顺序赋值
1 3 2
2.3 默认参数
在定义函数时,就已经为形参赋值,这类形参称之为默认参数
定义了默认参数可以不对其赋值,也可以对其赋值。
>>> def func(a,b,c=None): # c 默认参数,必须在位置参数后面,一般默认参数都使用不可变数据类型
if c == None: # 如果是可变数据类型,可采用此种方式
c = {}
print(a,b,c)
>>> func(1,2) # 可以不对默认参数赋值
1 2 {}
2.4 可变长度的参数
参数的长度可变指的是在调用函数时,实参的个数可以不固定。
1、可变长度的位置参数(*args)
如果在最后一个形参名前加号,那么在调用函数时,溢出的位置实参,都会被接收,以元组的形式保存下来赋值给该形参
>>> def func(a,b,c,*args): # 多余的位置参数会被*args以元组的形式接受
print(a,b,c,args)
>>> a = [4,5,6]
>>> func(1,2,3,4,a) # 可以传入多余形参的位置实参,但不能少于
1 2 3 (4, [4, 5, 6])
>>> func(1,2,3,*a) # 传入参数是个列表,前面加个*可将其拆分传入函数,等同于func(1,2,3,4,5,6)
1 2 3 (4, 5, 6)
2、可变长度的关键字参数(**kwargs)
如果在最后一个形参名前加号,那么在调用函数时,溢出的关键字参数,都会被接收,以字典的形式保存下来赋值给该形参
>>> def func(a,**kwargs): # 多余的关键字参数会被**kwargs以字典的形式接受
print(a,kwargs)
>>> a = {'y': 1, 'z': 2}
>>> func(1,x=1,z=a) # 可以传入多余形参的关键字参数,但不能少于
1 {'x': 1, 'z': {'y': 1, 'z': 2}}
>>> func(1,x=1,**a) # 传入参数是字典,前面加个**可将其拆分传入函数,等同于func(1,x=1,y=1,z=2)
1 {'x': 1, 'y': 1, 'z': 2}
2.5 命名关键字参数
需要在定义形参时,用 * 作为一个分隔符号,号之后的形参称为命名关键字参数。
对于这类参数,在函数调用时,必须按照key=value的形式为其传值,且必须被传值。
1、基本语法
>>> def func(a,b,*,c,d): # c,d为命名关键字参数
pass
>>> func('1',2,c='3',d=4) # 正确使用
>>> func('1',2,'3',4) # TypeError:未使用关键字的形式传值
>>> func('1',2,c='3') # TypeError没有为命名关键字参数d传值。
2、命名关键字参数也可以有默认值
>>> def func(a,b,*,c,d=4): # c,d为命名关键字参数
pass
>>> func('1',2,c='3',d=4) # 正确使用
>>> func('1',2,'3',4) # TypeError:未使用关键字的形式传值
>>> func('1',2,c='3') # 正常使用
2.6 组合使用
所有参数可任意组合使用,但定义顺序必须是:位置参数、默认参数、args、命名关键字参数、kwargs
可变参数args与关键字参数kwargs通常是组合在一起使用的,如果一个函数的形参为*args与kwargs,那么代表该函数可以接收任何形式、任意长度的参数
在该函数内部还可以把接收到的参数传给另外一个函数
>>> def func(*args,**kwargs): # 传入的参数被*args,**kwargs接受
print(args,kwargs)
foo(*args,**kwargs) # 将*args,**kwargs拆分传入foo等同于foo(1,2,d=3,c=2)
>>> def foo(a,b,c=1,d=2):
print(a,b,c,d)
>>> func(1,2,d=3,c=2)
(1, 2) {'d': 3, 'c': 2}
1 2 2 3
# 在为函数func传参时,其实遵循的是函数foo的参数规则。
3、名称空间
名称空间即存放名字与对象映射/绑定关系的地方。
名称空间的加载顺序是:内置名称空间->全局名称空间->局部名称空间,
查找一个名字,必须从三个名称空间之一找到,查找顺序为:局部名称空间->全局名称空间->内置名称空间。
3.1 内建名称空间
伴随python解释器的启动/关闭而产生/回收,因而是第一个被加载的名称空间,用来存放一些内置的名字,比如内建函数名
>>> print
<built-in function print> # built-in内建
3.2 全局名称空间
伴随python文件的开始执行/执行完毕而产生/回收,是第二个被加载的名称空间,文件执行过程中产生的名字都会存放于该名称空间中。
import os # 模块名os
a=1 # 变量名a
if a == 1:
b=2 # 变量名b
def func(x): # 函数名func
y=1
class Bar: # 类名Bar
pass
3.3 局部名称空间
伴随函数的调用/结束而临时产生/回收,函数的形参、函数内定义的名字都会被存放于该名称空间中
def func():
y=3 # 调用函数时,才会执行函数代码,名字y存放于该函数的局部名称空间中
4、作用域
4.1 全局作用域与局部作用域
按照名字作用范围的不同可以将三个名称空间划分为两个区域:
全局作用域:位于全局名称空间、内建名称空间中的名字属于全局范围,该范围内的名字全局存活、全局有效(在任意位置都可以使用);
局部作用域:位于局部名称空间中的名字属于局部范围。该范围内的名字临时存活(即在函数调用时临时生成,函数调用结束后就释放)、局部有效(只能在函数内使用)。
4.2 作用域与名字查找的优先级
4.2.1 在局部作用域查找名字
起始位置是局部作用域,所以先查找局部名称空间,没有找到,再去全局作用域查找:先查找全局名称空间,没有找到,再查找内置名称空间,最后都没有找到就会抛出异常
a=10 # 全局作用域的名字
b=20 # 全局作用域的名字b
def func():
a=30 # 局部作用域的名字a
print(a) # 在局部找得到a,则以局部为准
print(b) # 在局部找不到b,则去全局找b
func() # 结果为30 20
4.2.2 在全局作用域查找名字
起始位置便是全局作用域,所以先查找全局名称空间,没有找到,再查找内置名称空间,最后都没有找到就会抛出异常
a=10
def func():
a=30 # 在函数调用时产生局部作用域的名字a
func()
print(a) #在全局找a,结果为10
4.2.3 在内嵌的函数内查找名字
会优先查找自己局部作用域的名字,然后由内而外一层层查找外部嵌套函数定义的作用域,没有找到,则查找全局作用域
a=10
c=10
def outer():
a=20
b=20
def inner(): # 函数名inner属于outer这一层作用域的名字
a=30
print('inner a:%s,b:%s,c:%s' %(a,b,c))
# 先查找自己的局部,内部找到a=30,b内部找不到则去外部函数找到b=20,c内外函数都找不到,则去全局找到c=10
inner()
print('inner a:%s,b:%s,c:%s' %(a,b,c))
# 先查找自己的局部,内部找到a=20,b=20,c内部找不到,则去全局找到c=10
outer()
# 结果为
inner a:30,b:20,c:10
inner a:20,b:20,c:10
4.2.4 global关键字与nonlocal关键字
在函数内,无论嵌套多少层,都可以查看到全局作用域的名字,若要在函数内修改全局名称空间中名字的值,当值为不可变类型时,则需要用到global关键字,当实参的值为可变类型时,函数体内对该值的修改将直接反应到原值,
对于嵌套多层的函数,使用nonlocal关键字可以将名字声明为来自外部嵌套函数定义的作用域(非全局),nonlocal x会从当前函数的外层函数开始一层层去查找名字x,若是一直到最外层函数都找不到,则会抛出异常。
1、global关键字
a=1
b=[4,5,6]
def func():
global a # 变量为不可变类型时,声明a为全局名称空间的名字,则可修改全局变量
a=2
b.append(a) # 变量为可变类型时,函数体内对该值的修改将直接反应到原值,
func()
print(a)
print(b)
# 结果为
2
[4, 5, 6, 2]
2、nonlocal关键字
def f1():
x=2
def f2():
nonlocal x
x=3
f2() # 调用f2(),修改f1作用域中名字x的值
print(x) # 在f1作用域查看x
f1()
# 结果为
3
5、函数对象
函数对象指的是函数可以被当做’数据’来处理
5.1 函数可以被引用
def foo(x,y): # 定义函数
return x+y
func=foo # 将函数内存地址赋值给func
res = func(1,2) # func可以加括号传参直接调用函数
print(res)
# 结果为
3
5.2 函数可以作为容器类型的元素
def foo(x,y): # 定义函数
return x+y
dic={'foo':foo,'max':max} # 函数可以作为容器类型的元素
res = dic['foo'](1,2) # 直接通过key加括号传参调用函数
print(dic)
print(res)
# 结果为
{'foo': <function foo at 0x000002671B0AB1F0>, 'max': <built-in function max>}
3
5.3 传参函数和返回函数
def foo(x,y): # 定义函数
return x+y
def func(foo): # 函数可以作为参数传入另外一个函数
return foo # 函数的返回值可以是一个函数
bar = func(foo)
res =bar(1,2)
print(bar,foo,func)
print(res)
# 结果为
<function foo at 0x000002452611B1F0> <function foo at 0x000002452611B1F0> <function func at 0x000002452611B280>
3
6、闭包
基于函数对象的概念,可以将函数返回到任意位置去调用,但作用域的关系是在定义完函数时就已经被确定了的,与函数的调用位置无关。
也就是说函数被当做数据处理时,始终以自带的作用域为准。若内嵌函数包含对外部函数作用域(而非全局作用域)中变量的引用,那么该’内嵌函数’就是闭包函数,简称闭包(Closures)
“闭”代表函数是内部的,“包”代表函数外’包裹’着对外层作用域的引用。因而无论在何处调用闭包函数,使用的仍然是包裹在其外层的变量。
x=2
def f1():
x = 1
def f2():
print(x)
return f2
def f3():
x=3
f2=f1() # 调用f1()返回函数f2
f2() # 需要按照函数定义时的作用关系去执行,与调用位置无关
f3() # 结果为1
7、装饰器
函数装饰器分为:无参装饰器和有参装饰两种,二者的实现原理一样,都是’函数嵌套+闭包+函数对象’的组合使用的产物。
装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能
7.1 无参装饰器
模板
import time
# 定义一个装饰器
def outter(func): # 传需要执行的函数
def inter(*args,**kwargs): # 传入函数的参数
st = time.time() # 执行函数前的附加代码
res=func(*args,**kwargs) # 执行函数
et = time.time() # 执行函数后的附加代码
return res,et-st # 返回函数执行的结果
return inter
@outter # 装饰器的使用放法
def func(a,b=2): # 定义一个被装饰的函数
print('函数开始了。。。')
time.sleep(3)
s = a * b
print('函数结束了!')
return s
res = func(1,b=3) # 运行函数
print(res)
# 结果
函数开始了。。。
函数结束了!
(3, 3.0004255771636963) # 返回的结果为一个元组,原函数返回的值与运行时间
7.2 有参装饰器
模板
import time
# 定义一个装饰器
def wrapper(param): # 为装饰器传参
def outter(func): # 传需要执行的函数
def inter(*args,**kwargs): # 传入函数的参数
st = time.time() # 执行函数前的附加代码
res=func(*args,**kwargs) # 执行函数
et = time.time() # 执行函数后的附加代码
ti = param + (et-st) # 装饰器内需要其他参数
return res,ti # 返回函数执行的结果
return inter
return outter
@wrapper(1) # 装饰器的使用放法
def func(a,b=2): # 定义一个被装饰的函数
print('函数开始了。。。')
time.sleep(3)
s = a * b
print('函数结束了!')
return s
res = func(1, b=3) # 运行函数
print(res)
# 结果
函数开始了。。。
函数结束了!
(3, 4.000857830047607) # 返回的结果为一个元组,原函数返回的值与运行时间
7.3 装饰器叠加
@deco3
@deco2
@deco1
def index(): # 等同于index=deco3(deco2(deco1(index)))
pass
8、迭代器
迭代器即用来迭代取值的工具,而迭代是重复反馈过程的活动,其目的通常是为了逼近所需的目标或结果,每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,单纯的重复并不是迭代
8.1 可迭代对象
可迭代对象(Iterable):从语法形式上讲,内置有__iter__方法的对象都是可迭代对象,字符串、列表、元组、字典、集合、打开的文件都是可迭代对象。
8.2 迭代器对象
调用obj.iter()方法返回的结果就是一个迭代器对象(Iterator)。迭代器对象是内置有iter和next方法的对象,打开的文件本身就是一个迭代器对象,执行迭代器对象.iter()方法得到的仍然是迭代器本身,而执行迭代器.next()方法就会计算出迭代器中的下一个值。 迭代器是Python提供的一种统一的、不依赖于索引的迭代取值方式,只要存在多个“值”,无论序列类型还是非序列类型都可以按照迭代器的方式取值
8.3 for循环原理
# for 循环原理
a = [1,2,3,4,5]
i = iter(a) # 每次都需要重新获取一个迭代器对象
while True:
try:
print(next(i))
except StopIteration: # 捕捉异常终止循环
break
# for循环又称为迭代循环,in后可以跟任意可迭代对象,上述while循环可以简写为
a = [1,2,3,4,5]
for i in a:
print(i)
"""
for 循环在工作时,首先会调用可迭代对象goods内置的iter方法拿到一个迭代器对象,
然后再调用该迭代器对象的next方法将取到的值赋给item,执行循环体完成一次循环,
周而复始,直到捕捉StopIteration异常,结束迭代。
"""
9、生成器
9.1 生成器与yield
若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象
生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器
yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值
def my_range(start,stop,step=1): # 定义一个生成器
print('start...')
while start < stop:
yield start # 碰到yield函数暂停,挂起,知道收到__next__方法,再次运行
start+=step
print('end...')
g=my_range(0,3) # 生成器对象
print(g)
for i in g: # 通过for循环迭代取值
print(i)
# 结果
<generator object my_range at 0x0000017E29B28A50>
start...
0
1
2
end...
9.2 yield表达式应用
在函数内可以采用表达式形式的yield,可以拿到函数的生成器对象持续为函数体send值
针对表达式形式的yield,生成器对象必须事先被初始化一次,让函数挂起在food=yield的位置,等待调用g.send()方法为函数体传值,g.send(None)等同于next(g)。
定义个装饰器来初始化生成器
def init(func):
def wrapper(*args,**kwargs):
g=func(*args,**kwargs)
next(g)
return g
return wrapper
@init
def genter():
print('开始生成了。。。')
b = []
while True:
a = yield # 将yield接受的值赋值给a
b.append(a)
print('生成了 {} \n现在拥有:{}'.format(a,b))
g = genter() # 初始化生成器
g.send('圣剑') # 第一次赋值
g.send('圣兽') # 第二次赋值
g.send('圣衣') # 第三次赋值
# 结果
开始生成了。。。
生成了 圣剑
现在拥有:['圣剑']
生成了 圣兽
现在拥有:['圣剑', '圣兽']
生成了 圣衣
现在拥有:['圣剑', '圣兽', '圣衣']
9.3 三元表达式
语法:
res = 条件成立时返回的值 if 条件 else 条件不成立时返回的值
def func(x,y):
res = x if x > y else y # 三元表达式
return res
# 等同于:
def foo(x,y):
if x > y:
return x
else:
return y
res1 = func(1,2)
res2 = foo(1,2)
print(res1,res2)
# 结果
2 2
10、递归
函数不仅可以嵌套定义,还可以嵌套调用,即在调用一个函数的过程中,函数内部又调用另一个函数,而函数的递归调用指的是在调用一个函数的过程中又直接或间接地调用该函数本身
10.1 直接调用
在调用f1的过程中,又调用f1,这就是直接调用函数f1本身
def f1():
print('from f1')
f1()
f1()
10.2 间接调用
在调用f1的过程中,又调用f2,而在调用f2的过程中又调用f1,这就是间接调用函数f1本身
def f1():
print('from f1')
f2()
def f2():
print('from f2')
f1()
f1()
两种情况下的递归调用都是一个无限循环的过程,但在python对函数的递归调用的深度做了限制,因而并不会像大家所想的那样进入无限循环,会抛出异常,要避免出现这种情况,就必须让递归调用在满足某个特定条件下终止。
可以使用sys.getrecursionlimit()去查看递归深度,默认值为1000,虽然可以使用
sys.setrecursionlimit()去设定该值,但仍受限于主机操作系统栈大小的限制
11、匿名函数
对比使用def关键字创建的是有名字的函数,使用lambda关键字创建则是没有名字的函数,即匿名函数
11.1 lambda表达式
语法:
lambda 参数1,参数2,...: expression
1、定义
lambda x,y,z:x+y+z
# 等同于
def func(x,y,z):
return x+y+z
2、调用
# 方式一:
res=(lambda x,y,z:x+y+z)(1,2,3)
# 方式二:
func=lambda x,y,z:x+y+z # “匿名”的本质就是要没有名字,所以此处为匿名函数指定名字是没有意义的
res=func(1,2,3)
11.2 map、reduce、filter
函数map、reduce、filter都支持迭代器协议,用来处理可迭代对象
a = [1,2,3,4,5]
1、map
"""
对a的每个元素做平方处理,可以使用map函数
map函数可以接收两个参数,一个是函数,另外一个是可迭代对象
map会依次迭代a,得到的值依次传给匿名函数(也可以是有名函数),而map函数得到的结果仍然是迭代器。
"""
>>> res=map(lambda x:x**2,array) # map的使用
>>> res
<map object at 0x1033f45f8> # 得到的是个迭代器
>>> list(res) # 使用list可以依次迭代res,取得的值作为列表元素
[1, 4, 9, 16, 25]
2、reduce
"""
对a进行合并操作,比如求和运算,这就用到了reduce函数
reduce函数可以接收三个参数,一个是函数,第二个是可迭代对象,第三个是初始值
没有初始值,reduce函数会先迭代一次a得到的值作为初始值,作为第一个值数传给x,然后继续迭代一次a得到的值作为第二个值传给y,依次类推,直到迭代完a的所有元素。
"""
# reduce在python2中是内置函数,在python3中则被集成到模块functools中,需要导入才能使用
>>> from functools import reduce
>>> res=reduce(lambda x,y:x+y,a) # 未指定初始值
>>> res
15
>>> res=reduce(lambda x,y:x+y,a,100) # 指定初始值
>>> res
115
3、filter
"""
对ar进行过滤操作,这就用到了filter函数
filter函数可以接收两个参数,一个是函数,另外一个是可迭代对象
filter函数会依次迭代a,得到的值依次传给匿名函数,如果匿名函数的返回值为真,则过滤出该元素,而filter函数得到的结果仍然是迭代器。
"""
>>> res=filter(lambda x:x>3,a) # 过流小于3的值
>>> res
<filter object at 0x000001A552D0A250> # 得到的是个迭代器
>>> list(res) # 使用list可以依次迭代res,取得的值作为列表元素
[4, 5]
12、 内置函数
1、 len(s)
"""
方法返回对象(字符、列表、元组等)长度。
s: 传入对象
返回值:对象的长度
"""
>>>len([1,2,3,4,5]) # 获得列表的长度
5
2、range(start=0,stop,step=1)
"""
函数返回的是一个从start到stop间隔为strp的可迭代对象,可通过for循环取值或用list方法。
start: 计数从 start 开始。默认为 0 。
stop: 计数到 stop 结束,但不包括 stop。
step:步长,默认为1。
返回值:可迭代对象
"""
>>> range(1,10,2)
range(1, 10, 2) # 返回可迭代对象
>>> list(range(1,10,2)) # 通过list获得range里的值
[1, 3, 5, 7, 9]