以下为个人学习笔记整理
# HotFix 热更新
# 概念:
- 热更新是指在 Python 程序运行过程中,修改代码中的部分片段,并能够不需要重新启动程序,便能够在运行程序中生效。
 - 热更新一般都是基于 
module来进行的,所以热更新本质就是更新module - 为了能够保证热更之前创建的绝大多数对象是能够正常工作的,一般会尽量避免对对象直接进行替换,能修改的尽量不替换。
 
# 介绍:
- 一个 
module里面包含的内容大致可以分为以下几种:class:类function:方法global object:全局对象
 
看上去只有三种类似的对象,其实更新的时候注意的点还挺多的:
- 模块是新增或者类型发生了变更,可以直接替换。
 - 一般不对模块内建函数和内建全局对象做操作(大部分是不可修改的,还有一些平时也不会改,没有热更必要)。
 - 除此以外的就是对三种类型的分别更新了。
 
def reload_module(module_name:str):  | |
    """ | |
热更模块(module):  | |
①: 更新新增成员  | |
②: 跳过 builtins 模块  | |
③: 处理类型不同的成员  | |
④: 更新类成员  | |
⑤: 更新函数成员  | |
⑥: 更新成员变量  | |
"""  | |
	# 该模块之前没有被加载,不允许热更新 | |
old_module = sys.modules.get(module_name, None)  | |
if not old_module:  | |
raise Exception(f"{module_name} is not import can't reload")  | |
    # Python3.7 的机制,如果不 pop 掉旧的模块, import_module 操作只会从 moduels 取出旧的缓存数据,不会重新构建 | |
sys.modules.pop(module_name)  | |
new_module = importlib.import_module(module_name)  | |
if not inspect.ismodule(new_module):  | |
raise Exception(f"{new_module.__name__} is not a module")  | |
for name, new_member in inspect.getmembers(new_module):  | |
        # 模块名称和 member 的 key 可能不一致 例如 import xxx as x | |
member_name = getattr(new_member, "__name__", name)  | |
old_member = old_module.__dict__.get(name, None)  | |
        # 模块内的成员模块 和 built-in 函数不做处理 | |
if inspect.ismodule(new_member) or \  | |
inspect.isbuiltin(new_member) or \  | |
member_name in builtins.__dict__:  | |
            continue | |
        # 原模块没有的内容或者类型不同直接换 | |
elif not old_member or type(old_member) != type(new_member):  | |
setattr(old_module, name, new_member)  | |
        # 类,走类自己的热更 | |
elif inspect.isclass(new_member):  | |
            # 枚举类型强制替换,因为枚举定义后无法被修改,这里只能替换类的定义。 | |
if issubclass(type(old_member), enum.EnumMeta):  | |
setattr(old_module, name, new_member)  | |
else:  | |
reload_class(old_member, new_member)  | |
        # 函数,热更之 | |
elif inspect.isfunction(new_member):  | |
            # 热更失败直接换,失败的原因可能是函数本身的闭包参数变更: | |
            # 也不是说热更失败,只是这种情况下,热更也没办法兼容旧的逻辑,毫无意义,徒增烦恼 | |
            # 	@装饰器				------>		@装饰器 | |
            # 	def func(arg1, arg2)			  def func(arg1) | |
