对一个游戏来说,无论是client或server都非常需要一套代码热更新的机制。它能大大提高开发效率,又能超乎玩家期望地在运营期在线修正bug和增添功能。可谓必备机制。

热更新机制的目标是:

(1)更新代码定义

(2)不更新数据对象

(3)不要依赖热更新机制解决所有问题。过于复杂的改动,重启进程

具体到Python这个语言而言,目标便是:

(1)更新类/函数及衍生对象:class/function/method/classmethod/staticmethod

(2)不更新除了(1)中的其他类型对象

(3)不要依赖热更新机制解决所有问题。过于复杂的改动,重启进程

第(3)点将我解救出来了:不要把所有责任压在热更新机制上。

本文所指模块只限于.py/.pyc/.pyo...(即非dll/so/bulitin)为载体的模块。

Python的__builtins__中有一个众所周知的reload,但它在大项目中的可用性几乎为零也是众所周知的。它辜负了Python Documentation中对它的评价:

"This is useful if you have edited the module source file using an external editor and want to try out the new version without leaving the Python interpreter"

这里简单翻译一下Python内建的reload的说明:

当reload(M)被执行后:

* M模块将被重新解释字节码。并再执行模块级定义的执行语句(译注:由此应认识到在模块级就编写函数调用和类对象生成是多么坏的习惯呀)。并在M模块内定义一个新的 命名->新对象 的命名空间映射。

* M模块reload前的所有旧对象,直到它们的引用数量降到0,才可能被gc回收。

* M模块的命名空间中的命名全部指向了新的对象。

* 其他模块中对M模块reload前的旧对象的引用,仍然维持旧对象的引用; 如果你希望其他模块对M模块的相关对象引用能同时更新为M中的新对象, 那需要你自己动手。

一些reload函数的注意事项:

* 如果旧的模块M命名空间中的某个命名x在修改后的模块M中不存在,那reload(M)后,M.x仍然有效,并继续引用着reload(M)前的那个对象。 (译注:由于reload存在这个设定,所以下面要实现的reloadx将实现不了一个功能:即使修改模块M来删除命名,reloadx也不能删除原模块命名空间内的命名!)

* 由于存在上面一个设定,一个防止数据对象被reload重置的编码方案是:

try:

users

except NameError:

users = {"AKara", "Sheldon Cooper"} * 如果模块B使用 from M import ... 的方式从模块M中导入对象引用, 那么reload(M)不会令B中的已导入对象产生任何影响;如果你需要实现这种影响,那需要自己动手在执行一次from .. import;又或者修改代码,使用 M.name 的方式来引用A中的对象。

* 如果一个模块已经产生了它的某个class的instance,那重定义这个class并reload这个模块,并不能影响已经存在的instance的class————这个instance还在用着reload前的class。这个限制对派生类一样存在。

会发现我们其实更希望reload应该至少长成这样子:

[1] reload(M)后,所有reload前生成的M中的类的instance(无论它在哪里),自动引用新的类实现。

[2] reload(M)后,所有对M中的function对象的引用(无论以什么方式引用),自动更新到新版本函数定义。

[3] 不需要 try .. except NameError 的编码方式,便能令reload不重置数据对象。即所有cls inst,dict, list, set, frozenset, tuple, string, None, Boolean...对象复用旧对象。

有了功能需求定义,再联系上面的[热更新机制的目标],不妨实现一个reloadx。实现的核心思路有两种:

思路1(函数和方法的更新):

Python中,一切皆为对象。(有人欢喜有人愁呀;Python的慢是有理由的)

显然,function/method/staticmethod/classmethod/class 均为对象。而变量名和对象之间的关系其实只是一种命名空间和对象空间中的引用映射(或许这事实困扰不少初学者:"Python函数传参到底是传值还是传地址?"),而对象空间中的每个对象是唯一的,有唯一的address(即id(obj));

所以,要实现这点,只需要遵守一个原则:保持对象address不变,也即是保证reloadx前后的对象是同一个对象!

乍听起来很矛盾,但是大体上是可以的:

method /staticmethod / classmethod / function这四种对象类型其实都可以归结到function object的更新上(因为method/staticmethod/classmethod本质上都是对function的一个wrapper对象,都有途径获得被wrap的function)。

function object的功能其实本质上是一个函数块,它主要由func_code, func_defaults, func_doc三个成员组成,那我们用reload后的function对象相应内容替换到旧的function对象中即可。

