• DAY 12 PYTHON入门


    一、装饰器
    1.1什么是装饰器
    ‘装饰’代指为被装饰对象添加新的功能,’器’代指器具/工具,装饰器与被装饰的对象均可以是任意可调用对象。概括地讲,装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。
    装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景,装饰器是解决这类问题的绝佳设计,有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
    提示:可调用对象有函数,方法或者类,此处我们单以本章主题函数为例,来介绍函数装饰器,并且被装饰的对象也是函数。
    1.2为何要用装饰器
    软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
    对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。
    软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。
    1.3装饰器的实现
    函数装饰器分为:无参装饰器和有参装饰两种,二者的实现原理一样,都是’函数嵌套+闭包+函数对象’的组合使用的产物。
    无参装饰器的实现
    import time
    def index():
      time.sleep(3)
      print('Welcome to the index page’)
      return 200
    index() #函数执行
    遵循不修改被装饰对象源代码的原则,我们想到的解决方法可能是这样
    start_time=time.time()
    index() #函数执行
    stop_time=time.time()
    print('run time is %s' %(stop_time-start_time))
    考虑到还有可能要统计其他函数的执行时间,于是我们将其做成一个单独的工具,函数体需要外部传入被装饰的函数从而进行调用,我们可以使用参数的形式传入
    def wrapper(func): # 通过参数接收外部的值
      start_time=time.time()
      res=func()
      stop_time=time.time()
      print('run time is %s' %(stop_time-start_time))
      return res
    但之后函数的调用方式都需要统一改成
    wrapper(index)
    wrapper(其他函数)
    这便违反了不能修改被装饰对象调用方式的原则,于是我们换一种为函数体传值的方式,即将值包给函数,如下
    index=timer(index) #得到index=wrapper,wrapper携带对外作用域的引用:func=原始的index index()
    # 执行的是wrapper(),在wrapper的函数体内再执行最原始的index
    至此我们便实现了一个无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。
    但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常。
    def home(name):
      time.sleep(5)
      print('Welcome to the home page',name)
    home=timer(home)
    home('egon')
    #抛出异常 TypeError: wrapper() takes 0 positional arguments but 1 was given
    之所以会抛出异常,是因为home(‘egon’)调用的其实是wrapper(‘egon’),而函数wrapper没有参数。
    wrapper函数接收的参数其实是给最原始的func用的,为了能满足被装饰函数参数的所有情况,便用上*args+**kwargs组合(见4.3小节),于是修正装饰器timer如下
    def timer(func):
      def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper
    此时我们就可以用timer来装饰带参数或不带参数的函数了,但是为了简洁而优雅地使用装饰器,Python提供了专门的装饰器语法来取代index=timer(index)的形式,需要在被装饰对象的正上方单独一行添加@timer,当解释器解释到@timer时就会调用timer函数,且把它正下方的函数名当做实参传入,然后将返回的结果重新赋值给原函数名
    @timer
    # index=timer(index)
    def index():
      time.sleep(3)
      print('Welcome to the index page')
      return 200
    @timer # index=timer(home)
 def home(name):
      time.sleep(5)
      print('Welcome to the home page’,name)
    如果我们有多个装饰器,可以叠加多个
    @deco3
    @deco2
    @deco1
    def index():
      pass
    叠加多个装饰器也无特殊之处,上述代码语义如下:
    index=deco3(deco2(deco1(index)))
    1.4有参装饰器的实现
    了解无参装饰器的实现原理后,我们可以再实现一个用来为被装饰对象添加认证功能的装饰器,实现的基本形式如下
    def deco(func):
      def wrapper(*args,**kwargs):
        编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
      return wrapper
    如果我们想提供多种不同的认证方式以供选择,单从wrapper函数的实现角度改写如下
    def deco(func):
      def wrapper(*args,**kwargs):
        if driver == 'file':
          编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
        elif driver == 'mysql':
          编写基于mysql认证,认证通过则执行res=func(*args,**kwargs),并返回res
      return wrapper
    函数wrapper需要一个driver参数,而函数deco与wrapper的参数都有其特定的功能,不能用来接受其他类别的参数,可以在deco的外部再包一层函数auth,用来专门接受额外的参数,这样便保证了在auth函数内无论多少层都可以引用到
    def auth(driver):
       def deco(func):
       return deco
    此时我们就实现了一个有参装饰器,使用方式如下
    #先调用auth_type(driver='file'),得到@deco,deco是一个闭包函数,包含了对外部作用域名字driver的引用,@deco的语法意义与无参装饰器一样 @auth(driver='file')
    def index():
    pass
    @auth(driver='mysql')
    def home():
    pass
    可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的__doc__属性,但对于被装饰之后的函数,查看文档注释
    @timer
    def home(name):
    ''' home page function :param name: str :return: None '''
      time.sleep(5)
      print('Welcome to the home page',name)
    print(help(home))
    ''' 打印结果: Help on function wrapper in module __main__: wrapper(*args, **kwargs) None '''
    在被装饰之后home=wrapper,查看home.__name__也可以发现home的函数名确实是wrapper,想要保留原函数的文档和函数名属性,需要修正装饰器
    def timer(func):
    def wrapper(*args,**kwargs):
    start_time=time.time()
    res=func(*args,**kwargs)
    stop_time=time.time()
    print('run time is %s' %(stop_time-start_time))
    return res
    wrapper.__doc__=func.__doc__
    wrapper.__name__=func.__name__
    return wrapper
    按照上述方式来实现保留原函数属性过于麻烦,functools模块下提供一个装饰器wraps专门用来帮我们实现这件事,用法如下
    from functools import wraps
    def timer(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
    start_time=time.time()
    res=func(*args,**kwargs)
    stop_time=time.time()
    print('run time is %s' %(stop_time-start_time))
    return res
    return wrapper

    二、迭代器

    迭代器即用来迭代取值的工具,而迭代是重复反馈过程的活动,其目的通常是为了逼近所需的目标或结果,每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,单纯的重复并不是迭代

    while True:

        msg = input('>>: ').strip()

    print(msg)

    下述while循环才是一个迭代过程,不仅满足重复,而且以每次重新赋值后的index值作为下一次循环中新的索引进行取值,反复迭代,最终可以取尽列表中的值.

    goods=['mac','lenovo','acer','dell','sony']

    index=0

    while index < len(goods):

        print(goods[index])

    index+=1

    可迭代对象

    通过索引的方式进行迭代取值,实现简单,但仅适用于序列类型:字符串,列表,元组。对于没有索引的字典、集合等非序列类型,必须找到一种不依赖索引来进行迭代取值的方式,这就用到了迭代器。

    要想了解迭代器为何物,必须事先搞清楚一个很重要的概念:可迭代对象(Iterable)。从语法形式上讲,内置有__iter__方法的对象都是可迭代对象,字符串、列表、元组、字典、集合、打开的文件都是可迭代对象:

    {'name':'egon'}.__iter__

    {7,8,9}.__iter__

    ……

    迭代器对象

    调用obj.iter()方法返回的结果就是一个迭代器对象(Iterator)。迭代器对象是内置有__iter__和__next__方法的对象,打开的文件本身就是一个迭代器对象,执行迭代器对象.iter()方法得到的仍然是迭代器本身,而执行迭代器.next()方法就会计算出迭代器中的下一个值。 迭代器是Python提供的一种统一的、不依赖于索引的迭代取值方式,只要存在多个“值”,无论序列类型还是非序列类型都可以按照迭代器的方式取值

    >>> s={1,2,3} # 可迭代对象s

    >>> i=iter(s)  # 本质就是在调用s.__iter__(),返回s的迭代器对象i,

    >>> next(i) # 本质就是在调用i.__next__()

    1

    >>> next(i)

    2

    >>> next(i)

    3

    >>> next(i)  #抛出StopIteration的异常,代表无值可取,迭代结束

    for循环原理

    有了迭代器后,我们便可以不依赖索引迭代取值了,使用while循环的实现方式如下

    goods=['mac','lenovo','acer','dell','sony']

    i=iter(goods) #每次都需要重新获取一个迭代器对象

    while True:

        try:

            print(next(i))

        except StopIteration: #捕捉异常终止循环

            break

    for循环又称为迭代循环,in后可以跟任意可迭代对象,上述while循环可以简写为

    goods=['mac','lenovo','acer','dell','sony']

    for item in goods:  

    print(item)

    for 循环在工作时,首先会调用可迭代对象goods内置的__iter__方法拿到一个迭代器对象,然后再调用该迭代器对象的__next__方法将取到的值赋给item,执行循环体完成一次循环,周而复始,直到捕捉StopIteration异常,结束迭代。

    迭代器的优缺点

    基于索引的迭代取值,所有迭代的状态都保存在了索引中,而基于迭代器实现迭代的方式不再需要索引,所有迭代的状态就保存在迭代器中,然而这种处理方式优点与缺点并存:

    优点:

    1、为序列和非序列类型提供了一种统一的迭代取值方式。

    2、惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用__next__来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。

    缺点:

    1、除非取尽,否则无法获取迭代器的长度

    2、只能取下一个值,不能回到开始,更像是‘一次性的’,迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,你只能重新调用iter方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。

     三、生成器

    若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象。

    >>> def my_range(start,stop,step=1):

    ...     print('start...')

    ...     while start < stop:

    ...         yield start

    ...         start+=step

    ...     print('end...')

    ...

    >>> g=my_range(0,3)

    >>> g

    <generator object my_range at 0x104105678>

    生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器

    >>> g.__iter__

    <method-wrapper '__iter__' of generator object at 0x1037d2af0>

    >>> g.__next__

    <method-wrapper '__next__' of generator object at 0x1037d2af0>

    因而我们可以用next(生成器)触发生成器所对应函数的执行

    >>> next(g) # 触发函数执行直到遇到yield则停止,将yield后的值返回,并在当前位置挂起函数

    start...

    0

    >>> next(g) # 再次调用next(g),函数从上次暂停的位置继续执行,直到重新遇到yield...

    1

    >>> next(g) # 周而复始...

    2

    >>> next(g) # 触发函数执行没有遇到yield则无值返回,即取值完毕抛出异常结束迭代

    end...

    Traceback (most recent call last):

      File "<stdin>", line 1, in <module>

    StopIteration

    既然生成器对象属于迭代器,那么必然可以使用for循环迭代,如下:

    >>> for i in countdown(3):

    ...     print(i)

    ...

    countdown start

    3

    2

    1

    Done!

    有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值

    yield表达式应用

    在函数内可以采用表达式形式的yield

    >>> def eater():

    ...     print('Ready to eat')

    ...     while True:

    ...         food=yield

    ...         print('get the food: %s, and start to eat' %food)

    ...

    可以拿到函数的生成器对象持续为函数体send值,如下

    >>> g=eater() # 得到生成器对象

    >>> g

    <generator object eater at 0x101b6e2b0>

    >>> next(e) # 需要事先”初始化”一次,让函数挂起在food=yield,等待调用g.send()方法为其传值

    Ready to eat

    >>> g.send('包子')

    get the food: 包子, and start to eat

    >>> g.send('鸡腿')

    get the food: 鸡腿, and start to eat

    针对表达式形式的yield,生成器对象必须事先被初始化一次,让函数挂起在food=yield的位置,等待调用g.send()方法为函数体传值,g.send(None)等同于next(g)。

    我们可以编写装饰器来完成为所有表达式形式yield对应生成器的初始化操作,如下

    def init(func):

        def wrapper(*args,**kwargs):

            g=func(*args,**kwargs)

            next(g)

            return g

        return wrapper

    @init

    def eater():

        print('Ready to eat')

        while True:

            food=yield

            print('get the food: %s, and start to eat' %food)

    表达式形式的yield也可以用于返回多次值,即变量名=yield 值的形式,如下

    >>> def eater():

    ...     print('Ready to eat')

    ...     food_list=[]

    ...     while True:

    ...         food=yield food_list

    ...         food_list.append(food)

    ...

    >>> e=eater()

    >>> next(e)

    Ready to eat

    []

    >>> e.send('蒸羊羔')

    ['蒸羊羔']

    >>> e.send('蒸熊掌')

    ['蒸羊羔', '蒸熊掌']

    >>> e.send('蒸鹿尾儿')

    ['蒸羊羔', '蒸熊掌', '蒸鹿尾儿']

  • 相关阅读:
    bat常用指令记录
    物料主数据MM01扩充时默认值的设置 BADI_MATERIAL_REF
    CK11,CK11N 成本估算数据读取
    VUE中具名插槽和匿名插槽的使用
    VUE+element页面按钮调用dialog
    线程进程随笔
    "反直觉" 的Unity粒子系统API
    一个RingBuffer(C语言)
    一个极其简单(陋)的内存分配器
    nginx 转发接口出现 403 forbidden
  • 原文地址:https://www.cnblogs.com/DEJAVU888/p/14212921.html
Copyright © 2020-2023  润新知