思考
当开始一个Python项目,我的意思是不仅仅是快速脚本的拼凑,而是作为一个产品化的程序,如何组织代码和文档,是需要精心考虑的。参考成熟的开源项目编程风范,使自己的程序能够不断迭代优化,是需要从开始就做好规划的。
工具和概念
以下是开发Python项目的工具和概念,可以帮助我们更好组织项目,编写更清晰的代码:
- 项目层次(目录结构)
setuptools
和setup.py
文件- git版本控制
- 使用GitHub管理项目
- GigHub的"Issues"用于以下功能:
- bug跟踪
- 功能需求
- 未来计划
- 发布/版本管理
- GigHub的"Issues"用于以下功能:
- git-flow实现git工作流
- py.test实现单元测试
- tox实现测试标准化
- Sphinx用于自动生成HTML文档
- TravisCI用于持续测试集成
- ReadTheDocs用于持续文档集成
- Cookiecutter用于将上述步骤自动化以启动下一个项目
项目层次(目录结构)
当设置一个项目,layout
(或称为目录结构)是正确开始的重要步骤。一个恰当的项目层次意味着潜在的代码贡献者不必浪费时间找寻代码片段,凭直觉就能找到文件。
大多数项目都有一些顶层文件(类似setup.py
, README.md
, requirements.txt
等),然后有3个目录是每个项目都具备的:
docs
目录包含项目文档- 一个和项目名称相同的目录存储实际的Python包
- 一个
test
目录位于2个位置:- 在包目录下的
test
目录包含测试代码和资源 - 以及一个顶层的独立目录
- 在包目录下的
以下是一个简单的sandman
项目(注:这里采用了Open Sourcing a Python Project the Right Way文档中案例)
$ pwd
~/code/sandman
$ tree
.
|- LICENSE
|- README.md
|- TODO.md
|- docs
| |-- conf.py
| |-- generated
| |-- index.rst
| |-- installation.rst
| |-- modules.rst
| |-- quickstart.rst
| |-- sandman.rst
|- requirements.txt
|- sandman
| |-- __init__.py
| |-- exception.py
| |-- model.py
| |-- sandman.py
| |-- test
| |-- models.py
| |-- test_sandman.py
|- setup.py
上述,有一个docs
目录(其中generated
目录是一个空目录,sphinx将生成的文档存放在这个目录中),有一个sandman
目录,以及在sandman
目录下的test
子目录。
setuptools
和setup.py
文件
setup.py
文件类似于其他通过diskutils
包来使用其他包用于安装Python包。它是任何项目一个非常重要的文件,因为它包含了在PyPI中使用的版本信息,软件包环境需求,项目描述,以及你的名字和联系信息等等。这个文件使得软件包能够以一种程序化的方法被搜索和安装,提供了元数据和介绍工具。
setuptools
包(实际上是diskutils
的一个增强)将构建和分发Python软件包进行了简化。一个Python软件包可以通过setuptools
打包并且和diskutils
打包几乎完全一样。所以没有理由不使用setuptools
。
setup.py
位于项目的根目录。最重要的setup.py
部分称为setuptools.setup
,也就是包含了包的所有元信息部分。
以下是完整sandman项目的setup.py
from __future__ import print_function
from setuptools import setup, find_packages
from setuptools.command.test import test as TestCommand
import io
import codecs
import os
import sys
import sandman
here = os.path.abspath(os.path.dirname(__file__))
def read(*filenames, **kwargs):
encoding = kwargs.get('encoding', 'utf-8')
sep = kwargs.get('sep', '\n')
buf = []
for filename in filenames:
with io.open(filename, encoding=encoding) as f:
buf.append(f.read())
return sep.join(buf)
long_description = read('README.txt', 'CHANGES.txt')
class PyTest(TestCommand):
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
import pytest
errcode = pytest.main(self.test_args)
sys.exit(errcode)
setup(
name='sandman',
version=sandman.__version__,
url='http://github.com/jeffknupp/sandman/',
license='Apache Software License',
author='Jeff Knupp',
tests_require=['pytest'],
install_requires=['Flask>=0.10.1',
'Flask-SQLAlchemy>=1.0',
'SQLAlchemy==0.8.2',
],
cmdclass={'test': PyTest},
author_email='[email protected]',
description='Automated REST APIs for existing database-driven systems',
long_description=long_description,
packages=['sandman'],
include_package_data=True,
platforms='any',
test_suite='sandman.test.test_sandman',
classifiers = [
'Programming Language :: Python',
'Development Status :: 4 - Beta',
'Natural Language :: English',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Operating System :: OS Independent',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Software Development :: Libraries :: Application Frameworks',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
],
extras_require={
'testing': ['pytest'],
}
)
快速脚手架工具cookiecutter
cookiecutter 提供了一个快速创建模版的工具
pip install cookiecutter
然后执行
cookiecutter https://github.com/audreyr/cookiecutter-pypackage.git
会提示一些问题,然后为你创建好目录结构及相应文件
README.md
文件可以通过pandoc来自动生成README.rst
是正确开始的重要步骤。一个恰当的项目层次意味着潜在的代码贡献者不必浪费时间找寻代码片段,凭直觉就能找到文件。
详细使用setuptools
的方法参考Distributing Python Modules
python -m pip install setuptools wheel twine
源代码版本管理 - GitHub
使用git-flow来管理git工作流
建议使用 git-flow 分支模式。
develop
是最主要的工作分之,也是用于部署下一个发行版的分支代码。feature
分支是相当于功能分之并且还没有部署的版本- 通过创建
release
分支来更新master
主干
参考这篇来安装git-flow
在Mac平台有多种方法安装,我采用如下方法
curl -L -O https://raw.github.com/nvie/gitflow/develop/contrib/gitflow-installer.sh
sudo bash gitflow-installer.sh
然后执行以下命令将现有的项目迁移
git flow init
使用默认参数,完成后会自动创建develop
和master
分支。
大多数工作都是在develop
分支,包含了所有完成的功能和bug fix用于发布;nightly builds或持续集成服务器将标记为develop
。
要开始一个新功能,使用
git flow feature start <feature name>
此时就会创建一个新的分支: feature/<feature name>
。此时commit就会在这个分支,当功能就绪准备发布产品,就会合并到develop,则使用如下命令
git flow feature finish <feature name>
代码合并到develop
分支并且会删除掉feature/<feature name>
分支。
当准备发布产品时候,就从develop
分支创建release
分支。使用如下命令:
git flow release start <release number>
所有的完成并且准备发布功能的必须位于develop
,这样才能feature finish
。当创建了release分支,则可以发布代码。一些在release之后的小的bug fix则直接在release/<release number>
分支。如果已经解决并且没有bug则使用如下命令:
git flow release finish <release number>
这就会合并releae/<release number>
回到master
和develop
分支。
hotfix
类似从master
分离出的feature
:如果你已经关闭了release
分支但是意识到有必不可少的修改需要发布,则从master
分离出hotfix
分支(在git flow release finish <release number>
之后)类似:
git flow hotfix start <release number>
然后在修改完成后,执行
git flow hotfix finish <release number>
virtualenv
和virtualenvwrapper
如果在一个服务器上有多个Python项目,每个项目有不同的依赖,可以使用virtualenv
来创建虚拟机的Python安装,这样site-packages
,distribute
和pip
将按照这个方式独立。pip install
将软件包安装到virtualeve
而不是系统的Python安装。在不同的virtualenv
之间切换是非常简单的一条命令。
virtualenvwrapper
是一个单独的工具,用于创建和管理virtualenv
:
pip install `virtualenvwrapper`
安装完成后,需要将路径添加到环境中
echo "source /usr/local/bin/virtualenvwrapper.sh" >> ~/.bashrc
创建虚拟化环境
mkvirtualenv ossproject
在虚拟环境中,很容易生成requirements.txt
,因为pip
可以通过requirements.txt
和-r
参数安装任何项目依赖。要创建这个文件,执行以下命令
(ossproject)$ pip freeze > requirements.txt
测试py.test
Python标准库unittest
包:nose和py.test。两者都扩展了unittest
来使得容易添加附加功能。
测试设置:
在test
目录下,创建一个名为test_<project_name>.py
文件,py.test
的测试目录机制就会讲任何test_
开头的文件作为一个测试文件(除非告诉它不怎么做)。
测试覆盖:
结合py.test
,我们使用Ned Batchelder的coverage工具。要执行这个,线执行pip install pytest-cov
。
使用Tox完成标准化测试
Python的一个项目维护问题是兼容性
。如果目标是支持Python 2.x和Python 3.x,则需要确保代码可以在指定平台正常工作。tox提供了"Pythong标准化测试":它能够创建一个完整的沙盒环境并安装你的软件包及其环境来进行测试。
tox
是通过.ini
文件:tox.ini
来配置的,非常容易设置,以下是一个最小化的tox.ini
:
# content of: tox.ini , put in same dir as setup.py
[tox]
envlist = py26,py27
[testenv]
deps=pytest # install pytest in the venvs
commands=py.test # or 'nosetests' or ...
通过在envlist
中设置py26
和py27
,tox
就可以知道需要针对哪种环境测试。并且tox
默认就支持多种环境,例如jython
和pypy
。
dpes
是软件包的依赖,甚至可以告诉tox
从一个替代的PyPI URL安装所有或部分依赖。最后,执行所有环境的检查
tox
setuptools
集成
tox
可以集成到setuptools
这样python setup.py test
就可以运行tox
测试。以下代码片段是从tox
文档中摘录加入到setup.py
文件:
from setuptools.command.test import test as TestCommand
import sys
class Tox(TestCommand):
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
#import here, cause outside the eggs aren't loaded
import tox
errcode = tox.cmdline(self.test_args)
sys.exit(errcode)
setup(
#...,
tests_require=['tox'],
cmdclass = {'test': Tox},
)
使用Sphinx撰写文档
Sphinx是从pocoo演化出来的工具,用于生成Python文档,从代码尽可能方便地自动生成HTML文档。
sphinx有一个类似javadoc
的扩展,称为autodoc
,可以从代码文档字符串中提取出reStructured文本。要能够充分使用Sphinx和autodoc
,需要格式化代码文档字符串以便使用Sphinx的Python directives。
以下是一个模块功能使用了
def _validate(cls, method, resource=None):
"""Return ``True`` if the the given *cls* supports the HTTP *method* found
on the incoming HTTP request.
:param cls: class associated with the request's endpoint
:type cls: :class:`sandman.model.Model` instance
:param string method: HTTP method of incoming request
:param resource: *cls* instance associated with the request
:type resource: :class:`sandman.model.Model` or None
:rtype: bool
"""
if not method in cls.__methods__:
return False
class_validator_name = 'validate_' + method
if hasattr(cls, class_validator_name):
class_validator = getattr(cls, class_validator_name)
return class_validator(resource)
return True
然后在项目根目录下执行
sphinx-apidoc -F -o docs <package name>
则会在docs
目录下创建Sphinx的文档,并创建构建html输出的脚本。然后进入docs
目录,创建输出
cd docs
make html
生成的文档输出到docs
目录下的_build/html/
子目录中