以正確的方式開(kāi)源 Python 項(xiàng)目

大多數(shù)Python開(kāi)發(fā)者至少都寫(xiě)過(guò)一個(gè)像工具亥宿、腳本计呈、庫(kù)或框架等對(duì)其他人也有用的工具团赁。我寫(xiě)這篇文章的目的是讓現(xiàn)有Python代碼的開(kāi)源過(guò)程盡可能清晰和無(wú)痛喂急。我不是簡(jiǎn)單的指——“創(chuàng)建一個(gè)GitHub庫(kù)囊嘉,提交温技,在Reddit上發(fā)布,每天調(diào)用它”扭粱。在本文的結(jié)尾舵鳞,你可以把現(xiàn)有的代碼轉(zhuǎn)換成一個(gè)能夠鼓勵(lì)他人使用和貢獻(xiàn)的開(kāi)源項(xiàng)目。

然而每一個(gè)項(xiàng)目都是不同的琢蛤,但其中將現(xiàn)有代碼開(kāi)源的流程對(duì)所有的Python項(xiàng)目都是類似的蜓堕。在另一個(gè)受歡迎的文章系列里我寫(xiě)了“以正確方式開(kāi)始一個(gè)Django項(xiàng)目”,我將概述在開(kāi)源Python項(xiàng)目我發(fā)現(xiàn)的有必要的步驟博其。

更新 (8月17號(hào)): 感謝@pydann提醒我Cookiecutter的存在套才,@audreyr的一個(gè)不起的項(xiàng)目。我在文章結(jié)尾添加了其中的一段慕淡”嘲椋看一下Audrey的項(xiàng)目吧!

更新 2 (8月18號(hào)):感謝@ChristianHeimes(和其他人)關(guān)于ontox這一段峰髓。Christian也讓我想起了PEP 440和其他一些都已實(shí)現(xiàn)很棒的改進(jìn)建議傻寂。

工具和概念

特別是,我發(fā)現(xiàn)一些工具和概念十分有用或者說(shuō)是必要的携兵。下面我就會(huì)談及這方面主題疾掰,包括需要運(yùn)行的精確的命令和需要設(shè)置的配置值。其終極目標(biāo)就是讓整個(gè)流程簡(jiǎn)單明了眉孩。

  1. 項(xiàng)目布局(目錄結(jié)構(gòu))
  2. setuptools 和 setup.py文件
  3. git版本控制
  4. GitHub 項(xiàng)目管理
  5. bug跟蹤
  6. 請(qǐng)求新特性
  7. 計(jì)劃好的新特性
  8. 發(fā)布或者版本管理
  9. GitHub的”Issues” 如下作用:
  10. git-flow git工作流
  11. py.test 單元測(cè)試
  12. tox 標(biāo)準(zhǔn)化測(cè)試
  13. Sphinx 自動(dòng)生成HTML文檔
  14. TravisCI 持續(xù)測(cè)試集成
  15. ReadTheDocs 持續(xù)文檔集成
  16. Cookiecutter 為開(kāi)始下一個(gè)項(xiàng)目自動(dòng)生成這些步驟

項(xiàng)目布局

當(dāng)準(zhǔn)備一個(gè)項(xiàng)目時(shí)个绍,正確合理的布局(目錄結(jié)構(gòu))是十分重要的勒葱。一個(gè)合理的布局意味著想?yún)⑴c開(kāi)發(fā)者不必花時(shí)間來(lái)尋找某些代碼的位置; 憑直覺(jué)就可以找到文件的位置浪汪。因?yàn)槲覀冊(cè)谔幚硪粋€(gè)項(xiàng)目,就意味著可能需要到處移動(dòng)一些東西凛虽。

讓我們從頂層開(kāi)始死遭。大多數(shù)項(xiàng)目都有很多頂層文件(如setup.py, README.md, requirements等等)。每個(gè)項(xiàng)目至少應(yīng)該有下面三個(gè)目錄:

  1. doc目錄凯旋,包括項(xiàng)目文檔
  2. 項(xiàng)目目錄呀潭,以項(xiàng)目命名钉迷,存儲(chǔ)實(shí)際的Python包
  3. test目錄,包含下面兩部分
  4. 在這個(gè)目錄下包括了測(cè)試代碼和資源
  5. 作為一個(gè)獨(dú)立頂級(jí)包

為了更好理解文件該如何組織钠署,這里是一個(gè)我的簡(jiǎn)單項(xiàng)目:sandman 布局快照糠聪。


image.png

如你所看到那樣,這里有一些頂層文件谐鼎,一個(gè)docs目錄(建立一個(gè)空目錄舰蟆,因?yàn)閟phinx會(huì)將生成的文檔放到這里),一個(gè)sandman目錄狸棍,以及一個(gè)在sandman目錄下的test目錄身害。

setuptools 和 setup.py文件

setup.py文件,你可能已經(jīng)在其它包中看到過(guò)草戈,被distuils包用來(lái)安裝Python包的塌鸯。對(duì)于任何一個(gè)項(xiàng)目,它都是一個(gè)很重要的文件唐片,因?yàn)樗税姹颈蕾囆畔ⅲ琍yPi需要的項(xiàng)目描述费韭,你的名字和聯(lián)系信息淮悼,以及其它一些信息。它允許以編程的方式搜索安裝包揽思,提供元數(shù)據(jù)和指令說(shuō)明讓工具如何做袜腥。

setuptools包(實(shí)際上就是對(duì)distutils的增強(qiáng))簡(jiǎn)單化了建立發(fā)布python包。使用setuptools給python包打包钉汗,和distutils打包沒(méi)什么區(qū)別羹令。這實(shí)在是沒(méi)有任何理由不使用它。

setup.py應(yīng)該放在你的項(xiàng)目的根目錄损痰。setup.py中最重要的一部分就是調(diào)用setuptools.setup福侈,這里面包含了此包所需的所有元信息。這里就是sandman的setup.py的所有內(nèi)容

image

(感謝Christian Heimes的建議讓setup.py更符合人們的語(yǔ)言習(xí)慣卢未。反過(guò)來(lái)肪凛,也讓我借用其它的項(xiàng)目一目了然了。)

