Python 入门(三)- 包与模块

组织结构:包、模块、类

  • 顶级组织结构:包
  • 第二层级:模块
  • 第三层级:类(包含函数、变量)
1
2
3
4
5
6
7
8
9
10
package1 包
|- c1.py 模块
|- c2.py
|- c3.py
|_ c4.py
package2 包
|- c1.py 模块
|- c2.py
|- c3.py
|_ c4.py
  1. 若想访问某个文件夹下的某个文件,则可通过 包.文件名 来区分,也称为命名空间。例如:package1.c1package2.c1分别访问了package1文件夹下和package2文件夹下的名为c1.py的文件。
  2. 可以存在子包,子包和模块也可以是平行关系。
  3. 包和普通文件夹的区别:包里含有特定文件:__init__.py
  4. __init__.py文件的访问方式:不是package1.__init__.py,而是通过包名,package1访问的即是__init__.py文件。

导入模块

如果想在另一个文件引入其他模块,有如下两种方式:

1
2
3
4
5
# 方法一:导入模块,但无法导入具体变量??
import module_name[.var_name] as alias

# 方法二:导入模块或具体变量
from module_name import module_name/var_name

举个栗子,假设package1中的c3.py文件包含以下内容:

1
2
a = 1
b = 2

引入c3.py文件的方式为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 方法一
import package1.c3.a as m

print(package1.c3.a) // 1
print(m.a) // 1

# 方法二
from package1.c3 import a
print(a) // 1

from package1 import c3
print(c3.a) // 1

from package1.c3 import *
print(a) // 1
print(b) // 2
  • 只用 import 不能在时导入模块中的变量,只能导入模块。缺点是命名空间过长。
  • 不推荐使用 import *,导入的变量不明确,且编辑器会因找不到具体的引入变量而提示错误,但代码可以运行。
  • import * 无法导入所有的变量(由内置变量决定)

如在导入变量和模块时需要换行,则可以通过/()进行导入。

1
2
3
4
5
6
7
# 写法一
from c2 import a,b,/
c

# 写法二
from c2 import (a,b,
c)

模块的内置变量

all
package1中的c3.py包含以下内容:

1
2
3
4
5
__all__ = ['a','c']

a = 1
b = 2
c = 3

当 首先自动执行 __init__.py__init__.py通常做初始化

1
2
3
4
5
6
package1
|- t
|- __init__.py
|_ c4.py
|- c1.py
|_ c2.py

内置变量

内置变量通常为前后双下划线格式,与我们定义的变量含义相同,只是由系统定义的。

dir()

dir() 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
### 无参数
a = 2
b = 3
print(dir())

# ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b']

### 有参数
import sys
print(dir([ ])) # list模块的属性列表
print(dir(sys)) # sys模块的属性列表

#['__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'api_version', 'argv', 'base_exec_prefix', 'base_prefix', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_wrapper', 'getallocatedblocks', 'getcheckinterval', 'getdefaultencoding', 'getdlopenflags', 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info', 'intern', 'is_finalizing', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'set_asyncgen_hooks', 'set_coroutine_wrapper', 'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', 'version', 'version_info', 'warnoptions']
常见内置变量
  • __name__:当前模块的完整路径(命名空间)
  • __package__:当前文件所在模块
  • __file__:系统完整的物理路径
  • __doc__:模块的注释

举个栗子:

package1-c1.py

1
2
3
4
5
6
7
8
'''
This is c1
'''

print('name:'+__name__)
print('package:'+__package__)
print('doc:'+__doc__)
print('file:'+__file__)

package2-c1.py

1
2
3
4
5
6
7
import package1.c1

# name: package1.c1
# package: package1
# doc:
# This is c1
# file: /Users/Desktop/python/package1/c1.py

如果我们在package2的c1.py文件中打印相同的内容:

package2 c1.py

1
2
3
4
5
6
7
8
9
print('name:  '+__name__)
print('package: '+str(__package__))
print('doc: '+str(__doc__))
print('file: '+__file__)

# name: __main__
# package: None
# doc: None
# file: /Users/Desktop/python/package2/c1.py

更推荐的写法为:

package2 c1.py

1
2
3
4
5
6
7
8
9
print('name:  '+__name__)
print('package: '+ (__package__ or '当前模块不属于任何包'))
print('doc: '+ (__doc__ or '当前模块无文档注释'))
print('file: '+__file__)

# name: __main__
# package: 当前模块不属于任何包
# doc: 当前模块无注释文档
# file: /Users/Desktop/python/package2/c1.py

package2和package1中的c1.py均执行以后,打印出的结果完全不一样。package1. c1 被当做应用程序的入口文件执行的,而package2被当做普通的模块。入口文件的内置变量是不同的。

  1. 当文件被当做入口文件,则__name__会打印出__main__
  2. 当文件被当做入口文件,则顶级没有包(尽管看起来有package2)

