Python学习笔记第四周

目录

  一、基础概念

    1、装饰器

      1、装饰器本质就是函数

      2、实现装饰器知识储备

        1、函数即变量

        2、高阶函数

        3、嵌套函数

      例子

        1、将一个函数名作为实参传递给另外一个函数

        2、返回值为函数名

        3、函数嵌套函数

        4、完整装饰器例子

        5、装饰器高级版本

    2、列表生成器

    3、生成器

    4、斐波那契数列

    5、迭代器

    6、内置函数

    7、json、pickle序列化

    8、软件目录结构规范

一、基础概念

1、装饰器:

  1、装饰器本质上就是函数,用来装饰其他函数

  原则:装饰器写好后,原则上不能修改其他函数源代码

    1、不能修改被装饰函数源代码

    2、不能修改被装饰函数调用方式

    总结成一点就是装饰器对被装饰函数完全透明,用这个函数的人完全不知道装饰器的存在

  2、实现装饰器的知识储备

    1、函数即变量

     在python中,变量定义值,在内存中开辟一块空间存放该变量值,当变量名不再引用该变量值后,python解释器会自动回收该值所在内存,函数也是如此,通过函数名调用函数体,当函数名不再引用该函数体,函数体从内存中被解释器回收。

          2、高阶函数

         a)把一个函数名作为实参传递给另外一个函数,在不修改被装饰函数源代码情况下为其添加功能

         b)返回值为该函数名,不修改函数的调用方式

    3、嵌套函数

      在一个函数内通过def声明一个函数

  装饰器=高阶函数+嵌套函数

 例子

  1、将一个函数名作为实参传递给另外一个函数

def test1(func):  #传入函数名作为实参
    func()

def bar():
    print('in the bar!')
    
test1(bar)
#输出:
in the bar!
import time

def bar():
    print('in the bar!')

def test1(func):
    start_time = time.time()
    func()# 运行bar函数
    stop_time = time.time()
    print('the func running time is %s'  %(stop_time-start_time))


test1(bar)
#输出:
in the bar!
the func running time is 8.58306884765625e-05

  2、返回值为函数名

import time

def bar():
    print('in the bar!')

def test1(func):
    print(func)
    return func


bar = test1(bar)   #将bar函数名作为参数传递给函数test1内,同时返回值为bar函数名而不是bar函数执行结果,并将其重新赋值给bar
bar() #执行bar()会先执行test1,打印bar对应的内存地址,然后执行bar函数对应的函数体内容
#输出:
<function bar at 0x289a464>
in the bar!

  3、函数嵌套函数

def foo():
    print('in the  foo')
    def bar():
        print('in the bar')
    return bar() #执行foo时会返回bar函数执行结果

foo()
#输出:
in the  foo
in the bar

  4、完整装饰器例子

import time

def deco(func):
    def wrapper(*args,**kwargs):  #无论原始函数自身带任何参数均可以在这包含
        start_time = time.time()
        func(*args,**kwargs)   #当test1传入时,执行test1的返回结果,如果源函数携带参数,这里可以在执行原函数时带源函数所带参数
        stop_time = time.time()
        print('the func time is %s'  %(stop_time-start_time))
    return wrapper  #直接返回函数名

@deco   #@deco 等价于       test1 = deco(test1)
def test1(name):
  time.sleep(3)
  print('in the test1')
  print('the name is %s' %name)


test1('gavin')
#输出:
in the test1
the name is gavin
the func time is 3.0022261142730713
import time

def deco(func):
    def wrapper(*args,**kwargs):  #无论原始函数自身带任何参数均可以在这包含
        start_time = time.time()
        func(*args,**kwargs)   #当test1传入时,执行test1的返回结果,如果源函数携带参数,这里可以在执行原函数时带源函数所带参数
        stop_time = time.time()
        print('the func time is %s'  %(stop_time-start_time))
    return wrapper  #直接返回函数名

@deco   #@deco 等价于       test1 = deco(test1)
def test1(name):
  time.sleep(3)
  #print('in the test1')
  #print('the name is %s' %name)
  return name  #返回name值


print(test1('gavin'))
#输出:
the func time is 3.0015320777893066
None   #用该方法没法返回源函数需要返回的参数
import time