大多數(shù)內(nèi)容淺顯易懂辽社,可以從setuptools文檔查看到伟墙,所以我只會(huì)觸及”有趣”的部分。使用sandman.version和gettinglong_deion方法(盡管我也記不住是哪一個(gè)滴铅,但是卻可以從其它項(xiàng)目的setup.py中獲得)來(lái)減少我們需要寫(xiě)的引用代碼戳葵。相反,維護(hù)項(xiàng)目的版本有三個(gè)地方(setup.py, 包自身的version汉匙, 以及文檔)拱烁,我們也可以使用包的version來(lái)填充setup里面的version參數(shù)

long_deion被Pypi在你項(xiàng)目的PyPI主頁(yè)當(dāng)做文檔使用生蚁。這里有其他一個(gè)文件,README.md戏自,其中包含幾乎相同的內(nèi)容邦投,我使用pandoc依據(jù)README.md自動(dòng)生成README.rst,因此我們只需看README.rst就行了擅笔,并將它的內(nèi)容設(shè)置為long_deion尼摹。

py.test (上面討論過(guò)) 中有一個(gè)特殊的條目(pytest類)設(shè)置允許Python檢查setup.py可否正常工作。這段代碼直接來(lái)自py.test指導(dǎo)文檔剂娄。

文件中的其他內(nèi)容都是在設(shè)置文檔中描述的安裝參數(shù)蠢涝。

其他的setup.py參數(shù)

有一些sandman 用不到的啟動(dòng)參數(shù),在你的包里可能會(huì)用到阅懦。舉個(gè)例子和二,你可能正在分派一些腳本并希望你的用戶能夠從命令行執(zhí)行。在這個(gè)例子中耳胎,腳本會(huì)和你其他的代碼一起安裝在正常的site-packages位置惯吕。用戶安裝完后,沒(méi)有其他的簡(jiǎn)單方法運(yùn)行它怕午》系牵基于這一點(diǎn),setup可以帶有一個(gè)的腳本參數(shù)來(lái)指明Python腳本應(yīng)該如何安裝郁惜。在包中安裝一個(gè)調(diào)用go_foo.py的腳本堡距,這個(gè)用來(lái)啟動(dòng)的調(diào)用包括下面這行:

s = ['go_foo.py'],

確保在腳本中填入相對(duì)路徑,并不僅僅是一個(gè)名稱 (如s = [‘s/foo_s/go_foo.py’]).同樣兆蕉,你的腳本應(yīng)該以”shebang”行和”python”開(kāi)始羽戒,如下:

! /usr/bin/env python

distutils將會(huì)在安裝過(guò)程中自動(dòng)用當(dāng)前解釋器位置取代這一行。

如果你的包比我們這里討論的要復(fù)雜虎韵,你可在官方文檔中參看啟動(dòng)工具文檔和分布python模塊易稠。

在這兩者中,你可以解決一些你可能會(huì)遇到的問(wèn)題包蓝。

代碼管理:git驶社, 項(xiàng)目管理:gitHub

在“以正確的方式開(kāi)始一個(gè)Django項(xiàng)目”中,我建議版本控制使用git 或者 mercurial测萎。如果對(duì)于以共享與貢獻(xiàn)的項(xiàng)目來(lái)說(shuō)亡电,只有一個(gè)選擇:git。事實(shí)上绳泉,從長(zhǎng)遠(yuǎn)來(lái)說(shuō)逊抡,如果你想人們能使用和參與貢獻(xiàn),那么不僅使用git很有必要零酪,而且冒嫡,你也能夠使用GitHub來(lái)管理維護(hù)你的項(xiàng)目。

這并不是夸大其詞(盡管很多人會(huì)以它為嚼頭)四苇。然而孝凌,管它好與差,git和GitHub事實(shí)上已經(jīng)成為了開(kāi)源項(xiàng)目的實(shí)際標(biāo)準(zhǔn)了月腋。GitHub是很多潛在的貢獻(xiàn)者最想注冊(cè)的和最熟悉的蟀架。所以,我深信榆骚,這并不是掉以輕心片拍,而是深思熟慮的產(chǎn)物。

新建一個(gè)README.md文件

在GitHub的代碼倉(cāng)庫(kù)中妓肢,項(xiàng)目的描述是從項(xiàng)目的根目錄中的:README.md文件獲取的捌省。這個(gè)文件應(yīng)該包含下面幾點(diǎn):

  • 項(xiàng)目描述
  • 項(xiàng)目ReadTheDocs頁(yè)面連接[@Lesus 注:請(qǐng)查看 工具與概念 ]
  • 一個(gè)用來(lái)顯示當(dāng)前構(gòu)建狀態(tài)的TravisCI按鈕。
  • “Quickstart” 文檔 (怎么快速安裝和使用你的項(xiàng)目)
  • 若有非python依賴包碉钠,請(qǐng)列舉它以及怎么安裝它

它(README)讀起來(lái)很傻的感覺(jué)纲缓,但是確是一個(gè)很重要的文件。它可能是你未來(lái)的用戶或者貢獻(xiàn)者首先從它了解你的項(xiàng)目的喊废∽8撸花些時(shí)間來(lái)寫(xiě)一個(gè)清楚明白的說(shuō)明和使用GFM(GitHubFlavoredMarkdown)來(lái)使它更好看。實(shí)際上污筷,如果使用原生的Markdown來(lái)寫(xiě)文檔不爽工闺,那么可以在Github上使用立即預(yù)覽來(lái)創(chuàng)建或者修改這個(gè)文件

我們還沒(méi)觸及列表中的第二和第三項(xiàng)(ReadTheDocs和TravisCI),你會(huì)在接下來(lái)看到瓣蛀。

使用”Issues”頁(yè)

跟生活中的很多事情一樣斤寂,你投入GitHub越多,你收獲的越多揪惦。因?yàn)橛脩魰?huì)使用GitHub的“Issues”頁(yè)面反饋bug遍搞,使用該頁(yè)面跟蹤特性要求和改進(jìn)是很有意義的。

