• python中“生成器”、“迭代器”、“闭包”、“装饰器”的深入理解


    python中“生成器”、“迭代器”、“闭包”、“装饰器”的深入理解

    一、生成器

    1、生成器定义:在python中,一边循环一边计算的机制,称为生成器:generator.

    a. 语法上和函数类似:生成器函数和常规函数几乎是一样的。它们都是使用def语句进行定义,差别在于,生成器使用yield语句返回一个值,而常规函数使用return语句返回一个值。

    b. 自动实现迭代器协议:对于生成器,python会自动实现迭代器协议,以便应用到迭代中(如for循环,sum函数)。由于生成器自动实现了迭代器协议,所以,我们可以调用它的next方法,并且,在没有值可以返回的时候,生成器自动产生StopIteration异常

    c. 状态挂起:生成器使用yield语句返回一个值。yield语句挂起该生成器函数的状态,保留足够的信息,以便以后从它离开的地方继续执行

    2、生成器优点:

    节约内存。python在使用生成器时对延迟操作提供了支持。所谓延迟,是指在需要的时候才产生结果,而不是立即产生结果。这样在需要的时候才去调用结果,而不是将结果提前存储起来要节约内存。比如用列表的形式存放较大数据将会占用不少内存。这是生成器的主要好处。比如大数据中,使用生成器来调取数据结果而不是列表来处理数据,因为这样可以节约内存。

    l 迭代到下一次的调用时,所使用的参数都是第一次所保留下的。

    3、在python中,有两种创建生成器的方式:

    (1)生成器表达式:类似与列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表。

    使用列表推导,将会一次返回所有结果:

    list1 = [i**2 for i in range(5)]
    print(list) #[0,1,4,9,16]
    

    将列表推导的中括号,替换成圆括号,就是一个生成器表达式:

    list1 = (i**2 for i in range(5))
    print(list1)
    for x in range(5):
    	print(next(li))
    

    输出结果为:

    <generator object <genexpr> at 0x0C87B5D0>
    0
    1
    4
    9
    16
    

    注意:创建完成生成器后,可以使用next()来调用生成器的数据结果,每调用一次返回一个值,直到调用结束。调用结束后list1中为空的,不在有任何值。要想使用它,只能重新创建新的生成器。(生成器表达式的第四行代码也可以改成:print(x).)

    (2)生成器函数

    常规函数定义,但是使用yield语句而不是return语句返回结果。yield语句每次返回一个结果,但每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行。

    我们下面来看一个例子。下面为一个可以无穷生产奇数的生成器函数。

    def numall():
        n=1
        while True:
            yield n
            n += 2
    
    demo_numall = numall()
    count = 0
    for i in demo_numall:
        if count >= 5:
            break
        print(i)
        count += 1
    

    输出结果为:

    1
    3
    5
    7
    9
    

    上面的函数中,yield是必备的,当一个普通函数中包含yield时,系统会默认为是一个generator。

    再举一个例子。使用生成器函数来生成斐波纳契数列

    def fibn(times):
        n,a,b = 0,0,1
        while n < times:
            yield b
            a,b = b,a+b
            n += 1
    f = fibn(5)
    for i in f:
        print(i)
    

    输出结果为:

    1
    1
    2
    3
    5
    

    生成器的两种方法:__next__() 和 send() 方法。

    其中,__next__() 方法和next的作用是一样的。如下所示。

    def fibn(times):
        n,a,b = 0,0,1
        while n < times:
            yield b
            a,b = b,a+b
            n += 1
    f = fibn(5)
    for i in range(5):
        print(f.__next__())
    

    输出结果为:

    1
    1
    2
    3
    5
    

    从上面的程序中可以看出,f._next_() 和 next(f) 是作用是一样的。

    下面再来看看send() 方法的使用。

    def fibn(times):
        n,a,b = 0,0,1
        while n < times:
            temp = yield b
            print(temp)
            a,b = b,a+b
            n += 1
    f = fibn(5)
    print(f.__next__())
    print(f.send("1230"))
    print(f.send("abd"))
    

    输出结果为:

    1
    1230
    1
    abd
    2
    

    从上面代码可以看出:使用send()时,必须在yield前面加上一个变量,并打印这个变量。在调用send()方法时其前面需要至少使用一次next()或__next__()方法,因为生成器不可以在使用之前导入任何非None参数。由此可以知道,send()是用来向生成器中导入参数并返回该参数的,与该参数一起返回的还有生成器中原先保存的数据。

    4、示例

    首先,生成器的好处是延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。在大数据运行时,会很节省内存空间

    a=sum([i for i in range(1000000000)])
    print(a)
    
    b=sum(i for i in range(1000000000))
    print(b)
    

    二、迭代器

    迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历位置的对象。迭代器只能往前不能后退。

    1、可迭代对象(Iterable):— 集合数据类型,如 list 、tuple、dict、set、str 等;— 生成器和带yield 的generator function

    2、判断对象可迭代:

    from collections import Iterable
    
    isinstance([ ],Iterable) #True
    

      如上,命令行模式下,先导入Iterable模块,然后输入isinstance([ ],Iterable),括号中前面为待判断的对象,结果以布尔类型结束(True或False),列表是可迭代对象,因此返回True。注意:整数是不可迭代对象。

    3、迭代器:迭代器是可以被next() 函数调用并不断返回下一个值的对象称为迭代器。因此生成器是迭代器的子类,但是注意集合类型的可迭代对象不是迭代器。

    from collections import Iterator
    
    isinstance((x for x in range(10)),Iterator)
    

    这两行代码是用来判断是否为迭代器的,返回True或False。

    iter()函数:将可迭代对象转换成迭代器。

    三、闭包

    1、闭包:内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。

    闭包三要素: 1、嵌套函数 2、变量的引用 3、返回内部函数

    定义闭包时必须满足这三个要素。

    def test(num):
    	def test_in(num_in): #1.嵌套函数
    		sum = num +num_in  #2.内部引用外部变量
    		return sum,num,num_in
    	return test_in  #3.返回内部函数
    Test = test(10)
    print(Test)
    print(Test(10))
    
    

    输出结果为:

    <function test.<locals>.test_in at 0x0C503030>
    (20, 10, 10)

    2、闭包应用

    #求一元一次方程的值(y=a*x+b),输入x,求y

    def fun(a,b):
    	def fun_in(x):
    		return a*x+b
    	return fun_in
    f = fun(3,5)
    print(f(2))
    print(f(3))
    

    输出结果为:

    11
    14
    

    上面的函数中,利用闭包来求一元一次方程的值,更方便,直接输入x的值即可求出对应的y的值。因为这利用了闭包可以记住外部函数的参数的特性。

    global修改全局变量

    x = 2
    def outer():
        x = 0
        def inner():
            # global x
            x = 1
            print(x)
        print(x)
        return inner
    #调用
    outer()
    print(x)
    

    运行结果如下:

    0
    1
    1
    

    注意:nonlocal此时是对嵌套变量也就是x = 2 进行操作处理。

    四、装饰器

    1、装饰器(decorator:装饰器其实就是一个闭包,把一个函数当作参数然后返回一个替代版函数。

    装饰器有2个特性:

    1、可以把被装饰的函数替换成其他函数

    2、可以在加载模块时候立即执行

    2、装饰器的功能

    Ø 引入日志

    Ø 函数执行时间统计

    Ø 执行函数前预备处理

    Ø 执行函数后清理功能

    Ø 权限校验等场景

    Ø 缓存

    3、装饰器的分类

    装饰器可分为对有无参数函数进行装饰的装饰器和对有无返回值函数进行装饰的装饰器,组合起来一共有4种。即:装饰器对无参数无返回值的函数进行装饰,装饰器对无参数有返回值的函数进行装饰,装饰器对有参数无返回值的函数进行装饰,装饰器对有参数有返回值的函数进行装饰。

    A、装饰器对无参数函数进行装饰

    def makeRedColor(fn):
        def wrapper():
            return "33[31m" + fn()
        return wrapper
    
    def makeBoldFont(fn):
        def wrapper():
            return "33[32m " + fn()
        return wrapper
    
    @makeRedColor
    def test1():
        return "hello world - 1"
    
    @makeBoldFont
    def test2():
        return "hello world - 2"
    
    @makeRedColor
    @makeBoldFont
    def test3():
        return "hello world - 3"
    
    print(test1())
    print(test2())
    print(test3())
    

    输出结果为:

    image

    注意:对一个函数可以同时使用多个装饰器,装饰顺序由内而外。

    B、装饰器对有参数函数进行装饰

    def deco(fn):
        def wrapper(a,b): #内部函数的参数必须和被装饰的函数保持一致
            print("和值为:",end='')
            fn(a,b)
        return wrapper
    @deco
    def sum1(a,b):
        print(a+b)
    
    sum1(10,15)
    

    输出结果为:

    和值为:25

    当装饰器装饰有参数的函数时,装饰器内部的函数也必须带有和其相同的参数,因为被装饰的参数会被当成参数传进装饰器的内部函数中,如果两者的参数不一致,会报错。

    C、装饰器对不定长参数函数进行装饰

    import time
    def deco(func):
        def wrapper(*arg,**kwargs):
            print("{}在{}被调用执行".format(func.__name__,time.ctime()))
            func(*arg,**kwargs)
        return wrapper
    
    @deco
    def sum1(a,b,c):
        print(a+b+c)
    
    @deco
    def sum2(a,b):
        print(a+b)
    
    sum1(1,2,3)
    time.sleep(2)
    sum2(5,3)
    time.sleep(2)
    sum1(1,2,3)
    time.sleep(2)
    

    输出结果为:

    sum1在Tue Dec 4 11:45:56 2018被调用执行
    6
    sum2在Tue Dec 4 11:45:58 2018被调用执行
    8
    sum1在Tue Dec 4 11:46:00 2018被调用执行
    6
    

    D、装饰器对有返回值的函数进行装饰

    import time
    def deco(func):
        def wrapper(*arg,**kwargs):
            print("{}在{}被调用执行".format(func.__name__,time.ctime()))
            return func(*arg,**kwargs)
        return wrapper
    
    @deco
    def sum1(a,b,c):
        print(a+b+c)
    
    @deco
    def test():
        print("太阳当空照,花儿对我笑")
    
    sum1(1,2,3)
    test()
    

    输出结果为:

    sum1在Tue Dec 4 11:49:10 2018被调用执行
    6
    test在Tue Dec 4 11:49:10 2018被调用执行
    太阳当空照,花儿对我笑
    

    这个的装饰器是一个万能的装饰器,因为其可以用来装饰任何函数,包括有无参数函数和有无返回值函数。

    实例:

    模拟Django 注册登录装饰器

    import time
    a = ['while','for','django']
    def outer(func):
        def inner(name):
            func(name)
            print('开始判断有没有登录?')
            time.sleep(2)
            if name in a:
                print('{}已经登录成功,请尽情访问'.format(name))
                time.sleep(1)
            else:
                print('{}没有登录,没有权限访问'.format(name))
                time.sleep(1)
        return inner
    @outer
    def login(name):
        print('{}要浏览'.format(name))
    login('for')
    

    执行结果:

    for要浏览
    开始判断有没有登录?
    for已经登录成功,请尽情访问
    
  • 相关阅读:
    fread 和 read的区别
    Windows下的linux开发环境Cygwin的安装配置
    开机出现grub提示符后怎样进入系统?
    Matlab中函数句柄的优点
    UNIX环境高级编程的apue.h源码APUE
    装了一个ubuntu10.10,打印机不能添加了,
    迅雷上如何下载热映的电影大片~~
    我的linux 初始配置安装的东东,最好保存上一份,对于经常装linux的朋友
    HDU 1875 畅通工程再续
    HDU 1874 畅通工程续
  • 原文地址:https://www.cnblogs.com/pinpin/p/10107278.html
Copyright © 2020-2023  润新知