• Python(五)-生成器、迭代器、装饰器、程序目录规范


    目录:

      1、生成器

      2、迭代器

      3、装饰器

      4、软件目录结构规范

    第1章 生成器、迭代器、装饰器

    1.1 列表生成式

    现在有个需求,看列表[0123456789],需求要求把列表里的每个值加1,你怎么实现?

    实例1:

    #!/usr/bin/env python
    a=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    b=[]
    for i in a:
        b.append(i+1)
    a=b
    print(a)
    
    结果:
    
    [1, 2, 3, 4, 5, 6, 7, 8, 9,10]

    实例2:

    #!/usr/bin/env python
    a=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    a = map(lambda x:x+1,a)
    for i in a:
        print(i)
    
    结果:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    实例3:

    #!/usr/bin/env python
    a=[i+1 for i in range(10)]
    print(a)
    
    结果:
    
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
           注意:实例3就是列表生成式。

    1.2 生成器

      通过列表生成式,我们可以直接创建一个列表,但是受到内存限制,列表容量肯定式有限的,而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

      所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator。

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

    例如:

    #列表生成式
    print([ i*2 for i in range(10) ])  #使代码更简介
    #生成器   
    print( i*2 for i in range(10) )
    
    结果:
    
    [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
    
    <generator object <genexpr> at 0x101384830>
    
           注意:生成器打印出来是一个内存地址,只能使用__next__方法进行一个一个调用。

    1.2.1 生成器小结

    1、直邮在调用时才会生成相应数据

    2、使用__next__方法进行取下一个值,在2.7里面时next()

    3、它只保留一个值,只记住当前一个值,也不可以跳着取值。

    1.2.2 使用函数生成生成器(斐波那契算法)

    #斐波那契算法:算法为前面的两个数相加得出第三个数,一次类推。
    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            yield b     #有这个方法就会将打印出来的b,编程生成器的方式,因此,此函数就成为了生成器,想把谁返回到外面就用yield
                        #yield终端函数的状态,然后使用next调用就会回到函数
            a,b=b,a+b
            n=n+1
        return 'done'   #返回值,是为了后续抓去错误时使用。
    # #抓取报错信息
    g=fib(6)    #将斐波那契的函数返回值付值给g变量
    while True:
        try:    #尝试语法
            x = next(g) #使用next方法调用此函数,next同等与__next__
            print('g:',x)   #打印返回值
        except StopIteration as e: #使用except 语法抓去报错StopIteration,付值给e,抓去到报错进行返回一个值,这个返回值就是函数的return的值
                                    #为什么要抓去报错呢?因为我们不知道生成器有多长,等死循环到最后没值的时候,那么程序就会报错,所以需要抓去到,
                                    #进行指定返回值。
            print('生成器返回值为:',e.value)   #打印生成器的返回值
            break   #抓到错误后,推出循环

    小结:

      1、yield特性:可以停止函数,将函数停止在右yield这一状态,进行跳出函数,执行此函数后面的逻辑,等在使用next方法调用时,就回到此函数,进行执行函数下面的内容。

    1.2.3 使用yield特性进行单线程并发

    import time
    def chibaozi(name): #定义个一个函数
        print('%s 准备吃包子!' %name) #打印谁来吃包子
        while True:   #来一个循环
            baozi = yield #付值是为来后面使用send方法进行传值
            print('包子%s来了,被%s吃了'%(baozi,name))  #打印一下,为了显示返回到这个yield时执行下面的内容
    def shengchan(name):    #在定义一个函数
        c = chibaozi('A')   #调用上面的函数,进行付值,在这里是将函数变成生成器的过程
        c2 = chibaozi('B')
        c.__next__()        #在这里同时调用两次,在这里调用一次,是为了调用第一次,为了打印yield前面的逻辑内容
        c2.__next__()#同上面的next也是一样。
        print('老子开始吃包子了!') #打印开始
        for i in range(10):#循环十次
            time.sleep(1)       #睡眠1秒
            print('做了2个包子')#打印做的包子
            c.send(i)
            c2.send(i)      #调用并传入值
    
    shengchan('chenxin')    #进行调用生产函数
    
     

           注意:这种单线程并发又叫做协程,比线程单位还小的一个单位。

    1.3 迭代器

    1.3.1 迭代器说明

    我们知道,可以直接作用于for循环的数据类型有以下几种

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

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

    1.3.2 判断是否为可迭代对象

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

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

    from collections import Iterable
    print(isinstance([], Iterable)) #判断列表是不是可迭代对象
    print(isinstance({}, Iterable))#判断字典是不是可迭代对象
    print(isinstance('abc', Iterable))#判断字符串是不是可迭代对象
    print(isinstance((x for x in range(10)), Iterable))#判断生成器是不是可迭代对象
    print(isinstance(100, Iterable))#判断数字是不是可迭代对象
    结果:
    True
    True
    True
    True
    False

    1.3.3 判断是否为迭代器

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

    **可以被next函数条用并不断返回下一个值的对象称为迭代器:iterator

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

    #判断是不是迭代器
    from collections import Iterator
    print(isinstance((x for x in range(10)), Iterator))
    print(isinstance([], Iterator))
    print(isinstance({}, Iterator))
    print(isinstance('abc', Iterator))
    
    结果:
    True
    False
    False
    False
    生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。

    1.3.4 使用iter方法生成迭代器

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

    print(isinstance(iter([]), Iterator))
    
    print(isinstance(iter('abc'), Iterator))
    
    #结果
    True
    True

      你可能会问,为什么list、dict、str等数据类型不是Iterator?

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

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

    1.3.5 小结:

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

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

    3、集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

    4、python的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

    1.4 装饰器

    1.4.1 定义

    装饰器是由两种不同的函数类型组成的,分别是高阶函数与嵌套函数组成。

    1、高阶函数:

    a)                第一规则,修改了被装饰的函数的调用方法

    import time
    def bar():
        time.sleep(2)
        print('我是bar函数')
    def test1(func):
        start_time = time.time()
        func()
        stop_time = time.time()
        print('我是func运行的时间%s'%(stop_time-start_time))
    test1(bar)
    
    结果:
    
    我是bar函数
    
    我是func运行的时间2.0051779747009277

    b)               第二规则:给装饰器添加一个返回值,

    #高阶函数(第二个规则)
    import time
    def bar():
        time.sleep(2)
        print('我是bar函数')
    def test2(func):
        print(func)
        return func
    bar=test2(bar)
    bar()
    
    结果:
    
    <function bar at 0x10137ad08>
    
    我是bar函数
    
    2、嵌套函数
    
    def foo():
        print('in the foo')
        def bar():
            print('in the bar')
        bar()
    foo()
    
    结果:
    
    in the foo
    
    in the bar

    3、装饰器,高阶函数+嵌套函数

    import time
    def timer(func): #timer(test1) func=test1
        def deco(*args,**kwargs): #可以支持传入任意多个参数
            start_time = time.time()
            func(*args,**kwargs)    #此func事timer的参数传进来的,就是被装饰的函数的内存地址,func=test1
            stop_time = time.time()
            print('func的运行时间%s'%(stop_time-start_time))
        return deco #这个return返回deco的执行结果,也就是test1的执行结果
    @timer #test1=timer(test1) #装饰下面的test1
    def test1():
        time.sleep(2)
        print('in the test1')
    test1()
    
     
    
    结果:
    
    in the test1
    
    func的运行时间2.0028600692749023

    1.4.2 统一认证接口(高潮版)

    目前只有被装饰的函数可以传参数

    import time
    user,passwd='chenxin','123'
    def auth(func):#func=被装饰的函数的内存地址
            def wrapper(*args, **kwargs):
                username = input('username:').strip()
                password = input('password:').strip()
                if user == username and passwd == password:
                    print('authentication')
                    res = func(*args, **kwargs)  # 调用被装饰函数
                else:
                    exit('cuowu')
    
            return wrapper
    
    def index():
        print('welcome to index page')
    @auth
    def home():
        print('welcome to home page')
    @auth
    def bbs():
        print('welcome to bbs page')
    home()
    bbs()
    
    结果:
    
    username:chenxin
    
    password:123
    
    authentication
    
    welcome to home page
    
    username:chenxin
    
    password:123
    
    authentication
    
    welcome to bbs page

    1.4.3 统一认证接口(超级赛亚人高潮版)

    目前被装饰器的也可以传参数,装饰器本身也可以传参数,需求是auth有不同的认证地方和方式,例如:home只认证本地,而bbs认证数据库的用户名密码等。

    import time
    user,passwd = 'cx','abc123'
    def auth(auth_type):
        print('auth func:',auth_type)
        def outer_wrapper(func):
            def wrapper(*args, **kwargs):
                print('wrapper func args :', *args,**kwargs)
                if auth_type == 'local':
                    username = input('Username:').strip()
                    password = input('Password:').strip()
                    if user == username and passwd == password:
                        print('认证通过!!!')
                        return func(*args, **kwargs)
                    else:
                        exit('认证失败!!')
                elif auth_type == 'ldap':
                    print('不会ldap!!')
            return wrapper
        return outer_wrapper
    def index():
        print('welcome to index page')
    @auth(auth_type="local")
    def home():
        print('welcome to home page')
        return 'from home'
    @auth(auth_type="ldap")
    def bbs():
        print('welcome to bbs page')
    
    index()
    home()
    bbs()
    
    结果:
    
    auth func: local
    
    auth func: ldap
    
    welcome to index page
    
    wrapper func args :
    
    Username:cx
    
    Password:abc123
    
    认证通过!!!
    
    welcome to home page
    
    wrapper func args :
    
    不会ldap!!

    第2章 软件目录结构规范

    为什么要设计好目录结构?

    “设计项目目录结构”,就和“代码编码风格”一样,属于个人风格问题。对于这种风格的规范,一直都存在两种态度,

    1、一类认为,这种个人风格问题“无关紧要”。理由是能让城乡work就好,风格问题根本不是问题。

    2、另一类认为,规范化能更好的控制程序结构,让程序具有更高的可读性。

    我是比较偏向后者,

    "项目目录结构"其实也是属于"可读性和可维护性"的范畴,我们设计一个层次清晰的目录结构,就是为了达到以下两点:

    1、可读性高:不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪,配置文件在哪等等,从而非常快速的了解这个项目。

    2、可维护性高:定义好组织规则后,维护者就能很明确的知道,新增的哪个文件和代码应该放在什么目录下,这个好处是,随着时间的推移,代码/配置的规模增加,项目结构不会混乱,仍然能够组织良好。

    所以,我认为保持一个层次清晰的目录结构是有必要的,更何况组织一个良好的工程目录,其实是一件很容易的事。

    2.1 目录组织方式

    关于如何组织一个较好的Python工程目录结构,已经有一些得到了共识的目录结构,在Stackoverflow的这个问题上,能看到大家对Python目录结构的讨论。

    这里面说的已经很好了,我也不打算重新造轮子举例各种不同的方式,这里我说一下我的理解和体会。

    假如你的项目名为foo,我比较建议的最方便快捷目录结构这样就足够了:

    Foo/
    
    |-- bin/
    
    |   |-- foo
    
    |
    
    |-- foo/
    
    |   |-- tests/
    
    |   |   |-- __init__.py
    
    |   |   |-- test_main.py
    
    |   |
    
    |   |-- __init__.py
    
    |   |-- main.py
    
    |
    
    |-- docs/
    
    |   |-- conf.py
    
    |   |-- abc.rst
    
    |
    
    |-- setup.py
    
    |-- requirements.txt
    
    |-- README

    简要解释一下:

    1、bin:存放项目的一些可执行文件,当然你可以起名scripts之类的也可行。

    2、foo:存放项目的所有源代码。

      a)源代码中的所有模块、包都应该放在此目录,不要置于顶层目录

      b)其子目录tests存放单元测试代码

      c)程序的入口最好命名为main.py

    3、docs:存放一下文档

    4、setup.py:安装、部署、打包脚本。

    5、requirements.txt:存放软件依赖的外部Python包列表。

    6、README:项目说明文件

    除此之外,有一些方案给出了更加多的内容,比如LICENSE.txt,Changelog.txt等文件,我没有列在这里,因为这些东西主要是项目开源的时候需要用到,如果你想写一个开源软件,目录该如何组织,可参考

    2.1.1 关于README内容

    这个我觉得是每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。

    它需要说明一下几个事项:

    1、软件定位,软件基本功能。

    2、运行代码的方法:安装环境、启动命令等。

    3、简要的使用说明

    4、代码目录结构说明,更详细点可以说明软件的基本原理。

    5、常见问题说明

    以上几点是比较好的一个README,在软件开发初期,由于开发过程中以上内容可能不明确或者发生变化,并不是一个要在一开始就有信息都补全,但在项目完结的时候,是需要撰写这样的一个文档的,

    可以参考Redis源码中Readme的写法,这里面简洁但是清晰的描述了Redis功能和源码结构。

    2.1.2 关于requirements.txt和setup.py

    setup.py

           一般来说,用setup.py来管理代码的打包、安装、部署问题,业界标准的写法是用Python流行打包工具setuptools来管理这些事情,这种方式普遍应用于开源项目中,不过这里的核心思想不是用来标准化的工具来解决这些问题,而是说,一个项目一定要有一个安装部署工具,能快速便捷的在一台新机器上将环境装好、代码部署好和将程序运行起来。

           我们开始接触Python写项目的时候,安装环境、部署代码、运行程序这个过程全是手动完成,,遇过一下问题。

    1、安装环境时经常忘了最近又添加了一个新的Python包,结果一到线上运行,程序就出错了。

    2、Python包的版本依赖问题,有时候我们程序中使用一个版本的Python包,但是官方的已经是最新的包了,通过手动安装就可能安装错了。

    3、如果依赖的包很多的话,一个一个安装这些依赖是很费时的事情。

    4、新同学开始写项目的时候,将程序跑起来非常麻烦,因为可能经常忘了要怎么安装各种依赖。

    Setup.py可以将这些事情自动化起来,提高效率、减少出错的概率,“复杂的东西自动化,能自动化的东西一定要自动化”,是一个非常好的习惯。

    Setuptools的文档比较庞大,刚接触的话,可能不太好找到切入点,学习技术的方式就是看他人是怎么用的,可以参考一下Python的一个web框架,flash是如何写的:setup.py,当然,简单点自己写个安装脚本(depliy.sh)替代setup.py也未尝不可。

    requirements.txt

    这个文件存在的目的是:

    1、方便开发者维护软件的包依赖,将开发过程中新增的包添加进这个目录中,避免在setup.py安装依赖时漏掉软件包。

    2、方便读者明确项目使用了哪些Python包。

    这个文件的格式是每一行包含一个包依赖的说明,通常是flask>=0.10这种格式,要求是这个格式能被pip识别,这样就可以简单的通过pip install –r requirements.txt来把所有Python包依赖都装好了。

    2.1.3 关于配置文件的使用方法

    注意,在上面的目录结构中,没有将conf.py放在源代码目录下,而是放在docs目录下。

           很多项目对配置问的使用方法是:

    1、配置文件写在一个或多个Python文件中,比如此处的conf.py

    2、项目中哪个模块用到这个配置文件就直接通过import conf这种形式来在代码中使用配置。

    这种做法不是很好:

    1、这让单元测试变的困难(因为模块内部依赖了外部的配置)

    2、另一方面配置文件作为用户控制程序的接口,应当可以由用户自由指定该文件的路径。

    3、程序组件可复用性太差,因为这种贯穿所有模块的代码硬编程方式,使得大部分模块都依赖conf.py这个文件,

    所以为认为配置的使用,更好的方式为:

    1、模块的配置都是可以灵活配置的,不受外部配置文件的影响。

    2、程序的配置也是可以灵活控制的。

    能够佐证这个思想的是,用过nginx和MySQL的都知道,nginx、MySQL这些程序都可以自由指定用户配置。

    所以,不应该在代码中直接import conf来使用配置文件,上面目录结构中的conf.py,是给出的一个配置样例,不是在写死在程序中直接引用的配置文件,可以通过main.py启动参数指定配置路径的方式来让程序读取配置内容,当然,这里的conf.py你可以换个类似的名字,比如,settings.py,或者你可以使用其他格式的内容来编写配置文件,比如settings.yaml之类的。

  • 相关阅读:
    html+css实现简易下拉菜单
    Win10 设置外网多用户远程桌面连接
    ubuntu 14.04 下svn + apache2 配置
    JavaScript 学习笔记(一)
    生成Log文件的写法
    运行执行sql文件脚本的例子
    css实现文本框和下拉框结合的案例
    angularjs 1 开发简单案例(包含common.js,service.js,controller.js,page)
    将字符串转成只有首字母是大写
    java之springboot的spring-boot-starter-aop的切面编程的使用(四)
  • 原文地址:https://www.cnblogs.com/cxcx/p/6064785.html
Copyright © 2020-2023  润新知