写代码的时候路径很烦人!找不到指定路径?路径导入器了解一下?

写代码的时候路径很烦人!找不到指定路径?路径导入器了解一下?,第1张

概述pathentryfinders我们知道在sys.meta_path中默认存在三种Finder:BuiltinImporter,FrozenImporter和PathFinder。其中第三种就是默认的pathentryfinder。它的作用是完成所有基于路径导入工作。所有的对某个路径下

path

entry finders

我们知道在sys.Meta_path中默认存在三种Finder:Builtinimporter,FroZenimporter和pathfinder。其中第三种就是默认的path entry finder。它的作用是完成所有基于路径的导入工作。所有的对某个路径下的包或模块的导入(例如绝对导入和显式相对导入,甚至zipimport),都是由pathfinder完成的。需要强调的是,这里所说的路径可以是文件系统中的目录,也可以是一个URL,也可以是一个压缩包,甚至可以是数据库等。由于它也是一个Finder,那么它就存在方法find_spec用于寻找目标模块的spec对象。我们尝试从sys.Meta_path中删除这个Finder,那么导入自定义的模块就无法工作了:

# .# ├── a.py# └── b.py# b.pyimport sysfrom pprint import pprintpprint(sys.Meta_path)# [,# ,# ]# 尝试删除pathfindersys.Meta_path.pop(-1)import a# ModuleNotFoundError: No module named 'a'

path entry finders究竟是怎么工作的呢?

进群:548377875  即可获取数十套pdf哦!

当我们导入一个基于路径的模块时(例如一个自定义的模块),Python会从sys.Meta_path找到pathfinder来处理。pathfinder会在一个指定的路径列表中搜索finder,这个路径列表可能是sys.path,也可能是包的__path__属性。这里,Python利用了缓存的机制来加快搜索速度。因为某一个路径可能会被多次搜索到,Python会将路径与finder的对应关系缓存至sys.path_importer_cache中,这样,下次搜索相同路径就会直接调用缓存中的finder获取spec。如果缓存中不存在路径的finder,则会利用sys.path_hooks中的函数来尝试创建finder,并将路径作为参数传入函数中,否则缓存一个None表明该路径无法创建finde

整个流程略显复杂,其核心是三个变量的关系:sys.path,sys.path_importer_cache和sys.path_hooks。sys.path存储着搜索模块的路径,而sys.path_importer_cache则缓存着上述路径所对应的finders,最后,sys.path_hooks存储着用于从指定路径返回finder的可调用对象。

我们通过下面一个栗子来展示一下上述流程。首先我们定义一个Finder类,与之前不同的是,Finder类需要一个初始化方法接收一个path参数。find_spec是必须的。之后,我们将Finder类加入sys.path_hooks中,来看看其调用流程:

# 目录namespace下import sysfrom pprint import pprintimport importlib.util,importlib.machineryclass pathfinder: def __init__(self,path): print('Initial path {} for pathfinder'.format(path)) self._path = path def find_spec(self,fullname,path=None,target=None): if path is None: path = self._path print('fullname: {},path: {},target: {}'.format(fullname,path,target)) return importlib.machinery.ModuleSpec(fullname,loader=self) def create_module(self,fullname): return None def exec_module(self,module): return Nonesys.path_importer_cache.clear()sys.path_hooks.insert(0,pathfinder)import a# Initial path ~/namespace for pathfinder# fullname: a,path: ~/namespace,target: Nonepprint(sys.path_importer_cache)# {'~/namespace': <__main__.PathFinder object at 0x7f2c954cc400>}

我们在sys.path_hooks中插入了pathfinder类之后,导入一个基于路径的模块就会调用pathfinder(path)产生一个finder对象,之后一方面会将该对象缓存进sys.path_importer_cache中,另一方面,Python会调用该对象的find_spec方法以及create_module和exec_module来导入模块。由于sys.path_importer_cache的作用,下一次该路径下的模块导入就不再创建新的对象了:

import b# fullname: a,target: None

这个基于路径的导入流程我们用一段代码来简单展示:

def _get_spec(fullname,target=None): if path is None: path = sys.path for entry in path: try: finder = sys.path_importer_cache[entry] except KeyError: for hook in sys.path_hooks: try: finder = hook(entry) except importError: continue else: finder = None sys.path_importer_cache[entry] = finder if finder is not None: spec = finder.find_spec(fullname,target) if spec is None: continue return spec

上述代码简单展示了Python中path entry finders的内部机制,当然其中略去了很多细节,仅供理解。

二种“钩”

path_hooks有什么实际的用处吗?最常见的用法是代替Meta_path作为导入钩的另一种实现方式。我们可以将自定义的钩函数放进path_hooks中,在导入前做一些个性化工作。下面举个例子:

导入配置文件

通常情况下,我们想要导入一个配置文件,需要打开该文件,并依照某一格式来解析配置内容。这里我们尝试利用路径导入钩来实现配置文件的导入功能。我们假定配置文件均位于config目录下(时刻注意path entry finder是针对路径层级的finder),我们来尝试构建一个用于导入config中JsON格式的配置文件的path entry finder:

import Jsonimport typesimport pathlibimport importlib.machineryclass JsONimporter: @classmethod def hook(cls,path): '''这里定义用于放入path_hooks的钩函数,只处理config目录''' if path.endswith('config'): return cls(path) # 如果是config目录,则实例化一个对象。 def __init__(self,path): self._path = pathlib.Path(path) self._allconfig = self._path.glob('*.Json')  # glob用于遍历出所有.Json格式的文件 def find_spec(self,target=None): fullpath = self._path / pathlib.Path(fullname + '.Json') if fullpath in self._allconfig: spec = importlib.machinery.ModuleSpec(fullname,self,is_package=True) spec.origin = fullpath # 这里为了后续加载便利 return spec else: return None def create_module(self,spec): module = types.ModuleType(spec.name) module.__file__ = spec.origin # spec对象和module对象对同一个内容的属性名不同 return module def exec_module(self,module): try: config = Json.loads(module.__file__.read_text()) # 这里是实际加载语句 module.__dict__.update(config) except fileNotFoundError: raise importError('No module named '{}'.'.format(module.__name__)) from None except Json.JsONDecodeError: raise importError('Unable to load JsON,object format is corrupted,file: {}'.format(module.__name__)) from Noneimport syssys.path_hooks.insert(0,JsONimporter.hook) # 需要插入到path_hooks的第一项sys.path.append('./config') # 需要将路径加入sys.path# sys.path_importer_cache.pop('./config') 要保证cache中没有config路径

上面我们构建了一个导入配置的importer,下面来看看如何使用它,下面是development.Json配置文件的内容:

{ "host": "localhost","port": "8080","auth": { "username": "Python","password": "****" }}

下面是main.py文件的内容:

# 目录结构# .# ├── config# │ └── development.Json# ├── configimporter.py# └── main.py# main.pyimport configimporterimport development as devprint(dev.host)# localhostprint(dev.auth)# {'username': 'Python','password': '********'}from development import portprint(port)# 8080

更多的例子可以参考Python Cookbook中给出的两个较复杂的例子,一个是导入一个URL路径,另一个是在导入模块前修改模块内容。

VS

Meta path finders

path entry finders和Meta path finders有什么区别呢?虽然二者的工作流程几乎相同,但是它们还是有着细微的差别,path entry finders主要用于处理路径级别的导入,简单来说,一个路径对应一个path entry finders;而Meta path finders则用于对于特定类型模块的自定义导入方式。想要自定义path entry finders,需要插入到sys.path_hooks变量中,并保证目标路径位于sys.path中,且sys.path_importer_cache中没有缓存;而想要自定义Meta path finders,则需要插入到sys.Meta_path中。

总结

以上是内存溢出为你收集整理的写代码的时候路径很烦人!找不到指定路径?路径导入器了解一下?全部内容,希望文章能够帮你解决写代码的时候路径很烦人!找不到指定路径?路径导入器了解一下?所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1208675.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-04
下一篇 2022-06-04

发表评论

登录后才能评论

评论列表(0条)

保存