引言

简单的说,一个python文件就是一个模块,本文主要介绍以下3点:

  • 模块的建立及导入

  • 包的建立及导入

  • 发布和安装自定义模块

模块的建立及导入

我们在写c,或者c++时候,为了复用代码,总是将一系列相关的函数写在一个.c文件中,或者封装一个类写在一个.cpp文件中,方便其他程序调用。

python这里的模块起到同样的作用,我们可以把实现相同任务的一些类和函数写在一个.py文件中,称之为一个模块,方便其它程序调用。

新建一个模块

新建一个模块,也就是新建一个.py文件。

我们新建一个模块utils.py如下:

# utils.py test1和test2是我希望被别人重复利用的代码
def test1():
    print('-----test1-----')

def test2():
    print('-----test2-----')

导入一个模块

导入一个模块常用的几种方式如下:

# 第一种
import utils 
utils.test1()


# 第二种
from utils import test1
test1()

# 第三种
from utils import test1, test2
test1()
test2()

# 第四种
import utils as u
u.test1()

# 第五种,这种方式建议少用
from utils import *
test1()
test2()

上述方式都可以完成模块的导入,但是最后一种方式不建议使用。因为如果当两个模块中有同名的函数时,后面导入的文件会覆盖掉前面的文件,这是就很难分清到底使用的是哪个模块中的方法了。

举个实际中的例子:

import os 
import sys 
import numpy as np 
from skimage.io import imread, imsave

无论是新建一个模块和导入一个模块看起来都不难,接下来介绍一些需要注意的事情。

模块中的__name__

当一个程序导入另一个模块时,实际上会把这个模块从头到尾执行一遍。

这样就带来个一个问题:一般情况下,我在模块中为了测试每个函数的功能是否正常,需要添加一些测试的程序,如果不把这些程序去除,在被其它程序导入时就会执行了这些程序,显然这是我们不希望看到的。

__name__的使用在不删除模块内测试代码的前提下解决了上述问题。我们先来做个测试,在utils.py模块中打印一个变量__name__如下:

# utils.py
def test1():
    print('-----test1-----')

def test2():
    print('-----test2-----')

print(__name__)
# main.py
import utils # 注意,这里我除了导入模块之外其他什么也没做
             # 由于导入模块时会把整个模块文件执行一遍,因此会直接调用print(__name__)这句话。

当我执行如下两条命令时,请注意__name__的值是如何变化的:

>>> python utils.py
>>> __main__
>>>
>>> python main.py
>>> utils

通过上述执行过程我们可以看出,当执行utils.py文件时,__name__的值为'_main__',当执行main.py时,utils.py里面__name__的值为'utils'。因此我们只需要在utils.py的测试代码中加上如下代码即可:

# utils.py
def test1():
    print('-----test1-----')

def test2():
    print('-----test2-----')

if __name__ == '__main__':
    test1()
    print(__name__)

上述判断语句添加完之后,当再有其他程序导入utils模块时,就不会执行utils里面测试代码的部分了。当直接执行utils.py文件时,才会执行测试代码的部分。

我们总结一下,一般来说,一个标准一点的python程序具有如下的格式:

import  xxxx

class ClassName(object):
    def __init__():
        pass 

    def func1():
        pass 
    def 

def func():
    pass 

def func2():
    pass 

def main():
    pass 

if __name__ == '__main__':
    main() #注意这里的main()和c语言中的不一样,只是借用了C语言中的语境,表达一个程序的开始而已。

模块中的__all__

再回顾一下我们的utils.py模块

# utils.py test1和test2是我希望被别人重复利用的代码
def test1():
    print('-----test1-----')

def test2():
    print('-----test2-----')

上述模块在使用from utils import *的方式被导入时候,所有的函数都会被导入,如果我不希望部分函数被外部使用,而只是公开我想要给大家使用的代码,可以使用__all__变量来完成,其使用方式如下:

# utils.py
__all__ = ['TestClass', 'test1'] # 这里把函数,类的名字当成一个元素放在列表里。
                                 # 当其他函数使用from utils import *时,只能访问到`__all__`变量里面的内容
                                 # 这里test2函数就不能被其他模块使用了
