Python第三方库制作

做网格模型操作的python第三方库,还在补充完善。

文件结构

|--smart
|  |--static
|  |  |--icon.svg
|  |  |--confg.json
|  |--engine
|  |  |--__init__.py
|  |  |--core.py
|  |--__init__.py
|  |--__version__.py
|  |--api.py
|  |--utils.py
|--tests
|  |--__init__.py
|--LICENSE
|--README.rst
|--setup.py 

smart: 作为项目核心代码模块,提供所有的对外接口和实现。其内部可以包含子模块和静态文件
tests: 包含所有的测试用例
LICENSE: 编写相关版权信息
README: 提供项目基本描述和相关使用方法介绍等
setup.py: 项目的安装配置文件


setup.py 配置

setup.py 主要使用setuptools的setup模块,提供打包所需要的基本信息。python依赖此脚本中的配置信息。将相关模块、静态文件,打包成一个完整的模块安装到site-packages文件。

基本示例:

# 需要将那些包导入
packages = ["smart", "smart.engine"]

# 导入静态文件
file_data = [
    ("smart/static", ["smart/static/icon.svg", "smart/static/config.json"]),
]

# 第三方依赖
requires = [
    "pandas>=0.23.4"
]

# 自动读取version信息
about = {}
with open(os.path.join(here, 'smart', '__version__.py'), 'r', 'utf-8') as f:
    exec(f.read(), about)

# 自动读取readme
with open('README.rst', 'r', 'utf-8') as f:
    readme = f.read()

setup(
    name=about["__title__"],  # 包名称
    version=about["__version__"],  # 包版本
    description=about["__description__"],  # 包详细描述
    long_description=readme,   # 长描述,通常是readme,打包到PiPy需要
    author=about["__author__"],  # 作者名称
    author_email=about["__author_email__"],  # 作者邮箱
    url=about["__url__"],   # 项目官网
    packages=packages,    # 项目需要的包
    data_files=file_data,   # 打包时需要打包的数据文件,如图片,配置文件等
    include_package_data=True,  # 是否需要导入静态数据文件
    python_requires=">=3.0, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3*",  # Python版本依赖
    install_requires=requires,  # 第三方库依赖
    zip_safe=False,  # 此项需要,否则卸载时报windows error
    classifiers=[    # 程序的所属分类列表
        'Development Status :: 5 - Production/Stable',
        'Intended Audience :: Developers',
        'Natural Language :: English',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: Implementation :: CPython',
        'Programming Language :: Python :: Implementation :: PyPy'
    ],
)

安装

  • 打包之前,可以先验证setup.py的正确性

    python setup.py check
    

    如果没有任何错误或者警告,则说明您的setup.py是没问题的
    如果没有问题,就可以使用下方的命令正式打包

    python setup.py sdist
    

    打包完成后,会在顶层目录下生产dist和egg两个目录

  • 如果您的代码包仅供内部使用,又不想直接发送源码,则可以将代码打包成whl文件
    打包成whl命令

    # --wheel-dir: 为打包存储的路径 
    # 空格后为需要打包的工程路径
    pip wheel --wheel-dir=D:\\work\\base_package\\dist D:\\work\\base_package
    

    打包完成后就可以看到smart-0.0.1-py3-none-any.whl文件了,将此文件分享后后,对方就可以使用

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

    来安装当前库。

上传

如果您希望将代码包放到网络上,供所有人开放下载,可以将代码打包上传至PyPi。
上传代码之前,需要到官网注册pypi账户。https://pypi.org/

  • 直接上传

    使用register命令是最简单的上传方式,但是使用HTTP并未加密,有可能会泄露密码。

  # 注册包
  python setup.py register
  # 上传包
  python setup.py sdist upload
  • 使用twine上传
  1. 安装twine
pip install twine

​ 2.使用twine注册并上传代码

