屈臣上品 發表於 2019-6-25 21:34:00

Python工程目录组织

<h1 class="Post-Title">Python工程目录组织</h1>
<div class="Post-Author">
<div class="AuthorInfo">
<div class="Popover">
<div id="Popover2-toggle">from:&nbsp;https://zhuanlan.zhihu.com/p/36221226</div>
</div>
</div>
</div>
<div class="Post-RichTextContainer">
<div class="RichText ztext Post-RichText">
<h2>Python工程目录组织</h2>
<p>关于如何组织一个较好的Python工程目录结构,已经有一些得到了共识的目录结构。在Stackoverflow的这个问题上,能看到大家对Python目录结构的讨论。</p>
<p>这里面说的已经很好了,我也不打算重新造轮子列举各种不同的方式,这里面我说一下我的理解和体会。</p>
<p>假设你的项目名为foo, 我比较建议的最方便快捷目录结构这样就足够了:</p>
<div class="highlight">
<pre><code class="language-text">Foo/
|-- bin/
|   |-- foo
|
|-- foo/
|   |-- tests/
|   |   |-- __init__.py
|   |   |-- test_main.py
|   |
|   |-- __init__.py
|   |-- main.py
|
|-- docs/
|   |-- conf.py
|   |-- abc.rst
|
|-- setup.py
|-- requirements.txt
|-- README</code></pre>
</div>
<p>简要解释一下:</p>
<ol>
<li><code>bin/</code>: 存放项目的一些可执行文件,当然你可以起名<code>script/</code>之类的也行。</li>
<li><code>foo/</code>: 存放项目的所有源代码。(1) 源代码中的所有模块、包都应该放在此目录。不要置于顶层目录。(2) 其子目录<code>tests/</code>存放单元测试代码; (3) 程序的入口最好命名为<code>main.py</code>。</li>
<li><code>docs/</code>: 存放一些文档。</li>
<li><code>setup.py</code>: 安装、部署、打包的脚本。</li>
<li><code>requirements.txt</code>: 存放软件依赖的外部Python包列表。</li>
<li><code>README</code>: 项目说明文件。</li>
</ol>
<p>除此之外,有一些方案给出了更加多的内容。比如<code>LICENSE.txt</code>,<code>ChangeLog.txt</code>文件等,我没有列在这里,因为这些东西主要是项目开源的时候需要用到。如果你想写一个开源软件,目录该如何组织,可以参考这篇文章。</p>
<p>下面,再简单讲一下我对这些目录的理解和个人要求吧。</p>
<p class="ztext-empty-paragraph">&nbsp;</p>
<h2>关于README</h2>
<p>这个我觉得是每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。</p>
<p>它需要说明以下几个事项:</p>
<ol>
<li>软件定位,软件的基本功能。</li>
<li>运行代码的方法: 安装环境、启动命令等。</li>
<li>简要的使用说明。</li>
<li>代码目录结构说明,更详细点可以说明软件的基本原理。</li>
<li>常见问题说明。</li>
</ol>
<p>我觉得有以上几点是比较好的一个<code>README</code>。在软件开发初期,由于开发过程中以上内容可能不明确或者发生变化,并不是一定要在一开始就将所有信息都补全。但是在项目完结的时候,是需要撰写这样的一个文档的。</p>
<p class="ztext-empty-paragraph">&nbsp;</p>
<h2>关于 requirements</h2>
<p>python项目中必须包含一个 requirements.txt 文件,用于记录所有依赖包及其精确的版本号。以便新环境部署。</p>
<p>官方文档:</p>
<p>Pip’s documentation states</p>
<p>pip description</p>
<p>freeze Output installed packages in requirements format.</p>
<p>list List installed packages.</p>
<p>在虚拟环境中使用pip生成(否则会生成大量本地数据包,安装或升级包后,最好更新这个文件):</p>
<p>pip freeze &gt;requirements.txt # 输出本地包环境至文件</p>
<p class="ztext-empty-paragraph">&nbsp;</p>
<p>当需要创建这个虚拟环境的完全副本,可以创建一个新的虚拟环境,并在其上运行以下命令:</p>
<p>pip install -r requirements.txt # 根据文件进行包安装</p>
<p class="ztext-empty-paragraph">&nbsp;</p>
<p>需求文件requirements.txt的内容示例如下:</p>
<p>alembic==0.8.6</p>
<p>bleach==1.4.3</p>
<p>click==6.6</p>
<p>dominate==2.2.1</p>
<p class="ztext-empty-paragraph">&nbsp;</p>
<h2>关于setup.py</h2>
<p>一般来说,用<code>setup.py</code>来管理代码的打包、安装、部署问题。业界标准的写法是用Python流行的打包工具setuptools来管理这些事情。这种方式普遍应用于开源项目中。不过这里的核心思想不是用标准化的工具来解决这些问题,而是说,一个项目一定要有一个安装部署工具,能快速便捷的在一台新机器上将环境装好、代码部署好和将程序运行起来。</p>
<p>这个我是踩过坑的。</p>
<p>我刚开始接触Python写项目的时候,安装环境、部署代码、运行程序这个过程全是手动完成,遇到过以下问题:</p>
<ol>
<li>安装环境时经常忘了最近又添加了一个新的Python包,结果一到线上运行,程序就出错了。</li>
<li>Python包的版本依赖问题,有时候我们程序中使用的是一个版本的Python包,但是官方的已经是最新的包了,通过手动安装就可能装错了。</li>
<li>如果依赖的包很多的话,一个一个安装这些依赖是很费时的事情。</li>
<li>新同学开始写项目的时候,将程序跑起来非常麻烦,因为可能经常忘了要怎么安装各种依赖。</li>
</ol>
<p><code>setup.py</code>可以将这些事情自动化起来,提高效率、减少出错的概率。"复杂的东西自动化,能自动化的东西一定要自动化。"是一个非常好的习惯。</p>
<p>setuptools的文档比较庞大,刚接触的话,可能不太好找到切入点。学习技术的方式就是看他人是怎么用的,可以参考一下Python的一个Web框架,flask是如何写的:&nbsp;setup.py</p>
<p>当然,简单点自己写个安装脚本(<code>deploy.sh</code>)替代<code>setup.py</code>也未尝不可。</p>
<p class="ztext-empty-paragraph">&nbsp;</p>
<h2>关于conf.py</h2>
<p>注意,在上面的目录结构中,没有将<code>conf.py</code>放在源码目录下,而是放在<code>docs/</code>目录下。</p>
<p>很多项目对配置文件的使用做法是:</p>
<ol>
<li>配置文件写在一个或多个python文件中,比如此处的conf.py。</li>
<li>项目中哪个模块用到这个配置文件就直接通过<code>import conf</code>这种形式来在代码中使用配置。</li>
</ol>
<p>这种做法我不太赞同:</p>
<ol>
<li>这让单元测试变得困难(因为模块内部依赖了外部配置)</li>
<li>另一方面配置文件作为用户控制程序的接口,应当可以由用户自由指定该文件的路径。</li>
<li>程序组件可复用性太差,因为这种贯穿所有模块的代码硬编码方式,使得大部分模块都依赖<code>conf.py</code>这个文件。</li>
</ol>
<p>所以,我认为配置的使用,更好的方式是,</p>
<ol>
<li>模块的配置都是可以灵活配置的,不受外部配置文件的影响。</li>
<li>程序的配置也是可以灵活控制的。</li>
</ol>
<p>能够佐证这个思想的是,用过nginx和mysql的同学都知道,nginx、mysql这些程序都可以自由的指定用户配置。</p>
<p>所以,不应当在代码中直接<code>import conf</code>来使用配置文件。上面目录结构中的<code>conf.py</code>,是给出的一个配置样例,不是在写死在程序中直接引用的配置文件。可以通过给<code>main.py</code>启动参数指定配置路径的方式来让程序读取配置内容。当然,这里的<code>conf.py</code>你可以换个类似的名字,比如<code>settings.py</code>。或者你也可以使用其他格式的内容来编写配置文件,比如<code>settings.yaml</code>之类的。</p>
<p class="ztext-empty-paragraph">&nbsp;</p>
<h2>关于main.py</h2>
<p><code>__name__ == '__main__'</code>是Python的<code>main函数</code>入口。并非说,加入这句才能使用<code>python xxx.py</code>来执行,而是说,这里可以判断,当前是否是直接被python直接调用执行。</p>
<p><code>getopt</code>是一个包装方法,用以读取<code>main函数</code>后面跟着的参数。<code>getopt.getopt(args, options[, long_options])</code>有三个变量,args就是<code>python xxx.py</code>后面跟着的参数,通常就是<code>sys.argv</code>数组,不过我们一般会去除第一个元素,因为<code>sys.argv</code>的第一个元素,就是<code>文件名</code>本身。所以,我们的写法是<code>sys.argv</code>。</p>
<p><code>options</code>是一个字符串,描述了需要解析哪些参数。如果一个参数,不需要跟变量,比如<code>-h</code>,那么直接使用参数名即可。如果一个参数需要传入变量,则在后面加<code>:</code>,比如<code>n:</code>。所以本案例中<code>hn:w:</code>的意思是,我们有三个参数,分别是<code>-h</code>,&nbsp;<code>-n</code>,&nbsp;<code>-w</code>,其中<code>-h</code>无需传入变量,而<code>-n</code>,&nbsp;<code>-w</code>需要传入变量。</p>
<p><code>long_options</code>是一个字符串数组,也表示需要解析哪些参数。<code>long_options</code>是相对<code>options</code>而言的,我们在linux中,经常会看到一个命令的参数有多种写法,最常见的就是帮助参数,它有两种写法:<code>-h</code>,&nbsp;<code>--help</code>。前一种是就是我们的<code>options</code>,而后一种就是<code>long_options</code>。</p>
<p>假如我们有一个<code>--help</code>,那么在<code>long_options</code>中就是<code>['help']</code>。如果一个参数需要传入参数,比如<code>--name 'Good'</code>,那么在<code>long_options</code>中就是<code>['name=']</code>,是的,就是多一个<code>=</code>。</p>
<p><code>getopt.getopt</code>返回一个元组(opts, args),其中<code>opts</code>就是我们解析出来的参数,而<code>args</code>则是剩余没有解析的参数。<code>opts</code>是元组数组,每个元组,相当于key-value。<code>key</code>就是我们的参数名,而<code>value</code>就是参数的内容。</p>
<p>经典例子:</p>
<p class="ztext-empty-paragraph">&nbsp;</p>
<p># coding=utf-8</p>
<p>import getopt</p>
<p>import sys</p>
<p class="ztext-empty-paragraph">&nbsp;</p>
<p>if __name__ == '__main__':</p>
<p>opts, args = getopt.getopt(sys.argv, 'hn:w:', ['name=', 'word=', 'help'])</p>
<p>name = 'No Name'</p>
<p>word = 'Hello'</p>
<p>for key, value in opts:</p>
<p class="ztext-empty-paragraph">&nbsp;</p>
<p>if key in ['-h', '--help']:</p>
<p>print'一个向人打招呼的程序'</p>
<p>print'参数:'</p>
<p>print'-h\t显示帮助'</p>
<p>print'-n\t你的姓名'</p>
<p>print'-w\t想要说的话'</p>
<p>sys.exit(0)</p>
<p>if key in ['-n', '--name']:</p>
<p>name = value</p>
<p>if key in ['-w', '--word']:</p>
<p>word = value</p>
<p>print'你好,我叫', name, ',', word</p>
<p class="ztext-empty-paragraph">&nbsp;</p>
<p>类class中调用的优先级:</p>
<p>1、def__new__(cls):</p>
<p>2、def__init__(self):</p>
<p>3、def__call__(self, x):</p>
</div>
</div><br><br>
来源:https://www.cnblogs.com/bonelee/p/11086806.html
頁: [1]
查看完整版本: Python工程目录组织