更重要的是器腋,它允許貢獻(xiàn)者以一種優(yōu)雅的方式看到:一個(gè)可能實(shí)現(xiàn)特性的列表以及自動(dòng)化的管理合并請(qǐng)求流程(pull request)溪猿。GitHub的issues可以與評(píng)論、你項(xiàng)目里的其他issues及其他項(xiàng)目里的issues等交織纫塌,這使得“issues”頁(yè)面成為一個(gè)有關(guān)所有bug修復(fù)诊县、改進(jìn)和新特性要求信息匯總的地方。

確贝胱螅“Issues”及時(shí)更新依痊,至少及時(shí)回應(yīng)新的問(wèn)題。作為一個(gè)貢獻(xiàn)者,沒(méi)有什么比修復(fù)bug后看著它呈現(xiàn)在issues頁(yè)面并等待著被合并更有吸引力的了胸嘁。

使用git-flow這個(gè)明智的git工作流

為使事情對(duì)自己和貢獻(xiàn)者更容易瓶摆,我建議使用非常流行的git-flow分支模型。

概述

開(kāi)發(fā)分支是你工作的主要分支性宏,它也是將成為下一個(gè)release.feature的分支群井,代表著即將實(shí)現(xiàn)的新特性和尚未部署的修復(fù)內(nèi)容(一個(gè)完整的功能分支有開(kāi)發(fā)分支合并而來(lái))。通過(guò)release的創(chuàng)建更新master毫胜。

安裝

按照你系統(tǒng)平臺(tái)的git-flow安裝指導(dǎo)操作书斜,在這里。

安裝完后酵使,你可以使用下附命令遷移你的已有項(xiàng)目

$ git flow init

Branch細(xì)節(jié)

腳本將詢問(wèn)你一些配置問(wèn)題荐吉,git-flow的默認(rèn)建議值可以很好的工作。你可能會(huì)注意到你的默認(rèn)分支被設(shè)置成develop】谟妫現(xiàn)在样屠,讓我們后頭描述一下git-flow…嗯,flow搓劫,更詳細(xì)一點(diǎn)瞧哟。這樣做的最簡(jiǎn)單的方法是討論一下不同的分支及模型中的分支類型。

Master

master分支一直是存放“生產(chǎn)就緒”的代碼枪向。所有的提交都不應(yīng)該提交到master分支上勤揩。當(dāng)然深员,master分支上的代碼只會(huì)從一個(gè)產(chǎn)品發(fā)布分支創(chuàng)建并結(jié)束后合并進(jìn)來(lái)。這樣在master上的代碼一直是可以發(fā)布為產(chǎn)品的。并且,master也是一直處于可預(yù)計(jì)的狀態(tài),所以你永遠(yuǎn)不需要擔(dān)心如果master分支修改了而某一個(gè)其他分支沒(méi)有相應(yīng)的修改抓艳。

Develop

你的大部分工作是在develop分支上完成的。這個(gè)分支包含所有的完成的特性和修改的bug以便發(fā)布氛濒;每日構(gòu)建或者持續(xù)集成服務(wù)器需要針對(duì)develop分支來(lái)進(jìn)行确徙,因?yàn)樗碇鴮?huì)被包含在下一個(gè)發(fā)布里的代碼膘壶。

對(duì)于一次性的提交,可以隨便提交到develop上。

特性

對(duì)于一些大的特性遇西,就需要?jiǎng)?chuàng)建一個(gè)特性分支渗常。特性分支從develop分支創(chuàng)建出來(lái)皱碘。它們可以是對(duì)于下一個(gè)發(fā)布的一些小小的增強(qiáng)或者更進(jìn)一步的修改尸执。而這脆丁,依然需要從現(xiàn)在開(kāi)始工作震蒋。為了從一個(gè)新的分支上開(kāi)始工作,使用:

$ git flow feature start <feature name>

這命令創(chuàng)建了一個(gè)新的分支:feature/<feature name>噪窘。通常會(huì)把代碼提交到這個(gè)分支笋庄。當(dāng)特性已經(jīng)完成并且準(zhǔn)備好發(fā)布的時(shí)候,它就應(yīng)當(dāng)用一下的命令將它合并會(huì)develop分支:

git flow feature finish <feature name>

這會(huì)把代碼合并進(jìn)develop分支倔监,并且刪除 feature/<feature name>分支

Release

一個(gè)release分支是當(dāng)你準(zhǔn)備好進(jìn)行產(chǎn)品發(fā)布的時(shí)候從develop分支創(chuàng)建出來(lái)的直砂。使用以下的命令來(lái)創(chuàng)建:

$ git flow release start <release number>

注意,這是發(fā)布版本號(hào)第一次創(chuàng)建丐枉。所有完成的哆键,準(zhǔn)備好發(fā)布的分支必須已經(jīng)合并到develop分支上掘托。在release分支創(chuàng)建后瘦锹,發(fā)布你的代碼。任何小的bug修改需要提交到 release/<release number> 分支上闪盔。當(dāng)所有的bug被修復(fù)之后弯院,運(yùn)行以下的命令:

$ git flow release finish <release number>

這個(gè)命令會(huì)把你的release/<release number> 分支合并到master和develop分支,這意味著你永遠(yuǎn)不需要擔(dān)心這幾個(gè)分支會(huì)缺少一些必要的產(chǎn)品變更(可能是因?yàn)橐粋€(gè)快速的bug修復(fù)導(dǎo)致的)泪掀。

Hotfix

然而hotfix分支可能會(huì)很有用听绳,在現(xiàn)實(shí)世界中很少使用,至少我是這樣認(rèn)為的异赫。hotfix就像master分支下創(chuàng)建的feature分支: 如果你已經(jīng)關(guān)閉了release分支椅挣,但是之后又認(rèn)識(shí)到還有一些很重要的東西需要一起發(fā)布,那么就在master分支(由$git flow release finish <release number>創(chuàng)建的標(biāo)簽)下創(chuàng)建一個(gè)hotfix分支塔拳,就像這樣:

$ git flow hotfix start <release number>

當(dāng)你完成改變和增加你的版本號(hào)使之獨(dú)一無(wú)二(bump your version number)鼠证,然后完成hotfix分支:

$ git flow hotfix finish <release number>

這好像一個(gè)release分支(因?yàn)樗举|(zhì)上就是一種release分支),會(huì)在master和develop分支上提交修改靠抑。

