本文探讨Python模块和Python包,这两种机制有助于模块化编程。

模块化编程是指将大型笨拙的编程任务分解为单独的,较小的,更易于管理的子任务或模块的过程。然后可以像构建模块一样将各个模块拼凑在一起以创建更大的应用程序。

在大型应用程序中模块化代码有几个优点:

  • 简单性:模块通常只关注问题的一个相对较小的部分,而不是关注手头的整个问题。如果你是在一个模块上工作,你将有一个更小的问题域来解决。这使得开发更容易,也不容易出错。
  • 可维护性:模块通常经过设计,以便它们在不同问题域之间建立逻辑边界。如果以最小化相互依赖性的方式编写模块,则对单个模块的修改将对程序的其他部分产生影响的可能性降低。(您甚至可以在不了解模块外部应用程序的情况下,对模块进行更改。)这使得由许多程序员组成的团队在大型应用程序上协同工作更加可行。
  • 可重用性:在单个模块中定义的功能可以很容易地被应用程序的其他部分重用(通过适当定义的接口)。这样就不需要重复代码了。
  • 作用域:模块通常定义一个单独的名称空间,这有助于避免程序不同区域中的标识符之间发生冲突。

函数,模块和包都是Python中促进代码模块化的构造。

# 多行输出
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

1 Python模块概述

实际上,在Python中定义模块的方式有三种:

  1. 可以使用Python本身编写模块。
  2. 可以使用C语言编写模块,并在运行时动态加载该模块,例如re(正则表达式)模块。
  3. 内置模块本质上包含在解释器中,就像itertools模块一样。

在所有三种情况下,都以相同的方式访问模块的内容:使用import语句。在这里,重点将主要放在用Python编写的模块上。用Python编写的模块的妙处在于它们的构建极其简单。您需要做的就是创建一个包含合法Python代码的文件,然后为该文件命名并带有.py扩展名。

例如,假设有一个mod.py文件包含以下内容:

s = "Hello world!"
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
mod.foo(['quux', 'corge', 'grault'])
arg = ['quux', 'corge', 'grault']
x = mod.Foo()
Hello world!
arg = ['quux', 'corge', 'grault'] [100, 200, 300]

2 使用说明

2.1 模块搜索路径

继续上面的示例,让我们看一下Python执行该语句时发生的情况:

import mod

当解释器执行上面的import语句时,它会在一个目录列表中搜索mod.py,这些目录由以下来源组成:

  • 运行输入脚本的目录或当前目录(如果解释器正在交互运行)
  • PYTHONPATH环境变量(如果已设置)中包含的目录列表。(其格式PYTHONPATH取决于操作系统,但应模仿PATH环境变量。)
  • 安装Python时配置的与安装有关的目录列表

结果搜索路径可在Python变量中访问,该变量sys.path从名为的模块获取sys:

import sys
sys.path
['/home/aistudio',
'/opt/conda/envs/python35-paddle120-env/lib/python37.zip',
'/opt/conda/envs/python35-paddle120-env/lib/python3.7',
'/opt/conda/envs/python35-paddle120-env/lib/python3.7/lib-dynload',
'',
'/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages',
'/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/IPython/extensions',
'/home/aistudio/.ipython']

的确切内容sys.path取决于安装。几乎可以肯定,上述内容在您的计算机上看起来会稍有不同。因此,为了确保找到您的模块,您需要执行以下操作之一:

  • 如果交互的话,将mod.py放在输入脚本所在的目录或当前目录中
  • 在启动解释器之前,修改PYTHONPATH环境变量以包含其所在的目录mod.py
  • 将mod.py放在已经包含在PYTHONPATH变量中的一个目录中
  • 放入mod.py一个取决于安装的目录,您可能会或可能不会具有写访问权,具体取决于操作系统

实际上,还有一个附加选项:您可以将模块文件放在您选择的任何目录中,然后sys.path在运行时进行修改,使其包含该目录。例如,在这种情况下,您可以放入目录./data,然后输入以下语句:

sys.path.append(r'./data')
sys.path
import mod
['/home/aistudio',
'/opt/conda/envs/python35-paddle120-env/lib/python37.zip',
'/opt/conda/envs/python35-paddle120-env/lib/python3.7',
'/opt/conda/envs/python35-paddle120-env/lib/python3.7/lib-dynload',
'',
'/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages',
'/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/IPython/extensions',
'/home/aistudio/.ipython',
'./data']

