Python Miscellaneous 2 - Python 3.12 Hot Update
Record 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 technology is widely used in the gaming industry. When developers need to fix issues with games, they often use silent updates, or hot reloads, to avoid impacting players.
Python hot reload
Python itself is a dynamic language, where everything is an object and has the ability to achieve hot updates. We can roughly categorize the objects in Python that need hot updates into two types: data and functions.
Data can be understood as the numerical values or settings in the game, such as the player's level, equipment, and so on. Some data should not be updated in real-time. For example, the player's current level, the equipment the player has, should not be modified through real-time updates. Some data is what we want to update in real-time, such as the basic numerical settings of equipment, the basic numerical settings of skills, and the text on the UI, and so on.
Function, can be understood as game logic, which is basically what we want to hotfix, logic errors basically need to be implemented through hot update functions.
Let's take a closer look at what methods can be used to perform hot reloading on Python 3.12.
Hotfix
The first method we call Hotfix, it allows the program (both client and server programs) to execute a specific piece of Python code, achieving hot updates to 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 the usage of Hotfix. After the data/function is modified, the program will read the new data/function for subsequent access and execution.
If you are meticulous, you may have a question: What will happen if other code refers to these 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 work for this situation. The function fire_func
is like an extra copy in another module. The module calls the copy of the function, so the modifications to the original function do not affect the copy.
So it's important to note that in general, we should try to minimize module-level data references and function references in the code to avoid situations where Hotfixes don't take effect. If the code is already written this way, extra work will be needed for the Hotfix to work properly:
After modifying the data/function core with a hotfix, make additional modifications to the places where it is referenced. These additional modifications are easily overlooked, so we still recommend avoiding the practice of multiple references as much as possible from the code specification perspective.
In conclusion, Hotfix can meet the basic needs of hot updates, but it also has the following problems:
- If the data/function is explicitly referenced by other modules, additional references to these modules are required. Hotfix If there is a large amount of data/functions that need to be hotfixed, then the code of the hotfix will become very large, maintenance will become more difficult, and it will also be more prone to errors.
Reload
You can obtain the source code for this section from here: python_reloader
What we really want is automatic hot updates, without the need to write additional hotfixes. Just update the code files, execute a reload function, and the program will automatically replace the new functions and data. We call this automatic hot update feature "Reload."
Python 3.12 provides the importlib.reload
function, which can reload modules, but it is a full reload and returns a new module object, so it doesn't automatically update references in other modules. This means that if other modules import the reloaded module, they will still access the old module object. This functionality isn't much better than our Hotfix, especially since it's a full reload of the module and we can't control which data should be retained. We want to implement our own Reload feature that meets these requirements:
- Automatically replace the function, while the reference to the old function remains valid and will execute the content of the new function
- Automatically replace data while controlling partial replacement
- Keep the reference to the old module, so that the new content can be accessed through the old module.
- Modules that need reloading are controllable
To meet these requirements, we need to leverage the meta_path mechanism within Python. For detailed information, please refer to the official documentation the-meta-path。
In sys.meta_path
, we can define our meta path finder objects, for example, we name the finder used for reloading as reload_finder
. The reload_finder
needs to implement a function find_spec
and return the spec
object. Once Python gets the spec
object, it will sequentially execute spec.loader.create_module
and spec.loader.exec_module
to complete the module import.
If we execute new module code during this process and copy the functions and required data from the new module into the old module, we can achieve the purpose of reloading.
As mentioned above, find_spec
loads the latest module source code and executes the new module's code inside the __dict__
of the old module. Afterwards, we call ReloadModule
to handle the references and replacements of classes, functions, and data. The purpose of MetaLoader
is to adapt to the meta_path mechanism and return 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
inside will differentiate and handle different types of objects.
If it's a class, calling ReloadClass
will return the reference to the old module and update the class members.
If it is a function/method, calling ReloadFunction
will return a reference to the old module and update the internal function data.
If it's data and needs to be preserved, it will roll back new_dict[attr_name] = old_attr
- The rest should remain with the new citation.
- 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 process of Reload can be summarized as "putting new wine in old bottles." In order to maintain the effectiveness of modules, module functions, module classes, and module data, we need to retain references to these original objects (shells) and then update their specific data internally. For example, for functions, we update __code__
, __dict__
, and other data. When the function is executed, it will then execute the new code.
Summary
This article provides a detailed introduction to two hot update methods of Python3, each with its corresponding use cases, hoping to be helpful to you. Feel free to reach out if you have any questions.
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 is translated using ChatGPT, please feedback if any omissions.