Source code for soma.utils.functiontools

# -*- coding: utf-8 -*-

#  This software and supporting documentation are distributed by
#      Institut Federatif de Recherche 49
#      CEA/NeuroSpin, Batiment 145,
#      91191 Gif-sur-Yvette cedex
#      France
#
# This software is governed by the CeCILL-B license under
# French law and abiding by the rules of distribution of free software.
# You can  use, modify and/or redistribute the software under the
# terms of the CeCILL-B license as circulated by CEA, CNRS
# and INRIA at the following URL "http://www.cecill.info".
#
# As a counterpart to the access to the source code and  rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty  and the software's author,  the holder of the
# economic rights,  and the successive licensors  have only  limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading,  using,  modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean  that it is complicated to manipulate,  and  that  also
# therefore means  that it is reserved for developers  and  experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and,  more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL-B license and that you accept its terms.

'''
Utility classes and functions for Python callable.

* author: Yann Cointepas
* organization: `NeuroSpin <http://www.neurospin.org>`_
* license: `CeCILL B <http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html>`_
'''
from __future__ import absolute_import
from six.moves import zip
import six
__docformat__ = "restructuredtext en"

import inspect
# handle deprecation of getargspec in python3
getfullargspec = getattr(inspect, 'getfullargspec', inspect.getargspec)

#-------------------------------------------------------------------------
from soma.translation import translate as _

#-------------------------------------------------------------------------
class Empty(object):
    pass

#-------------------------------------------------------------------------


[docs]class SomaPartial(object): ''' This is a reimplementation of :py:func:`functools.partial`, which adds compatibility with the :py:mod:`traits` module. See the documentation of :py:func:`functools.partial` for details: https://docs.python.org/library/functools.html#functools.partial Example:: from soma.functiontools import partial def f(a, b, c): return (a, b, c) g = partial(f, 'a', c='c') g('b') # calls f('a', 'b', c='c') ''' def __init__(self, function, *args, **kwargs): self.func = function self.args = args self.keywords = kwargs def __call__(self, *args, **kwargs): merged_kwargs = self.keywords.copy() merged_kwargs.update(kwargs) return self.func(*(self.args + args), **merged_kwargs) @property def func_code(self): ''' This property make SomaPartial useable with traits module. The method on_trait_change is requiring that the function has a func_code.co_argcount attribute. ''' result = Empty() result.co_argcount = numberOfParameterRange(self)[0] return result @property def __code__(self): return self.func_code
try: from functools import partial except ImportError: partial = SomaPartial #-------------------------------------------------------------------------
[docs]def getArgumentsSpecification(callable): ''' This is an extension of Python module :py:mod:`inspect.getargspec` that accepts classes and returns only information about the parameters that can be used in a call to *callable* (*e.g.* the first *self* parameter of bound methods is ignored). If *callable* has not an appropriate type, a :class:`TypeError <exceptions.TypeError>` exception is raised. Parameters ---------- callable: callable *function*, *method*, *class* or *instance* to inspect Returns ------- tuple: As :func:`inspect.getargspec`, returns *(args, varargs, varkw, defaults)* where *args* is a list of the argument names (it may contain nested lists). *varargs* and *varkw* are the names of the ``*`` and ``**`` arguments or *None*. *defaults* is a n-tuple of the default values of the last *n* arguments. ''' if inspect.isfunction(callable): return getfullargspec(callable)[:4] elif inspect.ismethod(callable): args, varargs, varkw, defaults = getfullargspec(callable)[:4] args = args[1:] # ignore the first "self" parameter return args, varargs, varkw, defaults elif inspect.isclass(callable): try: init = callable.__init__ except AttributeError: return [], None, None, None return getArgumentsSpecification(init) elif isinstance(callable, (partial, SomaPartial)): args, varargs, varkw, defaults = getArgumentsSpecification( callable.func) if defaults: d = dict(zip(reversed(args), reversed(defaults))) else: d = {} d.update(zip(reversed(args), reversed(callable.args))) if callable.keywords: d.update(callable.keywords) if len(d): defaults = tuple((d[i] for i in args[-len(d):])) else: defaults = d return (args, varargs, varkw, defaults) else: try: call = callable.__call__ except AttributeError: raise TypeError(_('%s is not callable') % repr(callable)) return getArgumentsSpecification(call)
#-------------------------------------------------------------------------
[docs]def getCallableString(callable): ''' Returns a translated human readable string representing a callable. Parameters ---------- callable: callable *function*, *method*, *class* or *instance* to inspect Returns -------- string: type and name of the callable ''' if inspect.isfunction(callable): name = _('function %s') % (callable.__name__, ) elif inspect.ismethod(callable): name = _('method %s') % (six.get_method_self(callable).__class__.__name__ + '.' + callable.__name__, ) elif inspect.isclass(callable): name = _('class %s') % (callable.__name__, ) else: name = str(callable) return name
#-------------------------------------------------------------------------
[docs]def hasParameter(callable, parameterName): ''' Returns *True* if *callable* can be called with a parameter named *parameterName*. Otherwise, returns *False*. .. seealso:: :py:func:`getArgumentsSpecification` Parameters ---------- callable: callable *function*, *method*, *class* or *instance* to inspect parameterName: string name of the parameter Returns ------- bool: ''' args, varargs, varkw, defaults = getArgumentsSpecification(callable) return varkw is not None or parameterName in args
#-------------------------------------------------------------------------
[docs]def numberOfParameterRange(callable): ''' Returns the minimum and maximum number of parameter that can be used to call a function. If the maximum number of argument is not defined, it is set to *None*. .. seealso:: :func:`getArgumentsSpecification` Parameters ---------- callable: callable *function*, *method*, *class* or *instance* to inspect Returns ------- tuple: two elements (minimum, maximum) ''' args, varargs, varkw, defaults = getArgumentsSpecification(callable) if defaults is None or len(defaults) > len(args): lenDefault = 0 else: lenDefault = len(defaults) minimum = len(args) - lenDefault if varargs is None: maximum = len(args) else: maximum = None return minimum, maximum
#-------------------------------------------------------------------------
[docs]def checkParameterCount(callable, paramCount): ''' Checks that a callable can be called with *paramCount* arguments. If not, a RuntimeError is raised. .. seealso:: :func:`getArgumentsSpecification` Parameters ---------- callable: callable *function*, *method*, *class* or *instance* to inspect paramCount: int number of parameters ''' minimum, maximum = numberOfParameterRange(callable) if ( maximum is not None and paramCount > maximum ) or \ paramCount < minimum: raise RuntimeError( _('%(callable)s cannot be called with %(paramCount)d arguments') % {'callable': getCallableString(callable), 'paramCount': paramCount})
#-------------------------------------------------------------------------
[docs]def drange(start, stop, step=1): ''' Creates lists containing arithmetic progressions of any number type (int, float, ...) ''' r = start while r < stop: yield r r += step