python flask热更新_客户端python热更新

本文介绍了Python中实现客户端热更新的原理和方法,包括为何标准的`reload`函数不适用于游戏热更新,以及如何自定义`reload`以实现代码定义的更新,同时保持数据的不变化。通过更新函数、类内容以及静态方法和类方法,实现热更新功能,以提高开发效率和紧急BUG的修复能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍:

热更新,就是在维持服务不间断的情况下,对软件代码逻辑或配置数据进行更新修复。随着游戏项目引入了脚本语言以后,热更新技术逐渐成为了标配,在我经历过的游戏项目中,无论是服务端还是客户端,版本的更新迭代都是围绕着静态patch和动态patch(热更新)来进行的。下面来谈一下客户端Python热更新的处理。

原理:

标准import

我们知道,import可以导入一个标准的python模块,将模块载入内存,并加到sys.modules中。多次import同一模块只是将名称导入到当前的Local名字空间,也就是一个模块不会重复载入。

reload函数

reload()函数可以重新载入已经导入的模块,这样看起来就可以热更新python模块了。可惜的是,python原生的reload函数远不能满足游戏热更新的问题,原因如下:

reload重新加载的模块不会删除旧版本的模块,也就是已经引用的旧模块无法更新

同样因为不能旧对象的引用,使用from ... import ... 方式引用的模块同样不能更新

reloas(m)后,class及其派生class的实例对象,仍然使用旧的class定义。

加载模块失败时候,没有rollback机制,需要重新import该模块

因此,有必要结合游戏的情景,自定义适合的reload。新的自定义reload目的是为了达到在原程序不结束的情况下,让程序能动态加载改动后的代码。主要想达到下面两点:

提升开发期的开发效率

在游戏不重启的情况下修复紧急BUG

实现:

热更新当中实际的重点在于如何让已经创建的对象获得新代码的变化,以及在reload前后不产生类型上的不一致。刷新function,class内定义的method比较容易实现,但对于刷新module内定义的变量,class内定义的变量,还有新增加的成员变量,则需要有统一的约定。所以,在热更新过程中,我们只要考虑好代码更新和数据更新这两点,那么更新就是可行的。

下面罗列一下新的reload具备哪些特性:

更新代码定义(function/method/static_method/class_method)

不更新数据(除了代码定义外的类型都当作是数据)

在module中约定reload_module接口,class中约定reload_class接口,在这两个接口中手动处理数据的更新,还有更多的约定和接口待完成

替换函数对象的内容

# 用新的函数对象内容更新旧的函数对象中的内容,保持函数对象本身地址不变

def update_function(oldobj, newobj, depth=0):

setattr(oldobj, "func_code", newobj.func_code)

setattr(oldobj, "func_defaults", newobj.func_defaults)

setattr(oldobj, "func_doc", newobj.func_doc)

替换类的内容

# 用新类内容更新旧类内容,保持旧类本身地址不变

def _update_new_style_class(oldobj, newobj, depth):

handlers = get_valid_handlers()

for k, v in newobj.__dict__.iteritems():

# 如果新的key不在旧的class中,添加之

if k not in oldobj.__dict__:

setattr(oldobj, k, v)

_log("[A] %s : %s"%(k, _S(v)), depth)

continue

oldv = oldobj.__dict__[k]

# 如果key对象类型在新旧class间不同,那留用旧class的对象

if type(oldv) != type(v):

_log("[RD] %s : %s"%(k, _S(oldv)), depth)

continue

# 更新当前支持更新的对象

v_type = type(v)

handler = handlers.get(v_type)

if handler:

_log("[U] %s : %s"%(k, _S(v)), depth)

handler(oldv, v, depth + 1)

# 由于是直接改oldv的内容,所以不用再setattr了。

else:

_log("[RC] %s : %s : %s"%(k, type(oldv), _S(oldv)), depth)

# 调用约定的reload_class接口,处理类变量的替换逻辑

object_list = gc.get_referrers(oldobj)

for obj in object_list:

# 只有类型相同的才是类的实例对象

if obj.__class__.__name__ != oldobj.__name__:

continue

if hasattr(obj, "x_reload_class"):

obj.x_reload_class()

staticmethod

def _update_staticmethod(oldobj, newobj, depth):

# 一个staticmethod对象,它的 sm.__get__(object)便是那个function对象

oldfunc = oldobj.__get__(object)

newfunc = newobj.__get__(object)

update_function(oldfunc, newfunc, depth)

classmethod

def _update_classmethod(oldobj, newobj, depth):

oldfunc = oldobj.__get__(object).im_func

newfunc = newobj.__get__(object).im_func

update_function(oldfunc, newfunc, depth)

模块的更新也是相类似,就不一一粘贴了,只是在原来的reload基础上进行改良,对于模块热更新,还约定了一个reload_module接口,可以自定义数据的更新。

下面添加一些用例:

def x_reload_class(self):

""" 热更新后,每个重新对象的实例都会执行这个函数

由于新老对象的替换不会重新调用构造函数,因此有必要对热更新的类对象执行初始化逻辑

处理新老变量的修复,函数执行环境的修复

"""

self._new_var = 5000 # 新变量的初始化

self.init_widget() # 新修复的逻辑

目前的热更新模块已经在开发调试中使用,可以方便地完成一些更新任务,但是要更新到远程客户端,还需要更多的规范和接口来处理,如闭包,内部局部变量等,需要逐步地学习和完善。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值