Python 基础合集10:模块和包(下)-发布属于自己的第一个模块包

一、前言

本小节通过一个案例,梳理了模块和包的发布流程及相关细节,包括自建包的本地调用、包的配置文件、打包命令和测试安装、上传细节等。

环境说明:Python 3.6、windows11 64位

二、包的发布

2.1 包的结构及功能

这里我准备了一个包xindata_package_test(一个文件夹)做测试,包下面有2个文件夹,分别是test1test2;有3个.py文件,分别是__init__.pypgk_1.pypgk_2.py
test1下面也有三个.py文件,分别是__init__.pyfunction_1_1.pyfunction_1_2.py
test2下面也有三个.py文件,分别是__init__.pyfunction_2_1.pyfunction_2_2.py
image.png

__init__.py文件的作用是将文件夹变为一个Python模块,Python 中的每个包中,都有__init__.py文件。
__init__.py文件可以为空,也可以为它增加其他的功能。我们在导入一个包时,实际上是导入了它的__init__.py文件,所以可以在该文件中调用一些相关的模块和包,之后就不用再一个个导入,可以简单理解为初始化调用。

其他6个.py文件都是一些简单且相类似的功能:

  • 变量file_name,其值是对应文件的文件名;
  • 函数test_func(),打印对应的函数名;
  • Test_class,包含一个类属性、一个类方法、一个实例属性、一个实例方法和一个初始函数;
  • 类属性cls_attr,其值是对应类的属性名称;
  • 实例属性self_attr,其值是对应类实例的属性名称;
  • 类方法cls_func(),打印对应的类方法名;
  • 实例方法self_func(),打印对应的类实例方法名;
  • 初始化函数__init__(),初始化实例属性self_attr

相关代码具体如下:

# pkg_1.py
file_name = 'package_test.pkg_1'
def test_func():
    print('这是package_test.pkg_1.test_func')
class Test_class():
    cls_attr = 'package_test.pkg_1.Test_class.cls_attr'
    def __init__(self):
        self.self_attr = 'package_test.pkg_1.Test_class.self_attr'
    def self_func(self):
        print('这是package_test.pkg_1.Test_class.self_func')
    @classmethod
    def cls_func(cls):
        print('这是package_test.pkg_1.Test_class.cls_func')

# pkg_2.py
file_name = 'package_test.pkg_2'
def test_func():
    print('这是package_test.pkg_2.test_func')
class Test_class():
    cls_attr = 'package_test.pkg_2.Test_class.cls_attr'
    def __init__(self):
        self.self_attr = 'package_test.pkg_2.Test_class.self_attr'
    def self_func(self):
        print('这是package_test.pkg_2.Test_class.self_func')
    @classmethod
    def cls_func(cls):
        print('这是package_test.pkg_2.Test_class.cls_func')

# pt_test1_1.py
file_name = 'test1.pt_test1_1'
def test_func():
    print('这是test1.pt_test1_1.test_func')
class Test_class():
    cls_attr = 'test1.pt_test1_1.Test_class.cls_attr'
    def __init__(self):
        self.self_attr = 'test1.pt_test1_1.Test_class.self_attr'
    def self_func(self):
        print('这是test1.pt_test1_1.Test_class.self_func')
    @classmethod
    def cls_func(cls):
        print('这是test1.pt_test1_1.Test_class.cls_func')

# pt_test1_2.py
file_name = 'test1.pt_test1_2'
def test_func():
    print('这是test1.pt_test1_2.test_func')
class Test_class():
    cls_attr = 'test1.pt_test1_2.Test_class.cls_attr'
    def __init__(self):
        self.self_attr = 'test1.pt_test1_2.Test_class.self_attr'
    def self_func(self):
        print('这是test1.pt_test1_2.Test_class.self_func')
    @classmethod
    def cls_func(cls):
        print('这是test1.pt_test1_2.Test_class.cls_func')

