之前上传到pypi作为库的只是一个单独.py文件,而且py文件中无须引入任何多余的第三方依赖,即使在项目文件夹下没有源文件package也无所谓,只要在setup.py文件中使用py_modules说明要添加的py文件即可。
这一次制作的是包含资资源文件的项目,项目结构如下:
kitchen是项目文件夹,cookgame是源文件夹,cookgame下面的sound是声音资源文件夹,包括一个mp3和一个wav声音文件,textures是图片资源文件夹,包括若干png和jpg的图片。game.py需要使用sound和textures中的资源!所以这一次打包上传的时候,必须是保持目录结构的基础上带着资源文件一起上传才行!
带着资源文件一起上传,需要在setup.py中说明上传的文件夹,还要新增一个MANIFEST.in文件,说明具体的上传文件。
setup.py
import setuptools
with open("README.md", "r") as fh:
long_description = fh.read()
setuptools.setup(name="cookgame",
version="0.2.0",
author="piglite",
author_email="piglite@vip.sina.com",
description="kcode tutoring package",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/piglite",
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>3',
packages=setuptools.find_namespace_packages(
include=["cookgame", "cookgame.*"], ),
install_requires=['arcade'],
include_package_data=True)
新增的内容主要在最后三项:
1. packages是要打包的源文件夹。因为我项目中cookname文件夹下有__init__.py,所以可以使用setuptoolsd find_namespace_package帮我自动找到。第一行name属性的值,是上传到pypi后在pypi进行搜索并使用pip install进行安装时的库名字,源文件夹的名字是下载库后导入时的名字(也就是package名字):
pip install -U cookgame #这个是setupname属性对应的值
import cookgame #这个是setup时项目源文件夹的名字,也就是package名
find_namespace_packages函数里面有include和exclude两个参数,用来额外说明在package下面有那些文件夹是需要的,那些是不需要的。例子中cookgame和cookgame下的所有内容我都要!
2. install_requires,我的game.py需要依赖第三库arcade。在安装我的cookgame时,如果安装者的机器上有arcade就自动忽略,否则就自动下载。
3. include_package_data,需要包含packages下面的资源文件!
接下来就是要新增MANIFEST.in文件,说明具体添加哪些资源文件
MANIFEST.in文件内容:
recursive-include cookgame/sound *.mp3 *.wav *.ogg
recursive-include cookgame/textures *.png *.jpg *.gif
一目了然。虽然目前我的sound下没有ogg文件,textures下没有gif文件,但是加上也不会报错。
剩下的就与之前的操作都一样了:
python -m setup check #先检测下setup本身是否有语法侧面错误
python -m setup build #编译文件
python -m setup sdist #打包
python - m setup sdist upload -r pypi #将打包的内容上传到pypi(用twine也可以的)
这里,在执行完build之后,可以检查一下生成的build文件夹下是不是包含了所有cookgame源文件下的内容:
内容一致才是build成功,否则去检查setup.py或者MANIFEST.in,看看哪里缺项少项了。
执行完sdist之后,也可以检查一下egg.info文件夹下的文件内容:
需要检查文件的内容:
requires.txt 中是否有需要依赖的第三方库名称
SOURCES.txt是否按目录结构包含齐全了所有的py文件和资源文件(mp3,ogg,png,jpg等)
top_level.txt中的名字是库安装到本地后的文件夹的名字,这个文件夹名字也是库的名字,就是setup.py name属性的值
当然,只要build是正确的,sdist也应该是正确的。
最后,再说一些代码设计时 东西:
1. 模块中引用各种资源的时候要注意使用绝对路径,但不是自己电脑的绝对路径
要知道,库是给别人用的。那么所有的相对路径默认都是相对别人新建项目时的那个文件夹路径!
使用绝对路径要使用 os.path.abspath(__file__),每个使用你库的人的Python环境都不一样, os.path.abspath(__file__)得到的game.py真正的安装路径。再以次路径为基础,找到库安装位置的sound文件和textures文件夹:
示例代码:
path = os.path.abspath(__file__) #game.py的安装路径
folder = os.path.dirname(path) #game.py所在的文件夹路径
soundfolder = os.path.join(folder,'sound') #sound文件夹路径
texturefolder = os.path.join(folder,'textrues') #textures文件夹路径
#加载chip.png
texture_chip = arcade.load_texture(os.path.join(texturefolder,'chip.png'))
#加载happy.mp3
music = arcade.Music(os.path.join(soundfolder,'happy.mp3'))
2. 使用__init__.py简化导入过程
在game.py上有一个类叫Kitchen,要创建Kitchen对象正常的写法是:
from cookgame import game
k = game.Kitchen() #从cookgame包中导入game模块,使用game模块下的Kitchen类创建对象
因为__init__.py的特殊性,可以在__init__.py中加上一句导入指令:
from .game import Kitchen #也可以写成*
这样,在要再创建Kitchen对象时,只需要:
import cookgame
k = cookgame.Kitchen()
因为导入cookname package的时候,会自动优先触发__init__.py,在__init__.py中,把当前package下的game模块中的Kitchen导入,这样再使用Kitchen时,只需要cookname这个namespace就够了。