导入模块后,您可以使用模块的__file__属性确定找到模块的位置:

import mod
mod.__file__ import re
re.__file__
'/home/aistudio/mod.py'

'/opt/conda/envs/python35-paddle120-env/lib/python3.7/re.py'

2.2 import声明

import语句可将模块内容提供给调用方。该import语句采用许多不同的形式,如下所示。

import <module_name>

最简单的形式是上面已经显示的形式。请注意,这不会使调用者可以直接访问模块内容。每个模块都有其自己的专用符号表,该符号表用作模块中定义的所有对象的全局符号表。因此,模块已经创建了一个单独的名称空间。该语句import <module_name>仅放置<module_name>在调用者的符号表中。模块中定义的对象保留在模块的专用符号表中。该段话的意思就是你调用的模块变量和本地变量不冲突。

从调用方来看,只有通过点表示法以<module_name>作为前缀,才能访问模块中的对象,如下所示。在下面的import语句之后,mod被放置到本地符号表中。因此,mod在调用方的本地上下文中有意义:

import mod
mod
<module 'mod' from '/home/aistudio/mod.py'>

但是s并且foo保留在模块的专用符号表中,并且在本地上下文中没有意义:

s
foo('quux')
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-7-25b38fc580f1> in <module>
----> 1 s
2 foo('quux') NameError: name 's' is not defined

要在本地上下文中访问,模块中定义的对象名称必须加上mod前缀:

mod.s
mod.foo('quux')
'Hello world!'

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,Foo
s
foo('quux')
x = Foo()
x
'Hello world!'

arg = quux

<mod.Foo at 0x7f829d754350>

因为这种形式的导入将对象名称直接放入调用者的符号表中,所以任何已经存在的同名对象都将被覆盖,如下所示:

a = ['foo', 'bar', 'baz']
a
from mod import a
a
['foo', 'bar', 'baz']

[100, 200, 300]

它甚至可以不加选择地从一个模块导入所有内容:

from <module_name> import *

这会将把<module_name>所有对象的名称放入本地符号表,但以下划线(_)字符开头的对象除外。例如:

from mod import *
s
a
foo
Foo
'Hello world!'

[100, 200, 300]

<function mod.foo(arg)>

mod.Foo

在大规模生产代码中并不推荐这样做。这有点危险,因为您是在将名称全部输入到本地符号表中。除非您对它们都很了解,并且确信不会发生冲突,否则您很有可能会不小心覆盖现有的名称。但是,当您只是为了测试或发现的目的而随意使用交互式解释器时,这种语法非常方便,因为它可以让您快速访问模块必须提供的所有内容,而无需大量输入。

from <module_name> import as <alt_name>

也可以导入单独的对象,但是使用替代名称将它们输入到本地符号表中:

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
string
a
alist
'foo'

'Hello world!'

['foo', 'bar', 'baz']

[100, 200, 300]

import <module_name> as <alt_name>

您也可以使用备用名称导入整个模块:

import <module_name> as <alt_name>
import mod as my_module
my_module.a
my_module.foo('qux')
[100, 200, 300]

arg = qux

可以从函数定义中导入模块内容。在这种情况下,import只有在调用该函数后,才会发生:

def bar():
from mod import foo
foo('corge')
bar()
arg = corge

但是,Python 3不允许在函数内任意导入*的语法,如下所示:

def bar():
from mod import *
  File "<ipython-input-16-86f331738b46>", line 4
SyntaxError: import * only allowed at module level

最后,一个带有except ImportError子句的try语句可以用来防止不成功的导入尝试:

try:
# Non-existent module
import baz
except ImportError:
print('Module not found')
Module not found

2.3 dir()函数

内置函数dir()返回一个名称空间中定义的名称列表。如果没有参数,它会在当前本地符号表中产生一个按字母顺序排序的名称列表:

dir()[0:5]
['Foo', 'In', 'InteractiveShell', 'Out', '_']
qux = [1, 2, 3, 4, 5]
dir()[-10:-1]
['get_ipython', 'mod', 'my_module', 'quit', 'qux', 're', 's', 'string', 'sys']

注意dir()上面的第一个调用是如何列出几个自动定义的名称的,这些名称在解释器启动时已经存在于名称空间中。随着新的名称定义(qux),它们出现在的后续,调用dir()。这对于识别由import语句确切添加到名称空间的内容很有用。当给定参数作为模块名称时,dir()列出模块中定义的名称:

import mod
dir(mod)
['Foo',
'__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'a',
'foo',
's']

