python中万物皆对象。维度比较大的有模块、包。
一个.py文件就是一个python模块(module),如果一个目录下面有一个__init__.py文件,那么这个目录就是一个python包(package)。
当然,这只是极简版的概念。实际上包是一种特殊的模块,而任何定义了__path__
属性的模块都被当做包。
以两个下划线开头,以两个下划线结尾的属性,暂称魔法属性(自创的),对应的有魔法方法。——用特别的格式表示能实现一些特别的功能。
在python代码中使用模块或包,需要使用import语句。
有两种import方法:relative import和absolute import。
相对导入,是通过指出相对当前目录位置的偏移来导入对应目录下的模块。
绝对导入,就是直接指出哪个包,哪个模块。
当python解释器看到import语句后,要做两件事:找到module,将module加入到local namespace中。
具体就是,根据import的指示,去寻找(find_module)到对应的module,并导入(load_module)了。
在第一步查找时,遵循:
检查 sys.modules (保存了之前import的类库的缓存),如果module被找到,则⾛到第二步。
检查 sys.meta_path。meta_path 是一个 list,⾥面保存着一些 finder 对象,如果找到该module的话,就会返回一个finder对象。
检查⼀些隐式的finder对象,不同的python实现有不同的隐式finder,但是都会有 sys.path_hooks, sys.path_importer_cache 以及sys.path。
抛出 ImportError。
这里说几个典型应用吧。
判断模块是否已导入
若已导入,必定在sys.modules中。sys.modules是已导入模块的字典,key是模块名,value是模块对象。
获取某已导入模块的所有属性
dir(sys.modules[module_name])获取对应key==module_name的value值。
附help(dir)的输出:
Help on built-in function dir in module __builtin__:
dir(...)
dir([object]) -> list of strings
If called without an argument, return the names in the current scope.
Else, return an alphabetized list of names comprising (some of) the attributes
of the given object, and of attributes reachable from it.
If the object supplies a method named __dir__, it will be used; otherwise
the default dir() logic is used and returns:
for a module object: the module's attributes.
for a class object: its attributes, and recursively the attributes
of its bases.
for any other object: its attributes, its class's attributes, and
recursively the attributes of its class's base classes.
在其中提到了,可以使用__dir__魔法方法来自定义。
之后在inspect或是需要了解对象内部的情况时,还会用到它。
为什么会报ImportError,如何规定导入路径
sys.path 是当前环境的module搜索路径,如果想要找到某package,就需要将此package的目录加入到这个list当中,也就是对应package中的__init__.py文件所在的目录。
如使用pip安装的包就位于'/Library/Python/2.7/site-packages'目录。
通常,在这个列表中,会加入当前目录。
一个关于Class A和Class B递归导入的经典问题
见import 迷宫
sys.meta_path和其他path hook的区别
处理sys.path时会使用sys.path_hooks,它会顺序地检查sys.path中的每一项,如不能处理则抛出ImportError,如果可以返回一个importer对象,并返回。
sys.meta_path见下。
自定义导入,写一个import hook
可以通过import hook,来在模块导入时做一些工作。
我们已经知道,导入模块首先是要进行查找。而查找的第一步是检查sys.modules,第二步是检查sys.meta_path,使用其中的finder对象来查找所需要的module。
正常情况下,sys.meta_path是一个空列表。
finder对象必须实现find_module()方法,它可以找到模块,并返回一个load_module()方法。
loader对象负责加载模块,必须实现load_module()方法。
importer对象,实现了find_module()和load_module()方法,即在实际中,只需要实现一个importer类,此类有find_module()和load_module()两个方法。
具体可见上面引用的第一篇blog。
函数、类属于哪个模块
在一个文件中,可以通过from module_name import function_name或者from module_name import class_name,来导入其他模块的函数,或类。
在这种情况下,会发现这些导入的对象,也存在于该模块的dir输出中。
可以通过这些对象的__module__魔法属性来判断是在本模块中定义,还是从外部导入。
使用这个方法,也可以得知某一方法是与类属于同一模块,还是继承自基类(我并没有去验证基类和子类属于同一模块的情况,这种情况应该比较罕见吧?)。
相对导入和绝对导入
绝对导入就是明确指出是从哪个包或模块导入。
相对导入是指相对于目前模块所处的文件位置,来找到某个模块来导入。如from . import xxx是从当前模块所在的文件的同层目录中寻找,一个点表示同层目录,两个点表示上一层目录,以此类推。
相对导入会有一些麻烦。推荐使用绝对导入,这也是python推荐的。通过设置sys.path来将路径加入,这样在import时,python会到设置的路径中自动匹配。
pkgutil
最常用的有两个:
iter_modules(path=None, prefix='')
Yields (module_loader, name, ispkg) for all submodules on path, or, if path is None, all top-level modules on sys.path.
path是包的目录路径,prefix是输出时,所有包的名字的前缀。用来获取该path下的子模块或子包。
walk_packages(path=None, prefix='', οnerrοr=None)
Yields (module_loader, name, ispkg) for all modules recursively on path, or, if path is None, all accessible modules.
同上,但是这个方法是递归获取路径下的所有模块。
inspect
常同pkgutil结合用。
getmembers(object, predicate=None)
返回object的所有属性,即dir中的各项。
这些属性各种各样,通过设置predicate来进行筛选,如当predicate=ismethod,只有当其为类的方法时,才会被选中。
getdoc(object)
获取对象的docstring,即__doc__。
getargspec(func)
获取函数对象的arg。
还有很多其他方法,详见inspect的帮助文档。
最后的TODO
最近在做一个自动化测试的项目,第一步就是要找到所有的模块、类、方法、函数,以上就是涉及到的一部分基础知识。
过程中,发现dir输出的内容要远远多于真实在代码里写出来的东西,对于方法或属性,也需要去了解一下。
Python 魔术方法指南可以起到一定的帮助作用,剩下的还需要继续搜索。