if not reload_func(old_member, new_member):  | |
setattr(old_module, name, new_member)  | |
else:  | |
setattr(old_module, name, new_member)  | |
sys.modules[module_name] = old_module  | 
下面就来介绍一下 class 、 function 、 global variable 的热更新问题。
# 热更 Class:
热更新类的一些注意事项:
类的更新一般不进行替换,而是把新的类中的内容更新到旧的类里面,为了兼容一些已经创建的类实体。
对于以前的旧类中存在而新类不存在的内容,根据自身需求选择是否保留(这里删掉了)。
类中包含部分不能直接修改的对象,跳过它们的更新:
'__dict__', '__doc__', '__self__', '__func__'这些都是不可修改的。
和模块一样,把新增的内容和类型不一致的内容更新到旧的类里面。
和模块一样,跳过内建函数。
staticmethod、classmethod函数由于无法修改,只能单独热更__func__字段。property修饰的函数直接替换即可。类函数则走函数的正常更新流程。
类中定义的类依旧走类的更新。
其他内容直接覆盖即可。
def reload_class(old_class:type, new_class:type):  | |
    """ | |
热更类(class):  | |
①: 新增成员直接加  | |
②: builtins 成员不处理  | |
③: methoddescriptor 成员不处理  | |
④: 类型不同直接替换  | |
⑤: staticmethod,classmethod,property,method 直接更新  | |
⑥: function 直接更新  | |
⑦: class 递归更新  | |
⑧: class 属性成员直接更新  | |
"""  | |
    # 删除新类不存在的旧类成员 | |
for name, attr in list(old_class.__dict__.items()):  | |
if name in new_class.__dict__:  | |
            continue | |
if not inspect.isfunction(attr):  | |
            continue | |
type.__delattr__(old_class, name)  | |
ignore_attr_lst = [ "__dict__", # attribute objects is not writable  | |
'__doc__', '__self__', '__func__', # can't set attributes of built-in/extension  | |
    ] | |
for name, new_attr in new_class.__dict__.items():  | |
if name in ignore_attr_lst:  | |
            continue | |
old_attr = old_class.__dict__.get(name, None)  | |
        # 新增内容直接加 | |
if not old_attr:  | |
setattr(old_class, name, new_attr)  | |
elif inspect.isbuiltin(new_attr) or name in builtins.__dict__.keys():  | |
            continue | |
        # 类型不同直接换 | |
elif type(old_attr) != type(new_attr):  | |
setattr(old_class, name, new_attr)  | |
elif isinstance(new_attr, (staticmethod, classmethod)):  | |
if not reload_func(old_attr.__func__, new_attr.__func__):  | |
setattr(old_class, name, new_attr)  | |
elif isinstance(new_attr, property):  | |
setattr(old_class, name, new_attr)  | |
elif inspect.isfunction(new_attr):  | |
if not reload_func(old_attr, new_attr):  | |
setattr(old_class, name, new_attr)  | |
elif inspect.isclass(new_attr):  | |
reload_class(old_attr, new_attr)  | |
else:  | |
setattr(old_class, name, new_attr)  | 
# 热更 Function:
函数的更新算是热更里面最核心的内容了,注意点也挺多:
函数的热更新一般也不对函数本身进行替换,直接修改即可,迫不得已情况下可以考虑换掉。
函数本身因为没有涉及到过多的自定义内容,大部分都是逻辑,所以内置的东西粗略的看下来就几样:
__closure__:闭包的关联参数__code__:编译后的代码对象__defaults__:k-v 的默认值__dict__:命名空间支持的函数属性__globals__:全局变量字典__name__:函数名__qualname__:函数全名__annotations__:类型标注__kwdefaults__:关键字默认值字典
把上述的几个替换一下即可,这里要注意一下,有部分字段也是不可修改的:
__class__:assignment only supported for heap types or ModuleType subclasses__closure__:readonly attribute__globals__:readonly attribute
除此以外,还需要注意检验函数的闭包变量是否发生变更,如果变更了也要进行更新:
- 对于带有 super () 调用的函数,其闭包内会存储自身的引用,这个不需要更新:
 