2.4 将模块作为脚本执行

任何.py包含模块的文件本质上也是Python 脚本,没有任何理由它不能像一个脚本一样执行。

这里还是mod.py,正如上面定义的那样。比如命令行:

python mod.py

此外在mod.py文件中添加输出内容,如下所示:

s = "Hello world!"
a = [100, 200, 300] def foo(arg):
print(f'arg = {arg}') class Foo:
pass print(s)
print(a)
foo('quux')
x = Foo()
print(x)

现在输出如下:

Hello world!
[100, 200, 300]
arg = quux
<__main__.Foo object at 0x7f1e792b9e50>

不幸的是,现在当作为模块导入时,它还会生成输出:

import mod
Hello world!
[100, 200, 300]
arg = quux
<mod.Foo object at 0x7f7db3aba310>

这可能不是您想要的。导入模块时,模块通常不生成输出。如果您可以区分文件是作为模块加载时还是作为独立脚本运行时,这不是很好吗?

将.py文件导入为模块时,Python 会将特殊的dunder变量设置为模块__name__的名称。但是,如果文件作为独立脚本运行,__name__则(创造性地)设置为string '__main__'。利用这个事实,您可以识别出运行时是哪种情况,并相应地更改mod.py为:

s = "Hello world!"
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)

2.5 重新加载模块

为了提高效率,每个解释器会话仅加载一次模块。对于函数和类定义来说,这很好,它们通常占模块内容的大部分。但是一个模块也可以包含可执行语句,通常用于初始化。请注意,这些语句仅在第一次导入模块时执行。

考虑以下文件mod.py:

a = [100, 200, 300]
print('a =', a)

调用mod模块会以下结果:

>>> import mod
a = [100, 200, 300]
>>> import mod
>>> import mod >>> mod.a
[100, 200, 300]

该print()语句不会在后续导入上执行。(就此而言,赋值语句也不是,而是作为mod.ashows 值的最终显示,这无关紧要。完成赋值后,它会保留下来。)如果对模块进行了更改并需要重新加载,则需要重新启动解释器或使用reload()从module 调用的函数importlib:

>>> import mod
a = [100, 200, 300] >>> import mod >>> import importlib
>>> importlib.reload(mod)
a = [100, 200, 300]

3 Python包

3.1 Python包的使用

假设您开发了一个非常大的应用程序,其中包含许多模块。随着模块数量的增加,如果将它们倾倒到一个位置,则很难跟踪所有模块。如果它们具有相似的名称或功能,则尤其如此。您可能希望有一种分组和组织的方法。

包允许使用点表示法对模块名称空间进行分层结构。就像模块帮助避免全局变量名之间的冲突一样,包帮助避免模块名之间的冲突。

创建软件包非常简单,因为它利用了操作系统固有的分层文件结构。考虑以下结构目录安排:

- work
- mod1.py
- mod2.py

mod1.py:

def foo():
print('[mod1] foo()') class Foo:
pass

mod2.py:

def bar():
print('[mod2] bar()') class Bar:
pass

基于这种结构,如果mod1.py和mod2.py为workd文件夹下,你可以使用点符号引用这两个模块(work.mod1, work.mod2),并使用你已经熟悉的语法导入它们:

import <module_name>[, <module_name> ...]
from <module_name> import <name(s)>
from <module_name> import <name> as <alt_name>
from <package_name> import <modules_name>[, <module_name> ...]
from <package_name> import <module_name> as <alt_name>
import work.mod1, work.mod2
work.mod1.foo()
x = work.mod2.Bar()
x
[mod1] foo()

<work.mod2.Bar at 0x7f5944642c10>

也可以直接导入包

import work

但这没有用。尽管严格来说,这是语法上正确的Python语句,但它并没有做任何有用的事情。特别是,它不会将work任何模块放入本地名称空间中:

>>> import work
>>> work.mod1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'work' has no attribute 'mod1'
>>> work.mod2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'work' has no attribute 'mod2'

要实际导入模块或其内容,您需要使用上面显示的形式之一。

3.2 包初始化

如果包目录中存在一个名为__init__.py的文件,则在导入包或包中的模块时将调用该文件。这可用于执行程序包初始化代码,例如程序包级数据的初始化。

例如,考虑以下__init__.py文件:

print(f'Invoking __init__.py for {__name__}')
A = ['quux', 'corge', 'grault']

让我们从上面的例子将这个文件添加到work目录:

- work
- __init__.py
- mod1.py
- mod2.py

