zoukankan      html  css  js  c++  java
  • 5 Python3 函数进阶&迭代器与生成器

    1、函数进阶

    1.1、名称空间

    又名name space, 顾名思义就是存放名字的地方,存什么名字呢?举例说明,若变量x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方

    名称空间共3种,分别如下

    • locals: 是函数内的名称空间,包括局部变量和形参
    • globals: 全局变量,函数定义所在模块的名字空间
    • builtins: 内置模块的名字空间

    1.2、作用域

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

    查看作用域方法 globals(),locals()

    level = 'L0'
    n = 22
    
    
    def func():
        level = 'L1'
        n = 33
        print(locals())
    
        def outer():
            n = 44
            level = 'L2'
            print(locals(),n)
    
            def inner():
                level = 'L3'
                print(locals(),n) #此外打印的n是多少?
            inner()
        outer()
    func()

    输出结果

    {'level': 'L1', 'n': 33}
    {'level': 'L2', 'n': 44} 44
    {'level': 'L3', 'n': 44} 44

     
    LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__
    
    locals 是函数内的名字空间,包括局部变量和形参
    enclosing 外部嵌套函数的名字空间
    globals 全局变量,函数定义所在模块的名字空间
    builtins 内置模块的名字空间

    1.3、闭包

    关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数。

    当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。

    而当这个内部函数执行时,它仍然必需访问其外部函数的局部变量、参数以及其他内部函数。

    这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。

    def outer():
        name = 'alex'
    
        def inner():
            print("在inner里打印外层函数的变量",name)
    
        return inner
    
    
    f = outer() 
    
    f()

    闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,

    这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域

    2、装饰器

    有了闭包函数的概念,我们再去理解装饰器会相对容易一些。python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,

    装饰器的返回值也是一个函数对象(函数的指针)。装饰器函数的外部函数传入我要装饰的函数名字,返回经过修饰后函数的名字;内层函数(闭包)负责修饰被修饰函数。

    从上面这段描述中我们需要记住装饰器的几点属性,以便后面能更好的理解:     实质: 是一个函数     参数:是你要装饰的函数名(并非函数调用)     返回:是装饰完的函数名(也非函数调用)     作用:为已经存在的对象添加额外的功能     特点:不需要对对象做任何的代码上的变动 python装饰器有很多经典的应用场景,比如:插入日志、性能测试、事务处理、权限校验等。装饰器是解决这类问题的绝佳设计。

    并且从引入中的列子中我们也可以归纳出:装饰器最大的作用就是对于我们已经写好的程序,我们可以抽离出一些雷同的代码组建多个特定功能的装饰器

    ,这样我们就可以针对不同的需求去使用特定的装饰器,这时因为源码去除了大量泛化的内容而使得源码具有更加清晰的逻辑。

    带装饰器的函数

    user_status = False
    def
    login(func): #把要执行的模块从这里传进来 def inner():#再定义一层函数 _username = "alex" #假装这是DB里存的用户信息 _password = "abc!23" #假装这是DB里存的用户信息 global user_status if user_status == False: username = input("user:") password = input("pasword:") if username == _username and password == _password: print("welcome login....") user_status = True else: print("wrong username or password!") if user_status == True: func() # 看这里看这里,只要验证通过了,就调用相应功能 return inner #用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数 @login def america(): #login() #执行前加上验证 print("----欧美专区----")

    相当于先          america ==  login(america)---------------------login(func)

    括号里面的   (america)------(func)

    返回 inner的内存地址 ----即 american==login(america) = inner------然后加()   inner()---if user_status == True: 执行:func()== america()

     

    2.1、两个装饰器

    def w1(func):
        print('---正在装饰--')
        def inner():
            print('---正在验证权限1--')
            func()
        return inner
    
    
    def w2(func):
        print('---正在装饰2--')
        def inner():
            print('---正在验证权限2--')
            func()
        return inner
    
    # 只要python解释器执行到了这个代码,那么就会自动的进行装饰,而不是等到调用的时候才装饰的
    @w2
    @w1
    def f1():
        print('---f1')
    
    f1()
    输出结果:
    ---正在装饰--
    ---正在装饰2--
    ---正在验证权限2--
    ---正在验证权限1--
    ---f1

     在调用f1()函数之前已经进行装饰了

     2.2、带参数的装饰器

    exit_flag = False
    def login(f):
        def outer(fun):
            def inner(*args):  # 必须和 实参带参数保持一致   america('kfc')
    
                if f == 'qq':
                    user_info = {'alex':'abc'}
                    global exit_flag
                    if exit_flag == False:
                        username = input('username:>').strip()
                        password = input('password:>').strip()
                        if username in user_info and password == user_info[username]:
                            print('welcome login...')
                            exit_flag = True
    
                        else:
                            print('username or password is wrong')
                    if exit_flag == True:
                        fun(*args)
            return inner
        return outer
    
    @login('qq')
    def america(*args):
        print('welcome to the america')
    @login('qq')
    def japan(*args):
        print('welcome to the japan')
    @login('qq')
    def china(*args):
        print('welcome to the china')
    
    america('kfc') #带参数
    japan('daoguo')
    china('beautiful')

    输出:‘

    username:>alex
    password:>abc
    welcome login...
    welcome to the america
    welcome to the japan
    welcome to the china

    带参数的先执行 参数  login('qq') == login(auth_type)  返回 auth的地址

     

     3、列表生成器

    现在有个需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],要求你把列表里的每个值加1,你怎么实现?你可能会想到2种方式
    
    二逼青年版
    
    >>> a
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    >>> b = []
    >>> for i in a:b.append(i+1)
    ... 
    >>> b
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    >>> a = b
    >>> a
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    普通青年版
    
    a = [1,3,4,6,7,7,8,9,11]
    
    for index,i in enumerate(a):
        a[index] +=1
    print(a)
    文艺青年版
    
    >>> a
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    >>> a = map(lambda x:x+1, a)
    >>> a
    <map object at 0x101d2c630>
    >>> for i in a:print(i)
    ... 
    3
    5
    7
    9
    11

    列表生成器:

    >>> a = [i+1 for i in range(10)]
    >>> a
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    这样的写法就叫做列表生成式

    4、生成器

    通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。
    而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。 所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?
    这样就不必创建完整的list,从而节省大量的空间。

    在Python中,这种一边循环一边计算的机制,称为生成器:generator。
    要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
    
    >>> L = [x * x for x in range(10)]
    >>> L
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    >>> g = (x * x for x in range(10))
    >>> g
    <generator object <genexpr> at 0x1022ef630>
    创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
    
    我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
    
    如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:
    >>> next(g)
    0
    >>> next(g)
    1
    >>> next(g)
    4
    我们讲过,generator保存的是算法,每次调用next(g)就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
    
    当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:
    
    >>> g = (x * x for x in range(10))
    >>> for n in g:
    ...     print(n)

    所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。

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

    比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

    1, 1, 2, 3, 5, 8, 13, 21, 34, ...

    斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易: 

    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            print(b)
            a, b = b, a + b
            n = n + 1
        return 'done'

    4.1、创建生成器的方法

    方法1,只要把一个列表生成式的[]改成(),就创建了一个generator:
    方法2:
    yield 把函数变成生成器

    也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,

    只需要把print(b)改为yield b就可以了:

     

    def fib(max):
        n,a,b = 0,0,1
    
        while n < max:
            #print(b)
            yield  b #把函数执行冻结在这一步,并且把b的值,返回给next()
            a,b = b,a+b
    
            n += 1
    
        return 'done'
    f = fib(10)
    print(fib(10))#<generator object fib at 0x0000000001DE3750>
    print(next(f))# 1  #另外一种表达方式  print(f._next_())
    print(next(f))# 1
    print(next(f))# 2
    while True:
        try:
            x = next(g)
            print('g:',x)
        except StopIteration as e:
            print('generator return value:',e.value)
            break
    如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

     yield返回数据,并冻结当前执行过程    yield b,返回数据的效果相当于print(b)

    next:唤醒冻结的执行过程,继续执行,直到遇到下一个yield

    send:1、唤醒并继续执行,2、发送一个信息到生成器内部

    #_*_coding:utf-8_*_
    __author__ = 'Alex Li'
    
    import time
    def consumer(name):
        print("%s 准备吃包子啦!" %name)
        while True:
           baozi = yield
           print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
    
    
    def producer(name):
        c = consumer('A')
        c2 = consumer('B')
        c.__next__()
        c2.__next__()
        print("老子开始准备做包子啦!")
        for i in range(10):
            time.sleep(1)
            print("做了2个包子!")
            c.send(i)
            c2.send(i)
    
    producer("alex")
    
    # 通过生成器实现协程并行运算

    for i in rang(0,10)

    rang也是生成器

    >>> range(10)  # 直接生成list
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    打开文件的 f 也是生成器

    5、迭代器iter()

    迭代是Python最强大的功能之一,是访问集合元素的一种方式。

    迭代器是一个可以记住遍历的位置的对象。

    迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

    迭代器有两个基本的方法:iter() 和 next()。

    字符串,列表或元组对象都可用于创建迭代器:

    我们已经知道,可以直接作用于for循环的数据类型有以下几种:
    
    一类是集合数据类型,如list、tuple、dict、set、str等;
    
    一类是生成器generator,包括生成器和带yield的generator function。
    
    这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
    
    可以使用isinstance()判断一个对象是否是Iterable对象:
    >>> from collections import Iterable
    >>> isinstance([], Iterable)
    True
    >>> isinstance({}, Iterable)
    True
    >>> isinstance('abc', Iterable)
    True
    >>> isinstance((x for x in range(10)), Iterable)
    True
    >>> isinstance(100, Iterable)
    False
    *可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
    生成器都是Iterator对象,但list、dict、str虽然是Iterable(可迭代),却不是Iterator(迭代器)把list、dict、str等Iterable变成Iterator可以使用iter()函数:
    
    >>> isinstance(iter([]), Iterator)
    True
    >>> isinstance(iter('abc'), Iterator)
    True
    你可能会问,为什么list、dict、str等数据类型不是Iterator?
    
    这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。

    可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。 Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。 小结 凡是可作用于for循环的对象都是Iterable类型; 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列; 集合数据类型如list、dict、str等是Iterable(可迭代的)但不是Iterator(迭代器),不过可以通过iter()函数获得一个Iterator对象。 Python3的for循环本质上就是通过不断调用next()函数实现的,例如:
    for x in [1, 2, 3, 4, 5]: pass 实际上完全等价于: # 首先获得Iterator对象: it = iter([1, 2, 3, 4, 5]) # 循环: while True: try: # 获得下一个值: x = next(it) except StopIteration: # 遇到StopIteration就退出循环 break
    list = [1,2,34,5]
    it = iter(list)
    print(next(it))
    print(next(it))
    print(next(it))
    
    》:1
       2
       34
  • 相关阅读:
    Handler详细说明系列(六)——View的post()详解
    2013年6月17日星期一
    2013年6月16日星期日
    线程管理四种方法
    java 线程 ProducerAndConsumer
    POJ 2948 DP
    Java的递归算法
    开机黑屏 仅仅显示鼠标 电脑黑屏 仅仅有鼠标 移动 [已成功解决]
    网络安全审查制度即将推出 手机App安全加密成必定趋势
    递归算法浅谈
  • 原文地址:https://www.cnblogs.com/foremostxl/p/9494160.html
Copyright © 2011-2022 走看看