def deco(func):
    def wrapper(*args,**kwargs):  #无论原始函数自身带任何参数均可以在这包含
        start_time = time.time()
        res = func(*args,**kwargs)   #当test1传入时,执行test1的返回结果,如果源函数携带参数,这里可以在执行原函数时带源函数所带参数 
        stop_time = time.time()
        print('the func time is %s'  %(stop_time-start_time))
        return res   #当需要被修饰函数有返回值时,可以在装饰器中将其返回
    return wrapper  #直接返回函数名

@deco   #@deco 等价于       test1 = deco(test1)
def test1(name):
  time.sleep(3)
  #print('in the test1')
  #print('the name is %s' %name)
  return name


print(test1('gavin'))
#输出
the func time is 3.00175404548645
gavin

   5、装饰器高级版本:通过装饰器来划分不同的登录认证界面

#版本一,通过不带参数的auth装饰器来完成home与bbs认证
user,passwd = 'gavin','123'
def auth(func):
    def wrapper(*args,**kwargs):
        username = input('usernmae: ').strip()
        password = input('password: ').strip()
        if user == username and passwd == password:
            print('33[32;1mauthticatin passed33[32;0m'.center(50,'@'))
            func(*args,**kwargs)
        else:
            exit('wrong input!')
    return wrapper





@auth
def home():
    print('in the home page!')

@auth
def bbs():
    print('in the bbs page!')

home()
bbs()
#输出:
usernmae: gavin
password: 123
@@@@@@@@@authticatin passed@@@@@@@@@
in the home page!
usernmae: gavin
password: a
wrong input!
user,passwd = 'gavin','123'
def auth(func):
    def wrapper(*args,**kwargs):
        username = input('usernmae: ').strip()
        password = input('password: ').strip()
        if user == username and passwd == password:
            print('33[32;1mauthticatin passed33[32;0m'.center(50,'@'))
            res = func(*args,**kwargs)  #将home函数对应的运行结果返回
            return res
        else:
            exit('33[31;1mwrong input!33[32;0m')
    return wrapper





@auth
def home():
    return 'home page'  #需要装饰器可以返回该返回值

@auth
def bbs():
    print('in the bbs page!')

print(home())
bbs()
#输出:
usernmae: gavin
password: 123
@@@@@@@@@authticatin passed@@@@@@@@@
home page
usernmae: gavin
password: 123
@@@@@@@@@authticatin passed@@@@@@@@@
in the bbs page!
user,passwd = 'gavin','123'
def auth(auth_type):  #装饰器携带的参数会在第一层传入装饰器
    def outer(func): #装饰器要装饰的源函数会在第二层传入到装饰器
        def wrapper(*args,**kwargs):
            if auth_type == 'local':
                username = input('usernmae: ').strip()
                password = input('password: ').strip()
                if user == username and passwd == password:
                    print('33[32;1mauthticatin passed33[32;0m'.center(50,'@'))
                    res = func(*args,**kwargs)  #将home函数对应的运行结果返回
                    return res
                else:
                    exit('33[31;1mwrong input!33[32;0m')
            elif auth_type == 'ldap':
                print('暂时不支持')
                func(*args,**kwargs)
        return wrapper
    return outer





@auth(auth_type = 'local')
def home():
    return 'home page'  #需要装饰器可以返回该返回值

@auth(auth_type = 'ldap')
def bbs():
    print('in the bbs page!')

print(home())
bbs()
#输出
usernmae: gavin
password: 123
@@@@@@@@@authticatin passed@@@@@@@@@
home page
暂时不支持
in the bbs page!

2、列表生成器

>>> a = [ i * 2 for i in range(10)]
>>> a
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

3、生成器:generator

为了避免列表中数据过多占用太多内存空间,导致系统不可用,使用生成器来替代列表产生数据序列

>>> b = (i * 2 for i in range(10))
>>> b
<generator object <genexpr> at 0x10223d7d8> 

此时b为生成器,只有在调用的时候才会产生数据,所以使用通常的b[5]调用列表的方法是没法得到数据的

