在一個健康的開發(fā)周期中链韭,代碼風(fēng)格拦英,API設(shè)計(jì)和自動化是非常關(guān)鍵的心剥。同樣的,對于工程的架構(gòu) ,倉庫的結(jié)構(gòu)也是關(guān)鍵的一部分嚎莉。
當(dāng)一個潛在的用戶和貢獻(xiàn)者登錄到您的倉庫頁面時,他們會看到這些:
- 工程的名字
- 工程的描述
- 一系列的文件
如果您的倉庫的目錄是一團(tuán)糟沛豌,沒有清晰的結(jié)構(gòu)萝喘,他們可能要到處尋找才能找到您寫的漂亮的文檔。
倉庫樣例
README.rst
LICENSE
setup.py
requirements.txt
sample/__init__.py
sample/core.py
sample/helpers.py
docs/conf.py
docs/index.rst
tests/test_basic.py
tests/test_advanced.py
以下是一些細(xì)節(jié)介紹:
- 核心代碼
布局:./sample/ or ./sample.py
您的模塊包是這個倉庫的核心琼懊,它不應(yīng)該隱藏起來:
./sample/
如果您的模塊只有一個文件,那么您可以直接將這個文件放在倉庫的根目錄下:
./sample.py
這個模塊文件不應(yīng)該屬于任何一個模棱兩可的src或者python子目錄爬早。 - License
作用:許可證哼丈,在這個文件中要有完整的許可說明和授權(quán)。 - Setup.py
作用:打包和發(fā)布管理 - requirements.txt
作用:開發(fā)依賴
說明: requirements.txt應(yīng)該放在倉庫的根目錄筛严。它應(yīng)該指明完整工程的所有依賴包: 測試, 編譯和文檔生成醉旦。
如果您的工程沒有任何開發(fā)依賴,或者您喜歡通過 setup.py 來設(shè)置桨啃,那么這個文件不是必須的车胡。 - Documentsation
作用:包的參考文檔 - Test Suite
作用:包的集合和單元測試
最開始,一組測試?yán)又皇欠旁谝粋€文件當(dāng)中:
./test_sample.py
當(dāng)測試?yán)又鸩皆黾訒r照瘾,您會把它放到一個目錄里面匈棘,像下面這樣:
tests/test_basic.py
tests/test_advanced.py
當(dāng)然,這些測試?yán)有枰獙?dǎo)入我們的包來進(jìn)行測試析命,有幾種方式來處理:
1.將我們的包安裝到site-packages中主卫。
2.通過簡單直接的路徑設(shè)置來解決導(dǎo)入的問題。
推薦后者鹃愤。如果使用 setup.py develop 來測試一個持續(xù)更新的代碼庫簇搅,需要為每一個版本的代碼庫設(shè)置一個獨(dú)立的測試環(huán)境.太麻煩了。
可以先創(chuàng)建一個包含上下文環(huán)境的文件 tests/context.py软吐。 file:
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import sample
然后瘩将,在每一個測試文件中,導(dǎo)入:
from .context import sample
這樣就能夠像期待的那樣工作,而不用采用安裝的方式姿现。
- Makefile
作用:常規(guī)的管理任務(wù)
** 樣例 Makefile:**
init:
pip install -r requirements.txt
test:
py.test tests
PHONY: init test
一些其他的常規(guī)管理腳本(比如 manage.py 或者 fabfile.py)肠仪,也放在倉庫的根目錄下.
結(jié)構(gòu)是一把鑰匙
得益于Python提供的導(dǎo)入與管理模塊的方式,結(jié)構(gòu)化Python項(xiàng)目變得相對簡單建钥。 這里說的簡單藤韵,指的是結(jié)構(gòu)化過程沒有太多約束限制而且模塊導(dǎo)入功能容易掌握。 因而您只剩下架構(gòu)性的工作熊经,包括設(shè)計(jì)泽艘、實(shí)現(xiàn)項(xiàng)目各個模塊,并整理清他們之間 的交互關(guān)系镐依。
模塊
Python模塊是最主要的抽象層之一匹涮,并且很可能是最自然的一個。抽象層允許將代碼分為 不同部分槐壳,每個部分包含相關(guān)的數(shù)據(jù)與功能然低。請盡量保持模塊名稱簡單,以無需分開單詞务唐。 最重要的是雳攘,不要使用下劃線命名空間,而是使用子模塊枫笛。
# ok
import library.plugin.foo
# not OK
import library.foo_plugin
- 理解import的原理機(jī)制
具體來說吨灭,import modu 語句將 尋找合適的文件,即調(diào)用目錄下的 modu.py 文件(如果該文件存在)刑巧。如果沒有 找到這份文件喧兄,Python解釋器遞歸地在 "PYTHONPATH" 環(huán)境變量中查找該文件,如果仍沒 有找到啊楚,將拋出ImportError異常吠冤。
一旦找到 modu.py,Python解釋器將在隔離的作用域內(nèi)執(zhí)行這個模塊恭理。所有頂層 語句都會被執(zhí)行拯辙,包括其他的引用。方法與類的定義將會存儲到模塊的字典中颜价。然后薄风,這個 模塊的變量、方法和類通過命名空間暴露給調(diào)用方拍嵌,這是Python中特別有用和強(qiáng)大的核心概念遭赂。
在很多其他語言中,include file 指令被預(yù)處理器用來獲取文件里的所有代碼并‘復(fù)制’ 到調(diào)用方的代碼中横辆。Python則不一樣:include代碼被獨(dú)立放在模塊命名空間里撇他,這意味著您 一般不需要擔(dān)心include的代碼可能造成不好的影響茄猫,例如重載同名方法。
也可以使用import語句的特殊形式from modu import *
模擬更標(biāo)準(zhǔn)的行為困肩。但 import * 通常 被認(rèn)為是不好的做法划纽。使用 from modu import * 的代碼較難閱讀而且依賴獨(dú)立性不足。 使用from modu import func
能精確定位您想導(dǎo)入的方法并將其放到全局命名空間中锌畸。 比 from modu import * 要好些勇劣,因?yàn)樗鞔_地指明往全局命名空間中導(dǎo)入了什么方法,它和 import modu 相比唯一的優(yōu)點(diǎn)是之后使用方法時可以少打點(diǎn)兒字潭枣。
差
from modu import *
x = sqrt(4) # sqrt是模塊modu的一部分么比默?或是內(nèi)建函數(shù)么?上文定義了么盆犁?
稍好
from modu import sqrt
x = sqrt(4) # 如果在import語句與這條語句之間命咐,sqrt沒有被重復(fù)定義,它也許是模塊modu的一部分谐岁。
最好
import modu
[...]
x = modu.sqrt(4) # sqrt顯然是屬于模塊modu的醋奠。
除了簡單的單文件項(xiàng)目外,其他項(xiàng)目需要能夠明確指出類和方法 的出處伊佃,例如使用 modu.func 語句窜司,這將顯著提升代碼的可讀性和易理解性。
包
Python提供非常簡單的包管理系統(tǒng)航揉,即簡單地將模塊管理機(jī)制擴(kuò)展到一個目錄上(目錄擴(kuò)展為包)塞祈。
任意包含 init.py 文件的目錄都被認(rèn)為是一個Python包。導(dǎo)入一個包里不同模塊的方式和普通的導(dǎo)入模塊方式相似迷捧,特別的地方是 init.py 文件將集合所有包范圍內(nèi)的定義。
pack/目錄下的modu.py文件通過 import pack.modu
語句導(dǎo)入胀葱。 該語句會在 pack 目錄下尋找 init.py 文件漠秋,并執(zhí)行其中所有頂層語句。以上操作之后抵屿,modu.py 內(nèi)定義的所有變量庆锦、方法和類在pack.modu命名空間中均可看到。
一個常見的問題是往 init.py 中加了過多代碼轧葛,隨著項(xiàng)目的復(fù)雜度增長搂抒, 目錄結(jié)構(gòu)越來越深,子包和更深嵌套的子包可能會出現(xiàn)尿扯。在這種情況下求晶,導(dǎo)入多層嵌套 的子包中的某個部件需要執(zhí)行所有通過路徑里碰到的 init.py文件。如果包內(nèi)的模塊和子包沒有代碼共享的需求衷笋,使用空白的 init.py 文件是正常甚至好的做法芳杏。
最后,導(dǎo)入深層嵌套的包可用這個方便的語法:import very.deep.module as mod。 該語法允許使用 mod 替代冗長的 very.deep.module爵赵。
面向?qū)ο缶幊?/h2>
在Python中一切都是對象吝秕,并且能按對象的方式處理。這么說的意思是空幻,例如函數(shù)是一等對象烁峭。 函數(shù)、類秕铛、字符串乃至類型都是Python對象:與其他對象一樣约郁,他們有類型,能作為函數(shù)參數(shù)傳遞如捅,并且還可能有自己的方法和屬性棍现。這樣理解的話,Python是一種面向?qū)ο笳Z言镜遣。
然而己肮,與Java不同的是,Python并沒有將面向?qū)ο缶幊套鳛樽钪饕木幊谭妒奖亍7敲嫦驅(qū)ο蟮腜ython項(xiàng)目(比如谎僻,使用較少甚至不使用類定義,類繼承寓辱,或其它面向?qū)ο缶幊痰臋C(jī)制)也是完全可行的艘绍。
在一些情況下,需要避免不必要的面向?qū)ο箫ぁ.?dāng)我們想要將狀態(tài)與功能結(jié)合起來诱鞠,使用標(biāo)準(zhǔn)類定義是有效的。但正如函數(shù)式編程所討論的那個問題这敬,函數(shù)式的“變量”狀態(tài)與類的狀態(tài)并不相同航夺。
動態(tài)類型
Python是動態(tài)類型語言,這意味著變量并沒有固定的類型崔涂。實(shí)際上阳掐,Python中的變量和其他語言有很大的不同,特別是靜態(tài)類型語言冷蚂。變量并不是計(jì)算機(jī)內(nèi)存中被寫入的某個值缭保,它們只是指向內(nèi)存的 ‘標(biāo)簽’ 或 ‘名稱’ 。因此可能存在這樣的情況蝙茶,變量 'a' 先代表值1艺骂,然后變成字符串'a string' , 然后又變?yōu)橹赶蛞粋€函數(shù)。
Python 的動態(tài)類型常被認(rèn)為是它的缺點(diǎn)隆夯,的確這個特性會導(dǎo)致復(fù)雜度提升和難以調(diào)試的代碼彻亲。 命名為 'a' 的變量可能是各種類型孕锄,開發(fā)人員或維護(hù)人員需要在代碼中追蹤命名,以保證它 沒有被設(shè)置到毫不相關(guān)的對象上苞尝。
避免發(fā)生類似問題的參考方法:
- 避免對不同類型的對象使用同一個變量名
差
a = 1
a = 'a string'
def a():
pass # 實(shí)現(xiàn)代碼
好
count = 1
msg = 'a string'
def func():
pass # 實(shí)現(xiàn)代碼
使用簡短的函數(shù)或方法能降低對不相關(guān)對象使用同一個名稱的風(fēng)險(xiǎn)畸肆。即使是相關(guān)的不同 類型的對象,也更建議使用不同命名
重復(fù)使用命名對效率并沒有提升:賦值時無論如何都要創(chuàng)建新的對象宙址。然而隨著復(fù)雜度的 提升轴脐,賦值語句被其他代碼包括 'if' 分支和循環(huán)分開,使得更難查明指定變量的類型抡砂。 在某些代碼的做法中大咱,例如函數(shù)編程,推薦的是從不重復(fù)對同一個變量命名賦值注益。Java 內(nèi)的實(shí)現(xiàn)方式是使用 'final' 關(guān)鍵字碴巾。Python并沒有 'final' 關(guān)鍵字。盡管如此丑搔,避免給同一個變量命名重復(fù)賦值仍是是個好的做法厦瓢,并且有助于掌握 可變與不可變類型的概念。
可變和不可變類型
Python提供兩種內(nèi)置或用戶定義的類型啤月≈蟪穑可變類型允許內(nèi)容的內(nèi)部修改。典型的動態(tài)類型 包括列表與字典:列表都有可變方法谎仲,如 list.append() 和 list.pop()浙垫, 并且能就地修改。字典也是一樣郑诺。不可變類型沒有修改自身內(nèi)容的方法夹姥。比如,賦值為整數(shù) 6的變量 x 并沒有 "自增" 方法辙诞,如果需要計(jì)算 x + 1辙售,必須創(chuàng)建另一個整數(shù)變量并給其命名。
my_list = [1, 2, 3]
my_list[0] = 4
print my_list # [4, 2, 3] <- 原列表改變了
x = 6
x = x + 1 # x 變量是一個新的變量
這種差異導(dǎo)致的一個后果就是倘要,可變類型是不 '穩(wěn)定 '的圾亏,因而不能作為字典的鍵使用十拣。合理地 使用可變類型與不可變類型有助于闡明代碼的意圖封拧。例如與列表相似的不可變類型是元組, 創(chuàng)建方式為 (1, 2)夭问。元組是不可修改的泽西,并能作為字典的鍵使用。
Python 中一個可能會讓初學(xué)者驚訝的特性是:字符串是不可變類型缰趋。這意味著當(dāng)需要組合一個字符串時捧杉,將每一部分放到一個可變列表里陕见,使用字符串時再組合 ('join') 起來的做法更高效。 而且味抖,使用列表推導(dǎo)的構(gòu)造方式比在循環(huán)中調(diào)用append()來構(gòu)造列表更好也更快评甜。
差
# 創(chuàng)建將0到19連接起來的字符串 (例 "012..1819")
nums = ""
for n in range(20):
nums += str(n) # 慢且低效
print nums
好
# 創(chuàng)建將0到19連接起來的字符串 (例 "012..1819")
nums = []
for n in range(20):
nums.append(str(n))
print "".join(nums) # 更高效
更好
# 創(chuàng)建將0到19連接起來的字符串 (例 "012..1819")
nums = [str(n) for n in range(20)]
print "".join(nums)
最好Best
# 創(chuàng)建將0到19連接起來的字符串 (例 "012..1819")
nums = map(str, range(20))
print "".join(nums)
除了 str.join() 和 +,也可以使用 % 格式運(yùn)算符來連接確定數(shù)量的字符串仔涩,不過PEP 3101 建議使用 str.format() 替代 % 操作符忍坷。
foo = 'foo'
bar = 'bar'
foobar = '%s%s' % (foo, bar) # 可行
foobar = '{0}{1}'.format(foo, bar) # 更好
foobar = '{foo}{bar}'.format(foo=foo, bar=bar) # 最好