class c_1():
def __init__(self):
super().__init__()
>>> print(c_1.__init__.__code__.co_freevars[0])
__class__
>>> print(c_1.__init__.__closure__[0].cell_contents)
<class '__main__.c_1'>
闭包参数数量不一致的情况下,直接替换,更新意义不大。
def reload_func(old_func:types.FunctionType, new_func:types.FunctionType, depth = 0):  | |
    """ | |
热更函数(func):  | |
①: 更新旧函数里面的属性,详细内容见下方定义  | |
②: 部分属性不可写或无法修改的不做处理  | |
③: 处理闭包 cellvar  | |
④: 新增函数或变量直接添加  | |
⑤: 类型变更直接替换  | |
⑥: 其他情况也直接替换即可  | |
函数基本定义:  | |
class FunctionType:  | |
__closure__: Optional[Tuple[_Cell, ...]]  | |
__code__: CodeType  | |
__defaults__: Optional[Tuple[Any, ...]]  | |
__dict__: Dict[str, Any]  | |
__globals__: Dict[str, Any]  | |
__name__: str  | |
__qualname__: str  | |
__annotations__: Dict[str, Any]  | |
__kwdefaults__: Dict[str, Any]  | |
def __init__(self, code: CodeType, globals: Dict[str, Any], name: Optional[str] = ..., argdefs: Optional[Tuple[object, ...]] = ..., closure: Optional[Tuple[_Cell, ...]] = ...) -> None: ...  | |
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...  | |
def __get__(self, obj: Optional[object], type: Optional[type]) -> MethodType: ...  | |
闭包问题:  | |
- Python 函数调用时出现闭包参数不一致,热更后会导致报错  | |
def out_func():  | |
arg1 = 1  | |
def inner_func():  | |
print(arg1)  | |
return inner_func  | |
f = out_func # 热更前代码  | |
f = out_func # 热更后代码  | |
f() # 调用  | |
# out: requires a code object with 1 free vars, not 0  | |
闭包原理:  | |
- out_func 在执行过程中,会把自身运行栈中内层函数引用的变量以 ob_ref 的形式绑定到 co_cellvars 的tuple当中(out_func.__closure__)。  | |
- 在 inner_func 对象内,解开传递进来的 co_cellvars 的tuple并重新绑定到自己的 co_freevars 的tuple中  | |
- 如果想要热更闭包内容,只需要替换掉 inner_func.__closure__ 里的内容  | |
部分不可变属性:  | |
- "__class__" assignment only supported for heap types or ModuleType subclasses  | |
- "__closure__" readonly attribute  | |
- "__globals__" readonly attribute  | |
"""  | |
if depth > 2:  | |
return False  | |
    # 闭包参数不一致,无法更新 | |
old_cell_var_num = len(old_func.__closure__) if old_func.__closure__ else 0  | |
new_cell_var_num = len(new_func.__closure__) if new_func.__closure__ else 0  | |
if old_cell_var_num != new_cell_var_num:  | |
return False  | |
    # 更新属性 | |
setattr(old_func, '__code__', new_func.__code__)  | |
setattr(old_func, '__defaults__', new_func.__defaults__)  | |
setattr(old_func, '__dict__', new_func.__dict__)  | |
setattr(old_func, '__name__', new_func.__name__)  | |
setattr(old_func, '__qualname__', new_func.__qualname__)  | |
setattr(old_func, '__annotations__', new_func.__annotations__)  | |
    # 类型标注 | |
setattr(old_func, '__kwdefaults__', new_func.__kwdefaults__)  | |
    # def m(cls,a=1,b=2,*kwarg,g=1,v=2): __kwdefaults__ = {'g': 1, 'v': 2} | |
    # 更新闭包参数 | |
if old_cell_var_num > 0:  | |
ignore_idx = []  | |
for idx, freevar in enumerate(old_func.__code__.co_freevars):  | |
            # super () 操作不改变其指向的父类 __class__ | |
if freevar == "__class__":  | |
ignore_idx.append(idx)  | |
for idx, old_cellvar in enumerate(old_func.__closure__):  | |
if idx in ignore_idx:  | |
                continue | |
new_cellvar = new_func.__closure__[idx]  | |
            # 闭包参数是函数的话递归更新 | |
