更好的处理 Python 多工程 import 依赖
话说, 这段时间需要开发一个项目, 新项目对现有的几乎所有项目都有依赖。 豆瓣现存的几个大项目,基本都是围绕豆瓣主站shire的依赖, 也就是说, 其他项目对shire的单项依赖, 这些项目在需要主站shire模块的时候, 无一例外的将shire的工程路径加入到其他工程的sys.path中, 熟悉Python Import的人一定马上会意识到, 这个并不是什么好方法, 因为这样会造成Python Import的搜索路径产生一些不确定因素, 也就是Import 搜索空间污染, 比如说, 我在anduin(豆瓣说)项目中import 主站(shire)的工程,from luzong.user import User, 看起来好像是很magic的东西, 但是, 假如anduin中也有个luzong目录呢(当然这个没有), 这样就会造成搜索冲突, 严重点的可能隐士的错误的import了其他模块。 也就是说, 这个是很不确定的, 很有危险性的一个事情, 那豆瓣那些个大项目是怎么处理这个magic的危险的呢, 答: 每个新工程的顶级目录都与现存的各个工程的顶级目录不重名。
好吧, 一听就知道是个很蛋疼的想法, 我在skype上问了下anrs, 貌似他是遵守这个规定的。但是,随着内部工程量增多, 每个工程的命名都要考虑其他工程的命名是非常非常蛋疼的事情, 在我个人来看是非常要不得的, 那怎么办呢? 在这篇blog中我就循序渐进的介绍下我这两天探索到的方法, 真的非常的magic, 非常的好用:)
首先, 我们想到的是, 为了不污染import的导入空间, 我们何不把每个项目作为一个可以导入的顶层模块, 这样每个项目的内容都在自己独立的命名空间之内, 就不会出现那种很magic的隐式命名空间污染。 好吧, 这个好办, 在每个工程的顶层目录添加一个__init__.py 的空文件, 然后我们再开发一个类似import_project的东西:
def import_project(path):
sys.path.insert(0, os.path.dirname(path))
project = __import__(os.path.basename(path))
sys.path.remove(os.path.dirname(path))
globals()[os.path.basename(path)] = project
return project
然后,我们在我们项目的初始化文件中import依赖的project:
conf.__init__.py
shire = import_project(cfg.deps.shire)
使用的时候
from conf import shire
from shire.luzong.user import User
哇塞, 确实很magic, 是不是这样就完了呢, 答案往往不是那么简单, 不是那么如你所愿, 想一想anduin工程, 在豆瓣服务器上, 我们开发的时候部署了很多用来测试的工程, 比如shuo-test, anduin-test, auduin , 如果按照上面的方法, 线上的肯定没什么问题, 因为shire就是shire, 那如果我测试的那个工程顶级名目换成shire-test呢, 这下就不能处理了, 所以就有了后边的东西, 诸君且往下看:)
经过了半天的苦苦码字, 终于弄出来了一个还能用的东西, 思想就是, 对import部分进行类似勾子的hack, 让他不对这些shuo-test, anduin-test等类似的magic shring有任何的依赖。 于是开发一个函数, 这个函数实现类似普通import的功能, 但不会造成项目之间搜索空间污染的情况, 而且不依赖于这些类似shuo-test, anduin-test 的magic string, 下面我就来介绍一下我的东西, 完了之后再给出一个demo :)
码字了一天开发了下面这个东西 import_helper.py
# libs.import_helper
# -*- coding: utf-8 -*-
# luoweifeng@douban.com
import os
import sys
def import_deps(locals, path, imps=None):
from conf import cfg
pro_name, m_path = path[:path.find('.')], path[path.find('.')+1:]
re_pro_path = cfg.getByPath('deps.' + pro_name)
re_pro_dir = os.path.dirname(re_pro_path)
re_pro_name = os.path.basename(re_pro_path)
# import project
sys.path.insert(0,re_pro_dir)
project = __import__(re_pro_name)
sys.path.remove(re_pro_dir)
locals[pro_name] = project
imp_module = re_pro_name + '.' + m_path
__import__(imp_module)
module = getattr(project, m_path)
# add imps to locals
if imps:
for imp in imps:
if isinstance(imp, tuple):
locals[imp[1]] = getattr(module, imp[0])
else:
locals[imp] = getattr(module, imp)
主要的逻辑在这个文件中, 使用起来非常方便, 而且可以处理from XX import A as B等形式。 下面我就做个demo来演示一下:
1. 创建测试环境
$cd && mkdir -p test/test11 test22 && cd test
$touch test11/__init__.py && echo “age = 1″ > test11/app.py
$cd test22
我们这里创建了一个测试目录test, 两个工程test11 和test22, 这个test11 的工程名叫做test(尽管他的目录是test11, 但是就像shuo-test之于anduin一样:)
2. 创建配置文件(这里为了简单期间, 我用了个config的东西)
$mkdir conf && emacs config.cfg
path_prefix : `os.environ['HOME']`
deps :
{
test : $path_prefix + '/test/test11'
}
$emacs conf/__init__.py
import os
import posixpath
from config import Config
config_file = posixpath.abspath('conf/dimholt.cfg')
cfg = Config(file(config_file))
ok , 我们的配置文件就配置好了, 这里, 我配置了我这个工程依赖一个叫做test的工程, 这个工程的目录在我的主目录下test/test11.
3. 将import_helper.py(文章后边提供下载)放在test22目录下的libs目录下。
4. 使用演示
启动python解释器
>>> from libs.import_helper import import_deps
>>> import_deps(locals=locals(), 'test.app')
这样在你的工程中就可以访问test.app了, 如果想模拟from A import B
>>> import_deps(locals=locals(), 'test.app',['age'])
这样就会导入一个叫做age的变量, 如果还想模拟import A as B
>>> import_deps(locals=locals(), 'test.app',
[('age', 'g_age')])
再来个全面开花的
>>> import_deps(locals=locals(), 'test.app',
['age', ('name', 'screen_name')])
怎么样, 是不是很magic
总结一下, 使用这种方法之后解决了两个工程间import的问题, 一个就是import搜索路径污染, 我们通过把每个依赖的工程的import空间限制在每个工程名下面来实现, 第二个就是工程别名问题, 我们使用比较magic的这个开发的函数来解决。 个人感觉还是非常好用的,除了需要多输入一些东西。 但是好处是非常明显的, 还是值得的。
安, 北京:)
后记: (bugfixed)
话说, 本想今天好好看emacs的, 结果突然想到了一个问题, python locals和globals的处理方式是不同的, globals()是可以被修改的, 而locals()是不能被修改的, 所以上面的是不能用的, 尝试了各种试图修改locals的方法都不是很好, 所以更改了下工程的使用方式, 再加了一个对项目import的支持:
import_helper.py
# libs.import_helper
# -*- coding: utf-8 -*-
# luoweifeng@douban.com
import os
import sys
def import_deps(path, imps=None):
if '.' not in path:
pro_name, m_path = path, ''
else:
pro_name, m_path = path[:path.find('.')], path[path.find('.')+1:]
from conf import cfg
re_pro_path = cfg.getByPath('deps.' + pro_name)
re_pro_dir = os.path.dirname(re_pro_path)
re_pro_name = os.path.basename(re_pro_path)
sys.path.insert(0,re_pro_dir)
project = __import__(re_pro_name)
sys.path.remove(re_pro_dir)
if not m_path:
return project
imp_module = re_pro_name + '.' + m_path
__import__(imp_module)
module = getattr(project, m_path)
if imps:
return [getattr(module, imp) for imp in imps]
return module
使用的时候:
>>> from libs.import_helper import import_deps
可以直接import 顶层工程module
>>>shire = import_deps('shire')
也可以import任意工程外module
>>>user = import_deps('shire.luzong.user')
可以指定类似from import的方式
>>>User = import_deps('shire.luzong.user', ['User'])
当然如果你想模拟as的操作
>>>ShireUser = import_deps('shire.luzong.user', ['User'])
后边的部分可以是个list, 表示import多个参数
>>>get_user_rank, User = import_deps('shire.luzong.user',
['get_user_rank', 'User'])
总结, 因为locals空间不能修改, 所以使用这个方法来处理, 如果您能确定可以使用globals空间, 那加上globals的也行, 就跟以前那个方法一样, 不过传递globals参数, 在module层是没有locals的, 在module层传递locals其实是用的globals,切记。
摘自 python.cn
更好的处理 Python 多工程 import 依赖的更多相关文章
- python 小技巧(import模块、查询类继承关系、安装包)
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 在这里列举一些我使用Python时积累的小技巧.这些技巧是我在使用Python过程 ...
- python 之禅 import this
dongweiming的博客 前言 我这个博客一直都是一些技术分享,show code的地方,我从来没有写过个人生活或者情感杂谈,当然我也从来没有谈论过我对什么东西的喜恶. 很多人喜欢喷XX语言,喜欢 ...
- Python引用(import)文件夹下的py文件的方法
Python的import包含文件功能就跟PHP的include类似,但更确切的说应该更像是PHP中的require,因为Python里的import只要目标不存在就报错程序无法往下执行.要包含目录里 ...
- 孤荷凌寒自学python第四十六天开始建构自己用起来更顺手一点的Python模块与类尝试第一天
孤荷凌寒自学python第四十六天开始建构自己用起来更顺手一点的Python模块与类,尝试第一天 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 按上一天的规划,这是根据过去我自学其它编程语 ...
- python scrapy cannot import name xmlrpc_client的解决方案,解决办法
安装scrapy的时候遇到如下错误的解决办法: "python scrapy cannot import name xmlrpc_client" 先执行 sudo pip unin ...
- python 导入模块 import 理解
--python 导入模块 import 理解 -----------------------------------2014/03/18 python 导入一个模块的过程要求有一个叫做“路径搜索”的 ...
- python中的import,reload,以及__import__
python中的import,reload,以及__import__ 分类: UNIX/LINUX C/C++LINUX/UNIX shellpython2013-04-24 20:294536人阅读 ...
- Python中的import语句
Python中的import语句是导入一个文件,这条语句主要做三件事: 1 通过一定的方式,搜寻要导入的文件: 2 如果需要,就编译这个文件: 3 运行这个文件 但是,需要注意的是,所有这三个步骤,都 ...
- 终于解决了python 3.x import cv2 “ImportError: DLL load failed: 找不到指定的模块” 及“pycharm关于cv2没有代码提示”的问题
终于解决了python 3.x import cv2 “ImportError: DLL load failed: 找不到指定的模块” 及“pycharm关于cv2没有代码提示”的问题 参考 :h ...
随机推荐
- K8S 从入门到放弃系列文章目录(Kubernetes 1.14)
1)软件环境 软件 版本 系统 Centos7.5 Kubernetes 1.14.1 Docker 18.09 Calico 3.6 Etcd 3.3.12 2)部署过程简单概要 三台master节 ...
- 有关Linux服务器的一些配置
1.Redis部署 1.版本 redis-3.0.72.上传解压 3.编译 make && make install 问题:/bin/sh: cc: command not found ...
- Python 【模块】
A 什么是模块 最高级别的程序组织单元(模块什么都能封装) 模块中,我们不但可以直接存放变量,还能存放函数,还能存放类 定义变量需要用赋值语句,封装函数需要用def语句,封装类需要用class语句,但 ...
- STM32的I2C特性及架构
软件模拟协议:使用CPU直接控制通讯引脚(GPIO)的电平,产生出符合通讯协议标准的逻辑. 硬件实现协议:由STM32的I2C片上外设专门负责实现I2C通讯协议,只要配置好该外设,它就会自动根据协议要 ...
- python中集合set,字典dict和列表list的区别以及用法
python中set代表集合,list代表列表,dict代表字典 set和dict的区别在于,dict是存储key-value,每一个key都是唯一的,set相对于dict存储的是key,且key是唯 ...
- C# EntityCollection 和 List 互转
private EntityCollection<T> ToEntityCollection<T>(this List<T> list) where T : cla ...
- Unity场景间数据传递方法
在游戏开发中,会常用到场景间传递数据的方法(比如关卡选择,过关后自动回到关卡选择界面,以动画方式解锁下一关),目前研究了三种: 1. 使用DontDestroyOnLoad方法: (1)在场景A中做个 ...
- mini.DataGrid使用说明
mini.DataGrid表格.实现分页加载.自定义列.单元格渲染.行编辑器.锁定列.过滤行.汇总行等功能.Extend mini.PanelUsage <div id="dat ...
- 超详细Nginx的安装和配置教程
一. 编译安装nginx 下载nginx安装包 wget http://nginx.org/download/nginx-1.8.0.tar.gz 也可以选择其他版本,官网:http://nginx. ...
- 用cef Python打造自己的浏览器
背景 项目需要做一个客户端的壳,内置浏览器,访问指定 的url 采用技术 python3.5 cefpython https://github.com/cztomczak/cefpython#inst ...