# pt_test2_1.py
file_name = 'test2.pt_test2_1'
def test_func():
    print('这是test2.pt_test2_1.test_func')
class Test_class():
    cls_attr = 'test2.pt_test2_1.Test_class.cls_attr'
    def __init__(self):
        self.self_attr = 'test2.pt_test2_1.Test_class.self_attr'
    def self_func(self):
        print('这是test2.pt_test2_1.Test_class.self_func')
    @classmethod
    def cls_func(cls):
        print('这是test2.pt_test2_1.Test_class.cls_func')

# pt_test2_2.py
file_name = 'test2.pt_test2_2'
def test_func():
    print('这是test2.pt_test2_2.test_func')
class Test_class():
    cls_attr = 'test2.pt_test2_2.Test_class.cls_attr'
    def __init__(self):
        self.self_attr = 'test2.pt_test2_2.Test_class.self_attr'
    def self_func(self):
        print('这是test2.pt_test2_2.Test_class.self_func')
    @classmethod
    def cls_func(cls):
        print('这是test2.pt_test2_2.Test_class.cls_func')

注:如果觉得太过于复杂,可以对代码和文件进行删减,仅保留某个功能,如每个文件仅保留一个变量和函数,把类去掉,文件也可以把pkg_2.pypt_test1_2.py去掉。

2.2 包在本地调用测试

将以上文件放到Python的包路径(...\python\Lib\site-packages...\anaconda3\Lib\site-packages)中,便可以在本地调用该包了。

2.2.1 __init__.py为空

下面来测试一下,目前所有的__init__.py都是空。
导入包xindata_package_test之后,调用pkg_1.file_name报错:包xindata_package_test中没有pkg_1。也就是说,当xindata_package_test\__init__.py为空,没有调用pkg_1.py的时候,导入的包不能链式调用pkg_1,也就无法调用pkg_1.py中的元素。

>>> import xindata_package_test as pt
>>> pt.pkg_1.file_name
AttributeError: module 'xindata_package_test' has no attribute 'pkg_1'

虽然不能通过.进行链式调用,但是可以通过from…import…进行导入,如下:

>>> from xindata_package_test import pkg_1
>>> pkg_1.file_name
'package_test.pkg_1'
>>> pkg_1.test_func()
这是package_test.pkg_1.test_func
>>> pkg_1.Test_class.cls_attr
'package_test.pkg_1.Test_class.cls_attr'
>>> pkg_1.Test_class.cls_func()
这是package_test.pkg_1.Test_class.cls_func
>>> pkg_1.Test_class().self_attr
'package_test.pkg_1.Test_class.self_attr'
>>> pkg_1.Test_class().self_func()
这是package_test.pkg_1.Test_class.self_func

包下的__init__.py为空情况下,不能调用包下的.py文件,那如果是文件夹呢?用test1测试,也不行。报错类似,无法识别到test1。通过from…import…导入才可以。但是这有另外一种情况,如果仅仅是导入test1,由于test1\__init__.py为空,没有调用pt_test1_1.py,同样不能通过链式导入(如下代码第5行)。需要通过如下代码第6行进行导入使用。

>>> pt.test1.pt_test1_1.file_name
AttributeError: module 'xindata_package_test' has no attribute 'test1'
>>> from xindata_package_test import test1  # 导入test1
>>> test1.pt_test1_1.file_name      # 链式导入pt_test1_1
AttributeError: module 'xindata_package_test.test1' has no attribute 'pt_test1_1'
>>> from xindata_package_test.test1 import pt_test1_1
>>> pt_test1_1.file_name
'test1.pt_test1_1'

以上是导入整一个模块的形式调用,如果仅仅只需要导入某一个变量、方法或类,还可以单独指定导入,如:

>>> from xindata_package_test.test1.pt_test1_1 import file_name
>>> file_name
'test1.pt_test1_1'

2.2.2 __init__.py调用相关模块

