Pypi 使用
当然之前看过很多英文的技术文档了(关键人家只有英文的技术文档啊!),然而这篇是第一篇最想翻译出来的,因为当初在使用PyPi时找到的很多中文文档讲的都相当简略,当看到这篇文章之后,之前很多问题都有了结果~~~
作为第一篇翻译,不知道会查多少次字典了23333
作者: 我已经出于好意的表明了实际上有一个官方的上传程序包到Pypi的指南,链接在此。
我是一名明尼苏达大学语言信息学实验室的研究助理,最近的一项任务变成了将一个以前学生的Python脚本变得更加通用并且将他改造成一个符合规范的Python程序包,这花了我几周时间,来让这份代码能按照我们的需求发挥作用,包括减少重复代码,验证等工作。这份代码在本地能够很好的安装。然后在两天前,我试着在Pypi上注册这个程序包,那样我们就可以通过pip来安装这个程序包了,附带着我们的分析代码。
我觉得现在已经能让它正常使用了,但讲真,这真是一个噩梦。一开始我必须从文档,留言板(包括stackexchange和google groups),博客还有其他各种纷繁的网络抓取资源,为什么这一切都不能在某一个地方讷?最终,那些能用的东西都是来自于反复实验(trial-and-error
)。部分问题的原因是我的程序包些许复杂了一点:它需要包含一些应该被安装在同一个目录下的数据,同时也需要编译一个独立的C文件。但实际上,那不应该算是什么大不了的事。
为了记录下整个过程以防未来我不得不面对同样的问题,并且帮助指导一下那些正着手踏上这条没有经验的道路的骚年们,我将详细的写下我的笔记并和整个世界分享
它
我最近几年已经用Python完成了很多不同的项目了,尽管如此,我依然是一个科学家,而不是一个软件工程师,所以以下很多细节我并不是很清晰,如果以下的做法并不是最有效率的话,在此表示歉意,毕竟我只是写下一份自己几天前就想拥有的文档,希望能够对其他人有用。
程序包结构
这部分对我来说一直有一些混淆,但是通过这个项目已经让我清楚了一些,你的程序包的结构应该像这样:
1 | YOUR-PROJECT-FOLDER |
这里有几个需要注意的地方:
- 整个项目的目录名并不是很重要,并不一定要和你的程序包名一样(package name),对于我来说,如果这两者的名字不一样的话会更容易区分一些
PACKAGENAME
,这个目录的名字将被你用来引Python(imoprt),所以如果你的程序包叫做my_awesome_package
,那么你的代码目录名字也必须是my_awesome_package
。需要注意的是,Python支持者(python大神们)推荐使用没有大写字母的单名称程序包名(package name)
现在来说说几个重要的文件
CHANGES.txt
:(可选的)保存着程序包的版本信息等LICENSE.txt
:你使用的用来发布程序包的许可证。如果你想要分享它并希望人们来使用它,那么你应该包含一个许可证。就我的理解来说,没有许可证信息就相当于声明All Rights Reserved
,意味着大家不能合法的再分享你的代码。声明:我不是一个律师,我也不知道有关的各种许可证信息。(译者:个人在网上搜索到的关于各种License的介绍)MANIFEST.in
:当setup.py
在为你的程序包打包的时候,它默认包含进了你程序包中的*.py
文件,如果你想让其他文件也包含进最终打包好的.tar.gz
文件的话,那么你就需要在MANIFEST.in
里包含这些文件名。以下是我的这个文件的样子:
1 | #documentation |
我的程序包的名字叫vfclust
,所以我把在程序包目录下的指定目录中的用来保证程序正常运行的文件名(数据文件等)写进了这个文件。同时我也包含了CHANGES.txt和LICENSE.txt,因为我觉得他们没有默认被包含(作者在此表示不确定,不过的确默认没有包含这些文件诶),请大家注意recursive-include
这个语法,我的书写方式,指定的所有路径也都需要包含其中
README
:同样可以是README.md
或者README.rst
,但是格式必须要rst格式(reStructured Text),因为这要在Pypi上正确的显示出来。这一部分我在下面有更多的研究docs
:(可选的)我在这里放已经格式化的文档,之后会有更多的说明setup.py
:这是最神奇的地方,在这里你的程序包被定义,以及如何安装等。待会儿这部分在下面有更多的说明。PACKAGENAME
:这个目录里面有你真正的程序包的代码,FILE1.py,FILE2.py等就是你程序包起作用的文件__init__.py
这个文件来声明PACKAGENAME
是一个Python的程序包,并且使用import PACKAGENAME
。这个文件很重要:__init__.py
将一个满是Python脚本的目录变成了一个可以被引入的程序包,这可以是一个空的文件,也可以不是,但无论这个文件里面写了什么都会在被引入时执行。如果你想在python中使用其他模块(文件),那你极有可能需要在__init__.py
中写入from PACKAGENAME import *
data
和example
:这些目录可以被任意命名,或者省略。你想要和你的程序包一起被发布的数据文件都应该像这样放在程序包目录中
.tar.gz 与 site-packages
对我来说,这是我混淆最严重的来源之一。当你在打包你的程序包(之后有详细说明)的时候,setup.py
会创建一个dist/
目录在你的项目目录下,然后把所有程序包指定的东西(下面有更多说明)以及MANIFEST.in
指定的文件打包到一个.tar.gz
文件并放到这个dist/
这个目录下,然后你可以上传这个.tar.gz
文件到Pypi然后就可以从网上获取它了。然而,当你使用pip install PACKAGENAME
的时候,只有*.py
文件会被安装进site-packages这个目录,这意味着任何数据文件,C文件或者其他你在使用的时候想要获取的文件都必须同时
明确的包含在MANIFESET.in
以及setup.py
里。就个人对这样做的理解而言,有些东西(比如格式化的文档)你可能需要包含在完整的*.tar.bz
包文件中,但是不想放进site-packages
。这有一些道理,但是这样宝宝依然有些不开森,因为不得不在两个地方指定需要包含的文件,setuptools
的文档说你只需要在setup.py
中指定特别的文件就好了,然而这对我来说并米有任何用
setup.py
这个文件就像魔法一样,这是创建和安装你的程序包的关键(KEY)所在,同时也能记录版本变化以及帮助PyPi的规范化。有两个被普遍使用的工具来创建setup.py
文件:distutils
和setuptools
。网络上告诉我setuptools
更加现代化而且解决了一些distutils
没有解决的问题,但是我并不是很清楚是什么问题。两者的语法几乎完全一样,所以也很容易在两者之间切换。
setup.py
文件必须至少包含以下的东西。这是根据这个项目修改来的,我觉得这是一个有用的资源。
1 | from setuptools import setup, find_packages # Always prefer setuptools over distutils |
setup()
只是一个当你在这个项目的目录中执行python setup.py install
时候运行的一个函数,安装只是它能做的事情之一,你还可以执行python setup.py sdist
来将你的程序包打包到一个.tar.gz
文件中,执行python setup.py develop
会告诉Python,当你在引入程序包的时候,去查看项目目录而不是site-packages
。
接下来我将逐一说明setup()
函数的参数和其他东西
- name:这将是你的程序包的名字,在你新上传的程序包的PyPi页面上,用大大的粗体字显示的这个名字,这同时也会是你的
tar.gz
文件的根名称,他们以PACKAGENAME-VERSION.tar.gz
的形式命名。 - version:很显然,这是版本号。注意,PyPi强制你在每一次更新上传的时候都要使用新的版本号,所以你
必须
在每次上传的时候更新这个版本号(不像git或者其他版本控制系统一样在提交的时候不用担心版本号问题)。最终我用类似0.1.0.12酱紫的版本号完成了我在PyPi上的程序包的调试工作。幸运的是,你可以登录到PyPi删除不想要的版本号 - description:在Pypi上简短的说明
- long_description:PyPi上你的程序包的主页上显示的东西,意味着这应该以
rst
格式进行撰写。我就用我的README文件就好了 - author等:用来列出作者,联系信息等
- classifiers:又来在PyPi上给你的程序包分类,这样大家就可以在浏览或者搜索的时候找到他(译者:这是所有classifiers的清单)
- packages:你发布的包可能含有不止一个程序包,也就是说,当你在安装这个程序包的时候,可能需要
import package1
和import package2
,可以是很多程序包,也可以是很多子包,这些都必须被列在这里,你也可以这样做:1
packages = find_packages(exclude=['build', 'docs', 'templates'])
以上都是一些基础。接下来的几个部分将详细说明我最终使用的参数
包含数据文件
- package_data:就像之前提到的一样,如果你想要在用pip安装任何非python脚本以供自己的包使用的时候,在这里指定它们。在这里混淆的一塌糊涂让我得出了一个结论,那就是所有被包含的数据文件必须放在程序包目录子目录中(而不是更高一级的项目目录中)。举个栗子,如果我有一个“data”和“my-package”作为“my-project-folder”的子目录,那么就在这里的package_data中写入“data”,然后pip就会把“data”和“my-package”都作为独立的目录安装进“site-package”这个目录。在经过了无数的修改之后,我的目录结构变得像下面这样了:
1 | . |
这是最终的样子:
1 | include_package_data=True, |
依照我的看法,package_data
是一个每个键关联着一个子目录的字典,每一个值应该是那个子目录中你想要上传的文件列表。然而实际上,我并不能在最后的安装中把一切都合适的安置好,直到我制作好程序包的所有数据子目录。如果你知道更好的方法或者更加灵活的方式来处理这个的话,请教教我~(不是在这里嘲笑我,而是真真的发邮件给我!我想知道!)。通过这个安装,以下的东西就在执行pip install
的时候安装到了site-packages
了:
1 | vfclust |
这也是我所期望的。data_files
是另一个用来包含数据的参数,但是会把你的数据文件放到/Library/Frameworks/Python.framework/Versions/2.7/my-subfolder
(译者:看来作者用的是Mac OS!!!)至少在我的系统上是这样,我不知道为什么你想要把数据文件放到那里,无论如何我是不会这么做的
像脚本一样运行你的程序包
如果你的程序有像终端脚本一样跑起来的必要而不是(或不只是)变成可引入的程序包的话,你可以要求setup.py
在安装过程中添加你的脚本到系统路径中。这样的话,你需要使用entry_points
这样的参数,类似酱紫:
1 | entry_points={ |
如果我理解正确的话,下面左边的那个东西是你们从命令行调用的标志,
1 | $ vfclust args-go-here |
然后右边的东西是关联的python脚本。对我来说,我的vfclust.py
文件里有一个“main()”方法
编译文件
我的程序包的一个功能是依赖于一个编译的C文件来将文字从文本转换成语音形式。这并不是一个Python的扩展,而是一个完整而独立的C文件,我想从命令行调用这个文件,我想要在用pip安装的时候编译这个文件,所以它必须在当前主机上正确的编译。的确有方法用Python的扩展插件来做这个,但是这个文件实际上并不是一个扩展,让这一过程生效些许复杂了一点。
本质上,我从一个网站(这个网站的这篇文章已经不在了)得到了建议然后写了一个新的类,这个类通过使用subprocess来调用“make”,当我做好之后,就像下面这样:
1 | from os import path |
同时在setup()
里用一个关联的接口:
1 | cmdclass={'install': MyInstall}, |
有一些事情需要注意一下。首先,cmdclass={'install': MyInstall}
告诉setup()
来运行这段代码,不是很确定这是怎么实现的;其次,subprocess.call(['make'],cwd=path.join(here,'vfclust'))
有一些技巧:为了正确的运行make
,我不得不用subprocess
从它所在的目录运行。他在一个和setup.py
不一样的目录里,所以我想要指定子目录(叫vfclust
),here = path.abspath(path.dirname(__file__))
给出了setup.py
在系统中的位置,然后path.join(here,'vfclust')
创建子目录的绝对路径
完成 setup.py
最终,一个真的很长很长的setup.py
文件长得像这样,再一次使用这个的修改版本:
1 | from setuptools import setup, find_packages # Always prefer setuptools over distutils |
文档
有很多现存的复杂的工具帮助你撰写代码文档,但不幸的是,他们的复杂性意味着学习怎样使用他们会让没有经验的骚年们的头疼不已。这里有一些我的艰难学习过程的经验教训。
Readme
只有当你的README文件以reStructuredText
格式进行撰写时,你的程序包的PyPi页面才会正确的显示出你的README文件,而不是Markdown
格式。然而我会选择markdown格式来书写这个文件,这样它会在github上正确的显示出来,叫我懒人。幸运的是,有一个方便的Python包可以进行自动格式转换
1 | pip install pypandoc |
然后在终端中切换到你的README文件所在目录,运行python,这么做:
1 | import pypandoc |
如果你的文档是有效的markdown格式的话,你现在应该有一个reStructured版本的README文件存在于你的项目目录了。
Docstrings
为你python代码中的函数和方法写一个冗长的文档是很普遍的,这样别人就可以阅读并且帮助改善你的代码。这种文档叫做“docstrings”(注释),他们是在一个函数或者方法声明之后的一行或者多行字符串
1 | def my_function(): |
这对于阅读代码的人来说是很方便的。然而,如果你的注释是以reStructured格式的话,你可以用一个叫做“Sphinx”的工具来自动生成HTML/PDF/epub版本的文档,这些文档同时包含了README文件的内容和代码注释,而且格式优美,容易阅读
虽然可以包含测试代码和其他复杂的注释,但是简单的版本就像这样:
1 | def compute_similarity_score(self, word1, word2): |
这可能比需要的更加冗余了(PEP指南建议不应该在你的注释中这样做),但是我就像这样放在那里来表明代码的目的。还有一些值得注意的地方:
- 注释用的是三引号,直接跟在函数的声明后面
- 最后一行的三引号没有其他任何文本了
:param TYPE VARIABLE:
是reStructured的语法格式用来声明你的输入/输入变量,你可以读到更多,在这里
rst
文本可以变得相当复杂而且可以从很多方面影响你的代码。我只是停留在最基本而且必要的姿势来为我的小项目制作合理的文档
HTML 文档
迄今为止,我们已经有了README以及*.py
文件。由于我们用rst格式撰写README以及注释,我们现在可以创建一个”HTML/PDF“格式的文档了,使用一款叫做Sphinx的软件。我发现Sphinx非常之复杂,但是这里有一些基本的操作来让你起步(至少使用类unix系统,也就是说windows用户请绕行)
安装 Sphinx
1 | $ easy_install -U sphinx |
为文档创建一个目录
1 | $ mkdir docs |
运行 Sphinx
1 | $ sphinx-quickstart |
你已经在 docs/目录下,所以第一个选项就使用默认参数,在接下来的几个选项中填写自己的名字,项目名称等等,除了一项选项外,其他都使用了默认值
1 | autodoc: automatically insert docstrings from modules (y/n) [n]: y |
选择y
,让 Sphinx从你的注释中抓取信息,就像上面承诺的一样,然后让他变成一个小巧易读的HTML文档或者什么~~
Sphinx在这个docs/创建了一个Makefile
目录,这个目录用于生成你的文档,你可以输入make html
或者make latexpdf
来生成文档。如果你像我说的那样操作的话,你的文档会是空白的。看来你也是懒鬼!
让Sphinx从你的注释中生成文档
1 | $ sphinx-apidoc -o <dest-directory> <source-directory> |
你应该已经在你的docs/目录下了,我的docs/目录是和我包含注释的程序包的目录在同一级别,于是我这样操作了:
1 | $ sphinx-apidoc -o . ../vfclust |
最后,编辑这个新的 index.rst文件从
1 | Welcome to VFClust's documentation! |
变成
1 | Welcome to VFClust's documentation! |
你的项目名称会和我的不一样,但是重要的部分是.. include:: ../README
这一行,假设你的 README 文件位于项目根目录,这将会把你的 README 作为你的文档的主页,你可以点击”index“、”Moduel Index“或者”search“链接来查看文档里为你生成的函数和方法
生成文档,Finally!
使用
1 | $ make |
来查看可选项,创建HTML格式的文档使用:
1 | $ make html |
这将在”docs/_build/html/“目录中生成一套HTML文件,这里假设你没有改变”_build“这个默认值。打开”docs/_build/html/index.html“来查看你的新文档
PyPi
好吧。程序包已经有了正确的目录结构,文档已经格式化,我们也已经准备好和全世界分享他了,这样任何人都可以用pip install mypackage
来安装他了
测试
切换到你的setup.py
所在目录,首先,确认一切已经正确的配置了:
1 | $ python setup.py test |
希望你没有任何错误
然后,创建你准备发布到PyPi的发布版本:
1 | $ python setup.py sdist |
这将创建一个tar.gz
的源文件的打包,同时也会创建一个新的子目录dist/
,然后把你的这个tar.gz
文件放在其中。你需要检查这个打包文件的内容,因为这将是从Pypi能够下载下来的打包文件
在PyPi注册
你可以用命令行来完成这一步,你也可以到 https://pypi.python.org/pypi创建自己的账户。当你在其中时,在https://testpypi.python.org/pypi也创建一个账户,我们将用第二个网站进行测试,这是可选项,但是考虑到我在用pip时候遇到的问题,这是必要的,我个人也推荐这样
用你最喜欢的文本编辑器创建一个新的 ~/.pypirc文件(这个文件是由setup.py自动创建的),让他看起来更像下面这样:
1 | [distutils] |
你应该添加一个测试的软件仓库入口来进行测试。现在用PyPi注册你的程序包,确认你在自己项目的根目录,也就是和setup.py
同级的目录:
1 | $ python setup.py register |
这将发送你的程序包的元数据到PyPi
安装 twine
当你上传自己的程序包到PyPi时,你的登录信息是明文发送的,这样不太好,为了解决这个问题,安装twine
1 | $ pip install twine |
创建测试上传
一共分两步:一、用sdist
参数打包;二、上传这个文件到这个服务器
1 | $ python setup.py sdist #creates .tar.gz, puts it in dist/ folder |
-r test
参数将把这份打包的文件上传到之前的.pypirc
文件中的[test]
部分中指定的服务器
创建测试安装
Python有一种简洁的用来生成沙盒环境的方式,叫做虚拟环境(virtual environments)。我建议创建一个新的环境来测试你的安装,从而保证是一次全新安装
切换到其他某个目录下:
1 | $ virtualenv MY_VIRTUALENV |
很明显你可以用其他名字,而不是MY_VIRTUALENV,然后激活这个环境:
1 | $ cd MY_VIRTUALENV |
现在你已经在一个小型的Python沙盒安装环境中了,从PyPi测试服务器上安装你的程序包好了:
1 | $ pip install -i https://testpypi.python.org/pypi PACKAGENAME |
你可能需要使用sudo
,我不知道为什么。(译者:不应该使用sudo,如果出现权限问题,说明你在创建虚拟环境的时候使用了超级权限,因为install的时候写入了一个没有权限的虚拟环境目录)
无论如何,你的程序包应该已经被安装好了,测试一下捏~
1 | >>> import PACKAGENAME |
如果你像在setup.py
里面设置的那样引入的话,上面那句话应该不会报错
来真的了
如果一切看上去都能正常工作了的话(如果真的是这样的话,祝贺你,我花了两天才做到这一步……),你可以把他上传到PyPi的服务器了:
1 | $ twine upload dist/PACKAGENAME-VERSION.tar.gz |
喏~你成功了!现在你可以到https://pypi.python.org.pypi/搜索你的程序包了(译者:一般情况下,如果你刚上传的话,你的package会在首页就可以找到),你也应该可以用一下命令来安装他了:
1 | $ pip install PACKAGENAME |
看到了没~~并不是那么难不是么~~(想象作者笑的样子23333).
我希望这能帮到一些人(译者:对我来说的确帮到了不少)
以上就是所有翻译内容了,文章翻译花费了接近6小时,如果要不是中途在图书馆忘了带电源,又忘了关注电量,笔记本自动关机,导致回寝室的时间和开机后桌面崩溃问题困扰了我接近20分钟(好吧,好像实际上只有10分钟的样子),可能可以在12点之前完成的说23333。。。。不过还好的是,没有中途睡着(毕竟又不是乏味的文章),毕竟感觉又把整个过程认真的过了一遍,又有了新的收获吧~~~虽然我还是推荐骚年们去看原文好了~~虽然这篇文章也是我迄今为止的年度翻译大作2333
当然翻译这篇文章也算是心血来潮,现在markdown编辑器里面显示已经接近2w的字符了,好吧,原作者的代码一定占了很多233333,原文章发布于2014年6月26,现在完成个人翻译(至少我在网上没有搜到有谁翻译这篇文章,就算已经有了我也不觉的我有什么损失),翻译过程又重温了很多细节,曾经懒得翻词典(another bummer!),感觉请女王大人来帮我校对是不是有点残忍了。。。。(她可是一个文科僧啊!!)
好吧~某种程度上也是不想让自己的博客被别人看做求职的资本之一,我会的东西并没有全部写在博客上,我会的东西已经在某几台服务器上运行着了吧!
I will just do what I liked(我已经想好了这句话的日语翻译了!虽然我是不可能当日语翻译了233333)
本文作者 : hellflame
原文链接 : https://hellflame.github.io/2017/04/15/pypi-in-use/
版权声明 : 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!