>>> b[2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'generator' object is not subscriptable

生成器只有在调用时才会产生相应的数据,同时只记录当前的位置,只有通过__next__()方式或者next()内置函数才能调用

>>> b.__next__()
0
>>> b.__next__()
2
>>> b.__next__()
4
>>> c = next(b)
>>> print(c)
6

>>> b = (i * 2 for i in range(10)) 

>>> for n in b:print(n) #也可以使用for循环一次性全部取出,这样可以不用使用next方法同时避免最后遇到StopIteration错误
...
0
2
4
6
8
10
12
14
16
18

4、斐波那契数列

fibonacci除了第一个和第二个数外,其他任意数都是前面两个数相加得到的

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b  #yield类似断点,执行到这里会跳出生成器,然后下一次进入生成器会从这个位置进入继续执行
        a, b = b, a + b  #yield会保存函数的中断状态
        n = n + 1
    return 'Done'

res = fib(6)
for n in res:   #同样斐波那契函数也可以使用for循环方式取到所有想要的数值,同时也避免使用next方法,但是有一点for循环没法做到,就是返回return的值
    print(n)
#输出
1
1
2
3
5
8

如果想要取到return返回的值,需要使用下面的方法

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b  #yield类似断点,执行到这里会跳出生成器,然后下一次进入生成器会从这个位置进入继续执行
        a, b = b, a + b  #yield会保存函数的中断状态
        n = n + 1
    return 'Done'

res = fib(6)

while True:
    try:
        x = next(res)  #next()为内置函数,类似于__next__()
        print('res:', x)
    except StopIteration as e:  #如果想要获得return返回的值,必须抓到StopIteration错误
        print('Generator return valuel:', e.value)
        break #当结束后会进入exception,同时需要跳出该循环
#输出
res: 1
res: 1
res: 2
res: 3
res: 5
res: 8
Generator return valuel: Done

通过协程方法达到多线程效果:

#版本一:单独传入next与send,查看yield与send方法使用
import time

def consumer(name):
    print('%s准备吃包子啦' %name)
    while True:
        baozi = yield #yield保存当前状态,返回时直接返回到这个状态,但是单纯调用next不会给yield传值
        print('%s包子来啦,被%s吃了!' %(baozi,name))


eat = consumer('gavin')
eater = next(eat)  #只唤醒yield,不会给它传值
baozi1 = '韭菜馅'
eat.send(baozi1) #send可以调用yield,同时给yield传值,也就是唤醒yield同时给它传值
#输出
gavin准备吃包子啦
韭菜馅包子来啦,被gavin吃了!
#版本二:通过协程方式达到并行效果
import time

def consumer(name):
    print('%s准备吃包子啦' %name)
    while True:
        baozi = yield #yield保存当前状态,返回时直接返回到这个状态,但是单纯调用next不会给yield传值
        print('%s包子来啦,被%s吃了!' %(baozi,name))


def producter():
    c1 = consumer('gavin')#这个动作只是把它变成生成器
    c2 = consumer('alex')
    c1.__next__()#需要执行next才会执行生成器
    c2.__next__()
    print('老子开始做包子啦!')
    for i in range(1,5):
        time.sleep(1)
        print('做了一个包子,分两半,一个白菜馅,一个韭菜馅')
        c1.send(i)
        c2.send(i)

producter()
#输出
gavin准备吃包子啦
alex准备吃包子啦
老子开始做包子啦!
做了一个包子,分两半,一个白菜馅,一个韭菜馅
1包子来啦,被gavin吃了!
1包子来啦,被alex吃了!
做了一个包子,分两半,一个白菜馅,一个韭菜馅
2包子来啦,被gavin吃了!
2包子来啦,被alex吃了!
做了一个包子,分两半,一个白菜馅,一个韭菜馅
3包子来啦,被gavin吃了!
3包子来啦,被alex吃了!
做了一个包子,分两半,一个白菜馅,一个韭菜馅
4包子来啦,被gavin吃了!
4包子来啦,被alex吃了!

5、迭代器

可直接作用于for循环的数据结构包括:

  1:集合类型数据:list、tuple、dict、set、str

  2、generator,包括生成器和带yield的generator function函数

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

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

>>> from collections import Iterable

>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)  
True
>>> isinstance((), Iterable)   
True
>>> isinstance((x for x in range(20)), Iterable) 
True

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

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

>>> isinstance((x for x in range(20)), Iterator)
True
>>> isinstance({}, Iterator)                      
False
>>> isinstance([], Iterator)  
False
>>> isinstance((), Iterator)  
False

生成器都是Iterator对象,但是list dict set虽然是Iterable但是不是Iterator,如果想把它们变为Iterator,可以使用iter()函数

>>> isinstance(iter(()), Iterator)
True
>>> isinstance(iter([]), Iterator)    
True
>>> isinstance(iter({}), Iterator)  
True

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

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

6、内置函数