我猜想它們很少使用的原因是因?yàn)橐呀?jīng)存在一種可以給已發(fā)布的代碼做出修改的機(jī)制:提交到一個(gè)未完成的release分支量九。當(dāng)然,可能一開(kāi)始颂碧,團(tuán)隊(duì)使用git flow release finish .. 太早了荠列,然后第二天又發(fā)現(xiàn)需要快速修改。隨著時(shí)間的推移载城,他們就會(huì)為一個(gè)release 分支多留一些時(shí)間肌似,所以,不會(huì)再需要hotfix分支诉瓦。另一種需要hotfix分支情況就是如果你立即需要在產(chǎn)品中加入新的特性川队,等不及在develop分支中加入改變受楼。不過(guò)(期望)這些都是小概率事件。

virtualenv和virtualenvwrapper

lan Bicking的virtualenv工具事實(shí)上已經(jīng)成為了隔離Python環(huán)境的標(biāo)準(zhǔn)途徑了呼寸。它的目標(biāo)很簡(jiǎn)單:如果你的一臺(tái)機(jī)子中有很多Python項(xiàng)目艳汽,每個(gè)都有不同的依賴(可能相同的包,但是依賴不同的版本)对雪,僅僅在一個(gè)Python安裝環(huán)境中管理這些依賴幾乎是不可能的河狐。

virtualenv創(chuàng)建了一個(gè)“虛擬的”P(pán)ython安裝環(huán)境,每個(gè)環(huán)境都是相互隔離的瑟捣,都有自己的site-packages, distribute和 使用pip安裝包到虛擬環(huán)境而不是系統(tǒng)Python安裝環(huán)境馋艺。 而且在你的虛擬環(huán)境中來(lái)回切換只是一個(gè)命令的事。

Doug Hellmann的virtualenvwrapper使創(chuàng)建和管理多個(gè)虛擬環(huán)境更容易的隔離工具迈套。讓我們繼續(xù)前進(jìn)捐祠,馬上安裝這兩個(gè)工具:

image

如你所見(jiàn),后者依賴于前者桑李,所以簡(jiǎn)單的安裝virtualenvwrapper就足夠了踱蛀。注意,如果你使用的是Python3贵白,PEP-405通過(guò)venv包和pyvenv命令提供了Python原生虛擬環(huán)境的支持率拒,在python3.3中已實(shí)現(xiàn)。你應(yīng)該使用這個(gè)而不是前面提到的工具禁荒。

一旦你安裝了virtualenvwrapper猬膨,你需要添加一行內(nèi)容到你的.zhsrc文件(對(duì)bash用戶來(lái)說(shuō)是.bashrc文件):

$ echo "source /usr/local/bin/virtualenvwrapper.sh" >> ~/.zshrc

這樣在你的shell中增加了一些有用的命令(記得第一次使用時(shí)source一下你的.zshrc文件以使它生效)。雖然你可以使用mkvirtualenv命令直接創(chuàng)建一個(gè)virtualenv呛伴,但使用mkproject [OPTIONS] DEST_DIR創(chuàng)建一個(gè)“項(xiàng)目”將更有用勃痴。因?yàn)槲覀円呀?jīng)有一個(gè)現(xiàn)有的項(xiàng)目了,所有我們只需為我們的項(xiàng)目創(chuàng)建一個(gè)新的virtualenv热康,下附命令可以達(dá)到這效果:

image.png

你會(huì)注意到你的shell提示符在你的virtualenv之后(我的是“ossproject”沛申,你可以使用任何你喜歡的名字)。現(xiàn)在任何通過(guò)pip安裝的模塊將安裝到你的virtualenv下的site-packages褐隆。

要停止在你的項(xiàng)目上工作并切換回系統(tǒng)使用deactivate命令污它。你會(huì)看到命令提示符前你的virtualenv名字消失了。要重新回到你的項(xiàng)目上工作的話運(yùn)行workon <project name>庶弃,你會(huì)回到你的virtualenv衫贬。

除了簡(jiǎn)單地為你的項(xiàng)目創(chuàng)建virtualenv,你還會(huì)用它做其他事:生成你的requirements.txt文件歇攻,使用requirements.txt文件和-r標(biāo)識(shí)可安裝所有項(xiàng)目的依賴項(xiàng)固惯。要?jiǎng)?chuàng)建該文件,在你的virtualenv運(yùn)行以下命令(一旦你代碼和virtualenv一起工作缴守,就是那里):

(ossproject)$ pip freeze > requirements.txt

你會(huì)得到一個(gè)所有你項(xiàng)目需要模塊的列表葬毫,它以后可以被setup.py文件使用列出你的依賴關(guān)系镇辉。這里有一點(diǎn)需要注意:我經(jīng)常在requirements.txt中將“==”改為“>=“,這樣代表“我正使用包的任何的后來(lái)版本”贴捡。你是否應(yīng)該或需要在項(xiàng)目這樣做取決于實(shí)際情況忽肛,但我應(yīng)該指出來(lái)。

將requirements.txt提交到你的git代碼庫(kù)中烂斋。此外屹逛,你現(xiàn)在可以添加這里的列出的包列表作為install_requirement參數(shù)的值到setup.py文件中的distutils.setup。這樣做我們可以確保當(dāng)上傳包到PyPI后汛骂,它可以被pip安裝并自動(dòng)解決依賴關(guān)系罕模。

使用py.test測(cè)試

在Python的自動(dòng)測(cè)試系統(tǒng)里有兩個(gè)主要的Python標(biāo)準(zhǔn)單元測(cè)試包(很有用)的替代品:nose和py.test。兩個(gè)方案都將單元測(cè)試拓展的易于使用且增加額外的功能帘瞭。說(shuō)真的淑掌,哪個(gè)都是很好的選擇。我更喜歡py.test因?yàn)橄率鰩讉€(gè)原因:

  • 支持setuptools/distutils項(xiàng)目
  • Python的setup.py測(cè)試技能始終其作用
  • 支持常見(jiàn)的斷言(assert)語(yǔ)法 (而不是需要記住所有jUnit風(fēng)格的斷言函數(shù))
  • 更少的樣板
  • 支持多種測(cè)試風(fēng)格
  • 單元測(cè)試
  • 文檔測(cè)試
  • nose測(cè)試

注意

