astro py:docs

- Index
- Modules

### Navigation

- Astropy v1.1.1 »
- Module code »

# Source code for astropy.utils.codegen

    # -*- coding: utf-8 -*-
    # Licensed under a 3-clause BSD style license - see LICENSE.rst
    """Utilities for generating new Python code at runtime."""

    from __future__ import (absolute_import, division, print_function,
                            unicode_literals)

    import inspect
    import itertools
    import keyword
    import os
    import re
    import textwrap

    from .introspection import find_current_module
    from ..extern import six

    __all__ = ['make_function_with_signature']

    _ARGNAME_RE = re.compile(r'^[A-Za-z][A-Za-z_]*')
    """
    Regular expression used my make_func which limits the allowed argument
    names for the created function.  Only valid Python variable names in
    the ASCII range and not beginning with '_' are allowed, currently.
    """

    def make_function_with_signature(func, args=(), kwargs={}, varargs=None,
                                     varkwargs=None, name=None):
        """
        Make a new function from an existing function but with the desired
        signature.

        The desired signature must of course be compatible with the arguments
        actually accepted by the input function.

        The ``args`` are strings that should be the names of the positional
        arguments.  ``kwargs`` can map names of keyword arguments to their
        default values.  It may be either a ``dict`` or a list of ``(keyword,
        default)`` tuples.

        If ``varargs`` is a string it is added to the positional arguments as
        ``*<varargs>``.  Likewise ``varkwargs`` can be the name for a variable
        keyword argument placeholder like ``**<varkwargs>``.

        If not specified the name of the new function is taken from the original
        function.  Otherwise, the ``name`` argument can be used to specify a new
        name.

        Note, the names may only be valid Python variable names.
        """

        pos_args = []
        key_args = []

        if six.PY2 and varargs and kwargs:
            raise SyntaxError('keyword arguments not allowed after '
                              '*{0}'.format(varargs))

        if isinstance(kwargs, dict):
            iter_kwargs = six.iteritems(kwargs)
        else:
            iter_kwargs = iter(kwargs)

        # Check that all the argument names are valid
        for item in itertools.chain(args, iter_kwargs):
            if isinstance(item, tuple):
                argname = item[0]
                key_args.append(item)
            else:
                argname = item
                pos_args.append(item)

            if keyword.iskeyword(argname) or not _ARGNAME_RE.match(argname):
                raise SyntaxError('invalid argument name: {0}'.format(argname))

        for item in (varargs, varkwargs):
            if item is not None:
                if keyword.iskeyword(item) or not _ARGNAME_RE.match(item):
                    raise SyntaxError('invalid argument name: {0}'.format(item))

        def_signature = [', '.join(pos_args)]

        if varargs:
            def_signature.append(', *{0}'.format(varargs))

        call_signature = def_signature[:]

        if name is None:
            name = func.__name__

        global_vars = {'__{0}__func'.format(name): func}
        local_vars = {}
        # Make local variables to handle setting the default args
        for idx, item in enumerate(key_args):
            key, value = item
            default_var = '_kwargs{0}'.format(idx)
            local_vars[default_var] = value
            def_signature.append(', {0}={1}'.format(key, default_var))
            call_signature.append(', {0}={0}'.format(key))

        if varkwargs:
            def_signature.append(', **{0}'.format(varkwargs))
            call_signature.append(', **{0}'.format(varkwargs))

        def_signature = ''.join(def_signature).lstrip(', ')
        call_signature = ''.join(call_signature).lstrip(', ')

        mod = find_current_module(2)
        frm = inspect.currentframe().f_back

        if mod:
            filename = mod.__file__
            modname = mod.__name__
            if filename.endswith('.pyc'):
                filename = os.path.splitext(filename)[0] + '.py'
        else:
            filename = '<string>'
            modname = '__main__'

        # Subtract 2 from the line number since the length of the template itself
        # is two lines.  Therefore we have to subtract those off in order for the
        # pointer in tracebacks from __{name}__func to point to the right spot.
        lineno = frm.f_lineno - 2

        # The lstrip is in case there were *no* positional arguments (a rare case)
        # in any context this will actually be used...
        template = textwrap.dedent("""{0}\
        def {name}({sig1}):
            return __{name}__func({sig2})
        """.format('\n' * lineno, name=name, sig1=def_signature,
                   sig2=call_signature))

        code = compile(template, filename, 'single')

        eval(code, global_vars, local_vars)

        new_func = local_vars[name]
        new_func.__module__ = modname
        new_func.__doc__ = func.__doc__

        return new_func