为了使得调用包之后,可以通过链式调用后续的所有模块,就需要在 __init__.py模块中调用相关的模块。
xindata_package_test\__init__.py中加入以下代码并保存:

# xindata_package_test\__init__.py
from .pkg_1 import *      # 从同级目录的pkg_1模块中调用所有元素

重启Python测试环境,调用xindata_package_test包,然后调用pkg_1.file_name可以发现,返回结果正常。不过test1则依旧报错。

>>> import xindata_package_test as pt
>>> pt.pkg_1.file_name
'package_test.pkg_1'
>>> pt.test1
AttributeError: module 'xindata_package_test' has no attribute 'test1'

用同样的调用方法便可调用test1,即将xindata_package_test\__init__.py修改为以下代码并保存:

# xindata_package_test\__init__.py
from .pkg_1 import *   # 从同级目录的pkg_1模块中调用所有元素
from .test1 import *   # 从同级目录的test1模块(test1\__init__.py将test1文件夹变为一个Python模块)中调用所有元素

重启Python测试环境,调用xindata_package_test包,然后调用test1可以发现,返回结果正常。不过由于在test1\__init__.py没有调用pt_test1_1,调用pt_test1_1时依旧报错。
解决方法前面都涉及到了,可以通过from…import…调用,或在test1\__init__.py调用pt_test1_1from .pt_test1_1 import *

>>> import xindata_package_test as pt
>>> pt.test1
<module 'xindata_package_test.test1' from '...\\Lib\\site-packages\\xindata_package_test\\test1\\__init__.py'>
>>> pt.test1.pt_test1_1.file_name
AttributeError: module 'xindata_package_test.test1' has no attribute 'pt_test1_1'

【元素的调用顺序】
续上,在xindata_package_test\__init__.py文件对pkg_1test1进行调用之后。由于from .pkg_1 import *pkg_1.py模块的元素都初始化调用了,此时相当于xindata_package_test模块拥有了pkg_1.py模块的所有元素,所以可以通过xindata_package_test调用相关的元素(如下)。

>>> import xindata_package_test as pt
>>> pt.file_name
'package_test.pkg_1'
>>> pt.test_func()
这是package_test.pkg_1.test_func
>>> pt.Test_class.cls_attr
'package_test.pkg_1.Test_class.cls_attr'
>>> pt.Test_class.cls_func()
这是package_test.pkg_1.Test_class.cls_func
>>> pt.Test_class().self_attr
'package_test.pkg_1.Test_class.self_attr'
>>> pt.Test_class().self_func()
这是package_test.pkg_1.Test_class.self_func

如果在xindata_package_test\__init__.py文件重新赋值一个file_name会是什么样的呢?
修改xindata_package_test\__init__.py代码如下:

# xindata_package_test\__init__.py
from .pkg_1 import *   # 从同级目录的pkg_1模块中调用所有元素
from .test1 import *   # 从同级目录的test1模块(test1\__init__.py将test1文件夹变为一个Python模块)中调用所有元素
file_name = 'package_test'

修改完保存,重启Python测试环境,调用file_name,可以看到,返回的值是'xindata_package_test',也就是说,前面调用的pkg_1.file_name被后面定义的file_name重新赋值覆盖了。

>>> import xindata_package_test as pt
>>> pt.file_name
'package_test'

可能你会有这样的问题,导入test1模块不是也把所有的元素也导入了吗,为什么在xindata_package_test\__init__.py文件没有加file_name = 'package_test'时,没有覆盖到pkg_1.file_name
这是因为导入test1时,其__init__.py为空,没有导入任何内容,如果xindata_package_test\__init__.pytest1\__init__.py的内容如下,那么pt.file_name返回的结果便会是'test1.pt_test1_1'

# xindata_package_test\__init__.py
from .pkg_1 import *   # 从同级目录的pkg_1模块中调用所有元素
from .test1 import *   # 从同级目录的test1模块(test1\__init__.py将test1文件夹变为一个Python模块)中调用所有元素(即test1\__init__.py调用的元素)