如果你已經(jīng)有了一個(gè)自動(dòng)測(cè)試的解決方案那繼續(xù)使用它吧蝶念,跳過(guò)這一節(jié)抛腕。但請(qǐng)記住以后的章節(jié)你將被認(rèn)為在使用py.test測(cè)試,這可能會(huì)影響到配置值祸轮。

測(cè)試安裝

在測(cè)試目錄里兽埃,無(wú)論你如何決定都要有這個(gè)目錄,創(chuàng)建一個(gè)名為test_<project_name>.py的文件适袜。py.test的測(cè)試發(fā)現(xiàn)機(jī)制將把所有test_前綴的文件當(dāng)做測(cè)試文件處理(除非明確告知)。

在這個(gè)文件里放什么很大程度上取決于你舷夺。寫(xiě)測(cè)試是一個(gè)很大的話題苦酱,超出這篇文章的范圍。最重要的给猾,測(cè)試對(duì)你的和潛在的捐助者都是有用的疫萤。應(yīng)該標(biāo)識(shí)清楚每個(gè)用例是測(cè)試的什么函數(shù)。用例應(yīng)該以相同的“風(fēng)格”書(shū)寫(xiě)敢伸,這樣潛在的貢獻(xiàn)者不必猜測(cè)在你的項(xiàng)目中他/她應(yīng)該使用三種測(cè)試風(fēng)格中的哪種扯饶。

覆蓋測(cè)試

自動(dòng)化測(cè)試的覆蓋率是一個(gè)有爭(zhēng)議的話題。一些人認(rèn)為它給出了錯(cuò)誤的保證是一個(gè)毫無(wú)意義的度量池颈,其他人認(rèn)為它很有用尾序。在我看在,我建議如果你已經(jīng)使用自動(dòng)化測(cè)試但從來(lái)沒(méi)有檢查過(guò)你的測(cè)試覆蓋率躯砰,現(xiàn)在做這樣一個(gè)練習(xí)每币。

使用py.test,我們可以使用Ned Batchelder的覆蓋測(cè)試工具琢歇。使用pip安裝pytest-cov兰怠。如果你之前這樣運(yùn)行你的測(cè)試:

$ py.test

你可以通過(guò)傳遞一些新的標(biāo)識(shí)生成覆蓋率報(bào)告梦鉴,下面是運(yùn)行sandman的一個(gè)例子:

image

當(dāng)然不是所有項(xiàng)目都有100%的測(cè)試覆蓋率(事實(shí)上,正如你讀到的揭保,sandman沒(méi)有100%覆蓋)肥橙,但獲得100%的覆蓋率是一個(gè)有用的練習(xí)。它能夠揭示我之前沒(méi)有留意的缺陷與重構(gòu)機(jī)會(huì)秸侣。

因?yàn)榭炱鳛闇y(cè)試本身,自動(dòng)生成的測(cè)試覆蓋報(bào)可以作為你持續(xù)集成的一部分塔次。如果你選擇這樣做方篮,部署一個(gè)標(biāo)記來(lái)顯示當(dāng)前的測(cè)試覆蓋率會(huì)為你的項(xiàng)目增加透明度(大多數(shù)時(shí)候會(huì)極大的鼓勵(lì)他人貢獻(xiàn))。

使用Tox進(jìn)行標(biāo)準(zhǔn)化測(cè)試

一個(gè)所有Python項(xiàng)目維護(hù)者都需要面對(duì)的問(wèn)題是兼容性励负。如果你的目標(biāo)是同時(shí)支持Python 2.x和Python 3.x(如果你目前只支持Python 2.x藕溅,應(yīng)該這樣做),實(shí)際中你如何確保你的項(xiàng)目支持你所說(shuō)的所有版本呢继榆?畢竟巾表,當(dāng)你運(yùn)行測(cè)試時(shí),你只使用特定的版本環(huán)境來(lái)運(yùn)行測(cè)試略吨,它很可能在Python2.7.5中運(yùn)行良好但在Python 2.6和3.3出現(xiàn)問(wèn)題集币。

幸運(yùn)的是有一個(gè)工具致力于解決這個(gè)問(wèn)題。tox提供了“Python的標(biāo)準(zhǔn)化測(cè)試”,它不僅僅是在多個(gè)版本環(huán)境中運(yùn)行你的測(cè)試拂蝎。它創(chuàng)造了一個(gè)完整的沙箱環(huán)境全跨,在這個(gè)環(huán)境中你的包和需求被安裝和測(cè)試。如果你做了更改在測(cè)試時(shí)沒(méi)有異常当娱,但意外地影響了安裝,使用Tox你會(huì)發(fā)現(xiàn)這類問(wèn)題考榨。

通過(guò)一個(gè).ini文件配置tox:tox.ini跨细。它是一個(gè)很容易配置的文件,下面是從tox文檔中摘出來(lái)的一個(gè)最小化配置的tox.ini:

[圖片上傳失敗...(image-dee968-1548316101283)]

通過(guò)設(shè)置envlist為py26和py27河质,tox知道需要在這兩種版本環(huán)境下運(yùn)行測(cè)試冀惭。tox大約支持十幾個(gè)“默認(rèn)”的環(huán)境沙箱,包括jython和pypy掀鹅。tox這個(gè)強(qiáng)大的工具使用不同的版本進(jìn)行測(cè)試散休,在不支持多版本時(shí)可配置警示。

deps是你的包依賴列表淫半。你甚至可以讓tox從PyPI地址安裝所有或一些你依賴包溃槐。顯然,相當(dāng)多的想法和工作已融入了項(xiàng)目科吭。

實(shí)際在你的所有環(huán)境下運(yùn)行測(cè)試現(xiàn)在只需要四個(gè)按鍵:

$ tox

一個(gè)更復(fù)雜的設(shè)置

我的書(shū)——“寫(xiě)地道的Python”昏滴,實(shí)際上寫(xiě)的是一系列的Python模塊和代碼猴鲫。這樣做是為了確保所有的示例代碼按預(yù)期工作。作為我的構(gòu)建過(guò)程的一部分谣殊,我運(yùn)行tox來(lái)確保任何新的語(yǔ)法代碼能正常運(yùn)行拂共。我偶爾也看看我的測(cè)試覆蓋率,以確保沒(méi)有語(yǔ)法在測(cè)試中被無(wú)意跳過(guò)姻几。因此宜狐,我的tox.ini比上面的復(fù)雜一些,一起來(lái)看一看:

image

這個(gè)配置文件依舊比較簡(jiǎn)單蛇捌。而結(jié)果呢抚恒?

image

我從輸出列表里截取了一部分)。如果想看我的測(cè)試對(duì)一個(gè)環(huán)境的覆蓋率络拌,只需運(yùn)行:

image

結(jié)果很可怕啊俭驮。

setuptools整合

tox可以和setuptools整合,這樣python的setup.py測(cè)試可以運(yùn)行你的tox測(cè)試春贸。將下面的代碼段放到你的setup.py文件里混萝,這段代碼是直接從tox的文檔里拿來(lái)的:

image

現(xiàn)在Python的setup.py測(cè)試將下載tox并運(yùn)行它。真的很酷并且很節(jié)省時(shí)間萍恕。

Sphinx文檔生成器

Sphinx是由pocoo團(tuán)隊(duì)開(kāi)發(fā)的工具[@Lesus 注:pocoo團(tuán)隊(duì)開(kāi)發(fā)了很多優(yōu)秀的產(chǎn)品:如Flask, Jinja2等等]逸嘀。它已經(jīng)用來(lái)生成Python官方文檔和大多數(shù)流行的Python包的文檔。它以更容易的方式從Python代碼中自動(dòng)產(chǎn)生Python文檔允粤。

使用它完成工作

Sphinx不用了解Python程序以及怎樣從它們中提取出來(lái)崭倘。它只能翻譯reStructuredText文件,也就意味著你的代碼文檔的reStructuredText譯文需要讓Sphinx知道才能工作维哈,但是管理維護(hù)所有的.py文件[至少是函數(shù)和類的部分]的reStructuredText譯文顯然是不可行的绳姨。

幸運(yùn)的是,Sphinx有一個(gè)類似javadoc的擴(kuò)展阔挠,叫做autodoc,可以用來(lái)從你的代碼文檔中萃取出reStructuredText。為了能夠充分利用Sphinx和autodoc的能力脑蠕,你需要已一種特別的方式格式化你的文檔购撼。特別是,你需要使用Sphinx的Python指令時(shí)谴仙。這里就是使用reStructuredText指令來(lái)為一個(gè)函數(shù)生成文檔迂求,使輸出結(jié)果的HTML文檔更漂亮:

def06459793746d1849de96ea9c39424.jpeg

文檔需要花費(fèi)一點(diǎn)功夫,但是為了你的使用者晃跺,這個(gè)付出是值得的揩局。好吧,好的文檔使一個(gè)可用的項(xiàng)目去其糟粕掀虎。

Sphinx的autodoc擴(kuò)展讓我們可以使用很多指令凌盯,而這些指令可以自動(dòng)的從你文檔中生成文檔付枫。

安裝

確認(rèn)將Sphinx安裝在你的virtualenv內(nèi),因?yàn)槲臋n在項(xiàng)目里也是按版本來(lái)的驰怎。Sphinx不同的版本可能會(huì)產(chǎn)生不同的HTML輸出阐滩。通過(guò)將其安裝在你的virtualenv內(nèi),你可以以受控的方式升級(jí)你的文檔县忌。

我們要保持我們的文檔在docs文件夾掂榔,將文檔生成到docs/generated文件夾。在項(xiàng)目的根目錄運(yùn)行以下命令將根據(jù)你的文檔字符自動(dòng)重構(gòu)文本文檔:

$ sphinx-apidoc -F -o docs <package name>

這將產(chǎn)生一個(gè)包含多個(gè)文檔文件的docs文件夾症杏。此外装获,它創(chuàng)建了一個(gè)叫conf.py的文件,它將負(fù)責(zé)你的文檔配置厉颤。你還會(huì)發(fā)現(xiàn)一個(gè)Makefile穴豫,方便使用一個(gè)命令(生成html)構(gòu)建HTML文檔。

在你最終生成文檔之前走芋,確保你已經(jīng)在本地安裝了相應(yīng)的包(盡管可以使用pip绩郎,但python setup.py develop是最簡(jiǎn)單的保持更新的方法),否則sphinx-apidoc無(wú)法找到你的包翁逞。

配置:conf.py

conf.py文件創(chuàng)建用來(lái)控制產(chǎn)生的文檔的各個(gè)方面肋杖。它自己會(huì)很好生成文檔,所以我只簡(jiǎn)單地觸及兩點(diǎn)挖函。

版本和發(fā)布

首先状植,確保你的版本和發(fā)布版本號(hào)保持最新。這些數(shù)字會(huì)作為生成的文檔的一部分顯示怨喘,所以你不希望它們遠(yuǎn)離了實(shí)際值津畸。

保持你的版本最新的最簡(jiǎn)單方式就是在你的文檔和setup.py文件中都從你的包的version屬性讀取。我從Flask的conf.py借用過(guò)來(lái)配置sandman的conf.py:

image

這就是說(shuō)必怜,為了讓文檔產(chǎn)生正確的版本號(hào)肉拓,你只需在你的項(xiàng)目的虛擬環(huán)境中簡(jiǎn)單的需要運(yùn)行$python setup.py develop即可。現(xiàn)在你只需擔(dān)心保持version為最新梳庆,因?yàn)閟etup.py會(huì)使用它暖途。

html_theme

考慮到更改default到html_theme,我更喜歡原生態(tài)的東西膏执,顯然這是一個(gè)個(gè)人喜好的問(wèn)題驻售。我之所以提出這個(gè)問(wèn)題是因?yàn)镻ython官方文檔在Python 2和Python 3將默認(rèn)主題更改為Pydoc主題(后者的主題是一個(gè)自定義主題僅在CPython源代碼中可用)。對(duì)一些人來(lái)說(shuō)更米,默認(rèn)的主題使一個(gè)項(xiàng)目看起來(lái)“老”一些欺栗。

PyPI

