Python Chat 2 - Python 3.12 Hot Update
How to implement hot reloading in Python 3.12
Hot update
Hot Reload is a technology that allows updates to be made to a program without the need for a restart. This technique is widely used in the gaming industry. When developers need to fix issues in games without affecting players, they often resort to silent updates, also known as Hot Reload.
Python hot update
Python itself is a dynamic language where everything is an object, capable of hot updating. We can roughly categorize the objects that need hot updates in Python into two types: data and functions.
Data can be understood as the numerical values or settings within a game, such as the player's level, equipment, and so on. Some data should not be hot-updated (for example, the player's current level and which equipment the player possesses; modifications to this data should not be implemented through hot updates), while there is other data that we want to hot-update (for instance, the base numerical settings for equipment, the base numerical settings for skills, text on the UI, etc.).
Functions can be understood as game logic, which is basically what we want to hotfix. Logic errors generally need to be addressed through hot update functions.
Now let's take a closer look at the specific methods available for implementing hot updates in Python 3.12.
Hotfix
The first method we call Hotfix, which allows the program (either client-side or server-side) to execute a specific piece of Python code to achieve hot updates for data and functions. A simple Hotfix code might look like this:
# hotfix code
# hotfix data
import weapon_data
weapon_data.gun.damage = 100
# hotfix func
import player
def new_fire_func(self, target):
target.health -= weapon_data.gun.damage
# ...
player.Player.fire_func = new_fire_func
The above code simply demonstrates how to write a hotfix. After modifying the data/functions, the program will read the new data/functions for execution when accessed later.
If you are being meticulous, you might have a question: What will happen if other parts of the code reference the data and functions that need to be modified?
# attack.py module
player_fire = player.Player.fire_func
def player_attack_by_gun(player, target):
player_fire(player, target)
# ...
The answer is that the previous Hotfix does not apply to this situation. The fire_func
function is essentially an additional copy in another module; the copy is what is called in that module, and modifications to the original function do not affect the copy.
Therefore, it is important to be cautious and minimize module-level data and function references in the code to avoid situations where Hotfix does not take effect. If the code is already written this way, additional work is needed for the Hotfix to work properly:
After making the Hotfix modifications to the data / function body, additional changes should be made in the places where it is referenced. These extra modifications can easily be overlooked, so we recommend, from a coding standards perspective, to avoid writing multiple references as much as possible.
Taking everything into consideration, Hotfix can meet the basic requirements of real-time updates, but it also faces the following issues:
- If the data/function is explicitly referenced by other modules, an additional Hotfix for the references of these modules is required. If there is a large amount of data/functions that need to be hotfixed, the code for the hotfix will become very large, increasing maintenance difficulty and making it more prone to errors.
Reload
The source code for this chapter can be obtained from here: python_reloader
What we really want is automatic hot reloading, without the need to write additional hotfixes. Simply updating the code files and having the program execute a Reload function will automatically replace the new functions and data. We call this automatic hot reloading feature Reload.
Python 3.12 introduces the importlib.reload function, which allows for the reloading of modules. However, it performs a full reload and returns a new module object. References in other modules are not automatically updated; this means that if other modules have imported the reloaded module, they will still access the old module object. This functionality is not significantly better than our Hotfix, especially considering the full module reload, which we cannot control in terms of which data should be retained. We want to implement our own Reload feature that meets these requirements:
Automatically replace the function while preserving the validity of the old function's references, and executing the content of the new function. Automatically replace data, while also being able to control partial replacements. Retain references to the old module, so that the new content can be accessed through the old module. Modules that require reloading can be controlled.
To fulfill these requirements, we need to leverage the meta_path mechanism in Python. A detailed introduction can be found in the official documentation the-meta-path.
You can define our meta-path finder object in sys.meta_path, for example, if we name the finder used for reloading as reload_finder, reload_finder needs to implement a function find_spec and return a spec object. After Python obtains the spec object, it will sequentially execute spec.loader.create_module and spec.loader.exec_module to complete the module importation.
If we execute the new module code during this process and copy the functions and necessary data from the new module into the old module, we can achieve the purpose of Reload.
As mentioned above, find_spec
loads the latest source code of the module and executes the new module's code within the __dict__
of the old module. After that, we call ReloadModule
to handle the references and replacements of classes/functions/data. The purpose of MetaLoader
is to adapt to the meta_path mechanism, returning the processed module objects to the Python virtual machine.
After completing the loading process, let's take a look at the general implementation of ReloadModule
.
The 'ReloadDict' will differentiate and process objects of different types inside.
- If it is a class, then calling
ReloadClass
will return a reference to the old module and update the class's members. If it is a function/method, callingReloadFunction
will return a reference to the old module and update the internal data of the function. If it's data and needs to be preserved, it will roll back tonew_dict[attr_name] = old_attr
. Keep the rest of the citations fresh. Remove functions that do not exist in the new module.
The specific code for ReloadClass
and ReloadFunction
will not be further analyzed here, if you are interested, you can directly refer to the source code.
The entire Reload process can be summarized as: new wine in an old bottle. To maintain the validity of the module's functions/classes/data, we need to keep references to the original objects (shells) and instead update the specific data inside them. For example, for functions, we update the __code__
, __dict__
, and other data, so that when the function is executed, it will run the new code.
Summary
This article provides a detailed introduction to two hot update methods in Python 3, each suitable for specific scenarios. I hope it can be helpful to you. Feel free to ask if you have any questions or need further clarification.
Original: https://wiki.disenone.site/en
This post is protected by CC BY-NC-SA 4.0 agreement, should be reproduced with attribution.
Visitors. Total Visits. Page Visits.
This post has been translated using ChatGPT. Please provide feedback in FeedbackPlease point out any omissions.