# test1\__init__.py
from .pt_test1_1 import *

测试结果:
image.png
鉴于此,大家在开发的时候,需要注意命名,避免覆盖。

2.3 创建包的配置文件

主要创建3个文件LICENSE.txtREADME.mdsetup.py(或pyproject.toml)。
这3个文件和包在同一层级,所以需要再建一个文件夹把所有文件和文件夹移到其下,相关结构和功能如下:

xindata_package_test-0.0.1   # 新建文件夹,一般取包名加上版本号
├── LICENSE.txt    # 版权声明文件
├── README.md      # 包的介绍文件
├── setup.py       # 或pyproject.toml,为打包做准备的设置文件
├── tests          # 测试文件夹,一般用不到,可不加或置空
|   # 以下为上面介绍的结构
└── xindata_package_test
    |
    ├── test1
    │   ├── __init__.py
    │   ├── pt_test1_1.py
    │   └── pt_test1_2.py
    ├── test2
    │   ├── __init__.py
    │   ├── pt_test2_1.py
    │   └── pt_test2_2.py
    ├── __init__.py
    ├── pkg_1.py
    └── pkg_2.py

2.3.1 LICENSE.txt

发布的包都必须包含许可证文件(或称版权声明文件),告知使用者可以使用包的条款。更详细的许可证介绍可参阅网站:https://choosealicense.com/
一般可以选择MIT 许可证,具体内容如下:

Copyright (c) 2022 The Python Packaging Authority

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

注:2022是作品的创作/开发时间,根据需要修改。

2.3.2 README.md

README.md文件主要是用来说明发布的包的功能,包括用法和注意事项等,一般根据包的实际情况使用Markdown语法撰写。
以下是官方示例,可以通过示例中的链接查看Markdown语法。

# Example Package