if inspect.isfunction(old_cellvar.cell_contents) and inspect.isfunction(new_cellvar.cell_contents):  | |
if not reload_func(old_cellvar.cell_contents, new_cellvar.cell_contents, depth + 1):  | |
old_cellvar.cell_contents = new_cellvar.cell_contents  | |
            # 其他情况都视作替换 | |
else:  | |
old_cellvar.cell_contents = new_cellvar.cell_contents  | |
return True  | 
# 热更 global object:
全局对象的更新就比较的简单,大致可以分为两种:
- 能够获取到所有需要热更对象的引用,可以直接在对象上进行修改。
 - 不能获取的情况下,直接修改模块内的定义。缺点就是已经创建了的对象,内容还是旧的。
 
# 测试代码:
- 修改前的代码:
 
# -*- coding: utf8 -*- | |
import ccore | |
import enum | |
import functools | |
import dataclasses | |
import typing | |
import collections | |
# 全局变量测试 | |
a_1 = 1  | |
a_2 = [1,2]  | |
a_3 = (1,2,3)  | |
a_4 = {1:1,2:2}  | |
a_5 = {1,2,3}  | |
a_6 = 1.1  | |
a_7 = object  | |
a_8 = len  | |
a_9 = functools.partial(len, a_2)  | |
a_10 = collections.defaultdict(int)  | |
# 函数测试 | |
def b_1(arg1, arg2):  | |
b_100 = 1  | |
b_200 = 1  | |
return b_100+b_200  | |
def b_2(arg1, arg2):  | |
return arg1 + arg2  | |
def b_3(arg1, arg2, *args):  | |
return arg2 + arg1 + sum(args)  | |
def b_4(arg1 = 1, arg2 = 2, *args):  | |
return arg2 + arg1 + sum(args)  | |
def b_5(arg1 = 1, arg2 = 2, *args, **kwargs):  | |
return arg2 + arg1 + sum(args) + sum(kwargs.values())  | |
def b_6(arg1 = 1, arg2 = 2, *args, arg3 = 4, arg4 = 3):  | |
return arg2 + arg1 + sum(args) + arg3 + arg4  | |
def b_7(func, arg1 = 1, arg2 = 2):  | |
@functools.wraps(func)  | |
def inner(*args, **kwargs):  | |
return arg1 + arg2 + func()  | |
    return inner | |
@b_7 | |
def b_8(arg1 = 1, arg2 = 2, *args, arg3 = 4, arg4 = 3):  | |
return arg2 + arg1 + sum(args) + arg3 + arg4  | |
def b_9(arg1 = 1, arg2 = 2):  | |
def wapper(func):  | |
@functools.wraps(func)  | |
def inner(*args, **kwargs):  | |
return arg1 + arg2 + func()  | |
        return inner | |
    return wapper | |
@b_9(1,2)  | |
def b_10(arg1 = 1, arg2 = 2, *args, arg3 = 4, arg4 = 3):  | |
return arg2 + arg1 + sum(args) + arg3 + arg4  | |
# 类测试 | |
class c_1():  | |
c_100 = 1  | |
c_200 = 2  | |
def __init__(self):  | |
super().__init__()  | |
def __call__(self):  | |
return False  | |
def c_1000(self,arg1 = 1, arg2 = 2, *args, arg3 = 4, arg4 = 3):  | |
return arg2 + arg1 + sum(args) + arg3 + arg4  | |
    @property | |
def C_100(self):  | |
return self.c_100  | |
    @classmethod | |
def c_2000(cls,arg1 = 1, arg2 = 2, *args, arg3 = 4, arg4 = 3):  | |
return arg2 + arg1 + sum(args) + arg3 + arg4  | |
    @staticmethod | |
