函数
函数基础
一、为什么要使用函数
把重复的代码提取出来,节省代码量
二、函数分类
内置函数,如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