PyPI,Python包索引(以前被稱為“Cheeseshop”)是一個(gè)公開(kāi)可用的Python包中央數(shù)據(jù)庫(kù)。PyPI是你的項(xiàng)目發(fā)布的地方迟几。一旦你的包(及其相關(guān)的元數(shù)據(jù))上傳到PyPI消请,別人通過(guò)pip或easy_instal可以下載并安裝它。這一點(diǎn)得強(qiáng)調(diào)一下:即使你的項(xiàng)目托管在GitHub瘤旨,直到被上傳到PyPI后你的項(xiàng)目才是有用的梯啤。當(dāng)然,有些人可以復(fù)制你的git庫(kù)任何直接手工安裝它存哲,但更多的人想使用pip來(lái)安裝它因宇。

最后的一步

如果你已經(jīng)完成了所有的前面部分中的步驟,你可能急著想把你的包上傳到PyPI祟偷,供其他人使用察滑!

先別急著做上述事情,在分發(fā)你的包之前修肠,有一個(gè)叫做cheesecake的有用的工具有助于運(yùn)行最后一步贺辰。它分析你的包并指定一個(gè)分類的數(shù)字分?jǐn)?shù)。它衡量你的包在打包嵌施、安裝饲化、代碼質(zhì)量以及文檔的數(shù)量和質(zhì)量方面是否容易/正確。

除了作粗略衡量的“準(zhǔn)備”吗伤,cheesecake在完整性檢查方面很優(yōu)秀吃靠。你會(huì)很快看到你的setup.py文件是否有錯(cuò)或者有沒(méi)有忘記為一個(gè)文件制作文檔。我建議在上傳每個(gè)項(xiàng)目到PyPI之前運(yùn)行一下它足淆,而不僅只是第一個(gè)巢块。

初始化上傳

現(xiàn)在,你已經(jīng)確定了你的代碼不是垃圾和當(dāng)人們安裝它時(shí)不會(huì)崩潰巧号,讓我們把你的包放到PyPI上吧族奢!你將會(huì)通過(guò)setuptools和setup.py腳本交互。如果這是第一次上傳到PyPI丹鸿,你將首先注冊(cè)它:

$ python setup.py register

注意:如果你還沒(méi)有一個(gè)免費(fèi)的PyPI賬戶越走,你將需要現(xiàn)在去注冊(cè)一個(gè),才能注冊(cè)這個(gè)包[@Lesus 注:注冊(cè)之后還需要到郵箱去驗(yàn)證才行]靠欢。在你已使用了上面注冊(cè)之后弥姻,你就可以創(chuàng)建發(fā)布包和上傳到PyPI了:

$ python setup.py sdist upload

上面這個(gè)命令建立一個(gè)源碼發(fā)布版(sdist),然后上傳到PyPI.如果你的包不是純粹的Python(也就是說(shuō)掺涛,你有二進(jìn)制需要編譯進(jìn)去),你就需要發(fā)布一個(gè)二進(jìn)制版疼进,請(qǐng)看setuptools文檔薪缆,了解更多。

發(fā)布及版本號(hào)

PyPI使用發(fā)行版本模型來(lái)確定你軟件包的哪個(gè)版本是默認(rèn)可用的。初次上傳后拣帽,為使你軟件包的每次更新后在PyPI可用疼电,你需要指定一個(gè)新版本號(hào)創(chuàng)建一個(gè)發(fā)布。版本號(hào)管理是一個(gè)相當(dāng)復(fù)雜的課題减拭,PEP有專門的內(nèi)容:PEP 440——版本識(shí)別和依賴指定蔽豺。我建議參照PEP 400指南(明顯地),但如果你選擇使用不同版本的方案拧粪,在setup.py中使用的版本比目前PyPI中的版本“高”修陡,這樣PyPI才會(huì)認(rèn)為這是一個(gè)新版本。

工作流

將你的第一個(gè)發(fā)布版本上傳到PyPI后可霎,基本的工作流程如下:

繼續(xù)在你的項(xiàng)目上工作 (比如修復(fù)bug魄鸦,添加新特性等等)

確保測(cè)試通過(guò)

在git-flow中創(chuàng)建一個(gè)發(fā)布分支“凍結(jié)”你的代碼

在你項(xiàng)目的init.py文件里更新__version__number版本變量

多次測(cè)試運(yùn)行setup.py,將新版本上傳到PyPI

使用TravisCI持續(xù)集成

持續(xù)集成是指一個(gè)項(xiàng)目中所有變化不斷整合的過(guò)程(不是周期性的批量更新)癣朗。就我們而言拾因,這意味每次我們GitHub提交時(shí),我們通過(guò)測(cè)試運(yùn)行來(lái)發(fā)現(xiàn)是否有什么異常旷余,正如你想象的绢记,這是一個(gè)非常有價(jià)值的實(shí)踐。不要有“忘記運(yùn)行測(cè)試”的提交正卧。如果你的提交通不過(guò)測(cè)試蠢熄,你將收到一封電子郵件被告知。

TravisCI是一種使GitHub項(xiàng)目持續(xù)集成更容易的服務(wù)穗酥。如果你還沒(méi)有賬號(hào)到這看一下注冊(cè)一個(gè)护赊,完成這些之后,在我們進(jìn)入CI之前我們先需要?jiǎng)?chuàng)建一個(gè)簡(jiǎn)單的文件砾跃。

通過(guò).travis.yml配置

在TravisCI上的不同項(xiàng)目通過(guò)一個(gè).travis.yml文件來(lái)配置骏啰,這個(gè)文件在項(xiàng)目的根目錄。簡(jiǎn)要地說(shuō)抽高,我們需要告訴Travis:

  1. 我們項(xiàng)目使用的語(yǔ)言是什么
  2. 它使用的是語(yǔ)言的哪個(gè)版本
  3. 使用什么命令安裝它
  4. 使用什么命令運(yùn)行項(xiàng)目的測(cè)試

這些都是很直接的東西判耕。下面是sandman.travis.yml的內(nèi)容:

[圖片上傳失敗...(image-700ee7-1548316101283)]

在列出語(yǔ)言和版本后,我們告訴Travis如何安裝我們的包翘骂。在install這行壁熄,確認(rèn)包含下面這行:

  • "pip install -r requirements.txt --use-mirrors"

