让Python每次都import最新.py文件(module)的方法

出于某种执着的原因, 我对python的import+reload机制不太满意.

想想吧: import是一条statement, 连expression都不是, 而reload是一个builtin函数, 而二者要配合使用, 还有比这不科学的事吗!

最烦人的一点是: 如果在一个交互模式进程下调试一个自己写的py文件(模块), 你发现这个模块写的不好, 有bug, 或者干脆就是写的不漂亮. 你对它做了修改, 存盘了, 而你还不想新启动一个python交互模式进程, 因为进程下还有其他测试数据. 这时候一定要提醒自己: 要用reload(), 不要import. 后者压根不会重导入你修改过的模块, 关键还不会给任何提示! 多少次屁颠颠调了半天还奇怪为什么bug还在, 才想到是这里出了问题呢?

这个设定实在荒谬的让我浑身刺挠, 于是发愿一定要写一个import的替代品, 每次都载入最新的py文件.

想法其实也很简单: 既然reload才是重导入, 那我就每次都import一下, 再reload一下不就得了? (先要import一下是因为如果这个模块还没有导入过, 当前命名空间是没有这个模块的, reload会出错. 而我要建立的是一个不管这个模块导入没导入过, 都能每次导入最新版本的模块的, 吾道一以贯之的解决办法.)

说干就干, 于是我写了第一个版本:

# myimport1.py
def myimport(module_name): 
     cmd = "import {0}".format(module_name) 
     exec(cmd); 

     cmd = "reload({0})".format(module_name) 
     exec(cmd) 

运行起来就发现: 根本没屁用啊! 后来查了python的作用域原则(Learning Python 4th Charpter 17.): “所有变量名被赋值的位置决定了这个变量名能被访问到的范围”. 那么问题就显然了: 我是在一个模块myimport.py中定义了一个myimport的函数, 而import模块的位置又是在这个函数内, 那么import进来的函数的作用域也就限制在这个函数里面, 函数执行完, 导入的模块对象也就被销毁了.

看来我需要的是把模块命名在main模块的全局空间里, 在def中import是肯定不行了, 于是有了第二个版本:

# myimport2.py
modules=globals() 
def myimport(module_name): 
    modules[module_name]=__import__(module_name) 
    reload(eval(module_name)) 

if __name__==__main__:
    myimport('mytest1')

在idle下F5一试, 还真行啊! 执行完再dir(), 真的有mytest1的模块被导入进来了, 修改一下mytest1, 再执行此脚本, 果然mytest1就是新版本了呢. 但高兴没一会儿, 马上就发现这个方案也根本行不通, 因为只有把这个文件当成脚本来执行才有效, 而把它存成一个py文件, 在交互模式下执行from myimport import *, 再执行myimport(‘mytest1’), 就无效了.

这是因为:

  • 作为脚本执行时, 即idle编辑器里按F5, 是相当于把整个文件在交互模式下敲了一遍, 那么定义的函数, 执行的命令都是作用在main函数里的. 而将其单独存盘, 在python里无论是import还是from, 这个模块已经变成了次一级的空间, 将不会影响到_main_的全局空间的内容. 其中又有:
    • globals()返回的字典不只是全局变量的一个副本那么简单, 而是实实在在的全局变量的引用, 对这个字典的修改就相当于修改了真正的模块全局变量. 而对这个字典的添加也相当于增加了全局变量. (与之相对的, locals()返回的的确是一个副本, 修改locals()的变量是没屁用的. 例外在于, 将模块作为顶层模块(即main)执行时, locals()返回的也是真正的全局变量的引用, 此时locals()就相当于globals());
    • globals()作为非顶层模块使用时, 返回的就是模块中定义的全局变量, 即便在顶层模块中, 将这个模块的函数以from … import *的方式导入进来, 函数执行时的globals()也无法获得main的全局变量; 这其实也很好理解: 模块是无法通过globals()获得导入本模块的高层模块的变量信息的, 否则就乱套了.

那么我想要的功能是不是就真的无法实现了呢? 整理一下我想要的是什么: 我要的是写一个模块, 模块里有一个我写的方法, 在_main_里每次调用这个方法, 就能把模块调用到我_main_的空间里. 按照python的正常设计逻辑来说, 可以说答案是YES. 因为一个次一级的模块, 怎么可以干涉调用它的上级模块的全局空间里的对象呢? 但所谓的正常情况, 就一定有非正常情况与其对应. 尤其是python这种灵活的玩意儿, 还记得欧阳锋自创的为第二次华山论剑准备的秘密武器叫啥吗? 灵蛇拳法! 蛇这玩意儿最灵活了是不是? 于是我想到了python当中有个黑科技模块叫sys. 一查, 果然有个modules函数, 于是就有了下面这一版:

# myimport.py
def myimport(module_name):
    import sys
    exec("sys.modules['__main__'].%s = __import__('%s')" % (module_name,module_name))
    exec("reload(sys.modules['__main__'].%s)" % (module_name))

这个方法还是受到了learning python 4th Charpter 17一个例子的启发, 也就是获得一个module的引用后, 用”模块名.变量名=”的方法可以修改该模块的全局变量.

至此这个问题就基本得到了解决, 以后只要每次启动python后, 执行一条

>> from myimport import *

就可以实现myimport(‘模块名’)的每次自动导入, 自动reload了.
这里的遗憾之处在于, 每次还是要敲一行命令, 如果python环境能有个类似autoexec.bat的, 每次进去自动执行就好了, 慢慢发掘吧.

(2015-12-04 增加了自动加载最新版本的myfrom函数)

def myfrom(module, fromlist = ['*']):
    import sys

    if type(fromlist)==str:
        fromlist = fromlist.split(',')

    temp = __import__(module,globals(),locals(),fromlist, -1)
    reload(temp)

    if fromlist == ['*']:
        for var in dir(temp):
            if((not var.startswith('__')) and (not var.endswith('__'))):
                exec("sys.modules['__main__'].%s = temp.%s" % (var,var))
    else:
        for var in fromlist:
            exec("sys.modules['__main__'].%s = temp.%s" % (var,var))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值