class TestClass(object):
    pass 

def test1():
    print('-----test1-----')

def test2():
    print('-----test2-----')

if __name__ == '__main__':
    pass 

此时当main.py再次调用utils模块时候,就不能访问test2函数了。测试如下:

In [1]: from utils import *

In [2]: test1()
-----test1--------

In [3]: test2()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-35ebc1c532fc> in <module>()
----> 1 test2()

NameError: name 'test2' is not defined

In [4]: TestClass()
Out[4]: <utils.TestClass at 0x7fcf5055ae90>

一个需要注意的地方是:__all__变量只用来约束from xxx import *的形式,而其他形式还是可以正常调用的。

如:

In [10]: from utils import test2

In [11]: test2()
------test2--------

包的建立及导入

如果想快速了解使用方法,请直接看总结部分

包的建立

为了将具有相关联功能的模块放在一起管理,将这些模块放在同一个文件夹下。 比如有sengMsg.pyrevMsg.py两个模块,由于他们都是和消息相关的模块,因此我用msg文件夹来把它们放在一起。如下:

msg/
├── revMsg.py
└── sendMsg.py

此时我们还不能直接通过import的方式直接使用msg这两个模块。比如import msg是无法使用里面的模块的。

注意:在python3中,直接调用import msg不会出错,但是却无法导入模块并使用模块里面的功能。

# python3
In [1]: import msg

In [2]: msg.revMsg.test1()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-2-4c1d6211c490> in <module>()
----> 1 msg.revMsg.test1()

AttributeError: module 'msg' has no attribute 'revMsg'

python2中,即使是调用import msg也会直接报错,因为它们都将msg当做一个普通的文件夹处理

# python2
[1]: import msg
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-12-40dac918f9d8> in <module>()
----> 1 import msg

ImportError: No module named msg

__init__.py文件

为了解决上述问题,需要在msg文件夹下新建一个__init__.py,告诉python解释器不要把msg当成一个普通的文件夹处理,而当做一个对待。

注意:python3在没有__init__.py文件时已经默认msg是一个包,因此上面一段话仅对python2来说有用。

此时我们可以认为含有一个__init__.py文件,并且具有很多模块的一个文件夹,这个整体叫做包

msg/
├── __init__.py
├── revMsg.py
└── sendMsg.py

此时,我们就可以直接导入里面的模块了。

In [1]: from msg import revMsg

In [2]: revMsg.test1()
----test1 revMsg----

但是如果通过如下方式导入时即

In [1]: import msg

In [2]: msg.sengMsg.test1()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-2-be5fc6eec2f7> in <module>()
----> 1 msg.sengMsg.test1()

AttributeError: 'module' object has no attribute 'sengMsg'

还是无法导入。和模块一样,当一个包被导入的时候,里面的__init__.py文件会被执行一遍,因此需要在里面添加如下内容,从而使得我们能够去除掉述导包时出现的错误:

# __init__.py
from . import (sendMsg, revMsg) # 这种方式是python2和python3兼容的,因此建议使用这种方式。

此时就可以了:

In [2]: import msg

In [3]: msg.revMsg.test1()
----test1 revMsg----

__all__变量

和模块中的__all__变量一样,我们可以在__init__.py中写入__all__ = ['xxx', 'xxx']的形式定义import *时候可以使用哪些模块。

# __init__.py
__all__ = ['revMsg']

此时通过from msg import *方式导入模块只能是revMsg模块。

注意:__all__变量同样不会影响from msg.sendMsg import test1等类似方式的调用。只会影响到import *方式的调用。

我们总结一个__init__.py文件下最好有两个内容:

  1. __all__变量指定from xxx import *时候可以使用哪些模块。
  2. from .xx import (xxx, xxxx, xxxx)的内容,指定其他时候可以使用哪些模块。

总结