class则稍微特殊一些,它是由method / staticmethod / classmethod, 以及BASES关系(+MRO),数据成员等共同组成的一个对象体。但由于Python中对BASES tuple在运行时的替换有deallocator相等的限制,使得从Python脚本层次对派生关系重新定义不可行(但是增加基类是可以的:ClassA.__bases__ += (ClassB, ) ,所谓的Mix-in)。

函数和方法的更新是没问题的,替换方法和函数已经满足大部分的需求了。

优点:

- 无论这些function/class以什么方式引用,只要不深入直接引用到func_code/func_default对象,均可动态更新到

- 只需要更新一个对象,速度非常快

缺点:

- 不能动态更新class的派生关系相关的信息

思路2(新对象替换旧对象):

模块M被热更新后,找出所有对M中的class/function...有引用的对象,逐个执行新对象替换旧对象的操作。比如obj.__class__ = class_after_reload。

优点:

- 实现相对简洁

- 支持class对象的全更新

缺点:

- 对于将function/classobj.method跨模块不可变容器(tuple, frozenset...)引用的更新不了

- 如果引用对象众多,比(思路1)处理起来慢许多。

实现之前搭建一个简单的可持续测试环境,再实现reloadx,然后针对一些复杂用例进行反复测试(这是个漫长的过程)。

最终我实现了一个(思路1)的机制。机制伴随着几个约定的模块级函数调用,方便完成一些reload前后和模块初始化的数据定制。实现了reloadx后,对编写Python的良好模块的理解又进了一步。最好项目一开始便要实行系列规范。

后续可能还有一些改进措施可以做:

(1) 是否可以通过一些命名约定来实现模块级的 dict / list / set 等数据更新?

(2) 如果(1)可以实现,考虑实现 tuple frozenset 之类的固态容器更新?

(3) 监测两次update之间是否存在对象泄漏,防止reloadx多次后内存增大。

(4) 如果想偷懒,还可以开一个Python thread定时检查所有py的修改时间,自动reloadx。

(5) 实现(思路2)的版本对class处理更彻底。

reload的封装使用:

import sys

import os

class Reloader:

SUFFIX = '.pyc'

def __init__(self):

self.mtimes = {}

def __call__(self):

import pdb

pdb.set_trace()

for mod in sys.modules.values():

self.check(mod)

def check(self, mod):

if not (mod and hasattr(mod, '__file__') and mod.__file__):

return

try:

mtime = os.stat(mod.__file__).st_mtime

except (OSError, IOError):

return

if mod.__file__.endswith(self.__class__.SUFFIX) and os.path.exist(mod.__file__[:-1]):

mtime = max(os.stat(mod.__file__[:-1].st_mtime), mtime)

if mod not in self.mtimes:

self.mtimes[mod] = mtime

elif self.mtimes[mod] < mtime:

try:

reload(mod)

self.mtimes[mod] = mtime

except ImportError:

pass

reloader = Reloader()

reloader() 第一个知识点: self.__class__.SUFFIX,首先python一切皆对象,类实际上在python的世界里面也是一个对象,__class__至少在下面这种情况中是有用的:当一个类中的某个成员变量是所有该类的对象的公共变量时. 很像c++中的static变量。 第二个知识点: __import__与reload的区别: (1):多次重复使用import语句时,不会重新加载被指定的模块,只是把对该模块的内存地址给引用到本地变量环境。 (2)对已经加载的模块进行重新加载,一般用于原模块有变化等特殊情况,reload前该模块必须已经import过。reload会重新加载已加载的模块,但原来已经使用的实例还是会使用旧的模块,而新生产的实例会使用新的模块;reload后还是用原来的内存地址;不能支持from。。import。。格式的模块进行重新加载。 (3)通常在动态加载时可以使用到这个函数,比如你希望加载某个文件夹下的所用模块,但是其下的模块名称又会经常变化时,就可以使用这个函数动态加载所有模块了,最常见的场景就是插件功能的支持。比如__import__可以这样__import__('os',globals(),locals(),['path','pip']) ,reload就不行了。 具体可以参考看看这篇文章http://www.cnblogs.com/MaggieXiang/archive/2013/06/05/3118156.html 第三个知识点: __call__的用法,可调用对象,这个确实是第一次接触到,大概的用法就是我们只需要去重载这个方法,那么我们就可以像调用方法的形式去调用对象,那么它就会去回调这个__call__方法,具体的好处可以参看这个博客http://hi.baidu.com/feng2211/item/d55d0415602bfcfcdceeca45