def c_3000(arg1 = 1, arg2 = 2, *args, arg3 = 4, arg4 = 3):  | |
return arg2 + arg1 + sum(args) + arg3 + arg4  | |
def c_4000(self, arg1 = 1, arg2 = 2):  | |
def wapper(func):  | |
@functools.wraps(func)  | |
def inner(*args, **kwargs):  | |
...  | |
            return inner | |
        return wapper | |
@dataclasses.dataclass | |
class d_1:  | |
d_100:int = 1  | |
d_200:str = "1"  | |
d_300:typing.List[int] = dataclasses.field(default_factory=list)  | |
d_400:typing.Dict[int,int] = dataclasses.field(default_factory=dict)  | |
e_1 = collections.namedtuple("e_1", "e_100 e_200 e_300 e_400")  | |
class INFO(enum.IntEnum):  | |
m_1 = 1  | |
m_2 = 2  | |
m_3 = 3  | 
- 修改后的代码:
 
# -*- coding: utf8 -*- | |
import ccore | |
import enum | |
import functools | |
import dataclasses | |
import typing | |
import collections | |
# 全局变量测试 | |
a_1 = 2  | |
a_2 = [2,2]  | |
a_3 = (2,2,3)  | |
a_4 = {2:1,2:2}  | |
a_5 = {2,2,3}  | |
a_6 = 2.1  | |
a_7 = object  | |
a_8 = len  | |
a_9 = functools.partial(len, a_3)  | |
a_10 = collections.defaultdict(str)  | |
# 函数测试 | |
def b_1(arg1):  | |
b_100 = 3  | |
b_200 = 4  | |
return b_100+b_200  | |
def b_2(arg1):  | |
    return arg1 | |
def b_3(arg1, *args):  | |
return arg1 + sum(args)  | |
def b_4(arg1 = 1, *args):  | |
return arg1 + sum(args)  | |
def b_5(arg1 = 1, *args, **kwargs):  | |
return arg1 + sum(args) + sum(kwargs.values())  | |
def b_6(arg1 = 1, *args, arg3 = 4, arg4 = 3):  | |
return arg1 + sum(args) + arg3 + arg4  | |
def b_7(func, arg1 = 1):  | |
@functools.wraps(func)  | |
def inner(*args, **kwargs):  | |
return arg1 + func()  | |
    return inner | |
@b_7 | |
def b_8(arg1 = 1, *args, arg3 = 4, arg4 = 3):  | |
return arg1 + sum(args) + arg3 + arg4  | |
def b_9(arg1 = 1):  | |
def wapper(func):  | |
@functools.wraps(func)  | |
def inner(*args, **kwargs):  | |
return arg1+ func()  | |
        return inner | |
    return wapper | |
@b_9(1)  | |
def b_10(arg1 = 1, *args, arg3 = 4, arg4 = 3):  | |
return arg1 + sum(args) + arg3 + arg4  | |
# 类测试 | |
class c_1():  | |
c_100 = 10  | |
c_200 = 20  | |
def __init__(self):  | |
super().__init__()  | |
def __call__(self):  | |
return False  | |
def c_1000(self,arg1 = 1, *args, arg3 = 4, arg4 = 3):  | |
return arg1 + sum(args) + arg3 + arg4  | |
    @property | |
def C_100(self):  | |
return self.c_100  | |
    @classmethod | |
def c_2000(cls,arg1 = 1, *args, arg3 = 4, arg4 = 3):  | |
return arg1 + sum(args) + arg3 + arg4  | |
    @staticmethod | |
def c_3000(arg1 = 1, *args, arg3 = 4, arg4 = 3):  | |
return arg1 + sum(args) + arg3 + arg4  | |
def c_4000(self, arg1 = 1):  | |
def wapper(func):  | |
@functools.wraps(func)  | |
def inner(*args, **kwargs):  | |
...  | |
            return inner | |
        return wapper | |
