From: Python class, dict, named tuple performance and memory usage
    from __future__ import print_function
    
    import time
    import sys
    import random
    import datetime
    from datetime import date
    from typing import NamedTuple, Dict
    from dataclasses import dataclass
    from recordclass import RecordClass
    from itertools import chain
    from collections import deque
    try:
        from reprlib import repr
    except ImportError:
        pass
    
    
    
    def total_size(o, handlers={}, verbose=False):
        """ Returns the approximate memory footprint an object and all of its contents.
    
        Automatically finds the contents of the following builtin containers and
        their subclasses:  tuple, list, deque, dict, set and frozenset.
        To search other containers, add handlers to iterate over their contents:
    
            handlers = {SomeContainerClass: iter,
                        OtherContainerClass: OtherContainerClass.get_elements}
    
        """
        dict_handler = lambda d: chain.from_iterable(d.items())
        all_handlers = {tuple: iter,
                        list: iter,
                        deque: iter,
                        dict: dict_handler,
                        set: iter,
                        frozenset: iter,
                       }
        all_handlers.update(handlers)     # user handlers take precedence
        seen = set()                      # track which object id's have already been seen
        default_size = sys.getsizeof(0)       # estimate sizeof object without __sizeof__
    
        def sizeof(o):
            if id(o) in seen:       # do not double count the same object
                return 0
            seen.add(id(o))
            s = sys.getsizeof(o, default_size)
    
            if verbose:
                print(s, type(o), repr(o), file=sys.stderr)
    
            for typ, handler in all_handlers.items():
                if isinstance(o, typ):
                    s += sum(map(sizeof, handler(o)))
                    break
            return s
    
        return sizeof(o)
    
    
    ##### NamedTuple #####
    class PricesNamedTuple(NamedTuple("Prices", 
                             [('open', float), 
                              ('high', float), 
                              ('low', float), 
                              ('close', float)])):
        pass
    
    class TradeDayNamedTuple(NamedTuple("TradeDay", 
                              (("symbol", str), 
                               ("dt", date), 
                               ("prices", PricesNamedTuple)))):
    
        def return_change(self):
            return round((self.prices.close - self.prices.open) / self.prices.open, 4)
    
        def update_symbol(self, symb):
            self = self._replace(symbol = symb)
    
    
    
    ##### DataClass #####
    @dataclass
    class PricesDataClass: 
        open: float
        high: float
        low: float
        close: float
    
    @dataclass
    class TradeDayDataClass:
        symbol: str
        dt: date
        prices: PricesDataClass
    
        return_change = lambda self : round(
                (self.prices.close - self.prices.open) / self.prices.open, 
                4)
    
        def update_symbol(self, symb):
            self.symbol = symb
    
    
    ##### RecordClass #####        
    class PricesRecordClass(RecordClass):
        open: float
        high: float
        low: float
        close: float
    
    class TradeDayRecordClass(RecordClass):
        symbol: str
        dt: date
        prices: PricesRecordClass
    
        return_change = lambda self : round(
                (self.prices.close - self.prices.open) / self.prices.open, 
                4)
    
        def update_symbol(self, symb):
            self.symbol = symb
    
    
    ##### Regular Python class #####
    class PricesClass():
        open: float
        high: float
        low: float
        close: float
    
        def __init__(self, _open, _high, _low, _close):
            self.open = _open
            self.high = _high
            self.low = _low
            self.close = _close
    
    class TradeDayClass():
        symbol: str
        dt: date
        prices: PricesRecordClass
    
        def __init__(self, _symbol, _dt, _prices):
            self.symbol = _symbol
            self.dt = _dt
            self.prices = _prices
    
        def return_change(self):
            return round((self.prices.close - self.prices.open) / self.prices.open, 4)
    
        def update_symbol(self, symb):
            self.symbol = symb
    
    
    ##### Regular Python class with slots #####
    class PricesClassSlots():
        __slots__ = ['open', 'high', 'low', 'close']
        open: float
        high: float
        low: float
        close: float
    
        def __init__(self, _open, _high, _low, _close):
            self.open = _open
            self.high = _high
            self.low = _low
            self.close = _close
    
    class TradeDayClassSlots():
        __slots__ = ['symbol', 'dt', 'prices']
        symbol: str
        dt: date
        prices: PricesRecordClass
    
        def __init__(self, _symbol, _dt, _prices):
            self.symbol = _symbol
            self.dt = _dt
            self.prices = _prices
    
        def return_change(self):
            return round((self.prices.close - self.prices.open) / self.prices.open, 4)
    
        def update_symbol(self, symb):
            self.symbol = symb
    
    
    ##### Python dict #####
    def PricesDict(_open, _high, _low, _close):
        return {"open": _open, "high": _high, "low": _low, "close": _close}
    
    
    def TradeDayDict(symbol, dt, prices):
        return {"symbol": symbol, "dt": dt, "prices": prices}
    
    
    
    def run_test(objType):
        print("====== %s Performance Report ======" % objType)
        print("Time it takes to create 'day' object is: ")
        TradeDay = eval("TradeDay%s" % objType)
        Prices = eval("Prices%s" % objType)
        data: Dict[str, TradeDay] = {}
        # data = sorteddict()
        obj_count = 100000
    
        st = time.time()
        for i in range(0, obj_count):
            data[str(i)] = TradeDay("MA", datetime.date.today(), Prices(random.random(), 30.0, 5.0, 20.0))
        print("%s day %s created     at: %8s per second" % (obj_count, objType, int(obj_count / (time.time() - st))))
    
        st = time.time()
        for k, v in data.items():
            d = v
        print("%s day %s top-read    at: %8s per second" % (obj_count, objType, int(obj_count / (time.time() - st))))
    
        st = time.time()
        for k, v in data.items():
            if objType == 'Dict':
                d = v['prices']['open']
            else:
                d = v.prices.open
        print("%s day %s sub-read    at: %8s per second" % (obj_count, objType, int(obj_count / (time.time() - st))))
    
        st = time.time()
        for k, v in data.items():
            if objType == 'Dict':
                d = round((v['prices']['close'] - v['prices']['open']) / v['prices']['open'], 4)
            else:
                d = round((v.prices.close - v.prices.open) / v.prices.open, 4)
        print("%s day %s change      at: %8s per second" % (obj_count, objType, int(obj_count / (time.time() - st))))
    
        st = time.time()
        for k, v in data.items():
            if objType == 'Dict':
                v['symbol'] = "AAA"
            elif objType == 'NamedTuple':
                v = v._replace(symbol="AAA")
            else:
                v.symbol = "AAA"
        print("%s day %s mutate      at: %8s per second" % (obj_count, objType, int(obj_count / (time.time() - st))))
    
        st = time.time()
        for k, v in data.items():
            if objType == 'Dict':
                d = round((v['prices']['close'] - v['prices']['open']) / v['prices']['open'], 4)
            else:
                d = v.return_change()
        print("%s day %s class_read      at: %8s per second" % (obj_count, objType, int(obj_count / (time.time() - st))))
    
        st = time.time()
        for k, v in data.items():
            if objType == 'Dict':
                v['symbol'] = "AAA"
            else:
                v.update_symbol("AAA")
        print("%s day %s class_update      at: %8s per second" % (obj_count, objType, int(obj_count / (time.time() - st))))
    
        print("The size of single {}:{}: bytes".format(objType, int(total_size(data) / obj_count)))
    
    
    
    
    if __name__ == '__main__':
        run_test('Dict')
        run_test('Class')
        run_test('ClassSlots')
        run_test('DataClass')
        run_test('RecordClass')
        run_test('NamedTuple')