這是pip安裝我們項(xiàng)目的要求(如果有必要的話使用PyPI鏡像站點(diǎn))。另外的兩行內(nèi)容是sandman特有的碳竟。它使用一個(gè)額外的服務(wù)(coveralls.io)來(lái)連續(xù)監(jiān)測(cè)測(cè)試用例的覆蓋率草丧,這不是所有項(xiàng)目都需要的。

:列出能運(yùn)行該項(xiàng)目測(cè)試的命令莹桅。與上面一樣昌执,sandman還需要做一些額外的工作。你的項(xiàng)目需要的只有Python的setup.py測(cè)試,after_success部分也可以一塊刪掉懂拾。

一旦你提交了這個(gè)文件并在TravisCI中激活了你的項(xiàng)目的煤禽,push到GitHub。一會(huì)兒后岖赋,你會(huì)看到一個(gè)基于你最近提交的編譯結(jié)束結(jié)果檬果。如果成功了,你的編譯呈現(xiàn)“綠色”和并且狀態(tài)頁(yè)會(huì)顯示編譯通過(guò)唐断。你可以看到你項(xiàng)目在任何時(shí)間的編譯歷史选脊。這對(duì)對(duì)人開(kāi)發(fā)特別有用,在歷史頁(yè)可以看到特定開(kāi)發(fā)者出錯(cuò)和編譯的頻率…

你還會(huì)收到一封通知你編譯成功的電子郵件栗涂。當(dāng)然你也可以設(shè)置只有在出錯(cuò)或錯(cuò)誤被修復(fù)時(shí)才有郵件通知知牌,但編譯輸出結(jié)果相同時(shí)也不會(huì)發(fā)送。這是非常有用的斤程,你在不必被無(wú)用的“編譯通過(guò)角寸!”郵件淹沒(méi)的同時(shí)在發(fā)生改變?nèi)詴?huì)收到警示。

用ReadTheDocs做持續(xù)文檔集成

盡管PyPI有一個(gè)官方文檔站點(diǎn)(pythonhosted.org)忿墅,但是ReadTheDocs提供了一個(gè)更好的體驗(yàn)扁藕。為什么?ReadTheDocs有針對(duì)GitHub非常棒的集成疚脐。當(dāng)你注冊(cè)ReadTheDocs的時(shí)候亿柑,你就會(huì)看到你的所有GitHub 代碼庫(kù)。選擇合適的代碼庫(kù)棍弄,做一些小幅的配置望薄,那么你的文檔就會(huì)在你每次提交到GitHub之后自動(dòng)重新生成。

配置你的項(xiàng)目應(yīng)該是一個(gè)很直觀的事情呼畸。只有一些事需要記住痕支,盡管,這里有一個(gè)配置字段的列表蛮原,對(duì)應(yīng)的值可能不一定是你直接用得上的:

DRY 不要重復(fù)你自己

現(xiàn)在你已經(jīng)完成了對(duì)于一個(gè)現(xiàn)存代碼基礎(chǔ)的所有艱難的開(kāi)源工作卧须,你可能不會(huì)想在開(kāi)始一個(gè)新項(xiàng)目的時(shí)候把這些事重來(lái)一遍。幸運(yùn)的是儒陨,你并不需要這么做花嘶。有Andrey Roy的Cookiecutter工具(我鏈接到了Python版本,盡管還有一些不同語(yǔ)言的版本在the main repo))

Cookiecutter是一個(gè)命令行工具能夠自動(dòng)執(zhí)行新建項(xiàng)目的一些步驟來(lái)做這篇文章里提到的一些事情蹦漠。 Daniel Greenfeld (

@pydanny )寫(xiě)了一篇很好的關(guān)于它的博客并且提到了如何與這篇文章里提到的實(shí)踐聯(lián)系上椭员。你可以從這里看看這篇文章:

Cookiecutter: Project Templates Made Easy .

結(jié)論

我們已經(jīng)介紹了所有用來(lái)開(kāi)源一個(gè)Python包的命令,工具和服務(wù)笛园。當(dāng)然拆撼,你可以直接把它扔到GitHub上并且說(shuō)“自己安裝它”容劳,但是沒(méi)人會(huì)這么做。并且你僅僅是開(kāi)發(fā)源代碼并不算是真正的開(kāi)源軟件闸度。

另外,你可能不會(huì)為你的項(xiàng)目吸引外部貢獻(xiàn)者蚜印。通過(guò)這里列出的方法來(lái)設(shè)立你的項(xiàng)目莺禁,你就已經(jīng)創(chuàng)建了一個(gè)容易維護(hù)的Python包并且會(huì)鼓勵(lì)大家來(lái)使用和貢獻(xiàn)代碼。而這窄赋,就是開(kāi)源軟件的真正精神哟冬,不是嗎?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末忆绰,一起剝皮案震驚了整個(gè)濱河市浩峡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌错敢,老刑警劉巖翰灾,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異稚茅,居然都是意外死亡纸淮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門亚享,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)咽块,“玉大人,你說(shuō)我怎么就攤上這事欺税〕藁Γ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵晚凿,是天一觀的道長(zhǎng)亭罪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)晃虫,這世上最難降的妖魔是什么皆撩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮哲银,結(jié)果婚禮上扛吞,老公的妹妹穿的比我還像新娘。我一直安慰自己荆责,他們只是感情好滥比,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著做院,像睡著了一般盲泛。 火紅的嫁衣襯著肌膚如雪濒持。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,821評(píng)論 1 290
  • 那天寺滚,我揣著相機(jī)與錄音柑营,去河邊找鬼。 笑死村视,一個(gè)胖子當(dāng)著我的面吹牛官套,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蚁孔,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼奶赔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了杠氢?” 一聲冷哼從身側(cè)響起站刑,我...
    開(kāi)封第一講書(shū)人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鼻百,沒(méi)想到半個(gè)月后绞旅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡愕宋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年玻靡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片中贝。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡囤捻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出邻寿,到底是詐尸還是另有隱情蝎土,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布绣否,位于F島的核電站誊涯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蒜撮。R本人自食惡果不足惜暴构,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望段磨。 院中可真熱鬧取逾,春花似錦、人聲如沸苹支。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)债蜜。三九已至晴埂,卻和暖如春究反,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背儒洛。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工精耐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晶丘。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓黍氮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親浅浮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容