Python學習筆記第四周

目錄html

  1、基礎概念python

    一、裝飾器mysql

      一、裝飾器本質就是函數nginx

      二、實現裝飾器知識儲備git

        一、函數即變量github

        二、高階函數sql

        三、嵌套函數json

      例子flask

        一、將一個函數名做爲實參傳遞給另一個函數網絡

        二、返回值爲函數名

        三、函數嵌套函數

        四、完整裝飾器例子

        五、裝飾器高級版本

    二、列表生成器

    三、生成器

    四、斐波那契數列

    五、迭代器

    六、內置函數

    七、json、pickle序列化

    八、軟件目錄結構規範

1、基礎概念

一、裝飾器:

  一、裝飾器本質上就是函數,用來裝飾其餘函數

  原則:裝飾器寫好後,原則上不能修改其餘函數源代碼

    一、不能修改被裝飾函數源代碼

    二、不能修改被裝飾函數調用方式

    總結成一點就是裝飾器對被裝飾函數徹底透明,用這個函數的人徹底不知道裝飾器的存在

  二、實現裝飾器的知識儲備

    一、函數即變量

     在python中,變量定義值,在內存中開闢一塊空間存放該變量值,當變量名再也不引用該變量值後,python解釋器會自動回收該值所在內存,函數也是如此,經過函數名調用函數體,當函數名再也不引用該函數體,函數體從內存中被解釋器回收。

          二、高階函數

         a)把一個函數名做爲實參傳遞給另一個函數,在不修改被裝飾函數源代碼狀況下爲其添加功能

         b)返回值爲該函數名,不修改函數的調用方式

    三、嵌套函數

      在一個函數內經過def聲明一個函數

  裝飾器=高階函數+嵌套函數

 例子

  一、將一個函數名做爲實參傳遞給另一個函數

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

  二、返回值爲函數名

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!

  三、函數嵌套函數

def foo():
    print('in the  foo')
    def bar():
        print('in the bar')
    return bar() #執行foo時會返回bar函數執行結果

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

  四、完整裝飾器例子

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

   五、裝飾器高級版本:經過裝飾器來劃分不一樣的登陸認證界面

#版本一,經過不帶參數的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('\033[32;1mauthticatin passed\033[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('\033[32;1mauthticatin passed\033[32;0m'.center(50,'@'))
            res = func(*args,**kwargs)  #將home函數對應的運行結果返回
            return res
        else:
            exit('\033[31;1mwrong input!\033[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('\033[32;1mauthticatin passed\033[32;0m'.center(50,'@'))
                    res = func(*args,**kwargs)  #將home函數對應的運行結果返回
                    return res
                else:
                    exit('\033[31;1mwrong input!\033[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!

 

 

二、列表生成器

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

 

三、生成器: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

四、斐波那契數列

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吃了!

 

五、迭代器

可直接做用於for循環的數據結構包括:

  1:集合類型數據:list、tuple、dict、set、str

  二、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是永遠不可能存儲全體天然數的。

六、內置函數

>>> 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碼
'\xe4\xb8\u4e2d\xe5\u5348'
>>> 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'>

 

 

七、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))

 

 八、軟件目錄結構規範

 (摘抄於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之類的。

相關文章
相關標籤/搜索