Python function Heat more (at development time)

Source: Internet
Author: User
Tags function definition glob wrapper
Python function Hot More (run-time update)


Characteristics


    1. Implement function run-time modification (development environment!!!) , not on-line heat more!!! )
    2. Support for the co-function (tornado)


Usage


 
from realtimefunc import realtimefunc

@coroutine
@realtimefunc
def test():
    # function body


Introduction
The startup of the backend service generally requires a lot of preparation work, which results in a slower start-up, and it is difficult to achieve one-time pass in the development of a task, so it may be necessary to restart the service repeatedly, which is quite annoying.

In addition, when contacting a python project, when the logic of the code representation is difficult to understand, or when there is any doubt about the logic and actual effect of the code representation, it usually needs to be understood by printing the log, or through pdb debugging.
The former is more difficult to avoid playing a part of the log and needs to restart the service. The latter is powerful but difficult to use, and cannot be modified at the time, and will be effective in the next call (requires restart of the service).

 


Remark :

Many web frameworks have self-starting by detecting the mtime of the project file and then replacing the current service process. For example, tornado uses a timer to periodically detect the project file and implement autostart. But this kind of restart is the entire service, so it takes less time to reduce.

It's natural to think that it would be nice if you could change the code at any time without restarting the entire service.



Achieve the goal:
Implements an adorner, the decorated function arbitrarily modifies, does not need to restart the service, the new request takes effect immediately.



Implementation ideas:
When called by the decorator function, the function code is obtained from the. py file using Inspect.getsource, and the function is redefined and named by exec, allowing the modification of the function code to take effect in the next call. Latest Code Address Realtimefunc


 

"""A decorator is used to update a function at runtime."""

from __future__ import print_function
import sys
import os
import re
import ast
import linecache
import functools
import traceback
from inspect import getfile, isclass, findsource, getblock

__all__ = ["realtimefunc"]

Decorator = "@realtimefunc"

PY3 = sys.version_info >= (3,)


# The cache

# record, used to record functions decorated by realtimefunc,
# is a dict {filename:{func1, func2}
# refresh, used to to mark functions which source file stat has changed,
# is also a dict {filename:{func1, func2}}

# Note: the filename may be repeated but it doesn't matter.

record = {}
refresh = {}


def _exec(code, filepath, firstlineno, glob, loc=None):
    astNode = ast.parse(code)
    astNode = ast.increment_lineno(astNode, firstlineno)
    code = compile(astNode, filepath, 'exec')
    exec(code, glob, loc)


def _findclass(func):
    cls = sys.modules.get(func.__module__)
    if cls is None:
        return None
    for name in func.__qualname__.split('.')[:-1]:
        cls = getattr(cls, name)
    if not isclass(cls):
        return None
    return cls


def get_qualname(func):
    '''return qualname by look through the call stack.'''
    qualname = []
    stacks = traceback.extract_stack(f=None)
    begin_flag = False
    for stack in stacks[::-1]:
        if stack[3].strip() == Decorator:
            qualname.append(func.__name__)
            begin_flag = True
        if stack[2] == '<module>':
            break
        if begin_flag:
            qualname.append(stack[2])
    return '.'.join(qualname[::-1])


def get_func_real_firstlineno(func):
    start_lineno = 0
    lines = linecache.getlines(func.__code__.co_filename)
    cls = _findclass(func)
    if cls:
        lines, lnum = findsource(cls)
        lines = getblock(lines[lnum:])
        start_lineno = lnum

    #  referenced from inspect _findclass
    pat = re.compile(r'^(\s*)def\s*' + func.__name__ + r'\b')
    candidates = []
    for i in range(len(lines)):
        match = pat.match(lines[i])
        if match:
            # if it's at toplevel, it's already the best one
            if lines[i][0] == 'd':
                return (i + start_lineno)
            # else add whitespace to candidate list
            candidates.append((match.group(1), i))
    if candidates:
        # this will sort by whitespace, and by line number,
        # less whitespace first
        candidates.sort()
        return start_lineno + candidates[0][1]
    else:
        raise OSError('could not find function definition')


def get_source_code(func, func_runtime_name, firstlineno):
    lines = linecache.getlines(func.__code__.co_filename)
    code_lines = getblock(lines[firstlineno:])
    # repalce function name
    code_lines[0] = code_lines[0].replace(func.__name__, func_runtime_name, 1)
    i_indent = code_lines[0].index("def")
    # code indentation
    code_lines = [line[i_indent:] for line in code_lines]
    code = ''.join(code_lines)
    return code


def check_file_stat(filename):
    entry = linecache.cache.get(filename, None)
    change = False
    if not entry:
        change = True
    else:
        size, mtime, _, fullname = entry
        try:
            stat = os.stat(fullname)
        except OSError:
            change = True
            del linecache.cache[filename]
        if size != stat.st_size or mtime != stat.st_mtime:
            change = True
            del linecache.cache[filename]

    if change:
        global refresh
        for f in record[filename]:
            refresh.setdefault(filename, set()).add(f)


def realtimefunc(func):
    # python2 need set __qualname__ by hand
    if not PY3:
        func.__qualname__ = get_qualname(func)
    func_real_name = func.__qualname__.replace('.', '_') + '_realfunc'
    filename = getfile(func)
    filepath = os.path.abspath(filename)
    global record, refresh
    record.setdefault(filename, set()).add(func)
    refresh.setdefault(filename, set()).add(func)


    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        glob = func.__globals__
        check_file_stat(filename)
        if func in refresh[filename]:
            firstlineno = get_func_real_firstlineno(func)
            code_str = get_source_code(func, func_real_name, firstlineno)
            _exec(code_str, filepath, firstlineno, glob)
            refresh[filename].remove(func)
        func_realtime = glob[func_real_name]
        return func_realtime(*args, **kwargs)

    return wrapper

Effect
Implementing the Development runtime modification functions makes it easy to view and modify the data associated with the decorated function, as well as to construct simple test data and perform simple branch testing.
Attention
For a covariant function, for example, if you use a lower version of Tornado (for example, 4.1), change the return to yield, where the position is commented in the code
Inspect useful to linecache cache, in obtaining the function source code needs to check the cache



This article applies small white, big God detour, small white amuse oneself!!!


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.