Python基礎4

本節內容html

 1.迭代器 & 生成器python

 2.裝飾器mysql

 3.Json & pickle 數據序列化nginx

 4.軟件目錄結構規範git

 

1、列表生成式, 迭代器 & 生成器github

列表生成式redis

首先, 看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 如今讓列表裏面的每一個值都加1, 如何實現?算法

a = [0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9]
b = []
for i in a:b.append(i+1)
a = b
>>>>>>>>a的結果:
[1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10]
a = [0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9]
for index,i in enumerate(a):
    a[index]+=1
>>>>>>a 的結果爲:
[1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10]
a = [0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9]
a = map(lambda x:x+1, a)
print(a)
for i in a:
    print(i)
>>>>>>輸出:
<map object at 0x0000021EA94291D0>
1
2
3
4
5
6
7
8
8
9
10

還有種寫法以下:sql

a = [i + 1 for i in range (10)]
print(a)
>>>>>>輸出:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

上面的例子就叫作列表生成.json

 

生成器

經過列表生成式,就能夠直接建立一個列表。可是,受到內存限制,列表容量確定是有限的。並且,建立一個包含100萬個元素的列表,不只佔用很大的內存存儲空間,若是咱們僅僅須要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。

因此,若是列表元素能夠按照某種算法推算出來,那咱們是否能夠在循環的過程當中不斷推算出後續的元素呢?這樣就沒必要建立完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱爲生成器(generator)。

 

要建立一個generator,有不少種方法。第一種方法很簡單,只要把一個列表生成式的[]改爲(),就建立了一個generator:

a = [i + 1 for i in range (10)]
>>>>>>>>>>
b = (i + 1 for i in range (10))
print(b)
>>>>>>>>>>輸出結果爲:
<generator object <genexpr> at 0x0000028FFC57FFC0>

建立a, b的區別僅在於最外層的[] 和(), a是一個list,b是一個generator.

能夠直接打印出list a的元素, 那如何打印出生成器generator b裏面的元素呢?

若是想要一個個打印出來的話, 能夠經過帶哦用next()函數來獲取下一個返回值:

>>> a = (x * x for x in range(10))
>>> next(a)
0
>>> next(a)
1
>>> next(a)
4
>>> next(a)
9
>>> next(a)
16
>>> next(a)
25
>>> next(a)
36
>>> next(a)
49
>>> next(a)
64
>>> next(a)
81
>>> next(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

generator保存的實際上是算法,每次的調用next(a), 就計算出a的下一個值, 知道計算到最後一個元素, 沒有更多的元素時, 拋出StopIteration的報錯。

固然, 如上的不斷調用實在是太尷尬了, 正確的方法是使用for循環,由於generator也是可迭代對象:

>>> a = (x * x for x in range(10))
>>> for i in a:
...     print(i)
...
0
1
4
9
16
25
36
49
64
81

建立一個生成器後, 通常不會調用next, 而是用for循環來迭代它, 而且不須要關心StopIteration的錯誤。

generator很是強大。若是推算的算法比較複雜,用相似列表生成式的for循環沒法實現的時候,還能夠用函數來實現。

好比,著名的斐波拉契數列,除了第一個和第二個數外,任意一個數都是前2個數的和:

1, 1, 2, 3, 5, 8, 13, 21, 34, ......

斐波拉契數列用列表生成式寫不出來, 可是用函數是能夠解決的:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max :
        print(b)
        a, b = b, a + b
        n += 1
    return 'done'

fib(10)
>>>>>>>
1
1
2
3
5
8
13
21
34
55

注意上面的 a, b = b, a + b

並非想象中的:

a = b
b = a+b

而是至關於:

t = (b, a + b)# t是一個tuple
a = t[0]
b = t[1]

但沒必要顯示的寫出臨時變量t就能夠賦值。

能夠看出, fib函數其實是定義了斐波拉契的推算規則, 從而獲得後續元素, 跟generator的邏輯很類似。

其實上面的函數跟generator僅一步之遙。只須要把print(b)改爲yield(b)就能夠轉化了。

def fib(max):
    n, a, b = 0, 0, 1

    while n < max:
        yield b
        a, b = b, a + b
        n +=1
    return 'done'
f = fib(5)
print(f)

輸出結果:<generator object fib at 0x01D87180>

generator跟函數的運行流程不同。韓式是順序執行,遇到return語句或者最後一行函數語句就返回。而變成生成器後經過next()調用,遇到yield語句返回,再次執行從上次返回的yield語句繼續執行。

f = fib(5)
print(f)
print(f.__next__())
print(f.__next__())
print("作點其餘的事")
print(f.__next__())
print(f.__next__())

<generator object fib at 0x00A27180>
1
1
作點其餘的事
2
3

在上面的fib的例子中,咱們在循環過程當中不斷調用yield,就會不斷中斷。固然要給循環設計一個退出條件,否則會產生一個無限數列。

其實,一樣的, 函數改成generator後,咱們也能夠用for循環來迭代而不是next調用。

f = fib(5)
for i in f:
    print(i)

1
1
2
3
5

調用for循環後,會發現沒法獲得函數返回值,若是想拿到它,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:

f = fib(5)
while True:
    try:
        x = next(f)
        print('F:',x)
    except StopIteration as e:
        print("Generator return value:", e.value)
        break

結果:
F: 1
F: 1
F: 2
F: 3
F: 5
Generator return value: done

 還可經過yield實如今單線程的狀況下實現併發運算的效果  

#!/user/bin/env python
# -*- coding: utf-8 -*-

import time


def consumer(name):
    print("%s準備吃包子啦!" % name)
    while True:
        baozi =  yield
        print("包子[%s]來了,被[%s]吃了!"%(baozi, name))


def producer():
    c1 = consumer('Dandy')
    c2 = consumer('Claire')
    c1.__next__()
    c2.__next__()
    print(' 師傅開始準備作包子啦!')
    for i in range(10):
        time.sleep(1)
        print('作了一個包子,分2半。')
        c1.send(i)
        c2.send(i)

producer()
Dandy準備吃包子啦!
Claire準備吃包子啦!
 師傅開始準備作包子啦!
作了一個包子,分2半。
包子[0]來了,被[Dandy]吃了!
包子[0]來了,被[Claire]吃了!
作了一個包子,分2半。
包子[1]來了,被[Dandy]吃了!
包子[1]來了,被[Claire]吃了!
作了一個包子,分2半。
包子[2]來了,被[Dandy]吃了!
包子[2]來了,被[Claire]吃了!
作了一個包子,分2半。
包子[3]來了,被[Dandy]吃了!
包子[3]來了,被[Claire]吃了! 

 

迭代器  

咱們已經知道,能夠直接做用於for銷魂還得額數據類型有如下幾種:一類是集合數據類型,入list, list, dict, set, str等。另外一類是generator, 包括生成器和帶yield的generator function。

上述能夠直接做用於for循環的對象,統一稱爲可迭代對象:Iterable。

能夠用isinstance()判斷一個對象是不是Iterable對象。

>>> from collections import Iterable
>>> isinstance([],Iterable)
True
>>> isinstance({},Iterable)
True
>>> isinstance('abc',Iterable)
True
>>> isinstance((x for x in range(10)),Iterable)
True
>>> isinstance(100,Iterable)
False

 生成器都是Iterator對象,可是list,dict,str雖然是Iterable,但卻不是Iterator。

把list,dict,str等Iterable變成Iterator能夠用iter()函數:

>>> isinstance(iter([]),Iterator)
True
>>> isinstance(iter('abc'),Iterator)
True
>>>
你可能會困惑,爲何list,dict,str等不是Iterator?
這是由於python的Iterator對象表示的是一個數據流,Iterator對象能夠被next()函數調用並不斷返回下一個數據,直到沒有數據拋出StopIteration。能夠把這個數據流當作是一個有序的序列,
但咱們卻不能提早知道序列的長度,只能不斷的經過next()的調用實現計算出下一個數,因此Iterator的計算是惰性的,自由在須要返回下一個數據時纔會計算。

Iterator甚至能夠表示一個無限大的數據流,可是list等卻永遠不可能存儲總體的天然數。

小結

凡是可做用於for循環的對象都是Iterable類型(可迭代對象);

凡是可做用於next()函數的對象都是Iterator類型,它表示一個惰性計算的序列;

集合數據類型例如 list,dict,str等都是Iterable但不是Iterator,不過能夠經過iter()函數得到一個Iterator對象。

Python 的for循環其實本質上能夠說,就是一個不斷的調用next()函數的過程,例如

for x in [1, 2, 3, 4]:
    pass

 徹底等價於:

# 首先得到Iterator對象
it = iter([1, 2, 3, 4])
#循環
while True:
    try:
        #獲取下一個值:
        x = next(it)
    except StopIteration:
        #遇到StopIteration就退出循環
        break

 

2、裝飾器

模擬一種狀況,假如如今你手上的網站如今有如下幾個模塊

def home():
    print("---首頁----")
 
def america():
    print("----歐美專區----")
 
def japan():
    print("----日韓專區----")
 
def dalu():
    print("----大陸專區----")

如今須要對這幾個受歡迎的版塊進行改版,好比大陸跟日韓專區須要收費,拿到這個需求後,先要讓其進行用戶認證,認證經過後再盤點是否是VIP,付費會員就能夠觀看。需求其實很簡單,由於須要對多個板塊進行認證,就須要寫一個獨立而且通用的模塊,而後每一個版塊調用它。

#!/user/bin/env python
# -*- coding: utf-8 -*-

user_status = False


def login():
    _username = 'dandy'  # 僞裝是DB抓取的用戶信息
    _password = 'password.1'
    global user_status

    if user_status == False:
        username = input("user:")
        password = input("password:")

        if username == _username and password == _password:
            print("Welcome login...")
            user_status = True
        else:
            print("Wrong username or password !")
    else:
        print("用戶已登錄,驗證經過...")


def home():
    print("---首頁----")


def america():
    print("----歐美專區----")


def japan():
    login()#驗證
    print("----日韓專區----")


def dalu():
    login()#驗證
    print("----大陸專區----")

 這時候你可能信心滿滿的去提交審覈,沒想到幾分鐘就被打回來。雖然代碼是實現了功能,可是須要加認證在各個模塊代碼,這直接違反了軟件開發的一個原則「開放-封閉」,簡單來講,就是規定了已經實現的功能代碼不容許被修改,但能夠擴展,即:

· 封閉:已實現的功能代碼

· 開放:對擴展開放

這時候咱們能夠利用高階函數,把一個函數看成一個參數傳給另一個函數。這樣只須要寫一個認證方法,每次調用須要驗證的時候直接把功能的函數名看成參數傳給須要驗證的模塊,就能夠實現功能了。

 1 #!/user/bin/env python
 2 # -*- coding: utf-8 -*-
 3 
 4 user_status = False
 5 
 6 
 7 def login(func):
 8     _username = 'dandy'  # 僞裝是DB抓取的用戶信息
 9     _password = 'password.1'
10     global user_status
11 
12     if user_status == False:
13         username = input("user:")
14         password = input("password:")
15 
16         if username == _username and password == _password:
17             print("Welcome login...")
18             user_status = True
19         else:
20             print("Wrong username or password !")
21 
22     if user_status == True:
23         func()#只要驗證經過就會調用相應的功能模塊
24 
25 
26 def home():
27     print("---首頁----")
28 
29 
30 def america():
31     print("----歐美專區----")
32 
33 
34 def japan():
35     # login()#驗證
36     print("----日韓專區----")
37 
38 
39 def dalu():
40     # login()#驗證
41     print("----大陸專區----")
42 
43 home()
44 login(america)
45 login(dalu)
View Code

很開心,美滋滋。終於實現了老闆的要求,不改變原功能代碼的前提下,給功能加上驗證。可是這時候經理看到後發現你又犯了大忌,什麼大忌?

改變了調用方式!如今全部須要認證的模塊都須要從新調用你的login()方法並把本身的函數名傳給你,別人以前可不是這麼調用的,若是有100個模塊這樣調用是否是100個模塊都要更改調用方式,可能模塊跟方法是分開封裝的,怎麼去找那些模塊呢?

如今讓咱們提起來一個學過的知識:匿名函數(lambda)

def plus(n):
    return n+2
 
plus2 = lambda x:x+2

上面的2種寫法是否是表明一樣的意思?

給 lambda x:x+2 取了個名字叫plus2 , 是否是至關於def plus2(x)

給函數賦值變量名就像def func_name 是同樣的效果,以下面的plus(n)函數,你調用時能夠用plus名,還能夠再起個其餘名字,如

calc = plus
 
calc(n)

以前的驗證登錄的代碼:

home()
login(japan)
login(dalu)

之因此改變了調用方式,是由於用戶每次調用時須要執行login(參數), 能夠稍微改一下。

home()
japan= login(japan)
dalu = login(dalu)

這樣調用dalu的時候,其實至關於調用了login(dalu),經過login裏面的驗證後自動調用dalu的功能。

那就應該這樣寫

home()
japan= login(japan)#這裏其實替換了japan函數
dalu= login(dalu)

#那用戶調用時依然寫
japan()

可是,問題在於內部已經有func(), japan= login(japan) 還沒等用戶調用,你就會內部執行這句的時候顯把america執行了。應該是用戶調用的時候再調用啊?

這裏咱們又能夠用到嵌套函數,想實現一開始寫 japan= login(japan) 不觸發你函數的執行只須要在login裏面再定義一層函數,第一次調用 japan= login(japan)值調用到外層的login,這個login雖然會執行可是不會觸發認證,由於認證的全部的代碼都被封裝在login裏層的新定義的函數裏, login只返回了裏層的函數名,這樣再執行japan()時,就會調用裏層的函數。

可能你們也有點暈了,直接上代碼:

def login(func):  # 把要執行的模塊從這裏傳進來

    def inner():  # 再定義一層函數
        _username = 'dandy'  # 僞裝是DB抓取的用戶信息
        _password = 'password.1'
        global user_status

        if user_status == False:
            username = input("user:")
            password = input("password:")

            if username == _username and password == _password:
                print("Welcome login...")
                user_status = True
            else:
                print("Wrong username or password !")

        if user_status == True:
            func()  # 只要驗證經過就會調用相應的功能模塊

    return inner  # 用戶調用login的時候就會返回inner的內存地址,下次調用加上()纔會執行inner函數

按照python對於裝飾器的定義,咱們這時候能夠把這個代碼去掉:

japan= login(japan) #你在這裏至關於把japan這個函數替換了

只須要在須要裝飾的函數上面加上下面的代碼:

 

def home():
    print("---首頁----")


def america():
    print("----歐美專區----")


@login
def japan():
    # login()#驗證
    print("----日韓專區----")


@login
def dalu():
    # login()#驗證
    print("----大陸專區----")

效果同樣。太棒了。

 

你開心的看着這一串代碼,忽然又想試試能不能傳參給版塊:

@login
def japan():
    # login()#驗證
    print("----日韓專區----")
japan("3p")

結果發現報錯了。難道這個裝飾器不能傳參數麼?

固然!若是不能傳參數,還介紹了幹嗎

def login(func):  # 把要執行的模塊從這裏傳進來

    def inner(arg1):  # 再定義一層函數
        _username = 'dandy'  # 僞裝是DB抓取的用戶信息
        _password = 'password.1'
        global user_status

        if user_status == False:
            username = input("user:")
            password = input("password:")

            if username == _username and password == _password:
                print("Welcome login...")
                user_status = True
            else:
                print("Wrong username or password !")

        if user_status == True:
            func(arg1)  # 只要驗證經過就會調用相應的功能模塊

        return inner  # 用戶調用login的時候就會返回inner的內存地址,下次調用加上()纔會執行inner函數

咱們給inner函數加了個arg1參數func的地方也加了個arg1,這樣調用會傳到相應的版塊。

再試一次呢?

def login(func):  # 把要執行的模塊從這裏傳進來

    def inner(arg1):  # 再定義一層函數
        _username = 'dandy'  # 僞裝是DB抓取的用戶信息
        _password = 'password.1'
        global user_status

        if user_status == False:
            username = input("user:")
            password = input("password:")

            if username == _username and password == _password:
                print("Welcome login...")
                user_status = True
            else:
                print("Wrong username or password !")

        if user_status == True:
            func(arg1)  # 只要驗證經過就會調用相應的功能模塊

    return inner  # 用戶調用login的時候就會返回inner的內存地址,下次調用加上()纔會執行inner函數

就這樣咱們完成了裝飾器與參數的調用。。。神奇吧。

下面貼上全部的代碼:

 1 #!/user/bin/env python
 2 # -*- coding: utf-8 -*-
 3 
 4 user_status = False
 5 
 6 
 7 def login(func):  # 把要執行的模塊從這裏傳進來
 8 
 9     def inner(*args, **kwargs):  # 再定義一層函數
10         _username = 'dandy'  # 僞裝是DB抓取的用戶信息
11         _password = 'password.1'
12         global user_status
13 
14         if user_status == False:
15             username = input("user:")
16             password = input("password:")
17 
18             if username == _username and password == _password:
19                 print("Welcome login...")
20                 user_status = True
21             else:
22                 print("Wrong username or password !")
23 
24         if user_status == True:
25             func(*args, **kwargs)  # 只要驗證經過就會調用相應的功能模塊
26 
27     return inner  # 用戶調用login的時候就會返回inner的內存地址,下次調用加上()纔會執行inner函數
28 
29 
30 def home():
31     print("---首頁----")
32 
33 
34 def america():
35     print("----歐美專區----")
36 
37 
38 @login
39 def japan(style):
40     # login()#驗證
41     print("----日韓專區----")
42 
43 
44 @login
45 def dalu():
46     # login()#驗證
47     print("----大陸專區----")
48 
49 
50 japan('3p')
View Code

你很開心的帶着代碼去提交給經理。經理侃了也很滿意。

 

次日,經理又提出了新的需求,須要容許用戶選擇用qq\weixin\weibo 認證。這時候對於你來講一切就沒那麼難了。

#!/user/bin/env python
# -*- coding: utf-8 -*-

user_status = False


def login(auth_type):  # 把要執行的模塊從這裏傳進來
    def auth(func):
        def inner(*args, **kwargs):  # 再定義一層函數
            if auth_type == 'qq':
                _username = 'dandy'  # 僞裝是DB抓取的用戶信息
                _password = 'password.1'
                global user_status

                if user_status == False:
                    username = input("user:")
                    password = input("password:")

                    if username == _username and password == _password:
                        print("Welcome login...")
                        user_status = True
                    else:
                        print("Wrong username or password !")

                if user_status == True:
                    func(*args, **kwargs)  # 只要驗證經過就會調用相應的功能模塊
            else:
                print('wrong qq auth.')
        return inner  # 用戶調用login的時候就會返回inner的內存地址,下次調用加上()纔會執行inner函數
    return auth


def home():
    print("---首頁----")


def america():
    print("----歐美專區----")


@login('qq')
def japan(style):
    # login()#驗證
    print("----日韓專區----")


@login('weixin')
def dalu():
    # login()#驗證
    print("----大陸專區----")


japan('3p')
dalu()
View Code

 

 

3、Json & Pickle 數據序列化

用於序列化的兩個模塊

· Json,用於字符串 和python數據類型間的轉換

· Pickle,用於python特有類型和python的數據類型間的轉換

Json模塊提供了四個功能:dumps、dump、loads、load

Pickle模塊提供了四個功能:dumps、dump、loads、load

其實Python對於Json所寫的封裝功能已經很完美了,基本沒什麼好講的了上代碼吧:

#!/user/bin/env python
# -*- coding: utf-8 -*-
import json

dict_new = {'name': 'dandy',
            'age': 22,
            'salary': 30000
            }

with open("view2", 'w', encoding='utf-8') as fs:
    json.dump(dict_new, fs)#寫入文件

咱們看下文件

而後

with open('view2', 'r', encoding='utf-8') as fs:
    dict_new = json.load(fs)
print(dict_new)

結果爲:
{'name': 'dandy', 'age': 22, 'salary': 30000}

Pickle會加字符串轉換成二進制:

import pickle

dict_new = {'name': 'dandy',
            'age': 22,
            'salary': 30000
            }
p_str = pickle.dumps(dict_new,)
print(p_str)
結果爲一串二進制數:
b'\x80\x03}q\x00(X\x04\x00\x00\x00nameq\x01X\x05\x00\x00\x00dandyq\x02X\x03\x00\x00\x00ageq\x03K\x16X\x06\x00\x00\x00salaryq\x04M0uu.'

#寫入文件二進制
with open('view3','bw')as fp:
    pickle.dump(dict_new, fp)

#讀取文件
with open('view3','br')as fp:
    dict_new = pickle.load(fp)
print(dict_new)

結果:
{'name': 'dandy', 'age': 22, 'salary': 30000}

 

4、軟件目錄結構規範

爲何要設計好結構目錄?

"設計項目目錄結構",就和"代碼編碼風格"同樣,屬於我的風格問題。對於這種風格上的規範,一直都存在兩種態度:

  1. 一類同窗認爲,這種我的風格問題"可有可無"。理由是能讓程序work就好,風格問題根本不是問題。
  2. 另外一類同窗認爲,規範化能更好的控制程序結構,讓程序具備更高的可讀性。

我是比較偏向於後者的,由於我是前一類同窗思想行爲下的直接受害者。我曾經維護過一個很是很差讀的項目,其實現的邏輯並不複雜,可是卻耗費了我很是長的時間去理解它想表達的意思。今後我我的對於提升項目可讀性、可維護性的要求就很高了。"項目目錄結構"其實也是屬於"可讀性和可維護性"的範疇,咱們設計一個層次清晰的目錄結構,就是爲了達到如下兩點:

  1. 可讀性高: 不熟悉這個項目的代碼的人,一眼就能看懂目錄結構,知道程序啓動腳本是哪一個,測試目錄在哪兒,配置文件在哪兒等等。從而很是快速的瞭解這個項目。
  2. 可維護性高: 定義好組織規則後,維護者就能很明確地知道,新增的哪一個文件和代碼應該放在什麼目錄之下。這個好處是,隨着時間的推移,代碼/配置的規模增長,項目結構不會混亂,仍然可以組織良好。

因此,我認爲,保持一個層次清晰的目錄結構是有必要的。更況且組織一個良好的工程目錄,實際上是一件很簡單的事兒。

目錄組織方式

關於如何組織一個較好的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/: 存放項目的一些可執行文件,固然你能夠起名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。在軟件開發初期,因爲開發過程當中以上內容可能不明確或者發生變化,並非必定要在一開始就將全部信息都補全。可是在項目完結的時候,是須要撰寫這樣的一個文檔的。

能夠參考Redis源碼中Readme的寫法,這裏面簡潔可是清晰的描述了Redis功能和源碼結構。

關於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://github.com/triaquae/py3_training/tree/master/atm 

簡易流程圖:https://www.processon.com/view/link/589eb841e4b0999184934329  

相關文章
相關標籤/搜索