查看原文
其他

工具技巧 | 用Python3处理数据:“import”可以这样自由地调度函数?(内附代码)

企研数据技术部 数据Seminar 2021-06-03


      最近,某数据公司的小刘老师十分苦恼。 

      因为在做大数据分析任务时,涉及到数据处理环节,需要从不同路径下调用py文件中的函数却发现很麻烦。

      像勤学好问的小编一样,小刘老师遇到不懂的问题,就喜欢去找“度娘”。关于Python默认的调用方法,不是本文重点,感兴趣可以参考: Python实现调用另一个路径下py文件中的函数方法总结(点击左下角原文链接可查看)里面讲述了五种解决办法,但没有一种能满足小刘老师要求的。因为小刘老师说想实现从电脑中任意一个位置导入一个函数或者一个类,但又不想把这些函数或者类的路径放入环境变量里,感觉不够pythonic。更重要的是文件多起来,管理起来还比较麻烦,而且会牵涉到多个文件的import的更改。(写到这里,小编我都写不下去了,因为我觉得小刘老师是朵“奇葩”,整天“不想这样,想那样”,简直“强迫症患者”。)

      那么问题来了,能让挑剔的小刘老师都竖起大拇指的解决方案到底是什么呢?

      别着急,往下看,小编为您细细道来

如何使用import实现任意位置文件中函数的自由调用?


► 1号代码块


import pkgutil

def get_obj(dir_path, mod_name, obj_name=None):
 
 """  

dir_path: str
  mod_name: str
  """

   importer = pkgutil.get_importer(dir_path)
# 返回一个FileFinder对象
   loader = importer.find_module(mod_name)
# 返回一个SourceFileLoader对象
   mod = loader.load_module()
# 返回需要的模块
   
if obj_name:
       obj =
getattr(mod, obj_name)
       
return obj
   
else:
       
return mod


      Python3中管理“import”的库是importlib,但是似乎都无法避免通过修改sys.path来使用importlib。接着,小刘老师找到了pkgutil包(Python3标准库),从源码看,它也是基于importlib。上面代码中提到的“FileFinder”和“SourceFileLoader”对象,也都是由importlib库定义的。

      再看看pkgutil.get_importer的源码:

► 2号代码块


def get_importer(path_item):
   
"""Retrieve a finder for the given path item

  The returned finder is cached in sys.path_importer_cache
  if it was newly created by a path hook.

  The cache (or part of it) can be cleared manually if a
  rescan of sys.path_hooks is necessary.
  """

   
try:
       importer = sys.path_importer_cache[path_item]
   
except KeyError:
       
for path_hook in sys.path_hooks:
           
try:
               importer = path_hook(path_item)
               sys.path_importer_cache.setdefault(path_item, importer)
               
break
           
except ImportError:
               
pass
       
else:
           importer =
None
   
return importer

      get_importer涉及到两个关键要素:path_importer_cache与path_hooks,它们是一对“好邻居”。

相关定义解释:

sys.path_hooksA list of callables that take a path argument to try to create a finder for the path. If a finder can be created, it is to be returned by the callable, else raise ImportError. Originally specified in PEP 302.

sys.path_importer_cacheA dictionary acting as a cache for finder objects. The keys are paths that have been passed to sys.path_hooks and the values are the finders that are found. If a path is a valid file system path but no finder is found on sys.path_hooks then None is stored. Originally specified in PEP 302. Changed in version 3.3: None is stored instead of imp.NullImporter when no finder is found.  

      文档里面提及的finder以及相关的loader都是跟importlib中的FileFinder、SourceLoader对象有关,这里不做深入分析。



      结合源码和官方文档,大家可以看到:归根结底,还是要看我们想要的py文件中路径的对象,是否在path_hooks的路径中。get_importer函数会在path_hooks中进行“搜索”,没有的话,返回值None。这样看来,get_importer也不是一个稳定的函数。它需要不断地尝试,以返回符合要求的importer(实际上是importlib中的FileFinder)。

      因此,小刘老师在多进程的项目中并不想使用这个函数。但由于Python3中pickle的机制原因,也未如愿以偿。这是因为在Python3中无法序列化某个函数,强行序列化后,得到的结果只有一个函数名称,反序列化的时候就会报错。(小刘老师说有空会深入研究Python3中的finder和loader,有突破再详细介绍,让我们星星眼期待一下~)

      强行序列化的结果如下:

In [1]: import sql_reader

In [2]: import pickle

In [3]: import codecs

In [4]: with codecs.open('./test''wb'as f:

   ...:     pickle.dump(sql_reader.sql_reader.connect, f) # 这里是小刘老师自己的Python代码包


      得到的test文件中的内容是:

·cbuiltins

getattr

q csql_reader

sql_reader

q·X·   connectq·DZ·Rq·.


      关闭目前的Python解释器,换个目录重新打开Python解释器后,我们可以发现:

In [1]: import codecs

In [2]: import pickle

In [3]: with codecs.open('./qy_opt/test''rb'as f:

   ...:     func = pickle.load(f)

   ...:

------------------------------------------------------------

ModuleNotFoundError      

Traceback (most recent call last)

<ipython-input-5-7300734cdec3> in<module>

      1 with codecs.open('./qy_opt/test''rb') as f:

----> 2     func = pickle.load(f)

      3


ModuleNotFoundError: No module named 'sql_reader'

      已经没有办法反序列化这个connect函数了。



     

      回到1号代码块中,函数get_obj中仅需传入三个参数:

      1.dir_path是py文件夹的路径;

      2.mod_name是py文件名,不包括扩展名;

      3.obj_name是可选参数。不填,则函数返回module对象;否则,obj_name是类名就返回类的对象,是函数名就返回函数对象。

      get_obj函数使用效果如下:

In [1]: obj = get_obj('./', 'html_table_reader'


In [2]: type(obj)                                                                         

Out[2]: module


In [3]: ', '.join(dir(obj))

Out[3]: '__builtins__, __cached__, __doc__, __file__, __loader__, __name__, __package__, __spec__, bs4, data_standardize, np, pd, standardize, sys, table_tr_td, title_standardize'


In [4]: obj = get_obj('./', 'html_table_reader', 'table_tr_td')                          


In [5]: type(obj)

Out[5]: function


In [6]: ', '.join(dir(obj))     

Out[6]: '__annotations__, __call__, __class__, __closure__, __code__, __defaults__, __delattr__, __dict__, __dir__, __doc__, __eq__, __format__, __ge__, __get__, __getattribute__, __globals__, __gt__, __hash__, __init__, __init_subclass__, __kwdefaults__, __le__, __lt__, __module__, __name__, __ne__, __new__, __qualname__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__'


      提示一下,这里的obj,已经可以当成正式的module对象或function对象来用了。所引用的py文件根本不需要放在项目文件夹下即可调用,是不是很酷炫?!


   

      ↑帅气的小刘老师

       最后,小编了解到,小刘老师平常喜欢自己“造轮子”。这段代码虽然适用范围不是很广,但是对于编写框架又不知道要用到哪些自定义函数的初学者来说,还是有一定的实用价值的。

       说完,小刘老师开始翘起了二郎腿。








数据Seminar

这里是经济学与大数据的交叉路口


作者:企研数据 · 技术部 · Dyson

审阅:企研数据 · 研究中心 · 简华

编辑:青酱



    欢迎扫描👇二维码添加关注    



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存