本文探讨了 Python模块和 Python包,这两种机制有助于模块化编程。
模块化编程是指将大型、笨重的编程任务分解为单独的、更小、更易于管理的子任务或模块的过程。然后可以像构建块一样将各个模块拼凑在一起以创建更大的应用程序。
在大型应用程序中模块化代码有几个优点:
-
简单性:一个模块通常关注问题的一个相对较小的部分,而不是关注手头的整个问题。如果您正在处理单个模块,您将有一个较小的问题域来解决您的问题。这使得开发更容易,更不容易出错。
-
可维护性:模块通常被设计成在不同的问题域之间强制执行逻辑边界。如果模块以最小化相互依赖性的方式编写,则对单个模块的修改对程序的其他部分产生影响的可能性就会降低。(您甚至可以在不了解该模块之外的应用程序的情况下对模块进行更改。)这使得由许多程序员组成的团队在大型应用程序上协作工作更加可行。
-
可重用性:在单个模块中定义的功能可以很容易地被应用程序的其他部分重用(通过适当定义的接口)。这消除了重复代码的需要。
-
作用域:模块通常定义一个单独的命名空间,这有助于避免程序不同区域中标识符之间的冲突。( Python 之禅的原则之一是命名空间是一个非常棒的想法——让我们做更多这样的事情!)
函数、模块和包都是 Python 中促进代码模块化的结构。
Python 模块(module):概述
在 Python中定义模块实际上有三种不同的方法:
- 一个模块可以用 Python 本身编写。
- 一个模块可以用C 语言编写并在运行时动态加载,就像
re
(正则表达式)模块一样。 - 内置模块本质上包含在解释器中,例如itertoolsmodule。
在所有三种情况下,模块的内容都以相同的方式访问:使用import
语句。
在这里,重点将主要放在用 Python 编写的模块上。用 Python 编写的模块很酷的地方在于它们非常易于构建。您需要做的就是创建一个包含合法 Python 代码的文件,然后为该文件指定一个带有.py
扩展名的名称。而已!不需要特殊的语法或巫术。
例如,假设您创建了一个名为的文件,mod.py
其中包含以下内容:
mod.py
s = "一个普通的字符串"
a = [100, 200, 300]
def foo(arg):
print(f'arg = {arg}')
class Foo:
pass
mod.py中对象的定义
:
s
(一个字符串)a
(一个列表)foo()
(一个函数)Foo
(一类)
假设mod.py
位于适当的位置,您将很快了解更多信息,可以通过导入模块来访问这些对象,如下所示:
>>> import mod
>>> print(mod.s)
一个普通的字符串
>>> mod.a
[100, 200, 300]
>>> mod.foo(['quux', 'corge', 'grault'])
arg = ['quux', 'corge', 'grault']
>>> x = mod.Foo()
>>> x
<mod.Foo object at 0x03C181F0>
模块搜索路径
继续上面的例子,我们来看看 Python 执行语句时会发生什么:
import mod
当解释器执行上述语句时,它会在从以下来源组装的目录列表import
中进行搜索:mod.py
- 运行输入脚本的目录或当前目录(如果解释器以交互方式运行)
- 环境变量中包含的目录列表(PYTHONPATH如果已设置)。( 的格式
PYTHONPATH
取决于操作系统,但应该模仿PATH
环境变量。) - 安装 Python 时配置的依赖于安装的目录列表
生成的搜索路径可在 Python 变量中访问,该变量sys.path
是从名为 的模块中获得的sys
:
>>> import sys
>>> sys.path
['', 'C:\\Users\\john\\Documents\\Python\\doc', 'C:\\Python36\\Lib\\idlelib',
'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib',
'C:\\Python36', 'C:\\Python36\\lib\\site-packages']
注意:的确切内容
sys.path
取决于安装。以上几乎肯定会在您的计算机上看起来略有不同。
因此,为确保找到您的模块,您需要执行以下操作之一:
- 放入
mod.py
输入脚本所在的目录或当前目录,如果是交互的 - 修改
PYTHONPATH
环境变量以包含mod.py
启动解释器之前所在的目录- 或者:放入变量
mod.py
中已经包含的目录之一PYTHONPATH
- 或者:放入变量
- 放入
mod.py
依赖于安装的目录之一,您可能具有或不具有写入权限,具体取决于操作系统
实际上还有一个附加选项:您可以将模块文件放在您选择的任何目录中,然后sys.path
在运行时进行修改,使其包含该目录。例如,在这种情况下,您可以放入mod.py
目录C:\Users\john
,然后发出以下语句:
>>> sys.path.append(r'C:\Users\john')
>>> sys.path
['', 'C:\\Users\\john\\Documents\\Python\\doc', 'C:\\Python36\\Lib\\idlelib',
'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib',
'C:\\Python36', 'C:\\Python36\\lib\\site-packages', 'C:\\Users\\john']
>>> import mod
导入模块后,您可以使用模块的__file__
属性确定找到它的位置:
>>> import mod
>>> mod.__file__
'C:\\Users\\john\\mod.py'
>>> import re
>>> re.__file__
'C:\\Python36\\lib\\re.py'
的目录部分__file__
应该是sys.path
.
import
声明_
模块内容通过import
语句提供给调用者。该import
语句有多种不同的形式,如下所示。
import <module_name>
最简单的形式是上面已经显示的形式:
import <module_name>
请注意,这不会使调用者直接访问模块内容。每个模块都有自己的私有符号表,作为模块中定义的所有对象的全局符号表。因此,如前所述,一个模块创建了一个单独的命名空间。
该语句import <module_name>
仅放置<module_name>
在调用者的符号表中。模块中定义的对象保留在模块的私有符号表中。
从调用者那里,模块中的对象只有在前缀为<module_name>
via dot notation时才能访问,如下图所示。
在以下import
语句之后,mod
被放入本地符号表中。因此,mod
在调用者的本地上下文中具有意义:
>>> import mod
>>> mod
<module 'mod' from 'C:\\Users\\john\\Documents\\Python\\doc\\mod.py'>
但是s
andfoo
保留在模块的私有符号表中并且在本地上下文中没有意义:
>>> s
NameError: name 's' is not defined
>>> foo('quux')
NameError: name 'foo' is not defined
要在本地上下文中访问,模块中定义的对象名称必须以 为前缀mod
:
>>> mod.s
'If Comrade Napoleon says it, it must be right.'
>>> mod.foo('quux')
arg = quux
可以在单个import
语句中指定几个逗号分隔的模块:
import <module_name>[, <module_name> ...]
from <module_name> import <name(s)>
该语句的另一种形式import
允许将模块中的各个对象直接导入调用者的符号表:
from <module_name> import <name(s)>
执行上述语句后,<name(s)>
可以在调用者的环境中引用,不带<module_name>
前缀:
>>> from mod import s, foo
>>> s
'If Comrade Napoleon says it, it must be right.'
>>> foo('quux')
arg = quux
>>> from mod import Foo
>>> x = Foo()
>>> x
<mod.Foo object at 0x02E3AD50>
因为这种形式import
将对象名称直接放入调用者的符号表中,所以任何已经存在的同名对象都将被覆盖:
>>> a = ['foo', 'bar', 'baz']
>>> a
['foo', 'bar', 'baz']
>>> from mod import a
>>> a
[100, 200, 300]
甚至可以import
一举不分青红皂白地从一个模块中获取所有内容:
from <module_name> import *
这会将所有对象的名称放入本地符号表中,以下划线 ( ) 字符<module_name>
开头的对象除外。_
例如:
>>> from mod import *
>>> s
'If Comrade Napoleon says it, it must be right.'
>>> a
[100, 200, 300]
>>> foo
<function foo at 0x03B449C0>
>>> Foo
<class 'mod.Foo'>
在大规模生产代码中不一定建议这样做。这有点危险,因为您正在将名称输入到本地符号表中。除非您非常了解它们并且可以确信不会发生冲突,否则您很有可能会无意中覆盖现有名称。但是,当您只是为了测试或发现目的而使用交互式解释器时,这种语法非常方便,因为它可以让您快速访问模块必须提供的所有内容,而无需大量输入。
from <module_name> import <name> as <alt_name>
也可以import
单独的对象,但将它们输入到具有替代名称的本地符号表中:
from <module_name> import <name> as <alt_name>[, <name> as <alt_name> …]
这使得可以将名称直接放入本地符号表中,但避免与先前存在的名称冲突:
>>> s = 'foo'
>>> a = ['foo', 'bar', 'baz']
>>> from mod import s as string, a as alist
>>> s
'foo'
>>> string
'If Comrade Napoleon says it, it must be right.'
>>> a
['foo', 'bar', 'baz']
>>> alist
[100, 200, 300]
import <module_name> as <alt_name>
您还可以使用备用名称导入整个模块:
import <module_name> as <alt_name>
>>> import mod as my_module
>>> my_module.a
[100, 200, 300]
>>> my_module.foo('qux')
arg = qux
模块内容可以从函数定义中导入。在这种情况下,在调用import
函数之前不会发生:
>>> def bar():
... from mod import foo
... foo('corge')
...
>>> bar()
arg = corge
但是,Python 3不允许import *
在函数中使用不分青红皂白的语法:
>>> def bar():
... from mod import *
...
SyntaxError: import * only allowed at module level
最后,try带有except ImportError子句的语句可用于防止尝试失败import
:
>>> try:
... # Non-existent module
... import baz
... except ImportError:
... print('Module not found')
...
Module not found
>>> try:
... # Existing module, but non-existent object
... from mod import baz
... except ImportError:
... print('Object not found in module')
...
Object not found in module
dir()
功能_
内置函数dir()
返回命名空间中已定义名称的列表。如果没有参数,它会在当前本地符号表中生成按字母顺序排序的名称列表:
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']
>>> qux = [1, 2, 3, 4, 5]
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'qux']
>>> class Bar():
... pass
...
>>> x = Bar()
>>> dir()
['Bar', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'qux', 'x']
请注意上面的第一次调用如何dir()
列出解释器启动时自动定义并且已经在命名空间中的几个名称。随着新名称的定义(qux
, Bar
, x
),它们出现在随后的dir()
.
这对于识别通过 import 语句添加到命名空间的确切内容非常有用:
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']
>>> import mod
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'mod']
>>> mod.s
'If Comrade Napoleon says it, it must be right.'
>>> mod.foo([1, 2, 3])
arg = [1, 2, 3]
>>> from mod import a, Foo
>>> dir()
['Foo', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'a', 'mod']
>>> a
[100, 200, 300]
>>> x = Foo()
>>> x
<mod.Foo object at 0x002EAD50>
>>> from mod import s as string
>>> dir()
['Foo', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'a', 'mod', 'string', 'x']
>>> string
'If Comrade Napoleon says it, it must be right.'
当给定一个作为模块名称的参数时,dir()
列出模块中定义的名称:
>>> import mod
>>> dir(mod)
['Foo', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', '__spec__', 'a', 'foo', 's']
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']
>>> from mod import *
>>> dir()
['Foo', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'a', 'foo', 's']
将模块作为脚本执行
任何.py
包含模块的文件本质上也是一个 Python脚本,并且没有任何理由不能像一个那样执行它。
这又是mod.py
上面定义的:
mod.py
s = "If Comrade Napoleon says it, it must be right."
a = [100, 200, 300]
def foo(arg):
print(f'arg = {arg}')
class Foo:
pass
这可以作为脚本运行:
C:\Users\john\Documents>python mod.py
C:\Users\john\Documents>
没有错误,所以它显然有效。当然,这不是很有趣。正如它所写的,它只定义对象。它不会对它们做任何事情,也不会产生任何输出。
让我们修改上面的 Python 模块,使其在作为脚本运行时生成一些输出:
mod.py
s = "If Comrade Napoleon says it, it must be right."
a = [100, 200, 300]
def foo(arg):
print(f'arg = {arg}')
class Foo:
pass
print(s)
print(a)
foo('quux')
x = Foo()
print(x)
现在应该更有趣了:
C:\Users\john\Documents>python mod.py
If Comrade Napoleon says it, it must be right.
[100, 200, 300]
arg = quux
<__main__.Foo object at 0x02F101D0>
不幸的是,现在它在作为模块导入时也会生成输出:
>>> import mod
If Comrade Napoleon says it, it must be right.
[100, 200, 300]
arg = quux
<mod.Foo object at 0x0169AD50>
这可能不是你想要的。模块在导入时生成输出是不常见的。
如果您可以区分文件何时作为模块加载和何时作为独立脚本运行,那不是很好吗?
问,你就会收到。
当.py
文件作为模块导入时,Python 将特殊的dunder变量设置__name__为模块的名称。但是,如果文件作为独立脚本运行,__name__
则(创造性地)设置为 string '__main__'
。使用这个事实,您可以在运行时辨别出哪种情况并相应地改变行为:
mod.py
s = "If Comrade Napoleon says it, it must be right."
a = [100, 200, 300]
def foo(arg):
print(f'arg = {arg}')
class Foo:
pass
if (__name__ == '__main__'):
print('Executing as standalone script')
print(s)
print(a)
foo('quux')
x = Foo()
print(x)
现在,如果你作为脚本运行,你会得到输出:
C:\Users\john\Documents>python mod.py
Executing as standalone script
If Comrade Napoleon says it, it must be right.
[100, 200, 300]
arg = quux
<__main__.Foo object at 0x03450690>
但是,如果您作为模块导入,则不会:
>>> import mod
>>> mod.foo('grault')
arg = grault
模块通常设计为能够作为独立脚本运行,以测试模块中包含的功能。这称为单元测试。例如,假设您创建了一个fact.py
包含阶乘函数的模块,如下所示:
fact.py
def fact(n):
return 1 if n == 1 else n * fact(n-1)
if (__name__ == '__main__'):
import sys
if len(sys.argv) > 1:
print(fact(int(sys.argv[1])))
该文件可以被视为一个模块,并fact()
导入函数:
>>> from fact import fact
>>> fact(6)
720
但它也可以通过在命令行上传递一个整数参数来作为独立运行以进行测试:
C:\Users\john\Documents>python fact.py 6
720
重新加载模块
出于效率的原因,一个模块在每个解释器会话中只加载一次。这对于函数和类定义来说很好,它们通常构成模块内容的大部分。但是模块也可以包含可执行语句,通常用于初始化。请注意,这些语句只会在第一次导入模块时执行。
考虑以下文件mod.py
:
mod.py
a = [100, 200, 300]
print('a =', a)
>>> import mod
a = [100, 200, 300]
>>> import mod
>>> import mod
>>> mod.a
[100, 200, 300]
该print()
语句不会在后续导入中执行。(就此而言,赋值语句也不是,但作为显示值的最终显示mod.a
,这无关紧要。一旦进行赋值,它就会坚持。)
如果您对模块进行了更改并需要重新加载它,您需要重新启动解释器或使用reload()
从模块调用的函数importlib
:
>>> import mod
a = [100, 200, 300]
>>> import mod
>>> import importlib
>>> importlib.reload(mod)
a = [100, 200, 300]
<module 'mod' from 'C:\\Users\\john\\Documents\\Python\\doc\\mod.py'>
Python 包
假设您开发了一个包含许多模块的非常大的应用程序。随着模块数量的增加,如果它们被倾倒到一个位置,就很难跟踪它们。如果它们具有相似的名称或功能,则尤其如此。您可能希望有一种对它们进行分组和组织的方法。
包允许使用点符号对模块命名空间进行层次结构。就像模块有助于避免全局变量名之间的冲突一样,包也有助于避免模块名之间的冲突。
创建包非常简单,因为它利用了操作系统固有的分层文件结构。考虑以下安排:
在这里,有一个名为的目录pkg
,其中包含两个模块,mod1.py
并且mod2.py
. 模块的内容是:
mod1.py
def foo():
print('[mod1] foo()')
class Foo:
pass
mod2.py
def bar():
print('[mod2] bar()')
class Bar:
pass
给定这种结构,如果pkg
目录位于可以找到的位置(在 中包含的目录之一中),您可以使用点符号( , )sys.path
引用这两个模块并使用您已经熟悉的语法导入它们:pkg.mod1
pkg.mod2
import <module_name>[, <module_name> ...]
>>> import pkg.mod1, pkg.mod2
>>> pkg.mod1.foo()
[mod1] foo()
>>> x = pkg.mod2.Bar()
>>> x
<pkg.mod2.Bar object at 0x033F7290>
from <module_name> import <name(s)>
>>> from pkg.mod1 import foo
>>> foo()
[mod1] foo()
from <module_name> import <name> as <alt_name>
>>> from pkg.mod2 import Bar as Qux
>>> x = Qux()
>>> x
<pkg.mod2.Bar object at 0x036DFFD0>
您也可以使用这些语句导入模块:
from <package_name> import <modules_name>[, <module_name> ...]
from <package_name> import <module_name> as <alt_name>
>>> from pkg import mod1
>>> mod1.foo()
[mod1] foo()
>>> from pkg import mod2 as quux
>>> quux.bar()
[mod2] bar()
您也可以从技术上导入包:
>>> import pkg
>>> pkg
<module 'pkg' (namespace)>
但这无济于事。虽然严格来说,这是一个语法正确的 Python 语句,但它并没有做任何有用的事情。特别是,它不会将任何模块pkg
放入本地命名空间:
>>> pkg.mod1
Traceback (most recent call last):
File "<pyshell#34>", line 1, in <module>
pkg.mod1
AttributeError: module 'pkg' has no attribute 'mod1'
>>> pkg.mod1.foo()
Traceback (most recent call last):
File "<pyshell#35>", line 1, in <module>
pkg.mod1.foo()
AttributeError: module 'pkg' has no attribute 'mod1'
>>> pkg.mod2.Bar()
Traceback (most recent call last):
File "<pyshell#36>", line 1, in <module>
pkg.mod2.Bar()
AttributeError: module 'pkg' has no attribute 'mod2'
要实际导入模块或其内容,您需要使用上面显示的一种形式。
包初始化
如果一个名为的文件__init__.py
存在于包目录中,则在导入包或包中的模块时调用它。这可以用于包初始化代码的执行,例如包级数据的初始化。
例如,考虑以下__init__.py
文件:
__init__.py
print(f'Invoking __init__.py for {__name__}')
A = ['quux', 'corge', 'grault']
让我们将此文件添加到pkg
上面示例中的目录中:
现在当包被导入时,全局列表A
被初始化:
>>> import pkg
Invoking __init__.py for pkg
>>> pkg.A
['quux', 'corge', 'grault']
包中的模块可以通过依次导入来访问全局变量:
mod1.py
def foo():
from pkg import A
print('[mod1] foo() / A = ', A)
class Foo:
pass
>>> from pkg import mod1
Invoking __init__.py for pkg
>>> mod1.foo()
[mod1] foo() / A = ['quux', 'corge', 'grault']
__init__.py
也可用于实现从包中自动导入模块。例如,之前您看到该语句import pkg
仅将名称pkg
放在调用者的本地符号表中,并且不导入任何模块。但是如果目录__init__.py
中pkg
包含以下内容:
__init__.py
print(f'Invoking __init__.py for {__name__}')
import pkg.mod1, pkg.mod2
然后当你执行时import pkg
,模块mod1
和mod2
自动导入:
>>> import pkg
Invoking __init__.py for pkg
>>> pkg.mod1.foo()
[mod1] foo()
>>> pkg.mod2.bar()
[mod2] bar()
注意:很多 Python 文档都指出,在创建包时,包目录中必须__init__.py
存在一个文件。这曾经是真的。过去,Python 的存在就意味着正在定义一个包。该文件可能包含初始化代码甚至是空的,但它必须存在。__init__.py
从Python 3.3开始,引入了隐式命名空间包。这些允许创建没有任何__init__.py
文件的包。当然,如果需要包初始化,它仍然可以存在。但它不再需要。
*
从包中导入
出于以下讨论的目的,将先前定义的包扩展为包含一些附加模块:
现在pkg
目录中定义了四个模块。它们的内容如下图所示:
mod1.py
def foo():
print('[mod1] foo()')
class Foo:
pass
mod2.py
def bar():
print('[mod2] bar()')
class Bar:
pass
mod3.py
def baz():
print('[mod3] baz()')
class Baz:
pass
mod4.py
def qux():
print('[mod4] qux()')
class Qux:
pass
(富有想象力,不是吗?)
您已经看到 whenimport *
用于模块,模块中的所有对象都被导入到本地符号表中,除了名称以下划线开头的对象,一如既往:
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']
>>> from pkg.mod3 import *
>>> dir()
['Baz', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'baz']
>>> baz()
[mod3] baz()
>>> Baz
<class 'pkg.mod3.Baz'>
包的类似语句是这样的:
from <package_name> import *
那有什么作用?
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']
>>> from pkg import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']
哼。不多。您可能已经预料到(假设您完全有任何期望)Python 会深入到包目录中,找到它可以找到的所有模块,然后将它们全部导入。但正如您所看到的,默认情况下不会发生这种情况。
相反,Python 遵循这个约定:如果包目录中的__init__.py
文件包含一个名为的列表,则它被视为遇到该语句时应导入的模块列表。__all__
from <package_name> import *
对于本示例,假设您__init__.py
在pkg
目录中创建一个,如下所示:
package/__init__.py
__all__ = [
'mod1',
'mod2',
'mod3',
'mod4'
]
现在from pkg import *
导入所有四个模块:
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']
>>> from pkg import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'mod1', 'mod2', 'mod3', 'mod4']
>>> mod2.bar()
[mod2] bar()
>>> mod4.Qux
<class 'pkg.mod4.Qux'>
使用import *
仍然不被认为是很好的形式,对于包而不是对于模块。但是这个工具至少让包的创建者可以控制import *
指定时发生的事情。(事实上,它提供了完全禁止它的能力,只需拒绝定义即可__all__
。正如您所见,包的默认行为是不导入任何内容。)
顺便说一句,__all__
也可以在模块中定义并用于相同的目的:控制使用import *
. 例如修改mod1.py
如下:
package/mod1.py
__all__ = ['foo']
def foo():
print('[mod1] foo()')
class Foo:
pass
现在import *
from 的语句pkg.mod1
将只导入包含在 中的内容__all__
:
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']
>>> from pkg.mod1 import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'foo']
>>> foo()
[mod1] foo()
>>> Foo
Traceback (most recent call last):
File "<pyshell#37>", line 1, in <module>
Foo
NameError: name 'Foo' is not defined
foo()
(函数)现在定义在本地命名空间中,但Foo
(类)不是,因为后者不在__all__
.
总之,包和模块__all__
都使用它来控制指定时导入的内容。但默认行为不同:import *
- 对于包,当
__all__
未定义时,import *
不会导入任何内容。 - 对于一个模块,如果
__all__
没有定义,则import *
导入所有内容(除了——你猜对了——以下划线开头的名称)。
子包(Sub Package)
包可以包含任意深度的嵌套子包。例如,让我们对示例包目录再进行一次修改,如下所示:
四个模块(mod1.py
、mod2.py
和)mod3.py
的mod4.py
定义如前所述。但是现在,它们没有被集中到pkg
目录中,而是被分成两个子包目录,sub_pkg1
并且sub_pkg2
.
导入的工作方式仍与前面所示相同。语法类似,但额外的点符号用于分隔包名和子包名:
>>> import pkg.sub_pkg1.mod1
>>> pkg.sub_pkg1.mod1.foo()
[mod1] foo()
>>> from pkg.sub_pkg1 import mod2
>>> mod2.bar()
[mod2] bar()
>>> from pkg.sub_pkg2.mod3 import baz
>>> baz()
[mod3] baz()
>>> from pkg.sub_pkg2.mod4 import qux as grault
>>> grault()
[mod4] qux()
此外,一个子包中的模块可以引用兄弟子包中的对象(如果兄弟包含您需要的某些功能)。例如,假设您想从模块中导入和执行函数foo()
(在模块中定义mod1
)mod3
。您可以使用绝对导入:
pkg/sub__pkg2/mod3.py
def baz():
print('[mod3] baz()')
class Baz:
pass
from pkg.sub_pkg1.mod1 import foo
foo()
>>> from pkg.sub_pkg2 import mod3
[mod1] foo()
>>> mod3.foo()
[mod1] foo()
或者您可以使用相对导入,其中..
指的是上一级的包。从内部mod3.py
,在子包中sub_pkg2
,
..
评估为父包 (pkg
),并且..sub_pkg1
评估sub_pkg1
为父包的子包。
pkg/sub__pkg2/mod3.py
def baz():
print('[mod3] baz()')
class Baz:
pass
from .. import sub_pkg1
print(sub_pkg1)
from ..sub_pkg1.mod1 import foo
foo()
>>> from pkg.sub_pkg2 import mod3
<module 'pkg.sub_pkg1' (namespace)>
[mod1] foo()
结论
在本教程中,您涵盖了以下主题:
- 如何创建 Python模块
- Python 解释器搜索模块的位置
- 如何使用
import
语句获得对模块中定义的对象的访问权限 - 如何创建可作为独立脚本执行的模块
- 如何将模块组织成包和子包
- 如何控制包初始化
这有望让您更好地了解如何访问 Python 中可用的许多第三方和内置模块中可用的功能。
此外,如果您正在开发自己的应用程序,创建自己的模块和包将帮助您组织和模块化代码,从而使编码、维护和调试更容易。