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之类的。

 

posted @ 2016-11-15 10:48  技术处理你  阅读(322)  评论(0编辑  收藏  举报