函数
一、函数:工具,事先准备工具的过得不到即函数的定义,拿来就用即函数的调用
二、定义函数:先定义,后调用。函数的定义就相当于事先将函数体代码保存起来,然后将内存地址赋值给函数名,函数名就是对这段代码的引用。
定义函数的语法:def 函数名(参数1,参数2.....): 若函数体代码这辑依赖外部传参则需要定义为有参函数,否则定义为无参函数
函数体
return 值
- def: 定义函数的关键字;
- 函数名:函数名指向函数内存地址,是对函数体代码的引用。函数的命名应该反映出函数的功能;
- 括号:括号内定义参数,参数是可有可无的,且无需指定参数的类型;
- 冒号:括号后要加冒号,然后在下一行开始缩进编写函数体的代码;
- """文档描述""": 描述函数功能,参数介绍等信息的文档,非必要,但是建议加上,从而增强函数的可读性;
- 函数体:由语句和表达式组成;
- return 值:定义函数的返回值,return是可有可无的
三、调用函数与函数返回值
定义阶段与调用阶段,定义函数时只检测语法,不执行函数体代码,函数名加括号即函数调用,只有调用函数时才会执行函数体代码
#1、语句形式: foo() #2、表达式形式: m=my_min(1,2) #将调用函数的返回值赋值给x n=10*my_min(1,2) #将调用函数的返回值乘以10的结果赋值给n #3、函数调用作为参数的形式: # my_min(2,3)作为函数my_min的第二个参数,实现了取1,2,3中的较小者赋值给m m=my_min(1,my_min(2,3))
若需要将函数体代码执行的结果返回给调用者,则需要用到return。return后无值或直接省略return,则默认返回None,return的返回值无类型限制,且可以将多个返回值放到一个元组内。
1.函数名() 函数名后面+圆括号就是函数的调用。 2.参数: 圆括号用来接收参数。 若传入多个参数: 应按先位置传值,再按关键字传值 具体的传入顺序应按照函数定义的参数情况而定 3.返回值 如果函数有返回值,还应该定义“变量”接收返回值 如果返回值有多个,也可以用多个变量来接收,变量数应和返回值数目一致 无返回值的情况: 函数名() 有返回值的情况: 变量 = 函数名() 多个变量接收多返回值: 变量1,变量2,... = 函数名()
函数的参数
一、形参与实参
形参:在定义函数时,括号内声明的参数。形参本质就是一个变量名,用来接收外部传来的值。
实参:在调用函数时,括号内传入的值,值可以是常量、变量、表达式三者的组合
在调用有参函数时,实参值会赋值给形参(变量名)。变量名与值只是单纯的绑定关系,只在函数调用时生效,在调用后解除。
#1:实参是常量 res=my_min(1,2) #2:实参是变量 a=1 b=2 res=my_min(a,b) #3:实参是表达式 res=my_min(10*2,10*my_min(3,4)) #4:实参可以是常量、变量、表达式的任意组合 a=2 my_min(1,a,10*my_min(3,4))
二、形参与实参的具体使用
1、位置参数:定义函数时,按照从左到右的顺序依次定义形参,这种形式定义的形参都必须被一 一传值
def register(name,age,sex): #定义位置形参:name,age,sex,三者都必须被传值 print('Name:%s Age:%s Sex:%s' %(name,age,sex))
2、关键字参数:在调用函数时,实参可以是Key=value的形式,称为关键字参数。这种形式定义的实参可以完全不按照从左到右的顺序定义,但仍能为指定的形参赋值
需要注意在调用函数时,实参也可以是按位置或按关键字的混合使用,但必须保证关键字参数在位置参数后面,且不可以对一个形参重复赋值
3、默认参数:在定义函数时,就已经为形参赋值,这类形参称之为默认参数,当函数有多个参数时,需要将值经常改变的参数定义成位置参数,而将值改变较少的参数定义成默认参数
def register(name,age,sex='male'): #默认sex的值为male 默认参数必须在位置参数之后 ... print('Name:%s Age:%s Sex:%s' %(name,age,sex))
定义时就已经为参数sex赋值,意味着调用时可以不对sex赋值,这降低了函数调用的复杂度
register('tom',17) #大多数情况,无需为sex传值,默认为male
Name:tom Age:17 Sex:male
4、可变长度的参数
如果在最后一个形参名前加号,那么在调用函数时,溢出的位置实参,都会被接收,以元组的形式保存下来赋值给该形参
>>> def foo(x,y,z=1,*args): #在最后一个形参名args前加*号 ... print(x) ... print(y) ... print(z) ... print(args) ... >>> foo(1,2,3,4,5,6,7) #实参1、2、3按位置为形参x、y、z赋值,多余的位置实参4、5、6、7都被*接收,以元组的形式保存下来
,赋值给args,即args=(4, 5, 6,7)
def foo(x,y,*args):
... print(x)
... print(y)
... print(args)
...
>>> L=[3,4,5]
>>> foo(1,2,*L) # *L就相当于位置参数3,4,5, foo(1,2,*L)就等同于foo(1,2,3,4,5)
5、可变长度的关键字参数
溢出的关键字参数都会被接收,以字典的形式保存下来赋值给该形参 **kwargs
def foo(x,**kwargs): #在最后一个参数kwargs前加** ... print(x) ... print(kwargs) ... >>> foo(y=2,x=1,z=3) #溢出的关键字实参y=2,z=3都被**接收,以字典的形式保存下来,赋值给kwargs 1 {'z': 3, 'y': 2}
6、命名关键字参数
在定义了**kwargs参数后,函数调用者就可以传入任意的关键字参数key=value,如果函数体代码的执行需要依赖某个key,必须在函数内进行判断
想要限定函数的调用者必须以key=value的形式传值,需要在定义形参时,用作为一个分隔符号,号之后的形参称为命名关键字参数。对于这类参数,在函数调用时,必须按照key=value的形式为其传值,且必须被传值
def register(name,age,*,sex,height): #sex,height为命名关键字参数 ... pass ... >>> register('lili',18,sex='male',height='1.8m') #正确使用 >>> register('lili',18,'male','1.8m') # TypeError:未使用关键字的形式为sex和height传值 >>> register('lili',18,height='1.8m') # TypeError没有为命名关键字参数height传值。
综上所述所有参数可任意组合使用,但定义顺序必须是:位置参数、默认参数、args、命名关键字参数、*kwargs 可变参数*args与关键字参数kwargs通常是组合在一起使用的,如果一个函数的形参为*args与kwargs,
那么代表该函数可以接收任何形式、任意长度的参数
名称空间与作用域
一、名称空间
1、内置名称空间:伴随python解释器的启动、关闭而产和、回收,因而是第一个被加载的名称空间,用来存放一些内置的名字
2、全局名称空间:第二个被加载的名称空间,文件执行过程中产生的名字都会存放于该名称空间中
3、局部名称空间:函数的形参、函数内定义的名字都会被存放于该名称空间。
名称空间的加载顺序是:内置名称空间->全局名称空间->局部名称空间,而查找一个名字,必须从三个名称空间之一找到,查找顺序为:局部名称空间->全局名称空间->内置名称空间。
二、全局作用域与局部作用域
- 全局作用域:位于全局名称空间、内建名称空间中的名字属于全局范围,该范围内的名字全局存活(除非被删除,否则在整个文件执行过程中存活)、全局有效(在任意位置都可以使用);
- 局部作用域:位于局部名称空间中的名字属于局部范围。该范围内的名字临时存活(即在函数调用时临时生成,函数调用结束后就释放)、局部有效(只能在函数内使用)。
三、作用域与名字查找的优先级
在局部作用域查找名字时,起始位置是局部作用域,所以先查找局部名称空间,没有找到,再去全局作用域查找:先查找全局名称空间,没有找到,再查找内置名称空间,最后都没有找到就会抛出异常
在全局作用域查找名字时,起始位置便是全局作用域,所以先查找全局名称空间,没有找到,再查找内置名称空间,最后都没有找到就会抛出异常
函数对象和闭包
一、函数对象
指的是函数可以被当做‘数据’来处理,具体可以分为四个方面的使用
1.1 函数可以被引用
>>> def add(x,y):
... return x+y
...
>>> func=add
>>> func(1,2)
3
1.2 函数可以作为容器类型的元素
>>> dic={'add':add,'max':max}
>>> dic
{'add': <function add at 0x100661e18>, 'max': <built-in function max>}
>>> dic['add'](1,2)
3
1.3 函数可以作为参数传入另外一个函数
>>> def foo(x,y,func):
... return func(x,y)
...
>>> foo(1,2,add)
3
1.4 函数的返回值可以是一个函数
def bar():
return add
func=bar()
func(1,2)
3
闭包函数
一、闭与包
函数被当做数据处理时,始终以自带的作用域为准。若内嵌函数包含对外部函数作用域(而非全局作用域)中变量的引用,那么该’内嵌函数’就是闭包函数,简称闭包(Closures)
“闭”代表函数是内部的,“包”代表函数外’包裹’着对外层作用域的引用。因而无论在何处调用闭包函数,使用的仍然是包裹在其外层的变量。
二、闭包的用途
两种为函数体传值的方式,一种是直接将值以参数的形式传入,另外一种是将值包给函数
闭包函数的这种特性有时又称为惰性计算。
就是在一个函数里,定义了另外一个函数,然后,内层函数引用了外层函数空间里的变量
装饰器
1、为何要用装饰器
软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。
软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。
2、什么是装饰器
在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。
3、装饰器的实现
分为:无参装饰器和有参装饰器两种,二者的实现原理一样,都是函数嵌套+闭包+函数对象的组合使用的产物
1、无参装饰器的实现
import time def index(): time.sleep(3) print('Welcome to the index page’) return 200 index() #函数执行
@timer # index=timer(index) def index(): time.sleep(3) print('Welcome to the index page') return 200 @timer # index=timer(home) def home(name): time.sleep(5) print('Welcome to the home page’,name)
如果我们有多个装饰器,可以叠加多个
@deco3 @deco2 @deco1 def index(): pass
2、有参装饰器的实现
迭代器
1、什么是迭代器
迭代器即用来迭代取值的工具,而迭代是重复反馈过程的活动,每一次对过程的重复称为一次迭代,而每一次迭代得到的结果会作为下一次迭代的初始值
2、可迭代对象
内置有__iter__方法的对象都是可迭代对象,字符串、列表、元组、字典、集合、打开的文件都是可迭代对象
3、迭代器对象
调用obj.iter()方法返回的结果就是一个迭代器对象(iterator)。迭代器对象是内置有iter和next方法的对象,打开的文件本身就是一个迭代器对象,执行迭代器对象。iter()方法得到的仍然是迭代器本身,而执行迭代器.next()方法就会计算出规定代器中的下一个值。迭代器是python提供的一种统一的不依赖索引的迭代取值方式
4、for循环底层就是迭代器
5、优缺点
优点:
1、为序列和非序列类型提供了一种统一的迭代取值方式。
2、惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。
缺点:
1、除非取尽,否则无法获取迭代器的长度
2、只能取下一个值,不能回到开始,更像是‘一次性的’,迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,你只能重新调用iter方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。
生成器
一、生成器臧yield
若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象
def my_range(start,stop,step=1): ... print('start...') ... while start < stop: ... yield start ... start+=step ... print('end...') ... >>> g=my_range(0,3) >>> g
生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器
>>> next(g) # 触发函数执行直到遇到yield则停止,将yield后的值返回,并在当前位置挂起函数 start... 0 >>> next(g) # 再次调用next(g),函数从上次暂停的位置继续执行,直到重新遇到yield... 1 >>> next(g) # 周而复始... 2 >>> next(g) # 触发函数执行没有遇到yield则无值返回,即取值完毕抛出异常结束迭代 end... Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIterationy
yield表达式应用
>>> def eater(): ... print('Ready to eat') ... while True: ... food=yield ... print('get the food: %s, and start to eat' %food) >>> g=eater() # 得到生成器对象 >>> g <generator object eater at 0x101b6e2b0> >>> next(e) # 需要事先”初始化”一次,让函数挂起在food=yield,等待调用g.send()方法为其传值 Ready to eat >>> g.send('包子') get the food: 包子, and start to eat >>> g.send('鸡腿') get the food: 鸡腿, and start to eat
针对表达式形式的yield,生成器对象必须事先被初始化一次,让函数挂起在food=yield的位置,等待调用g.send()方法为函数体传值,g.send(None)等同于next(g)。
三元表达式
res = 条件成立时返回的值 if 条件 else 条件不成立时返回的值
def max2(x,y): if x > y: return x else: return y res = max2(1,2) 用三元表达式可以一行解决 x=1 y=2 res = x if x > y else y # 三元表达式
列表生成式
egg_list=[] for i in range(10): egg_list.append('鸡蛋%s' %i) 用列表生成式可以一行解决 egg_list=['鸡蛋%s' %i for i in range(10)]
生成器表达式
创建一个生成器对象有两种方式,一种是调用带yield关键字的函数,另一种就是生成器表达式,与列表生成式的语法格式相同,只需要将[]换成()
函数递归
1、函数的递归调用指的是在调用一个函数的过程中又直接或间接地调用该函数本身
2、递归的过程可分为两个阶段 :
回溯与递推