Generate new function where code is an input function code with all
    LOAD_GLOBAL statements changed to LOAD_CONST statements.

    :param function func: code function to transform.
    :param bool builtin_only: only transform builtin objects.
    :param list stoplist: attribute names to not transform.
    :param function verbose: logger function which takes in parameter a message

    .. warning::
        Be sure global attributes to transform are not resolved dynamically.

def _make_constants(func, builtin_only=False, stoplist=None, verbose=None):
    """Generate new function where code is an input function code with all
    LOAD_GLOBAL statements changed to LOAD_CONST statements.

    :param function func: code function to transform.
    :param bool builtin_only: only transform builtin objects.
    :param list stoplist: attribute names to not transform.
    :param function verbose: logger function which takes in parameter a message

    .. warning::
        Be sure global attributes to transform are not resolved dynamically."""

    result = func

    if stoplist is None:
        stoplist = []

    try:
        fcode = func.__code__
    except AttributeError:
        return func        # Jython doesn't have a __code__ attribute.
    newcode = list(fcode.co_code) if PY3 else [ord(co) for co in fcode.co_code]
    newconsts = list(fcode.co_consts)
    names = fcode.co_names
    codelen = len(newcode)

    env = vars(builtins).copy()
    if builtin_only:
        stoplist = dict.fromkeys(stoplist)
        stoplist.update(func.__globals__)
    else:
        env.update(func.__globals__)

    # First pass converts global lookups into constants
    changed = False
    i = 0
    while i < codelen:
        opcode = newcode[i]
        if opcode in (EXTENDED_ARG, STORE_GLOBAL):
            return func    # for simplicity, only optimize common cases
        if opcode == LOAD_GLOBAL:
            oparg = newcode[i + 1] + (newcode[i + 2] << 8)
            name = fcode.co_names[oparg]
            if name in env and name not in stoplist:
                value = env[name]
                for pos, val in enumerate(newconsts):
                    if val is value:
                        break
                else:
                    pos = len(newconsts)
                    newconsts.append(value)
                newcode[i] = LOAD_CONST
                newcode[i + 1] = pos & 0xFF
                newcode[i + 2] = pos >> 8
                changed = True
                if verbose is not None:
                    verbose("{0} --> {1}".format(name, value))
        i += 1
        if opcode >= HAVE_ARGUMENT:
            i += 2

    # Second pass folds tuples of constants and constant attribute lookups
    i = 0
    while i < codelen:

        newtuple = []
        while newcode[i] == LOAD_CONST:
            oparg = newcode[i + 1] + (newcode[i + 2] << 8)
            newtuple.append(newconsts[oparg])
            i += 3

        opcode = newcode[i]
        if not newtuple:
            i += 1
            if opcode >= HAVE_ARGUMENT:
                i += 2
            continue

        if opcode == LOAD_ATTR:
            obj = newtuple[-1]
            oparg = newcode[i + 1] + (newcode[i + 2] << 8)
            name = names[oparg]
            try:
                value = getattr(obj, name)
            except AttributeError:
                continue
            deletions = 1

        elif opcode == BUILD_TUPLE:
            oparg = newcode[i + 1] + (newcode[i + 2] << 8)
            if oparg != len(newtuple):
                continue
            deletions = len(newtuple)
            value = tuple(newtuple)

        else:
            continue

        reljump = deletions * 3
        newcode[i - reljump] = JUMP_FORWARD
        newcode[i - reljump + 1] = (reljump - 3) & 0xFF
        newcode[i - reljump + 2] = (reljump - 3) >> 8

        nclen = len(newconsts)
        newconsts.append(value)
        newcode[i] = LOAD_CONST
        newcode[i + 1] = nclen & 0xFF
        newcode[i + 2] = nclen >> 8
        i += 3
        changed = True
        if verbose is not None:
            verbose("new folded constant:{0}".format(value))

    if changed:

        codeobj = getcodeobj(newconsts, newcode, fcode, fcode)

        result = type(func)(
            codeobj,
            func.__globals__, func.__name__, func.__defaults__, func.__closure__
        )

        # set func attributes to result
        for prop in WRAPPER_ASSIGNMENTS:
            try:
                attr = getattr(func, prop)
            except AttributeError:
                pass
            else:
                setattr(result, prop, attr)

    return result