函数
函数基础
一、为什么要使用函数
把重复的代码提取出来,节省代码量
二、函数分类
内置函数,如len(),sun()
自定义函数
三、如何定义函数
def 函数名(参数1,参数2,参数3,...): '''注释''' 函数体 return 返回的值
四、函数使用的原则:先定义,后使用
五、调用函数
1、如何调用函数:函数名加括号
2、函数的返回值
函数的返回值通常都是一个,如果出现 return a,b 这种情况,其实是表示一个元组
六、函数的参数
#1、位置参数:按照从左到右的顺序定义的参数
位置形参:必选参数
位置实参:按照位置给形参传值,顺序必须一一对应
#2、关键字参数:按照key=value的形式定义的实参
无需按照位置为形参传值
注意的问题:
1. 关键字实参必须在位置实参右面
2. 对同一个形参不能重复传值
#3、默认参数:形参在定义时就已经为其赋值
可以传值也可以不传值,经常需要变得参数定义成位置形参,变化较小的参数定义成默认参数(形参)
注意的问题:
1. 只在定义时赋值一次
2. 默认参数的定义应该在位置形参右面
3. 默认参数通常应该定义成不可变类型,举例
def add_end(L=[]): print(id(L)) L.append('END') return L print(add_end()) print(add_end())
结果:
2822355733768
['END']
2822355733768
['END', 'END']
解释:python函数在定义好时,形参、内部参数等变量就已经有了固定的内存空间(变量始终指向那一块内存,不会更改)。默认参数形参变量如果是可变的,下一次调用该函数时,默认参数就已经发生了变化。
#4、可变长参数: 可变长指的是实参值的个数不固定
而实参有按位置和按关键字两种形式定义,针对这两种形式的可变长,形参对应有两种解决方案来完整地存放它们,分别是*args,**kwargs
===========*args===========
def foo(x,y,*args): print(x,y) print(args) foo(1,2,3,4,5) def foo(x,y,*args): print(x,y) print(args) foo(1,2,*[3,4,5]) def foo(x,y,z): print(x,y,z) foo(*[1,2,3])当形参为*args,实参可以用 *()或者 *[] 的方式将若干个参数打包发送给形参
===========**kwargs===========
def foo(x,y,**kwargs): print(x,y) print(kwargs) foo(1,y=2,a=1,b=2,c=3) def foo(x,y,**kwargs): print(x,y) print(kwargs) foo(1,y=2,**{'a':1,'b':2,'c':3}) def foo(x,y,z): print(x,y,z) foo(**{'z':1,'x':2,'y':3}) 当形参为**kwargs时,实参可以通过**{}的方式将若干参数打包发送给形参
===========*args+**kwargs===========
def foo(x,y): print(x,y) def wrapper(*args,**kwargs): print('====>') foo(*args,**kwargs)
#5、命名关键字参数:*后定义的参数,必须被传值(有默认值的除外),且必须按照关键字实参的形式传递,可以保证,传入的参数中一定包含某些关键字
和关键字参数**kw
不同,命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数(如果参数中已经有了可变参数*args,则不需要 * 来特别标明)
def foo(x,y,*args,a=1,b,**kwargs): print(x,y) print(args) print(a) print(b) print(kwargs) foo(1,2,3,4,5,b=3,c=4,d=5) 结果: 1 2 (3, 4, 5) 1 3 {'c': 4, 'd': 5}
闭包
一、函数是对象
函数可以当做参数传递,也可以返回一个函数
利用该特性,可以写成一个类似C语言switch case语法的功能,取代多分支if
def foo(): print('foo') def bar(): print('bar') dic={ 'foo':foo, 'bar':bar, } while True: choice=input('>>: ').strip() if choice in dic: dic[choice]()
二、函数嵌套、名称空间、作用域的概念
1、函数嵌套调用
函数嵌套定义
2、名称空间
存放名字的地方,x=1,1存3、放于内存中,那名字x存放在哪里呢?名称空间是存放名字x与1绑定关系的地方,L = [1,2,3],L存放在名称空间里,真正的列表在其他地方。
3、作用域
#1、作用域即范围
-全局范围(内置名称空间与全局名称空间属于该范围):全局存活,全局有效
-局部范围(局部名称空间属于该范围):临时存活,局部有效
内部的函数可以访问外部的变量,但是外面的函数访问不了内部函数
#2、作用域关系是在函数定义阶段就已经固定的,与函数的调用位置无关
#3、查看作用域:globals(),locals()
LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__
locals 是函数内的名字空间,包括局部变量和形参
enclosing 外部嵌套函数的名字空间(闭包中常见)
globals 全局变量,函数定义所在模块的名字空间
builtins 内置模块的名字空间
闭包函数
内部函数包含对外部函数而非全局作用域的引用
def counter(): n = 0 def incr(): nonlocal n x = n n += 1 return x return incr c = counter() print(c()) print(c()) print(c()) print(c.__closure__[0].cell_contents) # 查看闭包的元素
结果:
0
1
2
3
闭包函数的意义:
返回的函数对象,不仅仅是一个函数,在该函数外还包裹了一个外层作用域(一般含是变量),这使得,该函数无论在何处调用,都优先使用自己外层包裹的作用域,相对于更外层的比如全局作用域。
实现延迟计算的功能:
普通函数调用后立即函数执行,闭包则是给函数包裹上了一层外层作用域,传了参数的功能。
装饰器
1、装饰器是闭包函数的一种应用
2、装饰器可以在不修改原函数功能的前提下,添加额外功能。开放封闭原则
#不带参数的装饰器
import time def timmer(func): def wrapper(*args,**kwargs): start_time=time.time() res=func(*args,**kwargs) stop_time=time.time() print('run time is %s' %(stop_time-start_time)) return res return wrapper @timmer #相当于foo = timmer(foo) def foo(): time.sleep(3) print('from foo') foo()#利用闭包的特点:1、具有延迟计算的功能,函数不是立即执行;2、闭包相当于包裹了一个外层作用域的函数。当timmer函数执行后,形参 func,原函数作为一个对象传到了形参,传给了内层函数 wrapper,即使timmer执行完毕了,形参会一直存在着。
#带参数的装饰器
def auth(driver='file'): def auth2(func): def wrapper(*args,**kwargs): name=input("user: ") pwd=input("pwd: ") if driver == 'file': if name == 'egon' and pwd == '123': print('login successful') res=func(*args,**kwargs) #原函数其实就一个调用,其他都是额外功能,装饰作用 return res elif driver == 'ldap': print('ldap') return wrapper return auth2 @auth(driver='file') def foo(name): print(name) foo('egon')
迭代器
1、为什么要有迭代器:
有序序列,如列表,字符串可以通过索引的方式取出其中的元素,而对于字典,文件,集合等类型,是没用索引的,所以需要通过不依靠索引的迭代方式,就是迭代器。
2、什么是可迭代对象:
可迭代对象指的是实现了__iter__方法的对象
3、什么是迭代器(对象):
可迭代对象执行obj.__iter__()得到的结果就是迭代器对象,同时,迭代器内部还有__next__方法
文件类型是可迭代器对象 open('a.txt').__iter__() open('a.txt').__next__()
注意:
迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象
迭代器对象的使用
dic = {'a':1, 'b':2, 'c':3}
iter_dic = dic.__iter__() #得到一个迭代器
注意:迭代器.__iter__() 得到仍然是迭代器本身
print(iter_dic.__next__()) #等同于next(iter_dic)
print(iter_dic.__next__()) #等同于next(iter_dic)
print(iter_dic.__next__()) #等同于next(iter_dic)
print(iter_dic.__next__()) #抛出异常StopIteration,或者说结束标志
iter_dic=dic.__iter__() while 1: try: k=next(iter_dic) print(dic[k]) except StopIteration: break
通过next方法从迭代器取数据,需要手动捕获异常
for循环执行next方法,并且自动捕获异常
dic={'a':1,'b':2,'c':3} for k in dic: print(dic[k]) for循环的工作原理 1:执行in后对象的dic.__iter__()方法,得到一个迭代器对象iter_dic 2: 执行next(iter_dic),将得到的值赋值给k,然后执行循环体代码 3: 重复过程2,直到捕捉到异常StopIteration,结束循环
生成器
概念:
只要函数内部包含有yield关键字,那么函数名()的到的结果就是生成器,加括号不会执行函数内部的代码
注意:生成器是一种特殊的迭代器,生成器内置__next__,__iter__方法
send方法的使用:
def range2(n): count =0 while count < n: print('count',count) count += 1 sign = yield count print('-------sign',sign) return 333 new_range = range2(3) n1 = next(new_range) print('do sth else') new_range.send('stop!')
send与next的区别:
两者都可以唤醒生成器,来继续执行
send的作用:外界可以发送一个信号给生成器内部,表达式 sign = yield count, 而next是默认发送为None的
只使用send的情况:
#yield关键字的另外一种使用形式:表达式形式的yield def eater(name): print('%s 准备开始吃饭啦' %name) food_list=[] while True: food=yield food_list print('%s 吃了 %s' % (name,food)) food_list.append(food) g=eater('egon') g.send(None) #对于表达式形式的yield,在使用时,第一次必须传None,g.send(None)等同于next(g) g.send('蒸羊羔') g.send('蒸鹿茸') g.send('蒸熊掌') g.send('烧素鸭') g.close() g.send('烧素鹅') g.send('烧鹿尾')
来源:https://www.cnblogs.com/liyuexi/p/10702873.html