This is a simple example package. You can use
[Github-flavored Markdown](https://guides.github.com/features/mastering-markdown/)
to write your content.

如,我为xindata_package_test写的readme.md

# English
This is a CSDN blogger Xin_data test project, mainly used to explain the release of custom module testing.

Note: There is nothing nutritious in the package.
# 中文
这是csdn博主Xin学数据的一个测试项目,主要是用于讲解发布自定义模块测试。
注:包中没有什么有营养的东西。

2.3.3 setup.py

关于包分发工具的介绍:

  • disutils是包分发工具的始祖,它是Python 的一个标准库;
  • setuptoolsdistutils的升级版,不包括在标准库中。

在工具的选择上,推荐首选setuptools

相关参考文档:https://github.com/pypa/sampleproject/blob/main/setup.py

2.3.3.1 setuptools.setup()参数介绍

通过setuptools创建setup.py时,会使用到setuptools.setup()函数,下面介绍该函数的一些参数配置。(加粗为必须参数)

参数参数说明
name包名称,在上传PyPI上列出的名字。为了避免重复,官方建议加上自己的用户名作为后缀。该名称不区分大小写,并将任意长的下划线、连字符和/或句点视为相等。如包名a-b,使用者可以通过a-ba_ba.ba__-.-__b等下载它或声明对他的依赖
version包的版本号。如:0.0.1
description简述包的功能
long_description详述包的功能,可以是readme.md文档内容
long_description_content_type详述包功能的内容的文本类型,如:'text/markdown'
url包的主页链接,可以填写发布到GitHub的链接,如Python官方的一个sampleproject实例首页链接:‘https://github.com/pypa/sampleproject’
author作者名字
author_email作者的邮箱
classifiers- Development Status:3 - Alpha(内部测试版,不对外发布,一般只有测试人员使用)、4 - Beta(公开测试版)、5 - Production/Stable(稳定版)
- Intended Audience:目标受众,如Developers
- Topic:领域,如Software Development :: Build Tools
- License:版权声明,如OSI Approved :: MIT License
- Programming Language:如Python :: 3、Python :: 3.6
- Operating System:你的操作系统,如:Microsoft、OS Independent等
keywords给包打标签,如sample, setuptools, development
project_urls包项目的相关链接
- Documentation:使用文档
- Funding:资助链接
- Say Thanks!:感谢链接
- Source:包的来源,一般在GitHub上
- Bug Reports:错误报告,一般是GitHub的issue链接
packages包目录,可以传入列表,列表的元素是包的名称,如本项目可传入[xindata_package_test];也可以使用setuptools.find_packages()指定,如果包的源码在根目录,则不用加参数(如本项目),如果包的源码在根目录的src文件夹下,可传入参数where="src"include=['src', 'src.*']
package_dir当你的源代码在项目根目录下的子目录中,例如。’ src/ ‘时,必须指定’ package_dir '参数。如:{"": "src"},键为空字符串表示是根目录。
py_modules如果包的主体内容(实现某功能的代码文件)比较简单,仅在根目录下的一两个.py,通过该参数指定模块。将模块名去掉后缀,然后以列表形式传入,如['a','b']
install_requires包的依赖项,通过pip下载包的时候会查看已有的python环境中是否有相关依赖项,如果没有,则会进行安装。相关模块和包可指定版本,以列表形式传入,如['A>=1,<2', 'B>=2', 'C']
python_requiresPython版本的要求,如'>=2.6, !=3.0.*, !=3.1.*, !=3.2.*'
package_data包中使用到的数据,包含文本、表等。传入形式:{“数据文件所在文件夹”:“数据文件名”},如{'sample': ['package_data.dat']}表示数据文件的路径为sample\\package_data.dat
data_files当数据文件不内置,而是放在包外时,可使用该参数,示例:[("my_data", ["data/data_file"])],含义是在Python安装目录(假设是D:\\python3)下生成my_data(即D:\\python\\my_data),安装时,将源码包中的data/data_file放到my_data
2.3.3.2 创建xindata_package_test包的setup.py

setup.py文件相对比较关键,一般在该文件内注明本次分发的包的版本号、名称、作者、联系方式、项目地址、python版本要求等信息。

文件内最基本的主信息如下:

# setup.py
import setuptools # 导入setuptools打包工具

with open("README.md", "r", encoding="utf-8") as f:
    long_description = f.read()

setuptools.setup(
     name="xindata_package_test"         # 必要参数
    ,version="0.0.1"                     # 必要参数,包版本号,便于维护版本
    ,packages=setuptools.find_packages() # 必要参数,源代码所在文件夹在项目根目录下
    ,author="Xin_data"                     # 作者,可以写自己的姓名
    ,author_email="jaxmin.lee@outlook.com" # 作者联系方式,可写自己的邮箱地址
    ,description="A test package"          # 包的简述
    ,long_description=long_description     # 包的详细介绍,一般在README.md文件内
    ,long_description_content_type="text/markdown"
    ,classifiers=[
         "Programming Language :: Python :: 3"
        ,"License :: OSI Approved :: MIT License"
    ]
    ,python_requires='>=3.6'               # Python版本要求
)

2.4 打包和测试安装

2.4.1 打包(附切换路径)

接下来要使用上面生成的setup.py文件对整个文件夹进行打包,在调用命令行或终端操作时,需要切换路径到setup.py所在的文件夹下,常用切换路径的方法:

  • 切换盘符,直接输入对应的盘符加英文冒号,回车即可;
  • 切换盘下的某个文件夹,则是cd [路径]

image.png
除了调用命令行或终端+切换路径,还有一个更便捷的方式(上一小节:模块和包(上)也有提到过),就是打开setup.py文件所在的文件夹,在地址栏输入%comspec%(如下图),回车即可。
image.png

调出命令行或终端并切换到setup.py所在文件夹之后,就可以输入以下命令进行打包。
sdist指的是source dist,即源码发布,将setup.py一起打包,一般是以压缩包的形式存在,安装时解压,然后通过setup.py安装;bdist指的是built (binary) dist,即编译(为二进制)后发布,不含setup.py,一般是生成.whl.egg,安装时通过pip install [模块名]pip install [.whl或.egg文件]
Wheel 的出现是为了替代 Egg,它的本质是一个zip包,现在被认为是 Python 的二进制包的标准格式,即wheel 是新的 Python 的 disribution,用于替代 Python 传统的 egg 文件。本次采用.whl文件示例。

python setup.py sdist            # 生成.tar.gz压缩包和.egg-info文件夹
python setup.py bdist_wheel      # 生成.whl文件和.egg-info文件夹,没有build文件夹时会生成
python setup.py bdist_egg        # 生成.egg文件和.egg-info文件夹,没有build文件夹时会生成
# 可以两个命令写到一行执行
python setup.py sdist bdist_wheel

有一些教程可能会出现python setup.py build这个步骤,该步骤是构建包/模块,在包同层级创建build文件夹。在使用python setup.py bdist命令时也会完成相关的功能,构建包并生成build文件夹,故可省略。

运行python setup.py sdist bdist_wheel结束后(没有bug情况下),在与setup.py文件同一层级,生成三个文件夹,其中用于上传的两个分发包都在dist文件夹中,dist中的两个文件名分别是xindata_package_test-0.0.1.tar.gzxindata_package_test-0.0.1-py3-none-any.whl
image.png
image.png

至此,可以进入下一步进行包发布了。

2.4.2 测试.whl文件安装

下面针对这两个分布包的测试,先将安装目录的Lib\site-packages下的xindata_package_test删除。(前面3.2 本地使用测试时,把xindata_package_test放到了安装目录的Lib\site-packages下)
然后执行以下命令,安装xindata_package_test模块(注意切换路径,保证命令能找到.whl文件)

pip install xindata_package_test-0.0.1-py3-none-any.whl

执行结果没问题(下图1),在安装目录的Lib\site-packages下,也可以看到有两个文件夹(下图2),测试环境下,调用也正常(下图3)。
image.png
image.png
image.png

2.4.3 测试.tar.gz文件安装

同样,将刚刚已经安装的xindata_package_test模块卸载,命令:pip uninstall xindata_package_test,中间输入y确认卸载。
卸载完,将xindata_package_test-0.0.1.tar.gz文件解压到安装目录的Lib\site-packages下(或者解压完拷贝过去),然后切换路径到……\Lib\site-packages\xindata_package_test-0.0.1或在该路径下的地址栏输入%comspec%,回车。
然后执行安装命令:

python setup.py install

执行结果无异常情况下,在安装目录的Lib\site-packages下,生成一个新的文件xindata_package_test-0.0.1-py3.9.egg,同时在easy-install.pth中新增了一行调用的代码:

./xindata_package_test-0.0.1-py3.9.egg

补充说明:

  • .egg格式包含项目代码和资源的目录或zip文件,以及包含项目元数据的egg-info.egg格式的项目是在单个目录或文件中自包含的,不与任何其他项目的代码或资源混合在一起,非常适合分发和轻松地卸载或升级代码。
  • easy-install.pth的功能是使得setuptoolsEasy_Install工具利用easy-install.pth文件的“导入”特性来操作sys.path,确保easy-install.pth文件中的任何内容可以正常调用,并且总是出现在标准库和本地site-packages目录之前。关于这点,可以通过调用sys.path查看,代码如下:
import sys
print(sys.path)

2.5 上传至PyPi

上传包之前,需要先注册账户、添加API Tokens,然后通过相关的账户密码登录发布。

2.5.1 注册Pypi

Python官方提供了一个正式环境和一个测试环境的Pypi,二者是不相通的,需要分别注册。测试环境可用于熟悉上传步骤,当然,上传之后的包都可以下载安装使用。
正式环境和测试环境有一点小差别,测试环境的网址多了一个test.,其他的基本一致(包括网页等)。

正式环境注册链接:https://pypi.org/account/register/
测试环境注册连接:https://test.pypi.org/account/register/
正式环境生成token:https://pypi.org/manage/account/#api-tokens
测试环境生成token:https://test.pypi.org/manage/account/#api-tokens
官方教程1:https://packaging.python.org/en/latest/tutorials/packaging-projects/
官方教程2:https://packaging.python.org/en/latest/guides/using-testpypi/

注册成功后,需要到个人邮箱点击相关链接验证,完成绑定。完成后,在首页账户设置找到API tokens(或点击上面的生成token链接直达),点击蓝色Add API token按钮准备新增一个token。
注:上传包时,需要输入账户和密码,可以通过token传输,也可以直接使用刚刚注册使用的账户和密码。如果想直接使用注册时的账户和密码,也可以不用生成token。不过推荐使用token,安全性更高。
image.png

跳转到以下界面(下图1),填写相关的信息,点击蓝色Add Token按钮会跳转到另外一个界面(下图2),上面记录生成的token,和使用token登录的账户密码的值,该token仅在生成后该界面可见,之后找不回,注意自己保存好。

Token name: 自己随便取个名字
Permissions: Upload packages
Scope: Entire account (all projects)

image.png
image.png

2.5.2 上传包

如果没有twine模块,需要先安装。该包用于上传包。

pip install twine

注:安装时的一点发现,一开始使用pip install --user twine进行安装,然后在安装目录下……\Scripts下生成了一个文件夹,最终把twine.exe存放在……\Scripts\Python39\Scripts目录下,卸载之后,使用pip install twine进行安装,twine.exe才正常存放在……\Scripts目录下。
在上一节《模块与包(上)》讲到的Lib\site.py设置,在USER_SITEUSER_BASE默认的情况下,安装命令加上--user会安装到C:\Users\[你的用户名]\AppData\Roaming\Python\Python xx\目录下,大概是这个原因吧。--user本来就是为了解决无权限写入C:\Users\[你的用户名]\AppData的,所以在设置了Lib\site.py到安装目录之后,安装时可以不用指定--user。(当然,在Mac系统可能有不同,如果有什么不同,欢迎留言评论。)

切换路径到包下(2.4打包时有涉及2种切换方法),输入以下命令,回车,再依次输入你的账户和密码(testpypi和pypi的token值是不同的,注意分清),便会出现一个上传的进度条,等待上传完即可。

# 上传到testpypi
twine upload --repository testpypi dist/*
# 上传到pypi
twine upload dist/*

image.png
image.png
输入密码的时候,会有一个bug:Windows系统使用Ctrl+V不起作用,由于密码没有明文展示,所以看不出来,但是会返回报错:Invalid or non-existent authentication information.(无效或不存在身份验证信息。)这是Python的getpass模块的出现了bug。但token太长,几乎不可能逐个敲!
为了解决这个问题,官方提供了一个解决方案,在命令提示符或PowerShell的属性中,开启使用Ctrl+Shift+C/V作为复制/粘贴的按钮。设置完重启命令提示符或PowerShell便可使用Ctrl+Shift+V粘贴token。具体操作如下:
特别需要注意一点:用管理员身份运行命令提示符或PowerShell,才可使用Ctrl+Shift+V粘贴!!!
参考:https://test.pypi.org/help/#invalid-auth
image.png
PS:由于testpypi和pypi网站是国外的网址,连接会比较慢,上传的时候可能会连接超时,多试几次吧。如果翻墙会引发一个网络协议报错,目前为深入研究如何解决,知道的小伙伴可以评论留言,一起学习!

2.5.3 安装测试

打开命令提示符或终端,输入以下命令,便可安装成功。

pip install xindata_package_test

image.png

上面默认从正式环境下载安装包,也可以指定从testpypi下载,命令如下,二者仅是源不同,其他功能不受影响。

pip install xindata_package_test -i https://test.pypi.org/simple/ 

从正式和测试环境下载安装的包均可正常调用,具体可自行测试,效果如2.4.1,这里不再赘述。

三、单模块发布

如果是单一模块,发布时会更简单,把相关模块和3个配置文件放在同一个目录下,然后将目录发布即可。拿上面例子来修改做测试,将其他文件全部去掉,仅保留pkg_1.py,由于和pypi上的包重名,所以名称改为xindata_demo_pkg.py,结构如下:

# 包结构
xindata_demo_pkg-0.0.1   # 新建文件夹,一般取包名加上版本号
├── LICENSE.txt          # 版权声明文件
├── README.md            # 包的介绍文件
├── setup.py             # 或pyproject.toml,为打包做准备的设置文件
└── xindata_demo_pkg.py  # 单个模块

LICENSE.txtREADME.md同上述2.3的两个文件一致。xindata_demo_pkg.py的内容和pkg_1.py一致,如下:

# xindata_demo_pkg.py,同pkg_1.py
file_name = 'package_test.pkg_1'
def test_func():
    print('这是package_test.pkg_1.test_func')
class Test_class():
    cls_attr = 'package_test.pkg_1.Test_class.cls_attr'
    def __init__(self):
        self.self_attr = 'package_test.pkg_1.Test_class.self_attr'
    def self_func(self):
        print('这是package_test.pkg_1.Test_class.self_func')
    @classmethod
    def cls_func(cls):
        print('这是package_test.pkg_1.Test_class.cls_func')

setup.py文件需要做一些改动,加上py_modules参数指定呀打包的内容,以下代码第11行。

# setup.py
import setuptools # 导入setuptools打包工具

with open("README.md", "r", encoding="utf-8") as f:
    long_description = f.read()

setuptools.setup(
     name="xindata_demo_pkg"            # 必要参数
    ,version="0.0.1"                    # 必要参数,包版本号,便于维护版本
    ,packages=setuptools.find_packages()# 必要参数,源代码所在文件夹在项目根目录下
    ,py_modules=["xindata_demo_pkg"]    # 指定需打包的.py文件名
    ,author="Xin_data"                     # 作者,可以写自己的姓名
    ,author_email="jaxmin.lee@outlook.com" # 作者联系方式,可写自己的邮箱地址
    ,description="A test package"          # 包的简述
    ,long_description=long_description     # 包的详细介绍,一般在README.md文件内
    ,long_description_content_type="text/markdown"
    ,classifiers=[
         "Programming Language :: Python :: 3"
        ,"License :: OSI Approved :: MIT License"
    ]
    ,python_requires='>=3.6'               # Python版本要求
)

打包和上传的命令同步骤同2.4和2.5。

# 打包命令
python setup.py sdist bdist_wheel

# 上传命令
## 上传到testpypi
twine upload --repository testpypi dist/*
## 上传到pypi
twine upload dist/*

注意:需要切换到setup.py路径下执行以上命令,切换路径方法可参考2.4内容。

安装之后会生成一个文件和一个文件夹,xindata_demo_pkg.py文件可以直接调用。
image.png
image.png

四、总结

整个模块和包(包含上下两篇)的内容思维导图:
模块和包.png

其他分享:在一开始测试的时候我使用的名字是package_test,但是到了最后一步上传的时候,会报错:Invalid or non-existent authentication information.,这是因为Pypi已经有一个包名字叫做package_test,所以后面改为xindata_package_test。如果要发布某个包,建议先在pypi官网搜索包名,确认不重复之后再继续,以免开发完了,才发现名字重复了,还要返回修改一些配置等。

相关资源:传送门
资源包含打包前的xindata_package_testxindata_demo_pkg,以及打包后的xindata_package_testxindata_demo_pkg




<下节预告:文件读写>


- End -

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xin学数据

为你点亮一盏灯,愿你前进无阻。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值