# 注册包
twine register dist/smart.whl
# 上传包
twine upload dist/*

编写模块

1. 模块是程序

模块的本质是一个扩展名为.py的python程序。在使用时直接将函数引用过来,节省精力,不需要写雷同的代码。

但想把自己的.py文件作为模块import过来,必须先让python解释器能够找到你写的模块。比如,在某一目录下,写了这样一个文件:

# coding=utf-8
 
display = "hello world! "

并把它命名为hw.py|,那么这个文件可以作为一个模块被引入,但得首先告诉python解释器我写了一个这样的文件。

>>> import sys

>>> sys.path.append(""C:\\Users\\wenxc\\Desktop\\hw.py"")

然后把文件作为模块引入

>>> import hw

>>> hw.display

'helloworld!'

这时可以在源路径下看到python模块文件和 _ pycache _ 文件夹。

在 Python 中,解释器将.py 的文件转化为.pyc 文件,而.pyc 文件是由字节码(bytecode)构成的,然后这个.pyc 文件交给一个叫作 Python 虚拟机的东西去运行(那些号称编译型的语言也是这个流程,不同的是它们先有一个明显的编译过程,编译好了之后再运行)。如果.py 文件修改了,Python 解释器会重新编译,只是这个编译过程不全显示给你看。

有了.pyc 文件后,每次运行就不需要重新让解释器来编译.py 文件了,除非.py 文件修改了。这样,Python 运行的就是那个编译好了的.pyc 文件。

平时在写有关程序然后执行时常常要用到 if __ name __ == "__ main __",那时我们直接用"python filename.py"的格式来运行该文件,此时我们也有了同样的,py文件,不过是作为模块引入的。接下来探究一下,同样是.py文件,它怎么知道是被当作程序执行还是被当作模块引入?

先将hw.py文件改造:

# coding=utf-8

def display():

    return "hello world !"

if __name__ == "__main__":

    print(display())

沿用之前的做法:

$ python pm.py

hello world !

如果将这个程序作为模块导入,会是这样:

>>> import sys

>>> sys.path.append("C:\\Users\\wenxc\\Desktop\\hw.py")

>>> import hw

>>> hw.display()

'hello world !'

查看模块的属性和方法可以用dir()

>>> dir(hw)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'display']

同样的一个.py文件,可以把它当作程序来执行,也可以把它当作模块引入

>>> __name__
'__main__'
>>> hw.__name__
'hw'

如果要作为程序执行则 __ name__ == " __ main __ ";如果作为模块引入,则hw. __ name __ == "hw",即变量 __ name __ 的值是模块名称。这是一种简单的区分执行程序还是模块引入的方法。在一般情况下,如果仅仅是用作模块引入,不必写 if __ name __ == "__ main __"。

2.模块的位置

在前面的过程中,为了让自己写的模块能够被python解释器知道,需要用sys.path.append(),

>>> import pprint
>>> pprint.pprint(sys.path)
['',
 'C:\\Users\\wenxc\\python37.zip',
 'C:\\Users\\wenxc\\DLLs',
 'C:\\Users\\wenxc\\lib',
 'C:\\Users\\wenxc',
 'C:\\Users\\wenxc\\AppData\\Roaming\\Python\\Python37\\site-packages',
 'C:\\Users\\wenxc\\lib\\site-packages',
 'C:\\Users\\wenxc\\Desktop\\hw.py']

将模块化的文件放到指定位置是一种不错的方法,当然也能用 sys.path.append() ,不管把文件放在哪里,都可以把其位置告诉 Python 解释器。虽然这种方法在前面用了,但其实是很不常用的,因为它也有麻烦的地方,比如在交互模式下,如果关闭了,再开启,还得重新告知。比较常用的方法是设置 PYTHONPATH 环境变量。

环境变量设置,建立一个目录,然后将自己的》py文件放到这里,并设置环境变量。

3. __ all __在模块中的作用

前面的内容虽然都比较简单,但已经显示了编写模块以及在程序中导入模块的基本方式。在实践中,所编写的模块要更复杂,涉及的变量类型也更多。

# coding:utf-8


public_variable = "Hello, I am a public variable."

_private_variable = "Hi, I am a private variable."


def public_teacher():

    print("I am a public teacher, I am from JP.")


def _private_teacher():

    print("I am a private teacher, I am from CN.")

接下来重复之前的操作

>>> import sys

>>> sys.path.append("C:\\Users\\wenxc\\Desktop\\mz.py")

>>> import pp

>>> from mz import *

>>> public_variable()

'Hello, I am a public variable.'

>>> _private_variable()

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

NameError: name '_private_variable' is not defined

变量 public_variable 能够被使用,但是另外一个变量 _private_variable 不能被调用,先观察一下两者的区别,后者是以单下画线开头的,这样的是私有变量。而 from pp import * 的含义是“希望能访问模块(mz)中有权限访问的全部名称”,那些被视为私有的变量或者函数或者类,则没有权限被访问。

再如

>>> public_teacher()

  I am a public teacher, I am from JP.

>>> _private_teacher()

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

NameError: name '_private_teacher' is not defined

但这也不是绝对的,我们可以通过一定会的方式来访问带有私有性质的东西

>>> import mz

>>> mz._private_teacher()

I am a private teacher, I am from CN.

>>> mz._private_variable

'Hi, I am a private variable.'

然后在对mz.py进行修改:

# coding:utf-8


__all__ = ['_private_variable', 'public_teacher']


public_variable = "Hello, I am a public variable."

_private_variable = "Hi, I am a private variable."


def public_teacher():

    print(public_variable)


def _private_teacher():

        print(_private_variable)

在修改之后的 pp.py 中,增加了 __ all __ 变量以及相应的值,在列表中包含了一个私有变量的名字和一个函数的名字。这是在告诉引用本模块的解释器,这两个东西是有权限被访问的,而且只有这两个东西。

>>> import sys

>>> sys.path.append("~/Documents/StarterLearningPython/2code/pp.py")

>>> from pp import *

>>> public_variable
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'public_variable' is not defined

>>> _private_variable
'Hi, I am a private variable.'

>>> public_teacher()
Hello, I am a public variable.

>>> _private_teacher()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '_private_teacher' is not defined

可以看到不在__ all __ 中的变量都不再能被访问,而如果以 import mz 引入模块,再用mz. _ private_teacher的方式引用对私有变量也有效,这也是两种引用方式的区别。

4. 包和库

顾名思义,包和库都是比“模块”大的。一般来讲,一个“包”里面会有多个模块,当然,“库”是一个更大的概念了,比如 Python 标准库中的每个库都有好多个包,每个包都有若干个模块。

一个包由多个模块组成,即有多个.py 的文件,那么这个所谓的“包”就是我们熟悉的一个目录罢了。现在需要解决如何引用某个目录中的模块问题。解决方法就是在该目录中放一个 __ init __ .py 文件。__ init __.py 是一个空文件,将它放在某个目录中,就可以将该目录中的其他.py 文件作为模块被引用。

例如,建立一个目录,名曰:package_w,里面依次放了 hw.py 和 mz.py 两个文件,然后建立一个空文件 __ init __.py

接下来,需要导入这个包(package_w)中的模块,这里利用环境变量里已有的文件夹:C:\Users\wenxc\lib\site-packages

下面这种方法很清晰明了。

>>> import package_w.hw

>>> package_w.hw.display()

'hello world !'