@dataclasses.dataclass | |
class d_1:  | |
d_100:int = 2  | |
d_200:str = "2"  | |
d_300:int = 1  | |
d_400:str = "1"  | |
e_1 = collections.namedtuple("e_1", "e_100 e_200 e_300")  | |
class INFO(enum.IntEnum):  | |
m_1 = 3  | |
m_2 = 2  | |
m_3 = 1  | 
- 测试数据初始化(1 表示热更前,2 表示热更后)
 
def init_data(flag=1):  | |
    import collections | |
d = collections.OrderedDict()  | |
d["data"] = {  | |
"a_1" : fix_module.a_1,  | |
"a_2" : fix_module.a_2,  | |
"a_3" : fix_module.a_3,  | |
"a_4" : fix_module.a_4,  | |
"a_5" : fix_module.a_5,  | |
"a_6" : fix_module.a_6,  | |
"a_7" : fix_module.a_7,  | |
"a_8" : fix_module.a_8,  | |
"a_9" : fix_module.a_9,  | |
"a_10" : fix_module.a_10,  | |
    } | |
d["func"] = {  | |
"b_1": fix_module.b_1,  | |
"b_2": fix_module.b_2,  | |
"b_3": fix_module.b_3,  | |
"b_4": fix_module.b_4,  | |
"b_5": fix_module.b_5,  | |
"b_6": fix_module.b_6,  | |
"b_7": fix_module.b_7,  | |
"b_8": fix_module.b_8,  | |
"b_9": fix_module.b_9,  | |
"b_10": fix_module.b_10,  | |
    } | |
if flag == 1:  | |
d["class"] = {  | |
"c_1":fix_module.c_1(),  | |
"d_1":fix_module.d_1(d_100=1,d_200="1",d_300=[1,],d_400={1:1}),  | |
"e_1":fix_module.e_1(e_100 = 1,e_200 =2,e_300 =3,e_400 = 4)  | |
        } | |
else:  | |
d["class"] = {  | |
"c_1":fix_module.c_1(),  | |
"d_1":fix_module.d_1(d_100=1,d_200="1",d_300=2,d_400="2"),  | |
"e_1":fix_module.e_1(e_100 = 1,e_200 =2,e_300 =3)  | |
        }   | |
    return d | |
def Output(d, flag=1):  | |
for k,v in d["data"].items():  | |
print(f" name {k} val {v}")  | |
if flag == 1:  | |
for k,v in d["func"].items():  | |
print(f" name {k} val {v(1,2)}")  | |
c_1 = d["class"]["c_1"]  | |
print(f"c_1.c_100 {c_1.c_100}")  | |
print(f"c_1.c_200 {c_1.c_200}")  | |
print(f"c_1.c_1000(1,2) {c_1.c_1000(1,2)}")  | |
print(f"c_1.C_100 {c_1.C_100}")  | |
print(f"c_1.c_2000(1,2) {c_1.c_2000(1,2)}")  | |
print(f"c_1.c_3000(1,2) {c_1.c_3000(1,2)}")  | |
print(f"c_1.c_4000(1,2) {c_1.c_4000(1,2)}")  | |
else:  | |
for k,v in d["func"].items():  | |
print(f" name {k} val {v(1)}")  | |
c_1 = d["class"]["c_1"]  | |
print(f"c_1.c_100 {c_1.c_100}")  | |
print(f"c_1.c_200 {c_1.c_200}")  | |
print(f"c_1.c_1000(1) {c_1.c_1000(1)}")  | |
print(f"c_1.C_100 {c_1.C_100}")  | |
print(f"c_1.c_2000(1) {c_1.c_2000(1)}")  | |
print(f"c_1.c_3000(1) {c_1.c_3000(1)}")  | |
print(f"c_1.c_4000(1) {c_1.c_4000(1)}")  | |
d_1 = d["class"]["d_1"]  | |
print(f"d_1.d_100 {d_1.d_100}")  | |
print(f"d_1.d_200 {d_1.d_200}")  | |
print(f"d_1.d_300 {d_1.d_300}")  | |
print(f"d_1.d_400 {d_1.d_400}")  | |
e_1 = d["class"]["e_1"]  | |
print(f"e_1.e_100 {e_1.e_100}")  | |
print(f"e_1.e_200 {e_1.e_200}")  | |
print(f"e_1.e_300 {e_1.e_300}")  | |
if flag == 1:  | |
print(f"e_1.e_400 {e_1.e_400}")  | |
print(fix_module.INFO.m_1.value)  | |
print(fix_module.INFO.m_2.value)  | |
print(fix_module.INFO.m_3.value)  | 
- 最终的输出结果
 