关于模块和包的创建以及导入我们总结如下:

  1. 一个xx.py文件被称为一个模块。

  2. 将实现相关联功能的模块放在一起,再添加一个__init__.py文件,放在一个文件夹中,称之为一个包。

  3. __all__变量在模块中可以定义导入该模块时使用from xx import *时导入模块内的哪些内容。

  4. __all__变量在__init__.py文件中可以定义使用from xx import *时导入哪些模块。

  5. 当导入模块时,模块的对应文件会被从头到尾执行一遍,因此需要使用__name__变量的性质屏蔽掉测试代码。

  6. 当导入包时,里面的__init__.py文件会从头到尾被执行一遍,因此可以在这里加上导包的命令使得当一个程序导入该包时可以导入包里面的模块。

  7. 当然__init__.py也可以是一个空文件,只不过此时不能通过只导一个包的形式(import xxx)来调用包里面的模块(xxx.xxx_model).

最后我们在来看一个skimage中的其中一个包是怎么写的:

# xxx/site-packages/skimage/segmentation/__init__.py
from .random_walker_segmentation import random_walker #表示从当前文件夹下的random_walker_segmentation模块中导入random_walker函数
from .active_contour_model import active_contour
from ._felzenszwalb import felzenszwalb
from .slic_superpixels import slic
from ._quickshift import quickshift
from .boundaries import find_boundaries, mark_boundaries
from ._clear_border import clear_border
from ._join import join_segmentations, relabel_from_one, relabel_sequential
from ..morphology import watershed # 表示从上一层路径下的morphology包中导入watershed模块

# 当使用from segmentation import *时,下面这些函数可以被使用
__all__ = ['random_walker' 
           'active_contour',
           'felzenszwalb',
           'slic',
           'quickshift',
           'find_boundaries',
           'mark_boundaries',
           'clear_border',
           'join_segmentations',
           'relabel_from_one',
           'relabel_sequential',
           'watershed']

发布和安装自定义模块

这里只介绍一种通过setup.py文件制作包的方式,不能通过pipconda命令安装,但是基本上对于目前的应用来说已经够用了。因此其他方式不再介绍。

制作

  1. 首先确保包的目录结构如下:

     .
     ├── msg
     │   ├── __init__.py
     │   ├── revMsg.py
     │   └── sendMsg.py
     └── setup.py
    
  2. 然后编辑setup.py文件如下:

     from distutils.core import setup
    
     setup(name = 'xuyangcao', 
             version = '1.0', 
             description = 'xuyangcao\'s model',
             author = 'xuyangcao',
             py_modules = ['msg.sendMsg', 'msg.revMsg'])
    
  3. build 执行setup.py构建模块,命令如下:

     python setup.py build
    

    执行后其目录结构变化如下:

     .
     ├── build
     │   └── lib
     │       └── msg
     │           ├── __init__.py
     │           ├── revMsg.py
     │           └── sendMsg.py
     ├── msg
     │   ├── __init__.py
     │   ├── revMsg.py
     │   └── sendMsg.py
     └── setup.py
    

    发现build 文件夹下的东西就是原始文件夹里面内容的一个拷贝。

  4. sdist build结束后,再执行如下命令:

     python setup.py sdist
    

    运行完之后目录结构如下:

         .
     ├── build
     │   └── lib
     │       └── msg
     │           ├── __init__.py
     │           ├── revMsg.py
     │           └── sendMsg.py
     ├── dist
     │   └── xuyangcao-1.0.tar.gz
     ├── MANIFEST
     ├── msg
     │   ├── __init__.py
     │   ├── revMsg.py
     │   └── sendMsg.py
     └── setup.py
    

    我们需要的东西就在./dist下面的压缩包里。

发布与安装

发布:

./dist/xuyangcao-1.0.tar.gz解压,发布到需要的地方即可。

安装

这个应该是比较熟悉的了,我们安装其他第三方模块时肯定也用过这种方式。

解压缩压缩包里的文件,其目录结构如下:

.
├── build
│   └── lib
│       └── msg
│           ├── __init__.py
│           ├── revMsg.py
│           └── sendMsg.py
├── msg
│   ├── __init__.py
│   ├── revMsg.py
│   └── sendMsg.py
├── PKG-INFO
└── setup.py

接下来就可以安装了,直接输入我们熟悉的命令:

python setup.py install

大功告成!

测试一下吧

In [2]: import msg

In [3]: msg.revMsg.test1()
----test1 revMsg----

成功!