在 python中每个模块用什么来实现_Python代码模块热更新机制实现(reload)相关推荐

  1. python中match方法返回字符串的长度_Python re模块与正则表达式详解

    Python 中使用re模块处理正则表达式,正则表达式主要用来处理文本中的查找,匹配,替换,分割等问题:我们先来看一个问题,切入正则表达式. 问题: 匹配字符串,最少以3个数字开头.使用Python代 ...

  2. python中使用函数的目的是什么_Python代码中os.mknod()函数起什么作用呢?

    摘要: 下文讲述Python代码中os.mknod()函数的功能说明,如下所示: os.mknod()函数的功能 用于创建一个指定文件名的文件系统节点 (文件,设备特别文件或者命名pipe) os.m ...

  3. python绘制三维曲面图-python中Matplotlib实现绘制3D图的示例代码

    Matplotlib 也可以绘制 3D 图像,与二维图像不同的是,绘制三维图像主要通过 mplot3d 模块实现.但是,使用 Matplotlib 绘制三维图像实际上是在二维画布上展示,所以一般绘制三 ...

  4. python中变量的作用域有几种_Python中变量的作用域(variable scope)

    http://www.crifan.com/summary_python_variable_effective_scope/ 解释python中变量的作用域 示例: 1.代码版 #!/usr/bin/ ...

  5. python中修饰器的优点和作用_Python入门基础教程之装饰器

    Python装饰器的定义:在代码运行期间在不改变原函数定义的基础上,动态给该函数增加功能的方式称之为装饰器(Decorator) 装饰器的优点和用途: 1. 抽离出大量函数中与函数功能本身无关的的雷同 ...

  6. python if else语句例子,python 中if else 语句的作用及示例代码

    引入:if-else的作用,满足一个条件做什么,否则做什么. if-else语句语法结构 if 判断条件: 要执行的代码 else: 要执行的代码 判断条件:一般为关系表达式或bool类型的值 执行过 ...

  7. python中函数type可以测试对象类型_python类型检测最终指南--Typing模块的使用

    正文共:30429 字 预计阅读时间:76分钟 原文链接:https://realpython.com/python-type-checking/ 作者:Geir Arne Hjelle 译者:陈祥安 ...

  8. python中两个文件如何互相传参_argparse模块如何在jupyter notebook中用于传参?

    作者:于晨晨 研究方向:nlp 发表于公众号:AI技术日常 在python代码文件中,通常需要传参,传参就需要经常使用argparse.使用argparse模块在py文件中是正常的,但是jupyter ...

  9. python中的os abort_Python os.abort()用法及代码示例

    Python中的OS模块提供了与操作系统进行交互的功能.操作系统属于Python的标准实用程序模块.该模块提供了使用依赖于操作系统的功能的便携式方法. os.abort()Python中的方法用于生成 ...

最新文章

  1. 苹果四大供应商向高通索赔90亿;金立否认裁定破产清算
  2. eclipse外观设置
  3. 命令 检查Linux服务器性能
  4. Mysql学习(二)之安装、开启自启、启动、重启、停止
  5. 基于 Slax 构建译者专用 Linux
  6. java.lang.Exception: Socket bind failed: [730048]
  7. Android之在后台不显示activity方法
  8. 全程干货,requests模块与selenium框架详解
  9. Python裸奔也疯狂:批量爬取中国工程院院士信息
  10. pyqt5多线程使用方法及学习案例(QThread)
  11. C# Lambda表达式使用累加器例子
  12. 【备忘】老男孩IT教育_徐培成_大数据
  13. 多闭环PID控制算法
  14. android mp4 画面裁剪,说说Android的视频裁剪(三)
  15. 在单点登录中,如果cookie被禁用了怎么办?
  16. 中间件小师妹 de 年度工作总结
  17. 惹某人突然不舍de第七周(习题+感悟)
  18. 不到 100 行 Python 代码即可实现换脸功能
  19. matlab的多变量dmc源程序,基于MATLAB多变量DMC算法的仿真技术研究
  20. failed to allocate for range 0: no IP addresses available in range set:

热门文章

  1. Focal Loss 的Pytorch
  2. c++ map嵌套队列(队列嵌套结构体指针)ok
  3. Tensorflow csv文件读写与分批训练
  4. 青龙羊毛——鸡厂签到
  5. 学校开展计算机培训活动,计算机学院学习筑梦班开展义务清扫机房活动
  6. 歌手比赛系统c语言程序注释,C语言程序课程设计—歌手比赛系统(20页)-原创力文档...
  7. python stm32f401_NUCLEO-F401RE(STM32F401RE)开发板跑Micropython平台
  8. 版式文件 流式文件_银河麒麟操作系统V10全面适配各类流式版式软件
  9. Linux 热插拔(Hot Plug)处理机制系列
  10. Linux中变量#,#,@,0,0,1,2,2,*,$$,$?的含义