#------------------------------------------------------- 旧模块热更前的输出:------------------------------------------------------- | |
name a_1 val 1 | |
name a_2 val [1, 2]  | |
name a_3 val (1, 2, 3)  | |
name a_4 val {1: 1, 2: 2}  | |
name a_5 val {1, 2, 3}  | |
name a_6 val 1.1 | |
name a_7 val <class 'object'>  | |
name a_8 val <built-in function len>  | |
name a_9 val functools.partial(<built-in function len>, [1, 2])  | |
name a_10 val defaultdict(<class 'int'>, {})  | |
name b_1 val 2 | |
name b_2 val 3 | |
name b_3 val 3 | |
name b_4 val 3 | |
name b_5 val 3 | |
name b_6 val 10 | |
name b_7 val <function b_7.<locals>.inner at 0x000002E77A993700>  | |
name b_8 val 13 | |
name b_9 val <function b_9.<locals>.wapper at 0x000002E77A993700>  | |
name b_10 val 13 | |
c_1.c_100 1 | |
c_1.c_200 2 | |
c_1.c_1000(1,2) 10  | |
c_1.C_100 1 | |
c_1.c_2000(1,2) 10  | |
c_1.c_3000(1,2) 10  | |
c_1.c_4000(1,2) <function c_1.c_4000.<locals>.wapper at 0x000002E77A993700>  | |
d_1.d_100 1 | |
d_1.d_200 1 | |
d_1.d_300 [1]  | |
d_1.d_400 {1: 1}  | |
e_1.e_100 1 | |
e_1.e_200 2 | |
e_1.e_300 3 | |
e_1.e_400 4 | |
1 | |
2 | |
3 | |
#------------------------------------------------------- 旧模块热更后的输出:------------------------------------------------------- | |
name a_1 val 2 | |
name a_2 val [2, 2]  | |
name a_3 val (2, 2, 3)  | |
name a_4 val {2: 2}  | |
name a_5 val {2, 3}  | |
name a_6 val 2.1 | |
name a_7 val <class 'object'>  | |
name a_8 val <built-in function len>  | |
name a_9 val functools.partial(<built-in function len>, (2, 2, 3))  | |
name a_10 val defaultdict(<class 'str'>, {})  | |
name b_1 val 7 | |
name b_2 val 1 | |
name b_3 val 1 | |
name b_4 val 1 | |
name b_5 val 1 | |
name b_6 val 8 | |
name b_7 val <function b_7.<locals>.inner at 0x000002E77A9905E0>  | |
name b_8 val 9 | |
name b_9 val <function b_9.<locals>.wapper at 0x000002E77A9905E0>  | |
name b_10 val 9 | |
c_1.c_100 10 | |
c_1.c_200 20 | |
c_1.c_1000(1) 8  | |
c_1.C_100 10 | |
c_1.c_2000(1) 8  | |
c_1.c_3000(1) 8  | |
c_1.c_4000(1) <function c_1.c_4000.<locals>.wapper at 0x000002E77A9905E0>  | |
d_1.d_100 1 | |
d_1.d_200 1 | |
d_1.d_300 2 | |
d_1.d_400 2 | |
e_1.e_100 1 | |
e_1.e_200 2 | |
e_1.e_300 3 | |
3 | |
2 | |
1 |