如何将可执行文件当作一个模块来使用呢?在python命令中通过-m命令即可。前提是必须具备当作模块的条件。

1
2
3
4
5
6
7
python -m package2.c1
# 注:此时位置位于 package2

# name: __main__
# package: package2
# doc: 当前模块无注释文档
# file: /Users/Desktop/python/package2/c1.py

注:当需要把py文件作为普通模块,必须有包。因此必须进入到package目录,执行package底下的python文件才可以。

__name__的应用

关于__name__,有一句比较经典的话:

Make a script both importable and executable

说白了,就是让你的脚本既可以作为普通模块提供给其他程序调用,也可以作为可执行的文件。脚本不同的两种角色,会有不同的使用方式。下面的代码判断是否为入口文件:

1
2
if __name__ == '__main__':
pass

以下面的代码为例:

c1.py

1
2
3
4
if __name__ == '__main__':
print('This is app')

print('This is a module')

c2.py

1
import c1

如果执行c1.py文件,则说明c1为入口文件,输出的结果如下:

1
2
This is app
This is a module

如果执行c2.py文件,则说明c1为引入模块,输出的结果如下:

1
This is a module

相对导入与绝对导入

常规目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DEMO(顶级包)
|-package1 (包)
|- c1.py (模块)
|- c2.py
|- c3.py
|_ c4.py
|-package2 (包)
|- package4.py
| |_ m2.py
|_ m1.py (模块)
|-package2 (包)
|- c1.py (模块)
|- c2.py
|- c3.py
|_ c4.py
|_main.py

绝对导入

绝对引入必须从顶级包开始。

事实上,DEMO不一定为顶级包,与入口文件的位置有关。例如main.py不属于任何包

main.py

1
2
3
print(__package__)

# None

如果改成下面的写法:

m2.py

1
print(__package__)

main.py

1
2
import package2.package4.m2
# package2.package4

此时,对于m2.py来讲,顶级包为package2,并非DEMO。这是由于可执行文件main.pypackage2文件夹同级。因此,哪个文件夹为顶级包是由可执行文件(入口文件)决定的。

如果将main.py移动到DEMO文件夹同级,则m2.py的顶级包的位置也将修改。必须将main.py修改为:

main.py

1
2
import demo.package2.package4.m2
# demo.package2.package4

相对导入

import不可以使用相对路径,但使用from…import时可使用相对路径。

相对导入不能导入超过当前文件顶级包的文件。

入口文件无法使用相对路径导入。

.代表当前目录,..代表上一级目录,...代表上上级目录,以此类推。但是在可执行文件中,不能使用相对路径导入。

main.py

1
2
3
4
5
6
7
import .package2.package4.m2

### 报错信息
main.py", line 1
import .package2.package4.m2
^
SyntaxError: invalid syntax

在上面的例子中,如果在import中使用相对路径,则会报语法错误。

m2.py

1
m = 2

main.py

1
2
3
4
5
6
7
from .package2.package4.m2 import m

### 报错信息
Traceback (most recent call last):
File "/DEMO/main.py", line 1, in <module>
from .package2.package4.m2 import m
ModuleNotFoundError: No module named '__main__.package2'; '__main__' is not a package

在上面的例子中,使用from...import不再报语法错误。修改为如下目录结构:

1
2
3
4
5
6
7
8
9
DEMO(顶级包)
|-package2 (包)
|- package4.py
| |- m3.py
| |_ m2.py
|- m4.py
|_ m1.py (模块)
|- m5.py
|_ main.py

如果在 m2.py 中分别做如下引入,均为正确:

m2.py

1
2
import .m3 #正确
import ..m4 #正确

分别设置import .m3import ..m4时执行main.py文件,得到的结果为:

main.py

1
2
3
4
5
6
7
#import .m3
package2.package4
package2.package4

#import ..m4
package2 # m2中打印
package2.package4 # m4中打印

尝试引入更高层级的包,执行m2.py,则结果会报错。

m2.py

1
2
3
4
5
6
7
import ...m5

### 报错
Traceback (most recent call last):
File "m2.py", line 1, in <module>
from ...m5 import m
ValueError: attempted relative import beyond top-level package

因此,当试图引入超过顶级包的文件(包),则会报错。由于m2的顶级包为package2,当import ...m5时,已超过顶级包的位置,因此会报错。

为什么不能在入口文件中使用相对路径?

相对路径时根据当前的内置变量__name__来定位,当入口文件__name__ ==__main__时,__name__不存在的。因此不能使用相对路径。

如何在入口文件中使用相对路径?

进入入口文件的上一层目录,执行带-mpython命令。

1
2
3
cd ..
python -m demo.main
# demo.package2.package4 #m2的顶级包变为demo