3

原文引至:前端小吉米
对于python中的模块和包, 我简直就想说, js nmlgb 就是一个 trash... 在前端写js根本就没有什么模块和包, 全部都是全局... 真lj...

畅快了. 写了这么久的js, 连内部的模块的包都搞的这么复杂... 艹...
在python中, 已经定义好了完美的模块和包的引用机制.
我们先来看看Module

Module

在python中, 模块实际上,就相当于与业务逻辑解耦的可重用的一些函数方法. 所以, 我们可以预先定义一个模块:

// 命令为sam.py
def add(a,b):
    return a+b

之后在同目录中,打开一个 Terminator.
接着输入:

>>> import sam
>>> sam.add(1,2)
//结果为:
3

实际上, 这就是一个简单的模块. 但,当我们导入的时候, 会在Module的目录中生成一个.pyc文件, 该其实就是用来对Module进行缓存,并编译为Binary 文件, 以便py以后再次引入.
现在, 我们已经学会如何写入Module,引入Module. 这里,我们需要更进一步去探索, python是如何进行Module Path的搜索的.

模块路径搜索

在nodeJS中, 他的搜索方式是, 先看内部定义的Module 有没有. 没有的话,则会开始寻找每一层的node_modules. 然后在里面搜索你的文件.js
那么在py中,他是怎样一个过程呢?
py将搜索路径,放在了sys.path中.我们可以查看一下. sys.path里面的内容.

import sys
sys.path

然后就会出现, 一些路径,比如这个:

['',
'C:\\Python33\\Lib\\lib-dynload',
'C:\\Windows\\system32\\python33.zip',
'C:\\Python33\\DLLs',
'C:\\Python33\\lib',
'C:\\Python33\\plat-darwin',
'C:\\Python33\\lib\\site-packages']

可以看到, 第一个为空, 他实际代表的就是current directory.
具体的顺序是:

  • 当前目录

  • sys.path定义的相关目录

  • 安装依赖的目录

所以, 如果你想自定义自己的Module 直接添加在sys.path里面即可.
那应该怎样进行添加呢?
实际上, 这就是查找关于sys.path的方法了. 还记得help()吗?
查找后,我们基本上都会明白了. md, 这不就是list吗?
那剩下的不就是调用,list的方法进行添加和删除吗?
一般而言,python的工作目录是放在对应的pythonLib文件夹内.这里,我将我常用的py 模块路径添加进去

sys.path.append('/Users/jimmy_thr/Documents/pythonLib')

之后, 我只要将我写好的Module 放在指定的文件内即可.

模块进阶

上面, 我们只是学会使用基本的Module 导入, 实际上,py提供了 更加丰富的Module statement.
比如, 重命名, 指定导入, 全导入

Module rename

这个算是一个py Module的一个附加值吧.
具体用法很简单.

import sam as sb
sb.add(1,2)

就酱, 实际上,就是使用as将模块的名字换一个. 并且, 换了之后sam, 也就不存在了.

Specific Module

直接看demo吧:

//直接导入 add方法
from sam import add
// 也可以导入多个
from sam import add,minus

这样导入的结果也是, 不能使用sam.
如果你想, 将module里面所有的方法都导入的话, 直接使用* 即可

from sam import *

不过, 真强烈不建议这么做. 因为, 并没有什么卵用, 并且, 万一出现什么bug, 都不知道这个方法哪来的.

Module complement

在nodeJS中, 他的模块引用是值引用类型, 即就是, 一次引用之后, 就会放在缓存里面, 以后如果在引用的话, 会直接从缓存里面取了.
看一个demo:

def add(a,b):
    return a+b
print(add(1,2))
//接着,我多次引用
>>> import sam
3
>>> import sam

只会出现一次, 说明, sam Module 只能导入一次. 其余的就 nonesense. 但是,如果你的模块是内部循环型的, 那这样不就go die了吗?
hehe ~ py 早教看到这一点了, 在内置的imp中提供了reload方法, 来帮助我们实现, 模块的引用更新

>>> import imp
>>> imp.reload(sam)
3

