CommandLine:
        python -m utool.util_dev --test-get_object_nbytes
        python -m utool.util_dev --test-get_object_nbytes:1

    Example:
        >>> # ENABLE_DOCTEST
        >>> from utool.util_dev import *  # NOQA
        >>> import numpy as np
        >>> import utool as ut
        >>> obj = [np.empty(1, dtype=np.uint8) for _ in range(8)]
        >>> nBytes = ut.get_object_nbytes(obj)
        >>> result = ('nBytes = %s' % (nBytes,))
        >>> print(result)
        nBytes = 8

    Example:
        >>> # DISABLE_DOCTEST
        >>> # UNSTABLE_DOCTEST
        >>> from utool.util_dev import *  # NOQA
        >>> import ibeis
        >>> import utool as ut
        >>> species = ibeis.const.TEST_SPECIES.ZEB_PLAIN
        >>> ibs = ibeis.opendb(defaultdb='testdb1')
        >>> qaids = ibs.get_valid_aids(species=species)
        >>> daids = ibs.get_valid_aids(species=species)
        >>> qreq_ = ibs.new_query_request(qaids, daids, verbose=True)
        >>> nBytes = ut.get_object_nbytes(qreq_)
        >>> result = (ut.byte_str2(nBytes))
        >>> print('result = %r' % (result,))

    Ignore:
        import sys
        sizedict = {key: sys.getsizeof(key()) for key in [dict, list, set, tuple, int, float]}
        ut.print_dict(sizedict)
        sizedict = {
            <type 'tuple'>: 56,
            <type 'set'>: 232,
            <type 'list'>: 72,
            <type 'float'>: 24,
            <type 'int'>: 24,
            <type 'dict'>: 280,
        }

def get_object_nbytes(obj, fallback_type=None, follow_pointers=False, exclude_modules=True, listoverhead=False):
    """
    CommandLine:
        python -m utool.util_dev --test-get_object_nbytes
        python -m utool.util_dev --test-get_object_nbytes:1

    Example:
        >>> # ENABLE_DOCTEST
        >>> from utool.util_dev import *  # NOQA
        >>> import numpy as np
        >>> import utool as ut
        >>> obj = [np.empty(1, dtype=np.uint8) for _ in range(8)]
        >>> nBytes = ut.get_object_nbytes(obj)
        >>> result = ('nBytes = %s' % (nBytes,))
        >>> print(result)
        nBytes = 8

    Example:
        >>> # DISABLE_DOCTEST
        >>> # UNSTABLE_DOCTEST
        >>> from utool.util_dev import *  # NOQA
        >>> import ibeis
        >>> import utool as ut
        >>> species = ibeis.const.TEST_SPECIES.ZEB_PLAIN
        >>> ibs = ibeis.opendb(defaultdb='testdb1')
        >>> qaids = ibs.get_valid_aids(species=species)
        >>> daids = ibs.get_valid_aids(species=species)
        >>> qreq_ = ibs.new_query_request(qaids, daids, verbose=True)
        >>> nBytes = ut.get_object_nbytes(qreq_)
        >>> result = (ut.byte_str2(nBytes))
        >>> print('result = %r' % (result,))

    Ignore:
        import sys
        sizedict = {key: sys.getsizeof(key()) for key in [dict, list, set, tuple, int, float]}
        ut.print_dict(sizedict)
        sizedict = {
            <type 'tuple'>: 56,
            <type 'set'>: 232,
            <type 'list'>: 72,
            <type 'float'>: 24,
            <type 'int'>: 24,
            <type 'dict'>: 280,
        }
    """
    import utool as ut
    import types
    seen = set([])
    def _object_nbytes(obj):
        object_id = id(obj)
        if object_id in seen:
            return 0
        if (obj is None or isinstance(obj, (int, bool, float))):
            return sys.getsizeof(obj)
        elif isinstance(obj, six.string_types):
            return sys.getsizeof(obj)
        seen.add(object_id)

        if listoverhead:
            totalsize = sys.getsizeof(obj)
        else:
            totalsize = 0
        try:
            if isinstance(obj, (ut.DynStruct, ut.Pref)):
                # dont deal with dynstruct shenanigans
                return
            elif exclude_modules and isinstance(obj, types.ModuleType):
                return 0
            elif isinstance(obj, np.ndarray):
                if not obj.flags['OWNDATA']:
                    # somebody else owns the data, returned size may be smaller than sobj.nbytes
                    # because sys.getsizeof will return the container size.
                    # if owndata is true sys.getsizeof returns the actual size
                    if follow_pointers:
                        totalsize += obj.nbytes
                    pass
                # TODO: check if a view or is memmapped
                # Does sys.getsizeof do the right thing ?
                totalsize = obj.nbytes
            elif (isinstance(obj, (tuple, list, set, frozenset))):
                for item in obj:
                    totalsize += _object_nbytes(item)
            elif isinstance(obj, dict):
                try:
                    for key, val in six.iteritems(obj):
                        totalsize += _object_nbytes(key)
                        totalsize += _object_nbytes(val)
                except RuntimeError as dictex:
                    ut.printex(dictex, 'RuntimeError in parsing dict nbytes',
                               keys=['key', (type, 'obj')], iswarning=True)
                    raise
            elif isinstance(obj, object) and hasattr(obj, '__dict__'):
                if hasattr(obj, 'used_memory') and not isinstance(obj, type):
                    # hack for flann objects
                    totalsize += obj.used_memory()
                totalsize += _object_nbytes(obj.__dict__)
                return totalsize
            elif isinstance(obj, type):
                # use zero for class definitions
                return 0
            elif isinstance(obj, np.int32):
                return obj.nbytes
            else:
                print('Unknown type %r for parsing size' % (type(obj),))
                return 0
        #except TypeError as ex:
        except Exception as ex:
            ut.printex(ex, 'may be an error in _object_nbytes',
                       keys=[(type, 'obj')], iswarning=True, tb=True)
            pass
            #import utool as ut
            #print('obj = %r' % (obj,))
            #ut.printex(ex)
            #ut.embed()
            #raise RuntimeError(str(ex))  # from ex
        return totalsize
    return _object_nbytes(obj)