现在,当导入包时,将A初始化全局列表:

import work
work.A
Invoking __init__.py for work

['quux', 'corge', 'grault']

包中的模块可以通过依次导入全局变量来访问全局变量,让我们修改mod1.py

mod1.py

def foo():
from work import A
print('[mod1] foo() / A = ', A) class Foo:
pass
from work import mod1
mod1.foo()
[mod1] foo()

__init__.py也可以用于从软件包中自动导入模块。例如,在前面您已经看到,该语句import work仅将名称work放置在调用者的本地符号表中,而不会导入任何模块。但是,如果__init__.py在work目录中包含以下内容:

print(f'Invoking __init__.py for {__name__}')
import work.mod1, work.mod2

模块mod1和mod2会自动导入

3.3 从包中导入

出于以下讨论的目的,先前定义的程序包已扩展为包含一些其他模块:

- work
- mod1.py
- mod2.py
- mod3.py
- mod4.py

现在,work目录中定义了四个模块。它们的内容如下所示:

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

您已经看到,当import *用于模块时,模块中的所有对象都将导入到本地符号表中,除了那些名称以下划线开头的对象外,与往常一样。ython遵循以下约定:如果package目录中的__init__.py文件包含名为__all__的列表,则这个列表中视为要导入的模块。

对于本示例,假设您在work录中创建一个__init__.py:

__all__ = [
'mod1',
'mod2',
'mod3',
'mod4'
]

将会在from work import *自动导入所有四个模块

from work import *
mod1
mod2
<module 'work.mod2' from '/home/aistudio/work/mod2.py'>

顺便说一句,__all__也可以在模块中定义它,并且具有相同的目的:控制使用导入的内容import *。例如,修改mod1.py如下:

__all__ = ['foo']

def foo():
print('[mod1] foo()') class Foo:
pass

from work.mod1 import *只会导入__all__中的内容

from work.mod1 import *
>>> foo()
[mod1] foo()
>>> Foo
Traceback (most recent call last):
File "<pyshell#37>", line 1, in <module>
Foo
NameError: name 'Foo' is not defined

总之,包和模块__all__都使用它来控制在指定时导入的内容。但是默认行为不同:import *

  • 对于包,当__all__未定义时,import *不会导入任何内容。
  • 对于__all__未定义的模块,将import *导入所有内容(除非您猜对了,否则名称会以下划线开头)

3.4 子包

程序包可以包含嵌套子程序包到任意深度。例如,让我们对示例包目录进行如下修改:

- work
- subpackage1
- mod1.py
- mod2.py
- subpackage2
- mod3.py
- mod4.py

mod1等四个模块如先前定义,但引入两个子包。导入仍然与之前显示的相同。语法类似,但是使用其他点号将包名与子包名分开:

import work.subpackage1.mod1
work.subpackage1.mod1.foo() from work.subpackage1 import mod2
mod2.bar() from work.subpackage2.mod3 import baz
baz() from work.subpackage2.mod4 import qux as grault
grault()
[mod1] foo()
[mod2] bar()
[mod1] foo()
[mod3] baz()
[mod4] qux()

此外,一个子包中的模块可以引用同级子包中的对象(如果同级包中包含您需要的某些功能)。例如,假设您希望从模块mod3中导入和执行函数foo()(在模块mod1中定义)。你可以使用绝对导入:

work/subpackage2/mod3.py中的内容如下:

def baz():
print('[mod3] baz()') class Baz:
pass from work.subpackage1.mod1 import foo
foo()
from work.subpackage2 import mod3
mod3.foo()
[mod1] foo()

4 参考

https://realpython.com/python-modules-packages/

