zoukankan      html  css  js  c++  java
  • 函数进阶,装饰器,生成器,迭代器

    名称空间

    定义:假如有 x = 1 , 1是存放到内存的,那 x 存哪里的呢?名称空间是存放名字x与1绑定关系的地方。

    名称空间分3种:

    • locals:是函数内的名称空间,包括局部变量和形参。通过locals()可以打印当前名称空间的局部变量,如果在函数里就是函数里的局部变量。
    • globals:全局变量,函数定义所在模块的名字空间。通过globals()可以打印所有的全局变量,无论是在函数内还是函数外。
    • builtins:内置模块的名字空间。可通过dir(__builtins__)打印所有的内置方法

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

    作用域即范围:

    • 全局范围:全局存活,全局有效
    • 局部范围:临时存活,局部有效。

    作用域查找顺序:

    LEGB代表名字查找顺序:locals --> enclosing function --> globals --> __builtins__

    • locals:是函数内的名字空间
    • enclosing:外部嵌套函数的名字空间
    • globals:全局变量,函数定义所在模块的名字空间
    • builtins:内置模块的名字空间

    闭包

    首先看例子:

    def func():
        name = "Alex"
    
        def inner():
            print("在inner里打印name:", name)
    
        return inner
    
    f = func()
    f()
    View Code

    输出:

    在inner里打印name: Alex
    

    以上例子可以看出,嵌套函数中,直接执行func函数,里面的inner函数并没有执行,而是func函数将inner函数对象返回给了外部,给到了f ,这时候 f 实际上就是inner函数,f 加上括号就相当于直接执行inner函数。这样使得嵌套在函数内的子函数在外部也可被调用执行,调用的时候依然能够获取inner函数所在作用域的变量以及参数。

    关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含他们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必须访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。

    闭包的意义:

    返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得该函数无论在何处调用,优先使用自己外层包裹的作用域。

    定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).

    闭包详解引用:

    https://www.cnblogs.com/JohnABC/p/4076855.html

    装饰器

    装饰器就是在不修改原来程序的情况下给程序添加新的功能。

    • 软件开发中的一个原则:开放-封闭原则
    • 开放:已实现的功能代码块不应该被修改
    • 封闭:对现有功能的扩展开放

    装饰器练习题

     1 '''
     2 一:编写3个函数,每个函数执行的时间是不一样的,
     3 提示:可以使用time.sleep(2),让程序sleep 2s或更多,
     4 
     5 二:编写装饰器,为每个函数加上统计运行时间的功能
     6 提示:在函数开始执行时加上start=time.time()就可纪录当前执行的时间戳,函数执行结束后在time.time() - start就可以拿到执行所用时间
     7 
     8 三:编写装饰器,为函数加上认证的功能,即要求认证成功后才能执行函数
     9 
    10 四:编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,后续的函数都无需再输入用户名和密码
    11 提示:从文件中读出字符串形式的字典,可以用eval('{"name":"egon","password":"123"}')转成字典格式
    12 '''
    13 
    14 import time
    15 
    16 
    17 def runtime(func):
    18     def inner():
    19         start_time = time.time()
    20         func()
    21         end_time = time.time()
    22         run_time = end_time - start_time
    23         print("run time:", run_time)
    24 
    25     return inner
    26 
    27 
    28 def get_account_from_file():
    29     f = open("account2.txt", mode="r", encoding="utf-8")
    30     data = eval(f.read())
    31     f.close()
    32     return data
    33 
    34 
    35 login_status = False
    36 
    37 
    38 def login(func):
    39     def inner(*args, **kwargs):
    40         global login_status
    41         if login_status == False:
    42             username = input("username:>")
    43             password = input("password:>")
    44             account_dict = get_account_from_file()
    45             if username == account_dict["name"] and password == account_dict["password"]:
    46                 print("登录成功")
    47                 login_status = True
    48             else:
    49                 print("用户名或密码错误.")
    50 
    51         if login_status:
    52             print("已通过验证..")
    53             func(*args, **kwargs)
    54 
    55     return inner
    56 
    57 
    58 @login
    59 @runtime
    60 def func1():
    61     print("func1 running...")
    62     time.sleep(2)
    63 
    64 
    65 @login
    66 @runtime
    67 def func2():
    68     print("func2 running...")
    69     time.sleep(3)
    70 
    71 
    72 @login
    73 @runtime
    74 def func3():
    75     print("func3 running...")
    76     time.sleep(1)
    77 
    78 
    79 func1()
    80 func2()
    81 func3()

    列表生成式

    有一个需求:有一个列表,要对列表里的每个元素加1,如何做,有以下几种

    # 版本一:
    a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    b = []
    for i in a:
        b.append(i + 1)
    print(b)  # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    # 版本二:
    c = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    for index, i in enumerate(c):
        c[index] = i + 1
    print(c)  # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    # 版本三:
    d = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    d = list(map(lambda x: x + 1, d))
    print(d)  # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    # 版本四:列表生成式
    e = [i + 1 for i in range(10)]
    print(e)  # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    f = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    f = [i if i < 5 else i * i for i in f]    # 列表生成式
    print(f)  # 输出:[0, 1, 2, 3, 4, 25, 36, 49, 64, 81]  

    生成器

    通过前面的列表生成式,我们可以很容易的创建一个列表,但是假如我要创建100万的数据,那么这100万的数据就会全部在内存中,而内存是有限的,不可能让你无限制的存,而且我只需要访问前面的一部分数据,后面的其实根本不会使用,还以这样的方式创建就对资源消耗太大了。

    如果列表里的元素可以按照某种算法推算出来,那我们就可以循环的推算出后面的元素的值,就可以不用完整的创建整个列表从而大大节省资源消耗,这种一边循环一边计算的机制,就称为生成器(generator)

    要创建一个generator,有很多种方法,最简单的只要把列表生成式的 [ ] 改为 ( ) ,就创建了一个generator:

    >>> L=[i*i for i in range(10)]
    >>> L
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    >>>
    >>>
    >>> g=(i*i for i in range(10))
    >>> g
    <generator object <genexpr> at 0x0000022BD464E150>
    >>>
    

    如何获取generator里的元素呢?通过next()方法来获取。

    说明:

    生成器生成的只是方法,并不执行。

    next()只能往前走,不能往回退。意思就是已经通过next()获取的元素不能够再次获取。 

     generator保存的是算法,每次调用next(g)就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素的时候,抛出StopIteration异常。

    一步一步的nexg(g)很麻烦,我们可以通过for循环来获取元素值,还不会报StopIteration异常

    g = (i * i for i in range(10))
    for j in g:
        print(j)
    

    如果瑞算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

    比如斐波那契数列(Fibonacci),除第一个和第二个数外,任意一个数都是由前两个数相加得到,用列表生成式写不出来,但是用函数打印出来很容易:

    1 def fib(max):
    2     n, a, b = 0, 0, 1
    3     while n < max:
    4         print(b)
    5         a, b = b, a + b
    6         n = n + 1
    7     return 'done'
    8 
    9 fib(10)

    注意:

    a, b = b, a + b

    相当于:

    t = a + b

    a = b

    b = t

    说到这里我们还没有用到生成器,回头想一下生成器的实现逻辑,再来看看这个斐波那契数列的程序,fib函数实际上是定义了斐波那契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这个逻辑和生成器的逻辑是非常相似的。

    上面的函数里生成器 generator就是一步之遥,要把fib函数变成generator,只需要把print(b)n 改为 yield b 就可以了:

     1 def fib(max):
     2     n, a, b = 0, 0, 1
     3     while n < max:
     4         # print(b)
     5         yield b  # 改为 yield 就变成了generator,遇到yield程序就停在这里,等到下一次next()时,程序继续从这里开始运行,然后又等待下一次next(),循环往复,知道报出StopIteration异常
     6         a, b = b, a + b
     7         n = n + 1
     8     return 'done'
     9 
    10 
    11 g = fib(10)
    12 print(g)  # 输出 <generator object fib at 0x000001F14DE340F8>  说明函数变成了generator
    13 print(next(g))  # next(g)  出generator里的元素
    14 print(next(g))
    15 print(next(g))
    16 print(next(g))
    17 print(next(g))
    18 print("其他程序在这个地方执行一下")  # 在next的过程中依然可以执行其他的程序
    19 print(next(g))  # 还可以继续执行之前的next
    20 print(next(g))
    21 print(next(g))
    22 print(next(g))
    23 print(next(g))
    View Code

    输出结果

    <generator object fib at 0x000001F14DE340F8>
    1
    1
    2
    3
    5
    其他程序在这个地方执行一下
    8
    13
    21
    34
    55
    

    关于执行流程:

    函数是顺序执行,遇到 return 语句或者最后一行函数语句就返回。

    变成generator的函数,在每次调用 next() 的时候执行,遇到yield语句返回,再次被next()调用时,从上次返回的yield语句处继续执行。

    yield后面加上a,就表示返回a,类似于 return a ,但yield不会退出,return会退出。

    关于如何拿到generator里的return返回值:

    如果我们再加一个next(g) 这时候就要报StopIteration异常了,因为已经没有值可以拿了,但是函数最后有一个   return  "done"  的返回值,我们是可以看到的。

     1 Traceback (most recent call last):
     2 <generator object fib at 0x000001D4FA4540F8>
     3   File "D:/PycharmProjects/python_fullstack_middle/第二模块:函数编程/第1章·函数、装饰器、迭代器、内置方法/函数/生成器.py", line 41, in <module>
     4 1
     5 1
     6 2
     7 3
     8 5
     9 其他程序在这个地方执行一下
    10 8
    11 13
    12 21
    13 34
    14 55
    15     print(next(g))
    16 StopIteration: done
    View Code

    如果使用 for 循环则拿不到generator里面的 return 返回值。如果要拿到返回值,必须要捕获StopIteration异常,返回值在StopIteration的value中。

    g = fib(10)
    while True:
        try:
            x = next(g)
            print("g:", x)
        except StopIteration as e:
            print("Genrator return value:", e.value)
            break
    

    输出

    g: 1
    g: 1
    g: 2
    g: 3
    g: 5
    g: 8
    g: 13
    g: 21
    g: 34
    g: 55
    Genrator return value: done
    

    关于捕获异常,后面还会详细讲解。

    再来看看生成器的调用方法:

    g = (i for i in range(10))
    print(next(g))
    print(next(g))
    print(next(g))
    
    print("---------------")
    for i in g:
        print(i)

    输出

    0
    1
    2
    ---------------
    3
    4
    5
    6
    7
    8
    9
    

    next()之后,之前的值就没有了,所以看到for循环的时候是从3开始的

    for循环其实就是每循环一次就next一次

    range其实也是一个生成器

    关于range:

    在python2里:
    range(100000000000000) 会直接生成这么多个数据

    在python3里:
    range(100000000000000) 直接生成一个公式,根本就没有创建,在python2里也有同样的 不过叫 xrange(100000000000000) 和py3的range是一样的。

    python2:
      range = list
      xrange = 生成器
    python3
      range = 生成器
      xrange 没有  

    函数写生成器:

    生成器的创建方式:

      1. 列表生成式

      2. 函数

    区别:

      列表生成式最后只能写一个三元运算,更复杂的情况没办法办到。

      所以要用到函数

     1 def range2(n):
     2     count = 0
     3     while count < n:
     4         print("count", count)
     5         count += 1
     6         yield count
     7 
     8 
     9 new_range = range2(10)
    10 
    11 r1 = next(new_range)
    12 print(r1)
    13 r2 = next(new_range)
    14 print(r2)
    15 r3 = next(new_range)
    16 print(r3)
    View Code

    输出:

    count 0    # 由print打印的
    1          # 由yield返回的
    count 1
    2
    count 2
    3
    

     yield vs return:

    return:返回并终止function

    yield:返回数据,并冻结当前的执行过程。

      next 唤醒冻结的函数执行过程,继续执行,知道遇到下一个yield

     生成器send方法:

    在函数里已经有了yield后,再写return是不会返回return值的,并且会报StopIteration异常。

    函数有了yield之后:

    1. 调用时函数名加 ()  就便得到了一个生成器 

    2. return 在生成器里,代表生成器的中止,直接报错。

    send作用:

    1. 唤醒并继续执行

    2. 发送一个信息到生成器内部

     1 def range2(n):
     2     count = 0
     3     while count < n:
     4         print("count", count)
     5         count += 1
     6         sign = yield count
     7         print("收到来自send的消息::", sign)
     8         if sign == "stop":
     9             print("我要停止程序运行了,bye bye")
    10             break  # break后就执行return了,return后会报错,需要自己捕获
    11     return 333
    12 
    13 
    14 new_range = range2(3)
    15 n1 = next(new_range)
    16 new_range.send("stop")
    View Code

    输出:

    count 0
    Traceback (most recent call last):
    收到来自send的消息:: stop
    我要停止程序运行了,bye bye
      File "D:/PycharmProjects/python_fullstack_middle/第二模块:函数编程/第1章·函数、装饰器、迭代器、内置方法/函数/生成器send.py", line 16, in <module>
        new_range.send("stop")
    StopIteration: 333
    

    send()里面不写值的话,默认是发送一个None的。

    next()默认也是发了一个None的。  

    next(iterator, default=None)   如果后面的参数给了值的话,但next完所有元素的时候也不会报StopIteration异常。

    通过yield实现在单线程的情况下实现并发运算的效果:

     1 import time
     2 
     3 
     4 def consumer(name):
     5     print("%s 准备吃包子啦!" % name)
     6     while True:
     7         baozi = yield
     8         print("包子[%s]来了,被[%s]吃了!" % (baozi, name))
     9 
    10 
    11 def producer(name):
    12     c = consumer('A')
    13     c2 = consumer('B')
    14     c.__next__()
    15     c2.__next__()
    16     print("老子开始准备做包子啦!")
    17     for i in range(10):
    18         time.sleep(1)
    19         print("做了%s个包子!" % (i))
    20         c.send(i)
    21         c2.send(i)
    22 
    23 
    24 producer("alex")
    View Code

    输出:

     1 A 准备吃包子啦!
     2 B 准备吃包子啦!
     3 老子开始准备做包子啦!
     4 做了0个包子!
     5 包子[0]来了,被[A]吃了!
     6 包子[0]来了,被[B]吃了!
     7 做了1个包子!
     8 包子[1]来了,被[A]吃了!
     9 包子[1]来了,被[B]吃了!
    10 做了2个包子!
    11 包子[2]来了,被[A]吃了!
    12 包子[2]来了,被[B]吃了!
    13 做了3个包子!
    14 包子[3]来了,被[A]吃了!
    15 包子[3]来了,被[B]吃了!
    16 做了4个包子!
    17 包子[4]来了,被[A]吃了!
    18 包子[4]来了,被[B]吃了!
    19 做了5个包子!
    20 包子[5]来了,被[A]吃了!
    21 包子[5]来了,被[B]吃了!
    22 做了6个包子!
    23 包子[6]来了,被[A]吃了!
    24 包子[6]来了,被[B]吃了!
    25 做了7个包子!
    26 包子[7]来了,被[A]吃了!
    27 包子[7]来了,被[B]吃了!
    28 做了8个包子!
    29 包子[8]来了,被[A]吃了!
    30 包子[8]来了,被[B]吃了!
    31 做了9个包子!
    32 包子[9]来了,被[A]吃了!
    33 包子[9]来了,被[B]吃了!
    View Code

    迭代器

    可以理解为 迭代器=循环 ,迭代一次,循环一次。

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

    • 一类是集合数据类型,如 list、tuple、dict、set、str等;
    • 一类是generator,包括生成器和带 yield 的generator function

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

    使用 isinstance() 判断一个对象是否是 Iterable对象:

     1 >>> from collections import  Iterable
     2 >>> isinstance([],Iterable)
     3 True
     4 >>> isinstance({},Iterable)
     5 True
     6 >>> isinstance("abc",Iterable)
     7 True
     8 >>> isinstance((x for x in range(10)),Iterable)
     9 True
    10 >>> isinstance(100,Iterable)
    11 False
    12 >>>
    View Code

    生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,知道最后抛出StopIteration错误表示无法继续返回下一个值了。

    可以被next()函数调用并不断返回下一个值的对象称为迭代器:Itetator

    同样可以使用 isinstance()判断一个对象是否是 Iterator对象:

     1 >>> from collections import  Iterator
     2 >>> isinstance((x for x in range(10)),Iterator)
     3 True
     4 >>> isinstance([],Iterator)
     5 False
     6 >>> isinstance({},Iterator)
     7 False
     8 >>> isinstance("abc",Iterator)
     9 False
    10 >>>
    View Code

    生成器都是 Itrator 对象。

    list 、dict、str 是 Iterable,但不是 Iterator。

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

    >>> li = [1,2,3,4,5,7]
    >>> isinstance(li,Iterable)
    True
    >>> isinstance(li,Iterator)
    False
    >>> li2=iter(li)
    >>> isinstance(li2,Iterator)
    True  

    为什么 list、dict、str 等数据类型不是 Iterator ?

    因为Python的 Iterator对象表示的是一个数据流,Iterator对象可以被 next() 函数调用并不断返回下一个数据,知道没有数据时抛出 Stopitration 异常。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过 next() 函数实现按需计算下一个数据,所以 Iterator的计算是惰性的,只有在需要返回下一个数据时他才会计算。

    Iterator 甚至可以表示一个无限大的数据流,例如全体自然数。而 list 是永远不可能存储全体自然数的。

    小结:

    凡是可作用于 for 循环的对象都是 Iterable 类型;

    凡是可作用于 next() 函数的对象都是 Iterator类型,他们表示一个惰性计算的序列;

    集合数据类型如 list、dict、str等时Iterable但不是Iterator,不过可以通过iter() 函数变成 Itrator对象。

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

    for x in [1, 2, 3, 4, 5]
        pass
    

    完全等价于:  

    it = iter([1, 2, 3, 4, 5])  # 首先获得Itrator对象
    while True:
        try:
            x = next(it)  # 获取下一个值
        except StopIteration:
            break  # 遇到StopIteration就退出循环
     
    

      

  • 相关阅读:
    pdf文件预览实现
    RPC failed; curl 56 OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 10054 问题解决
    Git、GitHub、GitLab三者之间的联系及区别(转)
    html中:befoer和:after用法(转)
    html中box-shadow用法(转)
    js/vue实现滑块验证(方法3)
    vue实现滑块验证(使用awsc实现)(方法1)
    js/vue实现滑块验证(组件形式,可重复调用)(方法2)
    js实现图片上传
    【英语】IT English (随时更新...)
  • 原文地址:https://www.cnblogs.com/joneylulu/p/10211961.html
Copyright © 2011-2022 走看看