# package2/module3.py import Cx # 隐式相对导入 from . import Cx # 显式相对导入 from .subpackage1.module5 import Fy
代码中.表示当前文件所在的目录,如果是..就表示该目录的上一层目录,三个.、四个.依次类推。可以看出,隐式相对导入相比于显式相对导入无非就是隐含了当前目录这个条件,不过这样会容易引起混乱,所以在?PEP 328的时候被正式淘汰,毕竟“Explicit is better than implicit”。
Traceback (most recent call last): File "run.py", line 1, in from .package1 import module2 # 没有找到父级包 ImportError: attempted relative import with no known parent package
为什么会这样呢?根据?PEP 328的解释
Relative imports use a module’s name attribute to determine that module’s position in the package hierarchy. If the module’s name does not contain any package information (e.g. it is set to main ) then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.相对导入通过使用模块的__name__属性来确定模块在包层次结构中的位置。如果该模块的名称不包含任何包信息(例如,它被设置为__main__),那么相对引用会认为这个模块就是顶级模块,而不管模块在文件系统上的实际位置。
Imports should usually be on separate lines(不同包分行写)
Imports are always put at the top of the file, just after any module comments and docstrings, and before module globals and constants(位置在文件的顶部,就在任何模块注释和文档字符串之后,模块全局变量和常量之前)
Absolute imports are recommended, as they are usually more readable and tend to be better behaved (or at least give better error messages) if the import system is incorrectly configured (such as when a directory inside a package ends up on sys.path)(推荐绝对导入,涉及到项目结构,后面会提到)
Wildcard imports (fromimport *) should be avoided, as they make it unclear which names are present in the namespace, confusing both readers and many automated tools(尽量使用通配符导入)
Imports are always put at the top of the file, just after any module comments and docstrings, and before module globals and constants.
Imports should be grouped in the following order:
Standard library imports.
Related third party imports.
Local application/library specific imports.
You should put a blank line between each group of imports. (不同类别的包导入顺序、以及间隔为一行)
以及?PEP 328的
Rationale for Parentheses
Instead, it should be possible to use Python's standard grouping mechanism (parentheses) to write the import statement:
根据上面的规范,我们照样举个案例
# 多行拆分 # 建议 import os import sys # 同包允许不分 from subprocess import Popen, PIPE # 不建议 import os,sys
# 文件顶部 # 注释.... import os a = 1
# 导入你需要的,不要污染local空间 # 建议 from sys import copyright # 不建议 from sys import *
# 包导入顺序 import sys # 系统库
import flask # 三方库
import my # 自定义库
# 利用好python的标准括号分组 # 建议 from sys import (copyright, path, modules) # 不建议 from sys import copyright, path, \ modules
Python code in one module gains access to the code in another module by the process of importing it. The import statement is the most common way of invoking the import machinery, but it is not the only way. Functions such as importlib.import_module() and built-in import() can also be used to invoke the import machinery.The import statement combines two operations; it searches for the named module, then it binds the results of that search to a name in the local scope. The search operation of the import statement is defined as a call to the import() function, with the appropriate arguments. The return value of import() is used to perform the name binding operation of the import statement. See the import statement for the exact details of that name binding operation.A direct call to import() performs only the module search and, if found, the module creation operation. While certain side-effects may occur, such as the importing of parent packages, and the updating of various caches (including sys.modules), only the import statement performs a name binding operation.When an import statement is executed, the standard builtin import() function is called. Other mechanisms for invoking the import system (such as importlib.import_module()) may choose to bypass import() and use their own solutions to implement import semantics.
Import a module. Because this function is meant for use by the Python interpreter and not for general use, it is better to use importlib.import_module() to programmatically import a module.
The globals argument is only used to determine the context; they are not modified. The locals argument is unused. The fromlist should be a list of names to emulate ``from name import ...'', or an empty list to emulate ``import name''. When importing a module from a package, note that __import__('A.B', ...) returns package A when fromlist is empty, but its submodule B when fromlist is not empty. The level argument is used to determine whether to perform absolute or relative imports: 0 is absolute, while a positive number is the number of parent directories to search relative to the current module. """ pass
/* XOptions is initialized after first some imports. * So we can't have negative cache before completed initialization. * Anyway, importlib._find_and_load is much slower than * _PyDict_GetItemIdWithError(). */ if (import_time) { static int header = 1; if (header) { fputs("import time: self [us] | cumulative | imported package\n", stderr); header = 0; }
_NEEDS_LOADING = object() def _find_and_load(name, import_): """Find and load the module.""" # 加了多线程锁的管理器 with _ModuleLockManager(name): # 又在sys.modules中寻找了一遍,没有模块就调用_find_and_load_unlocked函数 module = sys.modules.get(name, _NEEDS_LOADING) if module is _NEEDS_LOADING: # name是绝对路径名称 return _find_and_load_unlocked(name, import_)
if module is None: message = ('import of {} halted; ' 'None in sys.modules'.format(name)) raise ModuleNotFoundError(message, name=name) _lock_unlock_module(name) return module
def _find_and_load_unlocked(name, import_): path = None # 拆解绝对路径 parent = name.rpartition('.')[0] if parent: if parent not in sys.modules: _call_with_frames_removed(import_, parent) # Crazy side-effects! # 再次寻找 if name in sys.modules: return sys.modules[name] parent_module = sys.modules[parent] try: # 拿父类模块的__path__属性 path = parent_module.__path__ except AttributeError: msg = (_ERR_MSG + '; {!r} is not a package').format(name, parent) raise ModuleNotFoundError(msg, name=name) from None # 这里体现出搜索的操作了 spec = _find_spec(name, path) if spec is None: raise ModuleNotFoundError(_ERR_MSG.format(name), name=name) else: # 这里体现出加载的操作了 module = _load_unlocked(spec) if parent: # Set the module as an attribute on its parent. parent_module = sys.modules[parent] setattr(parent_module, name.rpartition('.')[2], module) return module
def _find_spec(name, path, target=None): """Find a module's spec.""" # 获取meta_path meta_path = sys.meta_path if meta_path is None: # PyImport_Cleanup() is running or has been called. raise ImportError("sys.meta_path is None, Python is likely " "shutting down")
if not meta_path: _warnings.warn('sys.meta_path is empty', ImportWarning)
# We check sys.modules here for the reload case. While a passed-in # target will usually indicate a reload there is no guarantee, whereas # sys.modules provides one. # 如果模块存在在sys.modules中,则判定为需要重载 is_reload = name in sys.modules # 遍历meta_path中的各个finder for finder in meta_path: with _ImportLockContext(): try: # 调用find_spec函数 find_spec = finder.find_spec except AttributeError: # 如果沒有find_spec屬性,则调用_find_spec_legacy spec = _find_spec_legacy(finder, name, path) if spec is None: continue else: # 利用find_spec函数找到spec spec = find_spec(name, path, target) if spec is not None: # The parent import may have already imported this module. if not is_reload and name in sys.modules: module = sys.modules[name] try: __spec__ = module.__spec__ except AttributeError: # We use the found spec since that is the one that
# we would have used if the parent module hadn't # beaten us to the punch. return spec else: if __spec__ is None: return spec else: return __spec__ else: return spec else: return None
All methods are either class or static methods to avoid the need to instantiate the class.
""" @classmethod def find_spec(cls, fullname, path=None, target=None): if path is not None: return None # 判断是否是内置模块 if _imp.is_builtin(fullname): # 调用spec_from_loader函数,由参数可知,loader为自身,也就是表明BuiltinImporter这个类不仅是个查找器也是一个加载器 return spec_from_loader(fullname, cls, origin='built-in') else: return None
def spec_from_loader(name, loader, *, origin=None, is_package=None): """Return a module spec based on various loader methods.""" # 根据不同loader的方法返回ModuleSpec对象 if hasattr(loader, 'get_filename'): if _bootstrap_external is None: raise NotImplementedError spec_from_file_location = _bootstrap_external.spec_from_file_location
if is_package is None: return spec_from_file_location(name, loader=loader) search = [] if is_package else None return spec_from_file_location(name, loader=loader, submodule_search_locations=search)
if is_package is None: if hasattr(loader, 'is_package'): try: is_package = loader.is_package(name) except ImportError: is_package = None # aka, undefined else: # the default is_package = False # 最后返回ModuleSpec对象 return ModuleSpec(name, loader, origin=origin, is_package=is_package)
def _find_spec_legacy(finder, name, path): # This would be a good place for a DeprecationWarning if # we ended up going that route. loader = finder.find_module(name, path) if loader is None: return None return spec_from_loader(name, loader)
那find_spec和find_module的关系是?
在?PEP 451 -- A ModuleSpec Type for the Import System中就已经提供Python 3.4版本之后会以find_spec来替代find_module,当然,为了向后兼容,所以就出现了我们上面显示的错误捕获。
Finders are still responsible for identifying, and typically creating, the loader that should be used to load a module. That loader will now be stored in the module spec returned by find_spec() rather than returned directly. As is currently the case without the PEP, if a loader would be costly to create, that loader can be designed to defer the cost until later.
Finders must return ModuleSpec objects when find_spec() is called. This new method replaces find_module() and find_loader() (in the PathEntryFinder case). If a loader does not have find_spec(), find_module() and find_loader() are used instead, for backward-compatibility.
Adding yet another similar method to loaders is a case of practicality. find_module() could be changed to return specs instead of loaders. This is tempting because the import APIs have suffered enough, especially considering PathEntryFinder.find_loader() was just added in Python 3.3. However, the extra complexity and a less-than- explicit method name aren't worth it.
2.2 扩展的Finder
除了两个标准的Finder外,我们还需要注意到的是第三个扩展的类
# importlib/_bootstrap_external.py
class PathFinder:
"""Meta path finder for sys.path and package __path__ attributes.""" # 为sys.path和包的__path__属性服务的元路径查找器 @classmethod def _path_hooks(cls, path): """Search sys.path_hooks for a finder for 'path'.""" if sys.path_hooks is not None and not sys.path_hooks: _warnings.warn('sys.path_hooks is empty', ImportWarning) # 从sys.path_hooks列表中搜索钩子函数调用路径 for hook in sys.path_hooks: try: return hook(path) except ImportError: continue else: return None
@classmethod def _path_importer_cache(cls, path): """Get the finder for the path entry from sys.path_importer_cache.
If the path entry is not in the cache, find the appropriate finder and cache it. If no finder is available, store None.
""" if path == '': try: path = _os.getcwd() except FileNotFoundError: # Don't cache the failure as the cwd can easily change to # a valid directory later on. return None # 映射到PathFinder的find_spec方法注释,从以下这两个路径中搜索分别是sys.path_hooks和sys.path_importer_cache # 又出现了sys.path_hooks的新概念 # sys.path_importer_cache是一个finder的缓存,重点看下_path_hooks方法 try: finder = sys.path_importer_cache[path] except KeyError: finder = cls._path_hooks(path) sys.path_importer_cache[path] = finder return finder
@classmethod def _get_spec(cls, fullname, path, target=None): """Find the loader or namespace_path for this module/package name.""" # If this ends up being a namespace package, namespace_path is # the list of paths that will become its __path__ namespace_path = [] # 对path列表中的每个path查找,要不是sys.path要不就是包的__path__ for entry in path: if not isinstance(entry, (str, bytes)): continue # 再次需要获取finder finder = cls._path_importer_cache(entry) if finder is not None: # 找到finder之后就和之前的流程一样 if hasattr(finder, 'find_spec'): # 如果查找器具备find_spec的方法,则和之前说的默认的finder一样,调用其find_spec的方法 spec = finder.find_spec(fullname, target) else: spec = cls._legacy_get_spec(fullname, finder) if spec is None: continue if spec.loader is not None: return spec portions = spec.submodule_search_locations if portions is None: raise ImportError('spec missing loader') # This is possibly part of a namespace package. # Remember these path entries (if any) for when we # create a namespace package, and continue iterating # on path. namespace_path.extend(portions) else: # 没有找到则返回带有namespace路径的spec对象,也就是创建一个空间命名包的ModuleSpec对象 spec = _bootstrap.ModuleSpec(fullname, None) spec.submodule_search_locations = namespace_path return spec
@classmethod def find_spec(cls, fullname, path=None, target=None): """Try to find a spec for 'fullname' on sys.path or 'path'. 搜索是基于sys.path_hooks和sys.path_importer_cache的 The search is based on sys.path_hooks and sys.path_importer_cache. """ # 如果没有path就默认使用sys.path if path is None: path = sys.path # 调用内部私有函数_get_spec获取spec spec = cls._get_spec(fullname, path, target) if spec is None: return None elif spec.loader is None: # 如果没有loader,则利用命令空间包的查找方式 namespace_path = spec.submodule_search_locations if namespace_path: # We found at least one namespace path. Return a spec which # can create the namespace package. spec.origin = None spec.submodule_search_locations = _NamespacePath(fullname, namespace_path, cls._get_spec) return spec else: return None else: return spec
我们先了解下代码中新出现的一个概念,sys.path_hooks,它的具体内容是
>>> sys.path_hooks [ 'zipimport.zipimporter'>, <function FileFinder.path_hook..path_hook_for_FileFinder at 0x000001A014AB4708> ]
Interactions with the file system are cached for performance, being refreshed when the directory the finder is handling has been modified.
""" def _get_spec(self, loader_class, fullname, path, smsl, target): loader = loader_class(fullname, path) return spec_from_file_location(fullname, path, loader=loader, submodule_search_locations=smsl) def find_spec(self, fullname, target=None): """Try to find a spec for the specified module.
Returns the matching spec, or None if not found. """ # Check for a file w/ a proper suffix exists. for suffix, loader_class in self._loaders: full_path = _path_join(self.path, tail_module + suffix) _bootstrap._verbose_message('trying {}', full_path, verbosity=2) if cache_module + suffix in cache: if _path_isfile(full_path): return self._get_spec(loader_class, fullname, full_path, None, target) if is_namespace: _bootstrap._verbose_message('possible namespace for {}', base_path) spec = _bootstrap.ModuleSpec(fullname, None) spec.submodule_search_locations = [base_path] return spec return None
def spec_from_file_location(name, location=None, *, loader=None, submodule_search_locations=_POPULATE): """Return a module spec based on a file location.
To indicate that the module is a package, set submodule_search_locations to a list of directory paths. An empty list is sufficient, though its not otherwise useful to the import system.
The loader must take a spec as its only __init__() arg.
# Pick a loader if one wasn't provided. if loader is None: for loader_class, suffixes in _get_supported_file_loaders(): if location.endswith(tuple(suffixes)): loader = loader_class(name, location) spec.loader = loader break else: return None
# Set submodule_search_paths appropriately. if submodule_search_locations is _POPULATE: # Check the loader. if hasattr(loader, 'is_package'): try: is_package = loader.is_package(name) except ImportError: pass else: if is_package: spec.submodule_search_locations = [] else: spec.submodule_search_locations = submodule_search_locations if spec.submodule_search_locations == []: if location: dirname = _path_split(location)[0] spec.submodule_search_locations.append(dirname)
def module_from_spec(spec): """Create a module based on the provided spec.""" # 创建module对象,把spec的所有属性都赋予给module,参考_init_module_attrs方法 # Typically loaders will not implement create_module(). module = None if hasattr(spec.loader, 'create_module'): # If create_module() returns `None` then it means default # module creation should be used. module = spec.loader.create_module(spec) elif hasattr(spec.loader, 'exec_module'): raise ImportError('loaders that define exec_module() ' 'must also define create_module()') if module is None: module = _new_module(spec.name) _init_module_attrs(spec, module) return module
def _load_backward_compatible(spec): # (issue19713) Once BuiltinImporter and ExtensionFileLoader # have exec_module() implemented, we can add a deprecation # warning here. try: spec.loader.load_module(spec.name) except: if spec.name in sys.modules: module = sys.modules.pop(spec.name) sys.modules[spec.name] = module raise
def _load_unlocked(spec): # 此时,我们已经拿到了具体的ModuleSpec对象,要由_load_unlocked帮我们加载到系统当中 # A helper for direct use by the import system. if spec.loader is not None: # Not a namespace package. # 判断ModuleSpec对象是否具备exec_module方法 if not hasattr(spec.loader, 'exec_module'): # 没有的话,则调用load_module的方法 return _load_backward_compatible(spec) # 创建module对象 module = module_from_spec(spec)
# This must be done before putting the module in sys.modules # (otherwise an optimization shortcut in import.c becomes # wrong). spec._initializing = True try: # 先占位,此时还未加载好module sys.modules[spec.name] = module try: if spec.loader is None: if spec.submodule_search_locations is None: raise ImportError('missing loader', name=spec.name) # A namespace package so do nothing. else: # 调用各个loader特有的exec_module,真正开始加载module类似于不同finder的find_spec方法 spec.loader.exec_module(module) except: try: # 失败 del sys.modules[spec.name] except KeyError: pass raise # Move the module to the end of sys.modules. # We don't ensure that the import-related module attributes get # set in the sys.modules replacement case. Such modules are on # their own. module = sys.modules.pop(spec.name) sys.modules[spec.name] = module _verbose_message('import {!r} # {!r}', spec.name, spec.loader) finally: spec._initializing = False