最后在补充一个dir(module)方法. 他的作用,就是用来查看指定Module当中的全局变量和函数名.

>>> dir(sam)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'add']

其余的用underscore来连接的, 是py中 module的一部分. 比较重要的看一下__name__即可. 他是用来表示,你当前所在的程序是模块,还是主程序.

  • 如果你是主程序则 __name__ 为 main

  • 如果为模块则 __name__ 为 你的模块名

另外,如果你忽略参数, 直接使用dir()的话,就是用来查看当前 全局中的变量和函数名.

>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', '__warningregistry__', 'get', 'imp', 'sam', 'set']

但,如果你导入一个内置模块的话, 比如: copy

>>> import copy
>>> dir(copy)
['Error', 'PyStringMap', '_EmptyClass', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', ...]

仔细观察, 里面的内置属性, 会看到多出一个叫__all__的属性. 这其实和下面所说的包有一些关系. 实际上, __all__ 的作用, 是提供公共接口的, 但实际上, 当我们导入方法的时候, 往往会全部倒入.比如这样.

import copy

那么此时, __all__ 对于这种方法是没有任何作用, 该语句就是用来导入copy下所有的全局变量.
现在, 假设 当前模块sam.py下有2个方法,add,minus.
我将__all__ 设置为

__all__ = ['add']

我们来试验一下两者的区别:

//首先导入:
>>> import sam
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'sam']
>>> dir(sam)
['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'add', 'minus']

可以看到,有了sam这个模块名. 并且, 查看sam中,会看到有add和minus方法.
然后,我们换一种方式导入:

>>> from sam import *
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'add']

我们会发现, minus不见了.
所以, 这里我们就可以搞懂了__all__ 到底是用来做什么的.

  • __all__: 用来设置模块的公共接口. 并且只针对于 from module import * 的语句

最后在说一下,其中的另外两个默认属性 __doc__,__file__

  • __doc__: 实际上,就是我们使用help()函数查看的内容. 内容具体就是我们写在模块开头或者函数开头的注释

  • __file__: 模块真实的文件路径, 如果你想查看源码的时候就很重要了.

放图:
python Module

Package

说完了,py中的Module, 接着就是最让你66的包. Module只是帮助我们完成了一个组件的一些小功能, 但是如果我们想要写一个可以调用的整体组件的话, 那么一个single Module显然是不够了. 所以,py 推出了package 来帮助我们完成这个巨大的工程(project). 我们可以通过个文件,调用,该组件下所有的方法. 比如,我们需要写一个html的评论框, 那么里面肯定会设计, HTML,css,js样式设计, 接收评论,发布评论等很多功能. 在包里面, 我们就可以把这些小功能进行拆分,达到复用的效果.
先看张图:
python 包
这实际上, 就是我们python包的简单格式, 在每个文件根目录都会存在__init.py__ 文件. 他的作用实际上,就是用来定义, 引用包时, 暴露的相关接口. 而关键的关键, 就是上面提到的__all__ 内置的默认关键字. how to use?
请, stackoverflow.
__init__.py就是一个导入文件
现在,我们来写一个简单的包, 以上图为例。
在Game的根目录下, __init__.py内容为:

__all__ = ['Sound','Image','Level']
from Game import Sound,Image,Level

然后,我们就可以直接应用Game 包了.


>>>import Game
>>>Game.Sound.xxx

这里,需要说明的是, 关于包的导入, 其实用不用all不是很重要, 换句话说, 应该是不推荐, 因为前文我们已经了解到, __all__ 生效的机制是 使用 from xx import * 这样的语句. 而这样做的实际效果是, 完全破坏了python的namespace机制, 也是编程语言中最重要的一个. 所以, 给的建议就是, 尽量放弃all的使用, 直接使用 import 来判断你需要导出那些公共的接口即可.
引用一段话:

Leaving an __init__.py file empty is considered normal and even a good practice, if the package’s modules and sub-packages do not need to share any code.

出自: python guider


villainhr
7.8k 声望2.2k 粉丝

引用和评论

0 条评论