>>> abs(-1)  #取绝对值
1
>>> print(all([-1])) #如果里面包括0为false其余都为true,这个相当于and,只要有false就为false
True
>>> print(all([0,1]))
False
>>> print(any([0,1]))   #只要有真就为true,不管有没有false
True
>>> print(any([0,-1]))
True
>>> print(ascii('ä¸中å午'))  #将值转换为asii码
'xe4xb8u4e2dxe5u5348'
>>> print(bin(1))  #将十进制转为二进制
0b1
>>> bool([])
False
>>> bool([1])  #布尔函数,判断真假
True
>>> bool([-1])
True
>>> a = bytearray('abcde', encoding='utf-8')   #可以通过asii码的形式修改字符串
>>> print(a[1])
98
>>> a[1] = 50  #将b替换为2
>>> print(a)
bytearray(b'a2cde')
>>> print(callable([]))  #是否可以包含括号,列表不能包含括号
False
>>> def func():print('1')
... 
>>> print(callable(func)) #函数可以包含括号
True
>>> print(chr(98)) #通过数字得到对应的asii码
b
>>> print(ord('b'))#通过asii码得到对应的数字
98
>>> code = "for i in range(5):print(i)"
>>> exec(code)  #可以将字符串对应的函数执行
0
1
2
3
4
>>> 
>>> 
>>> a = dict()  #定义字典
>>> a
{}
>>> divmod(5,3)  #取两个数字的商和余数
(1, 2)
>>> a = '''{'a':1,'b':'aaaaa'}'''
>>> c = eval(a)  #取引号
>>> c
{'a': 1, 'b': 'aaaaa'}
>>> calc = lambda n:print(n)  #匿名函数,如果不反复调用执行一次就结束可以使用匿名函数,执行完毕后立即回收内存
>>> 
>>> calc(5)
5
>>> calc = lambda n:print(n+1)
>>> calc(5)
6
>>> res = filter(lambda n:n>5,range(10))  #过滤器:只取大于5的数值
>>> for i in res:print(i)
... 
6
7
8
9
>>> res1 = map(lambda n:n*n,range(5))  #和匿名函数配合
>>> for i in res:print(i)
>>> for i in res1:print(i)
... 
0
1
4
9
16
>>> import functools  #reduce函数从内置函数中去除
>>> res2 = functools.reduce(lambda x,y:x+y,range(10)) #操作x+赋值给x
>>> print(res2)
45
>>> float(10)  #将整数变为浮点数
10.0
>>> madlib = " I {verb} the {object} off the {place} ".format(verb="took", object="cheese", place="table")  #format是准备替换%s的内置函数,匹配更加精确,通过{}与format方法进行引用,此例format通过变量名进行引用
>>> madlib
' I took the cheese off the table '
>>> '{},{}'.format('a','b')  #通过顺序方式依次引用a  b
'a,b'
>>> a = set([1,1,1,1,33,4,1,45])  #将列表变为集合
>>> a
{1, 4, 45, 33}
>>> b = frozenset([1,1,1,1,33,4,1,45])  #被冻结的集合,没法添加
>>> b
frozenset({1, 4, 45, 33})
>>> print(hash('I have a apple')) #做hash操作
7983704463637394503
>>> len('I have a apple')
14
>>> print(hex(255)) #将十进制转换为16进制
0xff
>>> def test():local_var = 333,print(locals()) 
... 
>>> test()
{}
>>> print(globals())
{'test': <function test at 0x101a479d8>, 'res': <filter object at 0x101945b38>, 'functools': <module 'functools' from '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/functools.py'>, '__name__': '__main__', 'a': {1, 4, 45, 33}, '__builtins__': <module 'builtins' (built-in)>, 'b': frozenset({1, 4, 45, 33}), 'res1': <map object at 0x101a534e0>, 'res2': 45, 'c': {'a': 1, 'b': 'aaaaa'}, '__doc__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, 'calc': <function <lambda> at 0x101a478c8>, '__spec__': None, 'i': 16, 'Iterable': <class 'collections.abc.Iterable'>, 'Iterator': <class 'collections.abc.Iterator'>, 'func': <function func at 0x101a477b8>, 'madlib': ' I took the cheese off the table ', 'code': 'for i in range(5):print(i)', '__package__': None}
>>> print(globals().get('local_var'))
None
>>> print(max([1,2,3,4]))  #打印最大
4
>>> print(min([1,2,3,4]))   #打印最小
1
>>> print(oct(255))  #打印8进制
0o377
>>> a = 'Hello world'

#str与repr的区别在于str与repr虽然都是显示字符串、但是str显示的更人性化、所以一般和print一起调用,而repr一般是给解释器使用所以和eval()一起使用的多一些
>>> b = str(a)
>>> c = repr(a)  #对该字符串再增加引号
>>> d = eval(c)  #将c增加的引号脱去
>>> print(a==b)
True
>>> print(a==c)
False
>>> print(a==d)
True
>>> print(str(a))
Hello world
>>> print(repr(a))
'Hello world'
>>> print(eval(repr(a)))
Hello world
>>> print(list(reversed([3,4,55,23,45]))) #对列表里的数据倒序排列
[45, 23, 55, 4, 3]
>>> round(1.3345,3)#去小数点3位
1.335
>>> slice(2,6)
slice(2, 6, None)
>>> d = range(20)
>>> d
range(0, 20)
>>> d[slice(2,5)]  #分割
range(2, 5)
>>> sum([1,44,32,11,45])#将列表中的数据相加
133
>>> type([1,44,32,11,45]) #打印数据类型
<class 'list'>
>>> a = {6: 2,8:0, 1:4,-5:6,99: 11,4:22} 
>>> print(a)
{1: 4, 99: 11, 4: 22, 6: 2, 8: 0, -5: 6}
>>> print(sorted(a.items())) #对字典按照key值来排序
[(-5, 6), (1, 4), (4, 22), (6, 2), (8, 0), (99, 11)]
>>> print(sorted(a.items(),key=lambda x:x[1])) #对字典按照value值来排序
[(8, 0), (6, 2), (1, 4), (-5, 6), (99, 11), (4, 22)]
>>> print(sorted(a.items(),key=lambda x:x[1],reverse = False ))#对字典按照value值来排序,默认reverse = False采用正序排序
[(8, 0), (6, 2), (1, 4), (-5, 6), (99, 11), (4, 22)]
>>> print(sorted(a.items(),key=lambda x:x[1],reverse = True ))#对字典按照value值来排序,reverse = True采用反序排序

[(4, 22), (99, 11), (-5, 6), (1, 4), (6, 2), (8, 0)]
>>> print(sorted(a.items(),reverse = True )) #对字典的key值进行反向排序
[(
99, 11), (8, 0), (6, 2), (4, 22), (1, 4), (-5, 6)]
>>> a = [1,2,3,4]
>>> b = ['a','b','c','d']
>>> for i in zip(a,b):print(i) #合并打印
...
(
1, 'a') (2, 'b') (3, 'c') (4, 'd')
>>> c = ['a','b']
>>> for i in zip(a,c):print(i) #如果一方的值少,会按照少的那方的数据个数来打印
...
(
1, 'a') (2, 'b')
>>> __import__('os') #和import一样,但是是采用字符串方式来进行引用
<module 'os' from '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/os.py'
> >>> __import__('os',globals(),locals(),['path','pip'])
<module 'os' from '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/os.py'>

7、json、pickle与序列化

用于序列化的两个模块

  • json 用于字符串与python数据类型间进行转换
  • pickle用于python特有的类型与python数据类型间进行转换

Json模块提供了四个功能:dumps、dump、loads、load

pickle模块提供了四个功能:dumps、dump、loads、load

json默认情况下只字符串、字典、列表进行序列化,因为json是通用的,所有语言都支持json,在不进行特殊自定义情况下,不能序列化函数

import json
info = {
    'name': 'alex',
    'age': 23,
    'job': 'IT'
}

print(repr(info))
#输出
{'job': 'IT', 'name': 'alex', 'age': 23}
info_json = json.dumps(info,sort_keys=True)  #sort_keys用来对序列进行排序
print(info_json)
#输出
{"age": 23, "job": "IT", "name": "alex"}
info_json1 = json.dumps(info,sort_keys=True, indent=4) #indent可以对dumps的序列进行优化,表示数值离括号的距离
print(info_json1)
#输出
{
    "age": 23,
    "job": "IT",
    "name": "alex"
}
info_json2 = json.dumps(info,sort_keys=True, separators=(',',':')) #在网络传输过程中为了节省传输带宽,可以对无用的字符进行压缩,这里是对,和:进行压缩以节省更多传输空间
print(info_json2)
#输出
{"age":23,"job":"IT","name":"alex"}
import json
info = {
    'name': 'alex',
    'age': 23,
    'job': 'IT'
}

with open('seriliaztion.txt','w') as f:
    f.write(repr(info))  #按照普通方式写入文件,普通方式写入要求字符串格式进行写入


with open('seriliaztion.txt','r') as f:
    for line in f:
        print(type(line))
        print(line)  #对该形式存入的输出只能按照字符串方式进行打印

#输出
<class 'str'>
{'age': 23, 'name': 'alex', 'job': 'IT'}

with open('seriliaztion-json.txt','w') as f:
    f.write(json.dumps(info,sort_keys=True)) #在写入时对其进行json编码


with open('seriliaztion-json.txt','r') as f:
    for line in f:
        print(json.loads(line)['name'])  #对此进行解码,在解码后可以按照字典方式针对key打印value
        print(json.loads(line)['age'])
        print(json.loads(line)['job'])
#输出
alex
23
IT
with open('seriliaztion-json.txt','r') as f:
    data = json.load(f) #等同于data = json.loads(f.read())
print(data['job'])
#输出
IT
#pickle与json的区别在于pickle不需要特殊定义就默认支持将函数序列化,同时pickle生产的文件是二进制文件,需要加b
import pickle
def sayhi(name):
    print('hi {}'.format(name))

info = {
    'name': 'gavin',
    'age': 30,
    'func': sayhi
}

with open('seriliaztion-pickle.txt','wb') as f:  #需要加wb
    pickle.dump(info,f)  #等同于f.write(pickle.dumps(info))

 8、软件目录结构规范

 (摘抄于http://www.cnblogs.com/alex3714/articles/5765046.html)

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

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

  1. 这种个人风格问题"无关紧要"。理由是能让程序work就好,风格问题根本不是问题。
  2. 规范化能更好的控制程序结构,让程序具有更高的可读性。

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

  1. 可读性高: 不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪儿,配置文件在哪儿等等。从而非常快速的了解这个项目。
  2. 可维护性高: 定义好组织规则后,维护者就能很明确地知道,新增的哪个文件和代码应该放在什么目录之下。这个好处是,随着时间的推移,代码/配置的规模增加,项目结构不会混乱,仍然能够组织良好。

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

目录组织方式

假设你的项目名为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/: 存放项目的一些可执行文件,当然你可以起名script/之类的也行。
  2. foo/: 存放项目的所有源代码。(1) 源代码中的所有模块、包都应该放在此目录。不要置于顶层目录。(2) 其子目录tests/存放单元测试代码; (3) 程序的入口最好命名为main.py
  3. docs/: 存放一些文档。
  4. setup.py: 安装、部署、打包的脚本。
  5. requirements.txt: 存放软件依赖的外部Python包列表。
  6. README: 项目说明文件。

除此之外,有一些方案给出了更加多的内容。比如LICENSE.txt,ChangeLog.txt文件等

下面,再简单讲一下这些目录的理解和个人要求

关于README的内容

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

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

  1. 软件定位,软件的基本功能。
  2. 运行代码的方法: 安装环境、启动命令等。
  3. 简要的使用说明。
  4. 代码目录结构说明,更详细点可以说明软件的基本原理。
  5. 常见问题说明。

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

关于requirements.txt和setup.py

setup.py

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

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

  1. 安装环境时经常忘了最近又添加了一个新的Python包,结果一到线上运行,程序就出错了。
  2. Python包的版本依赖问题,有时候我们程序中使用的是一个版本的Python包,但是官方的已经是最新的包了,通过手动安装就可能装错了。
  3. 如果依赖的包很多的话,一个一个安装这些依赖是很费时的事情。
  4. 新同学开始写项目的时候,将程序跑起来非常麻烦,因为可能经常忘了要怎么安装各种依赖。

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

setuptools的文档比较庞大,刚接触的话,可能不太好找到切入点。学习技术的方式就是看他人是怎么用的,可以参考一下Python的一个Web框架,flask是如何写的: setup.py

当然,简单点自己写个安装脚本(deploy.sh)替代setup.py也未尝不可。

requirements.txt

这个文件存在的目的是:

  1. 方便开发者维护软件的包依赖。将开发过程中新增的包添加进这个列表中,避免在setup.py安装依赖时漏掉软件包。
  2. 方便读者明确项目使用了哪些Python包。

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

关于配置文件的使用方法

注意,在上面的目录结构中,没有将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之类的。

    

  

原文地址:https://www.cnblogs.com/xiaopi-python/p/6370150.html