一个常见的定义module的方法是写一个.py
文件,除此以外,Python的底层实现是C/C++,所以你也可以使用C/C++来写module,当然这是比较高级的方法了,暂时不作深入。
Module
通常,我们使用import
关键字来引入一个module:
import os
它会调用内置函数__import()__
,并把结果绑定到相应的变量名,__import()__
本身会搜索并创建一个module
对象,也就是说如果这样用也是可以的:
os = __import__('os')
print(os.path.exists('c:/'))
当然没有必要这么做。
import
关键字和__import()__
函数是在importlib
模块实现的,该模块甚至允许你编写你自己的importer来自定义import过程,当然这是比较高阶的内容了。
除此以外,也可以用from
关键字来指定module的package,以from [package name] import [module name]
的形式来import module。
另外,使用as
关键字,可以给module指定一个别名,方便使用一些名字较长的module:
import os.path as op
print(op.exists('c:/'))
Submodule
在__init__.py
中,可以使用import
关键字引入submodule,如此,会将submodule加入module的命名空间:
目录结构:
spam/
__init__.py
foo.py
__init__.py
from .foo import Foo
foo.py
def Foo():
print('foo')
如此,便可以如下引用Foo()
:
import spam
spam.Foo()
Package
Python的package可以看作是文件夹,而module就是文件夹中的文件(可以是普通的.py文件或者C文件),但是也可能有其它形式,module不一定是在本地文件系统里面的,为了简化,我们可以简单地看成文件夹跟文件。
package本身可以看作是一种特殊的module,一个判定方式是如果module具有__path__
属性,那么这个module就是一个package。
我们import一个module,打印它的__path__
,可以看到报错:NameError: name '__path__' is not defined
,但是我们import一个package以后,打印它的__path__
是可以看到结果的:
import os
import getkey
print(getkey.__path__)
print(os.__path__)
分别打印内置module os和第三方package getkey的__path__
属性,可以看到输出:
['C:\\Users\\***\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\getkey']
Traceback (most recent call last):
File "c:/Users/***/PycharmProjects/Test/test.py", line 5, in <module>
print(os.__path__)
AttributeError: module 'os' has no attribute '__path__'
package可以有subpackage,关系上类似文件系统的父目录跟子目录,import时使用.
来引用。
Regular package
Python有两种package:regular package
和namespace package
,其中regular package
是在Python 3.2以及之前的版本中定义的,如果你接触Python较早,应该对这个很熟悉,regular package
的目录下面需要有一个__init__.py
文件:
parent/
__init__.py
one/
__init__.py
two/
__init__.py
three/
__init__.py
在import module时,__init__.py
中的代码会被执行,比如import parent.one
,会执行parent/__init__.py
和parent/one/__init__.py
。
Namespace package
在3.3及以后的版本中,Python添加了namespace package
的特性,如前所述,package跟module不一定是在本地文件系统中的,或者不一定是以文件夹/.py文件
的形式,它可以是来自zip文件或者网络或者任何东西,并且同一个pakcage可以以分布式的形式存在于不同的地方,总之namespace package
相对于传统的package,具有分布式的特点且更加抽象化,在大型项目中非常有用,它允许开发者在不同的独立目录下创建属于同一个package的module,并且可以使用相同的package name。
参考PEP 420,Python提供了pkgutil.extend_path
来定义一个namespace package
,在__init__.py
里加入如下代码:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
如果你使用setuptools
这个工具,那么在setup.py
中写以下代码作用也是相同的:
import pkg_resources
pkg_resources.declare_namespace(__name__)
举个例子,我们创建如下目录结构:
└─libs
│ ├─a
│ │ └─pkg
│ │ └─__init__.py
│ │ └─a.py
│ └─b
│ └─pkg
│ └─__init__.py
│ └─b.py
└─test.py
在两个__init__.py
中加入上述代码,在a.py
和b.py
中分别定义两个函数:
a.py
def a():
print('hello a')
b.py
def b():
print('hello b')
那么,在test.py中可以如此import两个module:
import sys
sys.path += ['libs/a', 'libs/b']
from pkg.a import a
from pkg.b import b
a()
b()
注意,要先把两个package的路径加入sys.path
,并且由于extend_path
是运行时计算的,所以在编辑器里会报错,但是运行是没问题的。
此外,这种方式各个module的import顺序是无法保证的,也就是同名package下的module得是独立的,相互不要有依赖。
隐式Namespace package
除上述方法之外,在3.3及以后的版本中,package目录下可以不写__init__.py
,如此就把它隐式地声明为了一个namespace package
,也就是说,把上一节中的__init__.py
都删除,也是可以正常运行的。