zoukankan      html  css  js  c++  java
  • python-生成器、迭代器、装饰器

    动态语言和静态语言

    动态语言可以在运行的过程中修改代码,例如python在运行的过程中给已创建好的类添加属性和方法。

    静态语言在编译时已经确定好代码,在运行过程中不能修改代码

    那么问题来了,如果我们不想在python的运行中,或者让别人在调用我们的模块时添加新的属性,我们就需要使用python定义的一个特殊变量:_slots_

    _slots_

    在定义一个类的时候使用这个变量,可以限制类的实例添加属性

    class Person(object):
        __slots__ = ('name','age')
        
    p = person()
    p.name = 'laowang'
    p.age = 20
    p.heigt = 180
    >> AttributeError: Person instance has no attribute 'score'
    

    注意:

    使用__slots__定义的属性仅针对当前类的实例起作用,对继承自该类的子类不起作用

    生成器

    首先思考一个问题,当某个函数隔一段时间需要去从一个列表中获取一个数据,那假设这个函数要不断获取1百万个数据时,需要先把这一百万个数据全都放在列表里吗?答案是否定的,这会非常浪费内存空间。所以生成器产生了,在python中,一边循环一边计算的机制成为生成器:generator,我是这样理解的:先定义一个按需求循环生产数据的对象,不需要一次全部生产出来,当每次另一边需要时,从这个对象里面取出一个值使用就好了,这样可以节省内存空间,同时也方便多个对象来使用它。

    创建生成器的方法1
    G = ( x*2 for x in range(5))
    >>G
    >><generator object <genexpr> at 0x7f626c132db0>
    >>next(G)
    >>0
    >>next(G)
    >>2
    
    创建生成器的方法2

    除了用简单的for方法,我们还可以用函数来实现生成器

    def fib(times):
        n = 0
        a,b = 0,1
        while n<times:
            yield b
            a,b = b,a+b
            n+1
    	retrue 'done'
    
    F = fib(5)
    next(F)
    >>1
    next(F)
    >>1
    next(F)
    >>2
    

    注意,这里使用了yield,相当于执行到yield之后,这个函数会进入阻塞状态。再次调用时,从yield下方开始,再到yield停止。当这个生成器的值被取完后,再次取值程序会报错,返回done。所以一定要注意生成器的剩余值情况。

    生成器的send和__next__方法
    def gen():
        i = 0
        while i<5:
            temp = yield i
            print(temp)
            i += 1 
    
    >> f = gen()
    >> f.__next__()
    >> 0
    >> f.__next__()
    >> None
    >> 1
    >> f.send('laowang')
    >> laowang
    >> 2
    

    从上两块代码可见,_next_()方法跟next(f)实现了相同的效果。

    前面提到过,yield后,程序会进入阻塞状态,另外赋值语句是先执行等号的右边,再执行等号的左边,所以send方法发送的参数会传递给temp,这就意味着处理数据的函数和生成器可以“沟通”下一步该怎么生产数据

    迭代器

    迭代是访问集合的一种方式,迭代器是一个可以记住遍历的位置的对象,迭代器对象从集合的第一个元素开始访问,知道所有元素都被访问完结束。

    可迭代对象

    以直接作用于 for 循环的数据类型有以下几种:

    一类是集合数据类型,如 list 、 tuple 、 dict 、 set 、 str 等;

    一类是 generator ,包括生成器和带 yield 的generator function。

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

    生成器都是 Iterator 对象,但 list 、 dict 、 str 虽然是可迭代的 Iterable ,却不是迭代器(Iterator)。

    总结
    • 凡是可作用于 for 循环的对象都是 Iterable 类型;
    • 凡是可作用于 next() 函数的对象都是 Iterator 类型
    • 集合数据类型如 list 、 dict 、 str 等是 Iterable 但不是 Iterator ,不过可以通过 iter() 函数获得一个 Iterator 对象。

    闭包

    首先举个例子

    def test():
        print('233')
    
    x = test
    print(id(x))
    print(id(test))
    x()
    >> 140212571149040
    >> 140212571149040
    >> 233
    

    x实际上是引用了test函数的地址,并不是直接创建了一个新的函数或者使用了一个新的地址。

    什么是闭包

    在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数及用到的一些变量称之为闭包

    def test(num_out):
        def test_in(num_in):
            print(num_in)
        	return num_out + num_in
    	return test_in  # 注意返回的是内部函数地址
    
    ret = test(20)  # 20给num_out,因为都在一个函数内部,所以变量是可以使用的
    print(ret(100)) # 100给num_in
    >> 100
    >> 120
    

    这里需要注意ret = test(100)直接执行到return test_in 将内部函数的地址返回给ret,所以才能打印。

    闭包再理解
    def line_conf(a, b):
        def line(x):
            return a*x + b
        return line
    
    line1 = line_conf(1, 1)
    line2 = line_conf(4, 5)
    print(line1(5))
    print(line2(5))
    

    在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用。

    如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。

    闭包可以帮我们把相同规律的事情总结一下,初始化部分交给一个函数,后续操作交给另一个函数,让事情变得层次分明,同时也更方便操作。

    闭包思考
    1.闭包似优化了变量,原来需要类对象完成的工作,闭包也可以完成
    2.由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存
    

    装饰器

    作为python3的新特性,装饰器可以提高开发效率,也是python面试中经常会问的问题。

    首先对函数名要有这样的理解,名字只是代表了对一个空间的引用,它是可以改变的

    def foo():
    	print('ffffooo')
    foo = lambda x:x+1
    foo()
    >>TypeError: <lambda>() missing 1 required positional argument: 'x'
    肯定会报错,因为foo指向的引用地址改变了。
    
    装饰器(decorator)功能
    • 引入日志
    • 函数执行时间统计
    • 执行函数前预备处理
    • 执行函数后清理功能
    • 权限校验等场景
    • 缓存
    引入

    假设某公司让一个开发部门开发了一些功能,让四个部门分别使用自己的功能,所以要在四个部门使用前验证一次身份,以保证安全性。

    已实现的完整功能:
    def f1():
    	print('f1')
    def f2():
    	print('f2')
    调用:
    # A部门使用f1
    # B部门使用f2
    # 因为要验证,所以某开发人员又将函数写成了如下形式,
    def check()
    	验证身份
    def f1():
    	check() 
    	print('f1')
    def f2():
        check()
    	print('f2')
    # 然后再让A部门调用f1,B部门调用f2
    

    虽然也完成了想要的功能,但违反了开放封闭原则,即已实现的功能代码块不允许被修改,但可以扩展(修改复杂业务逻辑的代码容易出现bug,但可以在参数传入前加工一下或数据处理后再进行修正)

    • 开放:对扩展开发
    • 封闭:已实现的功能代码块

    所以需要结合闭包将代码改成如下形式

    def w1(func):
        def inner():
            print('check')
            func()
        print("i am inner", id(inner))
        return inner
    
    @w1
    def f1():
        print('i am f1', id(f1))
        print('f1')
    f1()
    
    >>
    i am inner 2814185593032
    check
    i am f1 2814185593032
    f1
    

    此时,我们再不修改f1的情况下完成了验证,那代码的执行逻辑是怎么的呢?

    最关键的一点是@w1 > f1 = w1(f1)

    w1执行后会将f1的引用传递给func(以供inner调用它),然后打印inner函数的id,再将inner的引用返回给f1
    f1():执行f1,则f1实际上的引用指向的是inner,所以会执行整个inner函数,而inner中又包含了f1的引用,这样就可以再f1功能执行前加入一些需要的操作,例如之前提到的验证身份
    

    所以@w1就是所谓的装饰器,@函数名 是python中的一种语法糖

    再谈装饰器
    # 定义函数:完成包裹数据
    def makeBold(fn2):
        def wrapped2():
            return "<b>" + fn2() + "</b>"
        return wrapped2
    
    # 定义函数:完成包裹数据
    def makeItalic(fn1):
        def wrapped1():
            return "<i>" + fn1() + "</i>"
        return wrapped1
    
    @makeBold   
    @makeItalic 
    def test():
        return "hello world"
    
    print(test())
    >> <b><i>hello world</i></b>
    

    @makeBold

    @makeItalic

    这里会先执行makeItalic,再执行makeBold,因为这个语法糖需要对函数操作,没有函数就让下面的先执行。

    @makeItalic  # test = makeItalic(test) -> makeItalic-wrapped1, fn1 -> test(原始内存空间的引用)
    @makeBold   # test(引用已改变) = makeBold(test) -> makeBold-wrapped2, fn2 -> makeItalic-wrapped1
    
    装饰器举例
    被装饰的函数有不定长参数
    from time import ctime, sleep
    def timefun(func):
        def wrappedfunc(*args, **kwargs):
            print("%s called at %s"%(func.__name__, ctime()))
            func(*args, **kwargs)
        return wrappedfunc
    
    @timefun
    def foo(a, b, c):
        print(a+b+c)
    
    foo(3, 5, 7)
    sleep(2)
    foo(1, 4, 6)
    
    被装饰的函数有return的处理
    from time import ctime, sleep
    
    def timefun(func):
        def wrappedfunc():
            print("%s called at %s"%(func.__name__, ctime()))
            func()
        return wrappedfunc
    
    @timefun
    def getInfo():
        return 'haha'
    print(getInfo())
    >> None
    # getInfo被装饰后,因为return的'haha'是给的func(),而不是getInfo,因此getInfo是没有返回值的,所以打印的是None。
    # 需要将func()修改成 return func(),才返回给getInfo,然后打印出'haha'
    

    总结:为了装饰器更通用,一般可以有return

    装饰器带参数,在原有装饰器的基础上,设置变量
    from time import ctime, sleep
    
    def timefun_arg(pre="hello"):
        print('tag1, ', pre)
        def timefun(func):
            def wrappedfunc():
                print("%s called at %s %s"%(func.__name__, ctime(), pre))
                return func()
            return wrappedfunc
        return timefun
    
    @timefun_arg("itcast")
    def foo():
        print("I am foo")
    
    print('tag2')
    sleep(2)
    foo()
    >>输出:
    tag1, itcast
    tag2
    foo called at Sun Dec  9 21:43:36 2018 itcast
    I am foo
    

    可见,当装饰器带有参数后,它会先去执行装饰器函数,而以前没有带入参数时它会直接装饰下方定义的函数。这样的话我们就可以给装饰器添加一些变量或者常用量


    作者:bitterz
    本文版权归作者和博客园所有,欢迎转载,转载请标明出处。
    如果您觉得本篇博文对您有所收获,请点击右下角的 [推荐],谢谢!
  • 相关阅读:
    CSS3盒模型display初探(display:box/display:flex)
    css伪元素研究(::before/::after)
    css后代选择器(div.class中间不带空格)
    css选择器(选择<div>内所有<p>元素)
    text-indent无效解决方案
    控制div位于最上层
    gulp用途
    Webpack打包工具实时更新操作(启用观察者模式)
    CLR/.NET/C#/Visual Studio/ASP.NET各版本之间的关系(转)
    前端打包/自动化构建工具:fis3
  • 原文地址:https://www.cnblogs.com/bitterz/p/10210561.html
Copyright © 2011-2022 走看看