zoukankan      html  css  js  c++  java
  • 函数进阶:闭包、装饰器、列表生成式、生成器、迭代器

    名称空间

    命名空间(又称“名称空间”): 存放名字的地方  (概念性的东西)。例如:变量x = 1, 1存放在内存中,命名空间就是存放名字x与1绑定关系的地方。

    名称空间有3种:

    • locals:是函数内的(或者是locals所在的那一层的)名称空间|,包括局部变量和形参
    • globals:全局变量
    • builtins: 内置模块的名字空间

     不同变量的作用域不同就是由这个变量所在的命名空间决定的

     作用域即范围:

    1. 全局范围: 全局存活,全局有效

    2. 局部范围: 临时存活, 局部有效

    查看作用域方法:

    globals()
    locals() 

    作用域的查找顺序: LEGB

    L: locals
    E: enclosing (相邻的)
    G:globals
    B: builtins 

    闭包:

    def func():
        n = 10
        def func2():
            print('func2:', n)
        return func2   #  没有执行func2, 只是把func2的函数名(func2的内存地址)
    
    f = func()   #执行func函数, 此时func()得到的结果是func2的内存地址,即f就是func2的内存地址
    
    f()    # 执行f, 由于f就是func2的内存地址,此时执行的就是func2函数
    
    # 输出结果: 
    # func2: 10    
    
    #  在函数(func)外部执行了函数内部的子函数(func2),而且子函数(func2)还能够调用其父级函数(func)作用域里面所有的值。 这种现象就是闭包

    装饰器:

    软件开发“开放-封闭” 原则:已经实现的功能代码不允许被修改,但可以被扩展。

    另外,不要改变别人函数的调用方式

    现在公司有一个网站模块功能如下:

    def home():
        print('---------主页----------')
    
    def america():
        print('--------欧美专区-------')
    
    def japan():
        print('--------日韩专区-------')

    现在需要在america 和 japan这两个板块前加上以下登录认证功能:

    login_status = False
    def login():
        info = ['neo', 'abc123']  # 储存的用户信息
        global login_status
        if not login_status:
            name_input = input('用户名:')
            password_input = input('密码:')
    
            if name_input == info[0] and password_input == info[1]:
                print('登陆成功')
                login_status = True
            else:
                print('密码错误')
                exit()
        else:
             print('用户已登录,通过检测')    

    现有如下几种方法可选: 

    1. 直接在America和Japan模块里面加上login认证程序, 如:

    def japan():
        login()   # 把自己写的认证程序加到别人写好的模块里面
        print('--------日韩专区-------')

    这种方式违反“封闭”原则,pass。

    2. 把america和japan这两个函数名当做参数传到login函数里面,如:

    login_status = False
    def login(func):
        info = ['neo', 'abc123']  # 储存的用户信息
        global login_status
        if login_status == False:
            name_input = input('用户名:')
            password_input = input('密码:')
    
            if name_input == info[0] and password_input == info[1]:
                print('登陆成功')
                login_status = True
            else:
                print('密码错误')
                exit()
        if login_status == True:
             print('用户已登录,通过检测')      
             func()   #把需要认证的模块函数名传进来调用执行
    
    def home():
        print('---------主页----------')
    
    def america():
        print('--------欧美专区-------')
    
    def japan():
        print('--------日韩专区-------')
    
    login(japan)   #需要认证时,就把函数名当做参数变量传给login函数

    这种方式改变了原模块(如japan())的调用方式

    3. 装饰器:

    login_status = False
    def login(func):
        def inner():
            info = ['neo', 'abc123']  # 储存的用户信息
            global login_status
            if login_status == False:
                name_input = input('用户名:')
                password_input = input('密码:')
    
                if name_input == info[0] and password_input == info[1]:
                    print('登陆成功')
                    login_status = True
                else:
                    print('密码错误')
                    exit()
            if login_status == True:
                 print('用户已登录,通过检测')      
             func()   #把需要认证的模块函数名传进来调用执行
        return inner  #login()执行的时候,得到的结果只是 把inner的函数名(内存地址)返回给login(func)
    
    def home():
        print('---------主页----------')
    
    def america():
        print('--------欧美专区-------')
    
    def japan():
        print('--------日韩专区-------')
    
    japan = login(japan)   # login(japan)的执行结果是得到了inner函数的内存地址,再赋值给前面的japan 变量后,变量japan就是inner函数的内存地址。
     japan()   # 此时执行japan函数其实执行的里面的inner函数,由于闭包的父级函数执行完后里面的变量内存并不会释放、子函数inner里面能够使用login函数里面的所有参数变量,所以inner里面的变量func执行的时候会向login函数调用其传入的先前的japan函数。 这就是装饰器的原理。
    
    
    #装饰器的正规写法:
    @login
    def japan():
        print('---------日韩专区--------')
    
    # 1.两个函数组成一个闭包,装饰函数做父级,被装饰的函数放在子级函数里面,子级函数名return给父级
    # 2. 被修饰的函数名作为参数传给父级函数
    # 3.子级函数里面包括装饰代码(如:登录认证)和 被装饰的函数(如:japan())

    附: 装饰器参数传递 

    利用非固定参数 *args和 **kwargs 传不固定个数的参数

    login_status = False
    def login(func):
        def inner(*args, **kwargs):
            info = ['neo', 'abc123']  # 储存的用户信息
            global login_status
            if login_status == False:
                name_input = input('用户名:')
                password_input = input('密码:')
    
                if name_input == info[0] and password_input == info[1]:
                    print('登陆成功')
                    login_status = True
                else:
                    print('密码错误')
                    exit()
            if login_status == True:
                 print('用户已登录,通过检测')      
             func(*args, **kwargs)   #把需要认证的模块函数名传进来调用执行
        return inner  #login()执行的时候,得到的结果只是 把inner的函数名(内存地址)返回给login(func)
    
    def home():
        print('---------主页----------')
    
    @login
    def america(x,y,z):
        print('--------欧美专区-------',x,y,z)
    
    @login
    def japan(sytle):  # 由于inner中的形参是非固定参数*args和**kwargs,所以传给inner函数多少个变量都行
        print('--------日韩专区-------',style)
    
    japan('3p'’)   # 此时japan(‘3p’)执行的是inner函数,由于原先的japan函数里面需要传一个变量,这个japan函数也需要传一个变量。变量传的顺序是: 新生成的这个japan函数 ---> inner函数 ---> inner里面的func函数(此时的func函数是原先的japan函数) ---> 原先的japan函数

    带参数的装饰器:

    login_status = False
    def login(style):   # 由于装饰器带参数,这里需要加一个形参
        def outer(func):   #这一层需要传入被装饰的函数名并利用return调用里面的inner函数
            def inner(*args, **kwargs):
                info = ['neo', 'abc123']  # 储存的用户信息
                global login_status
                if login_status == False:
                    name_input = input('用户名:')
                    password_input = input('密码:')
    
                    if name_input == info[0] and password_input == info[1]:
                        print('登陆成功')
                        login_status = True
                    else:
                        print('密码错误')
                        exit()
                 if login_status == True:
                     print('用户已登录,通过检测')      
                 func(*args, **kwargs)
            return inner   #把inner的内存地址返回给outer函数
        return outer   #把outer的内存地址返回给login函数
    
    def home():
        print('---------主页----------')
    
    @login('weixin')
    def america(x,y,z):
        print('--------欧美专区-------',x,y,z)
    
    @login('qq')   # 这句话可分解成两步理解: 1. 先是运行login('qq'),此时得到的结果是 outer函数的内存地址  2. login('qq')变成outer的内存地址后,就变成了不带参数的装饰器的情况(例如前面分析的那一种)
    def japan(style):
        print('--------日韩专区-------')
    
    japan('3p')  #执行装饰器

    # 注:
    即使是多层闭包,最里层的变量也可以去它的爷爷级去调用参数。 哪怕是多层闭包,在程序彻底运行完前,参数变量的内存地址也不会释放,还可以被里面的各层函数调用。
    
    

    生成器:

    现有需求: 给列表 [0,1,2,3,4,5,6,7,8,9] 中的每个值加1

    可以利用如下代码实现:

    a = [0,1,2,3,4,5,6,7,8,9]
    print(list(map(lambda x:x+1,a)))

    还有一种新写法:

    a = [0,1,2,3,4,5,6,7,8,9]
    a = [ i+1  for i in range(10)]   # 这种写法就叫列表生成式
    print(a)
    
    # 输出结果:
    # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 

    列表生成式语法: 

    [ 生成规则(三元运算) for i in 原先的列表  ] 

    如:

    a = [0,1,2,3,4,5,6,7,8,9]
    a = [ i if i<5 else i*i  for i in range(10)]   # for 后面可以循环任何iterables, 如字典、列表、字符串等, 通过前面的运算把结果放到一个列表(或元祖)里面,但不能放到字典里面
    print(a)
    
    # 输出结果:
    # [0, 1, 2, 3, 4, 25, 36, 49, 64, 81]
    a = {'neo':'abc','alex':123}
    
    b = [a[i] for i in a ]
    
    print(b)
    
    # 输出结果:
    # ['abc', 123]

    生成器(generator):

    列表生成式:   

    a = [ i  for i in range(10)]

    我们把上面的列表生成式改动一下:

    a = ( i  for i in range(10))   # 把列表符号[]改成括号()   # 利用这种方式就变成了一个生成器
    print(a)

    # 输出结果:
    # <generator object <genexpr> at 0x0000001885E70EB8> # 打印结果显示a是一个生成器(generator)的内存地址, 此时a里面没有任何东西,它只是一个算法,但它已经有了生成数据的能力,让a产生数据的方式是利用next()去调用a

    print(next(a)) # 输出结果为: 0
    print(next(a))  # 输出结果为: 1
    print(next(a))  # 输出结果为: 2
    print(next(a))  # 输出结果为: 3
    print(next(a))  # 输出结果为: 4
    print(next(a))  # 输出结果为: 5
    print(next(a))  # 输出结果为: 6
    print(next(a))  # 输出结果为: 7
    print(next(a))  # 输出结果为: 8
    print(next(a))  # 输出结果为: 9  # 每利用next()调用一次a,a都会向下产生一个数据,并且不能后退。每生产完一个数据,生成器就停在了这个位置,下次再调用生产时,它会接着前一次的数据继续向下一个生产。所以,每个数据只能生产一次。
    print(next(a))  # 输出结果为:  报错,并显示“StopIteration”   #  所以 利用next的方式在调用完生成器里面的数据后会报错

    生成器的特性:

    1. 我想要什么数据它不会立即产生,我取一次它才创建一次

    2. 生成器只能往前走(往下一个生产),不能往回退,原先生产过的值不可能再生产一次; 并且, 生产完数据后再利用next调用会报错

    调用生成器一种是利用next(),还可以利用for循环调用。 例如:

    a = ( i  for i in range(10))  
    for i in a:   # a为生成器内存地址
        print(i)
    
    # 输出结果:
    # 0
    # 1
    # 2
    # 3
    # 4
    # 5
    # 6
    # 7
    # 8
    # 9
    
    # 利用for循环取生成器里面的地址,取完之后也不会报错,所以通常利用这种方式。

    另外,next()可以手动调用生成器,也可以利用while调用,如:

    a = ( i  for i in range(5))
    while True:
        print(next(a))
    
    # 0
    # 1
    # 2
    # 3
    # 4
    # 报错,StopIteration 
    
    # 注: next()调用生成器有两种方式: 1. 手动一个个调用; 2. 利用while调用。 这两种方式的本质都是在利用next(),所以调用完之后都会报错。

    range() 底层就是用生成器实现的 (Python3中)

    Python2中 range(10)直接就是个列表(立即生成)

    a = range(10)
    print(a)
    
    # 输出结果:
    # [0,1,2,3,4,5,6,7,8,9]   #在Python2中range()直接就是列表, 但在Python3中,range()只是一个生成器,一种算法,根本没有创建列表

    # 在Python2中,xrange()的功能就相当于Python3中的range()
    # Python3中已经没有xrange()了

    利用函数实现斐波那契数列(前两数为1,后面的数等于前面的两个数之和):

    # 需求: 打印斐波那契数列的第10个值:
    def fib(max):
        a = 0
        b = 1
        count = 0
        while count < max:
            print(b)
            a,b = b,a+b  # 这种写法要注意: 此时b赋值给a,是把原先b的值赋值给a;a+b赋值给b,是把原先a的值和原先b的值赋给b
            count += 1
    fib(10)
    
    # 输出结果:
    # 1
    # 1
    # 2
    # 3
    # 5
    # 8
    # 13
    # 21
    # 34
    # 55

    上面的fib()函数稍作修改就能变成生成器:

    def fib(max):
        a = 0
        b = 1
        count = 0
        while count < max:
            yield b  # 只要加了yield,函数就会变成生成器。并且yield后面的对象(如:b)会return给调用fiber(max)函数的对象(例如:fiber(10)),只有在利用next()或for循环调用生成器的时候,fib()函数里面的代码才会一次次执行(就是说 不调用生成器的话,程序就永远冻结在了yield这一步,只有调用生成器(如next)它才会继续执行)。
            a,b = b,a+b  
            count += 1
    f = fib(10)   # 由于fib()函数里面有yield,所以fib(10)执行fib(max)函数时,函数内部的代码并没有任何的执行,只是把这个函数变成了一个生成器。  # 此时fib(10)的打印结果是:<generator object fib at 0x000000E6605D0EB8>   #  需要把生成器的内存地址赋值给一个变量
    
    print(next(f))   # 输出结果: 1
    print(next(f))   # 输出结果: 1
    print(next(f))   # 输出结果: 2
    print(next(f))   # 输出结果: 3
    print(next(f))   # 输出结果: 5
    print(next(f))   # 输出结果: 8
    print(next(f))   # 输出结果: 13
    print(next(f))   # 输出结果: 21
    print(next(f))   # 输出结果: 34
    print(next(f))   # 输出结果: 55

    总结1:

    1. 原先函数只能在里面执行,你要是想要里面的执行结果只能return,一return整个函数就停止执行了;但生成器可以把函数里面执行的每一个值、每一个结果返回出来

    2. 只要函数内部有yield, 函数名加()后,函数内部的代码根本不执行,只是生成了一个生成器对象

    总结2:

    生成器的创建方法:

    1. 类似列表生成式的方法(但不是列表)

    2. 函数内带有yield

    这两种创建方式的区别: 类似列表生成式的方法最复杂只能处理一个三元运算

    总结3:

    Python2中:  
    range == list
    xrange == 生成器
    
    Python3中:
    range == 生成器
    xrange 不存在 

    总结4: return vs yield

    return: 返回数据,并终止函数

    yield: 返回数据,并冻结函数当前的执行过程; 如果想要继续执行生成器,只能利用类似next()的方法再次唤醒程序。

    def fib(max):
        a = 0
        b = 1
        count = 0
        while count < max:
            yield b
            a,b = b,a+b
            count += 1
    f = fib(10)
    
    for i in f:
        print(i)

    next() 的作用: 唤醒被冻结(如yield)的函数执行过程,直到遇到下一个yield。

    注: 类似列表生成式的方法底层也是yield函数

    循环读取文件的原理也是生成器:

    f = open('test.txt','r')
    
    for i in f:   #在循环f的时候就是在循环一个生成器
        print(i)   # 循环一边就读取一行

    注: next(生成器)的另一种写法:  生成器.__next__()    (next两个各有两个下划线)

    只要函数里面有yield,它就会变成一个生成器,遇到return生成器就会终止,生成器就会报错:

    def range2(n):
        count=0
        while count < n:
            yield count
            count += 1
    
    new_range = range2(3)
    
    print(next(new_range))
    print(next(new_range))
    print(next(new_range))
    print(next(new_range))
    
    # 输出结果:
    # 0
    # 1
    # 2
    # 报错, stopiteration
    
    # 上面生成器中假如了return:
    
    def range2(n):
        return 33333
        count=0
        while count < n:
            yield count
            count += 1
    
    new_range =  range2(3)
    print(next(new_range))
    
    # 输出结果:
    # 报错,StopIteration: 33333    #  return让生成器终止执行,就会报错

    所以,函数有了yield之后:

    1. 函数名加()就得到了生成器

    2. return在生成器里,代表生成器的终止,就会报错

    生成器的send方法:

    可以利用  “ 生成器.send( '指令 ' )  ” 的方法给生成器内部发送指令, 发送的指令是发送到了 yield那一句。

    def range2(n):
        count=0
        while count < n:
            sign = yield count  # send的指令发送给yield语句
            print('-----检测-----',sign)
            count += 1
    
    new_range = range2(5)
    
    print(next(new_range))
    print(next(new_range))
    print(new_range.send('test'))
    
    # 输出结果:
    # 0
    # -----检测----- None
    # 1
    # -----检测----- test
    # 2

    # 注: 1. send发送的括号里面的内容不能为空
    # 2. can't send non-None value to a just-started generator (大致意思就是说,你要是想用send方法调用生成器的第一次执行,send括号里面的内容必须为“None”(生成器起始的第1次并没有yield的等待)
    # 3. 可以这么理解: next()这种调用方式的底层其实就是调用的send方法,只不过send的内容为“None”
    # 4. next()只能唤醒程序执行,发送给生成器内部的指令只能是“None”; 而send不但可以唤醒生成器继续执行,而且可以给生成器内部发送任何指令

     迭代器:

     可直接作用于for循环的数据类型有:

    1. 集合数据类型,如 list、tuple、dict、set、str等

    2. generator,包括生成器和带yield的generator function

    这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。

    可以使用 isinstance()判断一个对象是不是Iterable对象:

    from collections import Iterable
    print(isinstance(['a','b','c'],Iterable))
    
    # 输出结果:
    # True
    
    from collections import Iterable
    
    print(isinstance((i for i in range(10)),Iterable))
    
    # 输出结果:
    # True

    定义:可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator  (生成器只是迭代器的一种)

    可以用 isinstance()判断一个对象是不是Iterator对象:

    from collections import Iterator
    print(isinstance(['a','b','c'],Iterator))
    
    # 输出结果:
    # False
    
    from collections import Iterator
    print(isinstance((i for i in range(10)),Iterator))
    
    # 输出结果:
    # True

    生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。

    把list、str、dict等Iterable变成Iterator可以使用iter()函数:

    from collections import Iterator
    print(isinstance(iter(['a','b','c']),Iterator))
    
    # 输出结果:
    # True
    
    a = ['a','b','c']
    b = iter(a)
    print(b)
    print(a)
    
    print(next(b))
    print(next(b))
    
    # 输出结果:
    # <list_iterator object at 0x0000004C9B7BA080>  # 生成器内存地址
    # ['a', 'b', 'c']
    # a
    # b  # 可利用next()函数调用该生成器

     注: Python3中的for循环本质上就是通过不断调用next()函数实现的 

  • 相关阅读:
    springboot mybatis自定义枚举enum转换
    springboot结合swagger生成接口文档
    Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggre
    java枚举enum equal与==
    linux安装redis
    springboot添加第三方的jar或本地jar
    SpringBoot Junit测试Controller
    List集合分页
    SpringBoot集成redisson分布式锁
    转载- ACM常见的各种说法
  • 原文地址:https://www.cnblogs.com/neozheng/p/8366725.html
Copyright © 2011-2022 走看看