[编程基础] Python模块和包使用笔记的更多相关文章

  1. 二十五. Python基础(25)--模块和包

    二十五. Python基础(25)--模块和包 ● 知识框架   ● 模块的属性__name__ # my_module.py   def fun1():     print("Hello& ...

  2. Python模块、包、异常、文件(案例)

    Python模块.包.异常.文件(案例) python.py #模块 # Python中的模块(Module),是一个Python文件,以.py文件结尾,包含了Python对象定义和Python语句, ...

  3. (三)运用Python模块和包

    1 引言 为了能够在Python项目中高效地运用Python模块和包,我们需要进一步地来了解它们是如何在Python项目中进行定义.使用和工作的. 2 Python模块和包 Python模块和包的基本 ...

  4. Python/模块与包之模块

    Python/模块与包之模块 1.什么是模块? 模块就是py文件 2.为什么要用模块? 如果在解释器上进行编码,把解释器关闭之前写的文件就不存在了,如果使用模块的话就能永久保存在磁盘中. 3.如何使用 ...

  5. 【Python】解析Python模块与包

    模块 模块是非常简单的Python文件,单个Python文件就是一个模块,两个文件就是两个模块. import语句是用来导入模块或者从模块里导入特定的类或者函数.如前面我们用过的math模块,从而可以 ...

  6. python 模块和包深度学习理解

    python 模块和包 简单说相当于命名空间 1,python 模块        python模块就是一个文件,里面有函数,变量等 import 模块 模块.方法 from 模块 import fu ...

  7. Python模块04/包/logging日志

    Python模块04/包/logging日志 目录 Python模块04/包/logging日志 内容大纲 1.包 2.logging日志 3.今日总结 内容大纲 1.包 2.logging日志 1. ...

  8. Python基础之模块与包

    一.模块 1.什么是模块? 一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀. 2.为何要使用模块? 如果你退出python解释器然后重新进入,那么你之前定义的函 ...

  9. Python 入门基础13 --模块与包

    本节内容: 一.模块及使用 1.模块及使用 2.起别名.from导入 3.自执行与模块 二.包的使用 2.1 包中模块的使用:import 2.2 包的嵌套 2.3 包中模块的使用:from ...i ...

  10. python基础(22):模块、包

    1. 模块 1.1 什么是模块 别人写好的函数.变量.方法放在一个文件里 (这个文件可以被我们直接使用)这个文件就是个模块 常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模 ...

随机推荐

  1. liunx之expect操作详解

    导航: 一.expect安装.介绍.使用场景二.expect使用原理三.expect使用语法四.expect使用举例五.expect相关错误处理 - - - - - - - - - 分割线 - - - ...

  2. P8548 小挖的买花 方法记录

    原题链接 小挖的买花 题目背景 小挖喜欢买花,但是 ta 太懒了!所以这个任务全权交给了你. 题目描述 花店里只有 \(n\) 株花,每一株花都有三个属性:价格 \(cost_i\).美丽度 \(be ...

  3. 支持 Java 8/11/17/19 的框架,Solon v1.10.5 版本发布

    Java 轻量级应用开发框架.可用来快速开发 Java 应用项目,主框架仅 0.1 MB. 相对于 Spring Boot 和 Spring Cloud 的项目: 启动快 5 - 10 倍. (更快) ...

  4. 测试开发HTTP请求过程(一)

    测试开发HTTP请求过程 HTTP请求过程: 首先要熟悉http请求过程: 1,服务端建立socket监听 2,客户端发送http请求 3,客户端与服务端建立socket连接 4,客户端------t ...

  5. 7_vue的数据代理,双向绑定

    回顾 object.defineProperty() 方法 区别 defineProperty == 给对象定义属性用的 需要传递三个基本参数 需要定义属性的对象名 你要定义的属性叫什么名字(比如给p ...

  6. oracle expdp/exp ora-600/ora-39014报错处理

    在一次数据迁移的时候,expdp导出报错,错误信息如下: 版本号:11.2.0.1 没有打PSU,查看报错的aler部分日志如下: 其中的某一些trc日志文件截图: Trace file d:\ora ...

  7. python实现鼠标手动截图(类似于QQ截图)

    由于在网上找了很久,只找到按像素位置截图和全屏截图的,所以决定自己写一个. 本程序实现原理是现用PIL里的ImageGrab进行全屏截图,然后通过模拟鼠标操作,进行截图,最后删除全屏截图,只留下鼠标截 ...

  8. virtualenv +virtualenvwrapper

    一.虚拟环境virtualenv 1.安装:pip3 install virtualenv 2.创建虚拟环境:virtualenv venv #venv为虚拟环境目录名,目录名自定义 #virtual ...

  9. 16.-admin管理后台

    一.admin管理后台 Django提供给了比较完善的后台管理数据库接口,可供开发过程中调用和测试使用 Django会搜集所有已注册的模型类,为这些模型类提供数据管理界面,供开发者使用   命令:py ...

  10. javascript编程单线程之异步模式Asynchronous

    异步模式Asynchronous 不会等待这个任务结束才开始执行下一个任务,开启之后立即执行下一个任务,后续逻辑一般会通过回调函数的方式定义,异步模式对js 非常重要,没有异步任务单线程的 js 语言 ...