diff --git a/dataclasses_json/cfg.py b/dataclasses_json/cfg.py index 930e51e0..54f8ac49 100644 --- a/dataclasses_json/cfg.py +++ b/dataclasses_json/cfg.py @@ -30,6 +30,8 @@ def __init__(self): Union[type, Optional[type]], MarshmallowField ] = {} + self.enable_cache = False + self.include_class_info = False # self._json_module = json # TODO: #180 diff --git a/dataclasses_json/core.py b/dataclasses_json/core.py index bf7b7cc8..4af7a622 100644 --- a/dataclasses_json/core.py +++ b/dataclasses_json/core.py @@ -1,4 +1,5 @@ import copy +import importlib import json import sys import warnings @@ -23,7 +24,7 @@ _is_optional, _isinstance_safe, _get_type_arg_param, _get_type_args, _is_counter, - _NO_ARGS, + _NO_ARGS,_cache, _issubclass_safe, _is_tuple) Json = Union[dict, list, str, int, float, bool, None] @@ -31,6 +32,10 @@ confs = ['encoder', 'decoder', 'mm_field', 'letter_case', 'exclude'] FieldOverride = namedtuple('FieldOverride', confs) # type: ignore +_fields = fields +@_cache(2**12) +def _cached_fields(cls): + return _fields(cls) class _ExtendedEncoder(json.JSONEncoder): def default(self, o) -> Json: @@ -52,13 +57,13 @@ def default(self, o) -> Json: result = json.JSONEncoder.default(self, o) return result - +@_cache(2**12) def _user_overrides_or_exts(cls): global_metadata = defaultdict(dict) encoders = cfg.global_config.encoders decoders = cfg.global_config.decoders mm_fields = cfg.global_config.mm_fields - for field in fields(cls): + for field in _cached_fields(cls): if field.type in encoders: global_metadata[field.name]['encoder'] = encoders[field.type] if field.type in decoders: @@ -72,7 +77,7 @@ def _user_overrides_or_exts(cls): cls_config = {} overrides = {} - for field in fields(cls): + for field in _cached_fields(cls): field_config = {} # first apply global overrides or extensions field_metadata = global_metadata[field.name] @@ -140,15 +145,36 @@ def _decode_letter_case_overrides(field_names, overrides): return names + +@_cache(2**14) +def _load_type(package_name, class_name): + try: + module = importlib.import_module(package_name) + type_ = getattr(module, class_name) + return type_ + except (ImportError, AttributeError) as e: + raise ImportError(f"Failed to load type {class_name} from package {package_name}: {e}") + +@_cache(maxsize=2**12) +def _cached_get_type_hints(cls): + return get_type_hints(cls) + +def get_class(base_class,kvs): + if cfg.global_config.include_class_info: + if '__module__' in kvs and "__name__" in kvs: + return _load_type(kvs['__module__'],kvs['__name__']) + return base_class + def _decode_dataclass(cls, kvs, infer_missing): + cls = get_class(cls,kvs) if _isinstance_safe(kvs, cls): return kvs overrides = _user_overrides_or_exts(cls) kvs = {} if kvs is None and infer_missing else kvs - field_names = [field.name for field in fields(cls)] + field_names = [field.name for field in _cached_fields(cls)] decode_names = _decode_letter_case_overrides(field_names, overrides) kvs = {decode_names.get(k, k): v for k, v in kvs.items()} - missing_fields = {field for field in fields(cls) if field.name not in kvs} + missing_fields = {field for field in _cached_fields(cls) if field.name not in kvs} for field in missing_fields: if field.default is not MISSING: @@ -162,8 +188,8 @@ def _decode_dataclass(cls, kvs, infer_missing): kvs = _handle_undefined_parameters_safe(cls, kvs, usage="from") init_kwargs = {} - types = get_type_hints(cls) - for field in fields(cls): + types = _cached_get_type_hints(cls) + for field in _cached_fields(cls): # The field should be skipped from being added # to init_kwargs as it's not intended as a constructor argument. if not field.init: @@ -171,6 +197,7 @@ def _decode_dataclass(cls, kvs, infer_missing): field_value = kvs[field.name] field_type = types[field.name] + if field_value is None: if not _is_optional(field_type): warning = ( @@ -253,7 +280,7 @@ def _support_extended_types(field_type, field_value): res = field_value return res - +@_cache(2**12) def _is_supported_generic(type_): if type_ is _NO_ARGS: return False @@ -406,27 +433,30 @@ def _asdict(obj, encode_json=False): """ if is_dataclass(obj): result = [] - overrides = _user_overrides_or_exts(obj) - for field in fields(obj): + if cfg.global_config.include_class_info: + result.append(('__module__',obj.__class__.__module__)) + result.append(('__name__',obj.__class__.__name__)) + overrides = _user_overrides_or_exts(obj.__class__) + for field in _cached_fields(obj.__class__): if overrides[field.name].encoder: value = getattr(obj, field.name) else: value = _asdict( - getattr(obj, field.name), - encode_json=encode_json + getattr(obj, field.name) ) result.append((field.name, value)) result = _handle_undefined_parameters_safe(cls=obj, kvs=dict(result), usage="to") - return _encode_overrides(dict(result), _user_overrides_or_exts(obj), + return _encode_overrides(dict(result), _user_overrides_or_exts(obj.__class__), encode_json=encode_json) elif isinstance(obj, Mapping): - return dict((_asdict(k, encode_json=encode_json), - _asdict(v, encode_json=encode_json)) for k, v in + return dict((_asdict(k), + _asdict(v)) for k, v in obj.items()) # enum.IntFlag and enum.Flag are regarded as collections in Python 3.11, thus a check against Enum is needed elif isinstance(obj, Collection) and not isinstance(obj, (str, bytes, Enum)): - return list(_asdict(v, encode_json=encode_json) for v in obj) + return list(_asdict(v) for v in obj) else: - return copy.deepcopy(obj) + # return copy.deepcopy(obj) + return obj diff --git a/dataclasses_json/utils.py b/dataclasses_json/utils.py index 942842ba..f2dde319 100644 --- a/dataclasses_json/utils.py +++ b/dataclasses_json/utils.py @@ -4,7 +4,21 @@ from collections import Counter from typing import (Collection, Mapping, Optional, TypeVar, Any, Type, Tuple, Union, cast) - +from dataclasses import _FIELDS +from dataclasses_json import cfg +import functools + +def _cache(maxsize=128): + def decorator(func): + cached_func = functools.lru_cache(maxsize=maxsize)(func) + @functools.wraps(func) + def wrapper(*args, **kwargs): + if cfg.global_config.enable_cache: + return cached_func(*args, **kwargs) + else: + return func(*args, **kwargs) + return wrapper + return decorator def _get_type_cons(type_): """More spaghetti logic for 3.6 vs. 3.7""" @@ -66,6 +80,8 @@ def _hasargs(type_, *args): else: return res +def _is_dataclass(obj): + return hasattr(obj.__class__,_FIELDS) or hasattr(obj,_FIELDS) class _NoArgs(object): def __bool__(self): @@ -111,7 +127,7 @@ def _isinstance_safe(o, t): else: return result - +@_cache(maxsize=2**12) def _issubclass_safe(cls, classinfo): try: return issubclass(cls, classinfo) @@ -136,7 +152,7 @@ def _is_new_type_subclass_safe(cls, classinfo): def _is_new_type(type_): return inspect.isfunction(type_) and hasattr(type_, "__supertype__") - +@_cache(maxsize=2**12) def _is_optional(type_): return (_issubclass_safe(type_, Optional) or _hasargs(type_, type(None)) or diff --git a/include_class_info.ipynb b/include_class_info.ipynb new file mode 100644 index 00000000..6ca772ce --- /dev/null +++ b/include_class_info.ipynb @@ -0,0 +1,523 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1. include class info in the json result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from dataclasses import dataclass,field\n", + "from typing import Set, Optional\n", + "\n", + "from dataclasses_json import dataclass_json,global_config\n", + "\n", + "\n", + "@dataclass_json\n", + "@dataclass\n", + "class Animal:\n", + " id: int = 0\n", + " health: int = 100\n", + "\n", + "\n", + "@dataclass_json\n", + "@dataclass\n", + "class Cat(Animal):\n", + " age: int = 1\n", + "\n", + "@dataclass_json\n", + "@dataclass\n", + "class Dog(Animal):\n", + " age: int = 1\n", + "\n", + "@dataclass_json\n", + "@dataclass\n", + "class PetCat(Cat):\n", + " name: str = ''\n", + "\n", + "@dataclass_json\n", + "@dataclass\n", + "class Person:\n", + " name:str = 'zyx'\n", + " animals: list[Animal] = field(default_factory=lambda:[])\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'zyx',\n", + " 'animals': [{'id': 0, 'health': 100},\n", + " {'id': 0, 'health': 100, 'age': 1},\n", + " {'id': 0, 'health': 100, 'age': 1, 'name': ''}]}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p1=Person(animals=[Animal(),Cat(),PetCat()])\n", + "p1.to_dict()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'zyx',\n", + " 'animals': [{'id': 0, 'health': 100},\n", + " {'id': 0, 'health': 100},\n", + " {'id': 0, 'health': 100}]}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p2 = Person.from_dict(p1.to_dict())\n", + "p2.to_dict()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "some fields are missing!\n", + "\n", + "to solve this, we need to include class info into the result" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'__module__': '__main__',\n", + " '__name__': 'Person',\n", + " 'name': 'zyx',\n", + " 'animals': [{'__module__': '__main__',\n", + " '__name__': 'Animal',\n", + " 'id': 0,\n", + " 'health': 100},\n", + " {'__module__': '__main__',\n", + " '__name__': 'Cat',\n", + " 'id': 0,\n", + " 'health': 100,\n", + " 'age': 1},\n", + " {'__module__': '__main__',\n", + " '__name__': 'PetCat',\n", + " 'id': 0,\n", + " 'health': 100,\n", + " 'age': 1,\n", + " 'name': ''}]}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "global_config.include_class_info=True\n", + "p1.to_dict()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'zyx',\n", + " 'animals': [{'id': 0, 'health': 100},\n", + " {'id': 0, 'health': 100, 'age': 1},\n", + " {'id': 0, 'health': 100, 'age': 1, 'name': ''}]}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p2 = Person.from_dict(p1.to_dict())\n", + "global_config.include_class_info=False\n", + "p2.to_dict()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "now the fields are all restored!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. use cache to save time" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "if i have thousands of objects to jsonize, the code will waste much time on get dataclass info, which will not change however in the process of jsonization " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Wed Feb 28 21:25:23 2024 profile_stats\n", + "\n", + " 11802740 function calls (10302726 primitive calls) in 6.541 seconds\n", + "\n", + " Ordered by: cumulative time\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 2 0.000 0.000 6.541 3.270 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\IPython\\core\\interactiveshell.py:3541(run_code)\n", + " 2 0.000 0.000 6.541 3.270 {built-in method builtins.exec}\n", + " 1 0.000 0.000 6.541 6.541 C:\\Users\\ZYX\\AppData\\Local\\Temp\\ipykernel_21292\\461415860.py:1()\n", + " 1 0.004 0.004 6.541 6.541 d:\\workspace\\dataclasses-json\\dataclasses_json\\api.py:26(to_json)\n", + " 1 0.000 0.000 6.488 6.488 d:\\workspace\\dataclasses-json\\dataclasses_json\\api.py:72(to_dict)\n", + " 300003/1 0.772 0.000 6.488 6.488 d:\\workspace\\dataclasses-json\\dataclasses_json\\core.py:429(_asdict)\n", + " 100001 0.057 0.000 6.462 0.000 d:\\workspace\\dataclasses-json\\dataclasses_json\\core.py:459()\n", + "700007/300003 0.361 0.000 3.900 0.000 d:\\workspace\\dataclasses-json\\dataclasses_json\\utils.py:14(wrapper)\n", + " 200002 1.658 0.000 3.450 0.000 d:\\workspace\\dataclasses-json\\dataclasses_json\\core.py:60(_user_overrides_or_exts)\n", + " 500005 0.156 0.000 1.349 0.000 d:\\workspace\\dataclasses-json\\dataclasses_json\\core.py:36(_cached_fields)\n", + " 500005 0.808 0.000 1.192 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\dataclasses.py:1269(fields)\n", + "1500321/700313 0.349 0.000 1.065 0.000 {built-in method builtins.isinstance}\n", + " 400004 0.167 0.000 0.827 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\typing.py:1175(__instancecheck__)\n", + " 400004 0.202 0.000 0.660 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\typing.py:1446(__subclasscheck__)\n", + " 400004 0.148 0.000 0.348 0.000 {built-in method builtins.issubclass}\n", + " 300003 0.160 0.000 0.266 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\dataclasses.py:1292(is_dataclass)\n", + " 100001 0.071 0.000 0.227 0.000 d:\\workspace\\dataclasses-json\\dataclasses_json\\utils.py:203(_handle_undefined_parameters_safe)\n", + " 400004 0.113 0.000 0.215 0.000 :1()\n", + " 1500015 0.205 0.000 0.205 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\dataclasses.py:1284()\n", + " 400004 0.091 0.000 0.199 0.000 :121(__subclasscheck__)\n", + " 800008 0.197 0.000 0.197 0.000 {method 'update' of 'dict' objects}\n", + " 100001 0.142 0.000 0.164 0.000 d:\\workspace\\dataclasses-json\\dataclasses_json\\core.py:109(_encode_overrides)\n", + " 100001 0.131 0.000 0.131 0.000 d:\\workspace\\dataclasses-json\\dataclasses_json\\utils.py:189(_undefined_parameter_action_safe)\n", + " 400004 0.116 0.000 0.116 0.000 {method 'get' of 'mappingproxy' objects}\n", + " 700011 0.115 0.000 0.115 0.000 {built-in method builtins.getattr}\n", + " 400004 0.108 0.000 0.108 0.000 {built-in method _abc._abc_subclasscheck}\n", + " 500307 0.108 0.000 0.108 0.000 {method 'values' of 'dict' objects}\n", + " 400004 0.102 0.000 0.102 0.000 {built-in method __new__ of type object at 0x00007FFC09F2B9B0}\n", + " 200002 0.051 0.000 0.051 0.000 {method 'append' of 'list' objects}\n", + " 1 0.000 0.000 0.048 0.048 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\json\\__init__.py:183(dumps)\n", + " 1 0.000 0.000 0.048 0.048 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\json\\encoder.py:183(encode)\n", + " 1 0.048 0.048 0.048 0.048 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\json\\encoder.py:205(iterencode)\n", + " 300003 0.046 0.000 0.046 0.000 {built-in method builtins.hasattr}\n", + " 100001 0.025 0.000 0.025 0.000 {method 'lower' of 'str' objects}\n", + " 100001 0.023 0.000 0.023 0.000 {method 'items' of 'dict' objects}\n", + " 302 0.002 0.000 0.004 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\ipykernel\\ipkernel.py:770(_clean_thread_parent_frames)\n", + " 151 0.001 0.000 0.001 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\threading.py:1533(enumerate)\n", + " 755 0.001 0.000 0.001 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\threading.py:1196(ident)\n", + " 604 0.000 0.000 0.000 0.000 {method 'keys' of 'dict' objects}\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\codeop.py:121(__call__)\n", + " 2 0.000 0.000 0.000 0.000 {built-in method builtins.compile}\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + " 151 0.000 0.000 0.000 0.000 {method '__exit__' of '_thread.RLock' objects}\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\contextlib.py:299(helper)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\traitlets\\traitlets.py:676(__get__)\n", + " 4 0.000 0.000 0.000 0.000 {built-in method builtins.next}\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\contextlib.py:104(__init__)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\contextlib.py:141(__exit__)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\contextlib.py:132(__enter__)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\traitlets\\traitlets.py:629(get)\n", + " 4 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\IPython\\core\\compilerop.py:180(extra_flags)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\json\\encoder.py:105(__init__)\n", + " 2 0.000 0.000 0.000 0.000 {method 'get' of 'dict' objects}\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\IPython\\core\\interactiveshell.py:3493(compare)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\IPython\\core\\interactiveshell.py:1277(user_global_ns)\n", + " 1 0.000 0.000 0.000 0.000 {method 'join' of 'str' objects}\n", + " 4 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\typing.py:2132(cast)\n", + "\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import cProfile\n", + "import pstats\n", + "global_config.enable_cache=False\n", + "p3 = Person(animals=[Animal() for _ in range(100000)])\n", + "\n", + "# Create a cProfile object\n", + "pr = cProfile.Profile()\n", + "\n", + "# Enable profiling\n", + "pr.enable()\n", + "\n", + "# Run your code\n", + "result_without_cache = p3.to_json()\n", + "\n", + "# Disable profiling\n", + "pr.disable()\n", + "\n", + "# Save the stats to a file\n", + "pr.dump_stats('profile_stats')\n", + "\n", + "# Open the saved stats file in Jupyter Notebook\n", + "stats = pstats.Stats('profile_stats')\n", + "stats.sort_stats('cumulative')\n", + "stats.print_stats()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Wed Feb 28 21:26:30 2024 profile_stats\n", + "\n", + " 5702755 function calls (4602444 primitive calls) in 2.713 seconds\n", + "\n", + " Ordered by: cumulative time\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 100001 0.041 0.000 2.664 0.000 d:\\workspace\\dataclasses-json\\dataclasses_json\\core.py:459()\n", + "1500355/700052 0.493 0.000 1.168 0.000 {built-in method builtins.isinstance}\n", + "400006/400004 0.158 0.000 0.783 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\typing.py:1175(__instancecheck__)\n", + "400006/400005 0.191 0.000 0.625 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\typing.py:1446(__subclasscheck__)\n", + "400006/400005 0.141 0.000 0.327 0.000 {built-in method builtins.issubclass}\n", + " 300003 0.147 0.000 0.241 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\dataclasses.py:1292(is_dataclass)\n", + " 100001 0.066 0.000 0.208 0.000 d:\\workspace\\dataclasses-json\\dataclasses_json\\utils.py:203(_handle_undefined_parameters_safe)\n", + "400006/400005 0.085 0.000 0.186 0.000 :121(__subclasscheck__)\n", + " 100001 0.130 0.000 0.153 0.000 d:\\workspace\\dataclasses-json\\dataclasses_json\\core.py:109(_encode_overrides)\n", + " 300003 0.129 0.000 0.129 0.000 d:\\workspace\\dataclasses-json\\dataclasses_json\\utils.py:14(wrapper)\n", + " 100001 0.118 0.000 0.118 0.000 d:\\workspace\\dataclasses-json\\dataclasses_json\\utils.py:189(_undefined_parameter_action_safe)\n", + " 400006 0.098 0.000 0.098 0.000 {built-in method _abc._abc_subclasscheck}\n", + " 200002 0.048 0.000 0.048 0.000 {method 'append' of 'list' objects}\n", + " 1 0.000 0.000 0.048 0.048 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\json\\__init__.py:183(dumps)\n", + " 1 0.000 0.000 0.047 0.047 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\json\\encoder.py:183(encode)\n", + " 1 0.047 0.047 0.047 0.047 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\json\\encoder.py:205(iterencode)\n", + " 200009 0.043 0.000 0.043 0.000 {built-in method builtins.getattr}\n", + " 300006 0.043 0.000 0.043 0.000 {built-in method builtins.hasattr}\n", + " 100001 0.024 0.000 0.024 0.000 {method 'lower' of 'str' objects}\n", + " 100001 0.022 0.000 0.022 0.000 {method 'items' of 'dict' objects}\n", + " 286 0.002 0.000 0.004 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\ipykernel\\ipkernel.py:770(_clean_thread_parent_frames)\n", + " 143 0.001 0.000 0.001 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\threading.py:1533(enumerate)\n", + " 715 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\threading.py:1196(ident)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\asyncio\\base_events.py:1908(_run_once)\n", + " 12 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\zmq\\sugar\\socket.py:621(send)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\zmq\\eventloop\\zmqstream.py:687(_rebuild_io_state)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\asyncio\\events.py:86(_run)\n", + " 1 0.000 0.000 0.000 0.000 {method 'run' of '_contextvars.Context' objects}\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\tornado\\ioloop.py:730(_run_callback)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\zmq\\eventloop\\zmqstream.py:718()\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\zmq\\eventloop\\zmqstream.py:607(_handle_events)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\ipykernel\\iostream.py:276()\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\ipykernel\\iostream.py:278(_really_send)\n", + " 12 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\enum.py:1531(__or__)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\zmq\\sugar\\socket.py:698(send_multipart)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\zmq\\eventloop\\zmqstream.py:710(_update_handler)\n", + " 3 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\zmq\\sugar\\attrsettr.py:42(__getattr__)\n", + " 572 0.000 0.000 0.000 0.000 {method 'keys' of 'dict' objects}\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\codeop.py:121(__call__)\n", + " 3 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\zmq\\sugar\\attrsettr.py:65(_get_attr_opt)\n", + " 2 0.000 0.000 0.000 0.000 {built-in method builtins.compile}\n", + " 21 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\enum.py:713(__call__)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\zmq\\eventloop\\zmqstream.py:648(_handle_recv)\n", + " 286 0.000 0.000 0.000 0.000 {method 'values' of 'dict' objects}\n", + " 1 0.000 0.000 0.000 0.000 C:\\Users\\ZYX\\AppData\\Local\\Temp\\ipykernel_21292\\1644171530.py:1()\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\selectors.py:319(select)\n", + " 5 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\enum.py:1541(__and__)\n", + " 143 0.000 0.000 0.000 0.000 {method '__exit__' of '_thread.RLock' objects}\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\tornado\\platform\\asyncio.py:215(add_callback)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\zmq\\sugar\\socket.py:777(recv_multipart)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\zmq\\eventloop\\zmqstream.py:566(sending)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\selectors.py:313(_select)\n", + " 21 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\enum.py:1116(__new__)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\queue.py:97(empty)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method select.select}\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\asyncio\\base_events.py:783(call_soon)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\asyncio\\base_events.py:812(_call_soon)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\zmq\\eventloop\\zmqstream.py:580(_run_callback)\n", + " 3 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\typing.py:378(inner)\n", + " 3 0.000 0.000 0.000 0.000 :1390(_handle_fromlist)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\contextlib.py:299(helper)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\queue.py:209(_qsize)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\asyncio\\base_events.py:732(time)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\asyncio\\events.py:36(__init__)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\ipykernel\\iostream.py:216(_check_mp_mode)\n", + " 4 0.000 0.000 0.000 0.000 {built-in method builtins.next}\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\contextlib.py:132(__enter__)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\traitlets\\traitlets.py:676(__get__)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\contextlib.py:141(__exit__)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\contextlib.py:104(__init__)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\ipykernel\\iostream.py:213(_is_master_process)\n", + " 3 0.000 0.000 0.000 0.000 {built-in method builtins.max}\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\typing.py:1239(__hash__)\n", + " 6 0.000 0.000 0.000 0.000 {built-in method builtins.len}\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\ipykernel\\iostream.py:157(_handle_event)\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\traitlets\\traitlets.py:629(get)\n", + " 3 0.000 0.000 0.000 0.000 {method 'upper' of 'str' objects}\n", + " 4 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\IPython\\core\\compilerop.py:180(extra_flags)\n", + " 2 0.000 0.000 0.000 0.000 {method 'popleft' of 'collections.deque' objects}\n", + " 2 0.000 0.000 0.000 0.000 {built-in method time.monotonic}\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\json\\encoder.py:105(__init__)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method _asyncio.get_running_loop}\n", + " 2 0.000 0.000 0.000 0.000 {method 'get' of 'dict' objects}\n", + " 1 0.000 0.000 0.000 0.000 {method 'append' of 'collections.deque' objects}\n", + " 2 0.000 0.000 0.000 0.000 {method '__exit__' of '_thread.lock' objects}\n", + " 3 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\zmq\\eventloop\\zmqstream.py:562(receiving)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method _contextvars.copy_context}\n", + " 1 0.000 0.000 0.000 0.000 {built-in method nt.getpid}\n", + " 1 0.000 0.000 0.000 0.000 {built-in method builtins.min}\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\IPython\\core\\interactiveshell.py:3493(compare)\n", + " 1 0.000 0.000 0.000 0.000 {method 'join' of 'str' objects}\n", + " 2 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\IPython\\core\\interactiveshell.py:1277(user_global_ns)\n", + " 2 0.000 0.000 0.000 0.000 {built-in method builtins.hash}\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\ipykernel\\iostream.py:255(closed)\n", + " 5 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\typing.py:2132(cast)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\asyncio\\selector_events.py:750(_process_events)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\asyncio\\base_events.py:538(_check_closed)\n", + " 1 0.000 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\asyncio\\base_events.py:2003(get_debug)\n", + " 1/0 0.000 0.000 0.000 c:\\Users\\ZYX\\miniconda3\\envs\\dsj\\Lib\\site-packages\\IPython\\core\\interactiveshell.py:3541(run_code)\n", + " 300001/0 0.684 0.000 0.000 d:\\workspace\\dataclasses-json\\dataclasses_json\\core.py:429(_asdict)\n", + " 1/0 0.000 0.000 0.000 {built-in method builtins.exec}\n", + "\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import cProfile\n", + "import pstats\n", + "global_config.enable_cache=True\n", + "p3 = Person(animals=[Animal() for _ in range(100000)])\n", + "\n", + "# Create a cProfile object\n", + "pr = cProfile.Profile()\n", + "\n", + "# Enable profiling\n", + "pr.enable()\n", + "\n", + "# Run your code\n", + "result_with_cache = p3.to_json()\n", + "\n", + "# Disable profiling\n", + "pr.disable()\n", + "\n", + "# Save the stats to a file\n", + "pr.dump_stats('profile_stats')\n", + "\n", + "# Open the saved stats file in Jupyter Notebook\n", + "stats = pstats.Stats('profile_stats')\n", + "stats.sort_stats('cumulative')\n", + "stats.print_stats()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The improvement in program speed is huge, from 6.6s to 2.5s in my laptop\n", + "\n", + "now let's check if the results are the same" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_with_cache==result_without_cache" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "dsj", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}