Exemplo n.º 1
0
 def _get_func_decorator(f):
     argspec = inspect.getargspec(f)
     typespec_dict = {}
     # remember zip always truncates at the length of the shortest iterator
     for spec, argname in zip(typespecs_or_func, argspec.args):
         typespec_dict[argname] = spec
     for argname, spec in kwargs.items():
         if argname in typespec_dict:
             raise TypeError("multiple type specifications given for "
                             "argument {} to overloaded function {}()".format(argname, self.__name__))
         if argname not in argspec.args:
             raise TypeError("type specifiation for unknown argument {} given to overloaded "
                             "function {}()".format(argname, self.__name__))
         typespec_dict[argname] = spec
     # fill in None for anything missing, indicating any type will do
     for argname in argspec.args:
         if argname not in typespec_dict:
             typespec_dict[argname] = None
     self._versions.append(argtypespec(f, typespec_dict))
     return self
Exemplo n.º 2
0
 def _get_func_decorator(f):
     argspec = inspect.getargspec(f)
     typespec_dict = {}
     # remember zip always truncates at the length of the shortest iterator
     for spec, argname in zip(typespecs_or_func, argspec.args):
         typespec_dict[argname] = spec
     for argname, spec in kwargs.items():
         if argname in typespec_dict:
             raise TypeError(
                 "multiple type specifications given for "
                 "argument {} to overloaded function {}()".format(
                     argname, self.__name__))
         if argname not in argspec.args:
             raise TypeError(
                 "type specifiation for unknown argument {} given to overloaded "
                 "function {}()".format(argname, self.__name__))
         typespec_dict[argname] = spec
     # fill in None for anything missing, indicating any type will do
     for argname in argspec.args:
         if argname not in typespec_dict:
             typespec_dict[argname] = None
     self._versions.append(argtypespec(f, typespec_dict))
     return self
Exemplo n.º 3
0
    def overload_with(self, *typespecs_or_func, **kwargs):
        """ Add a overloaded version of the decorated function to the available options for a given overloaded function.
        Types can be specified several ways.  The simplest is to give the types as plain old arguments to
        the `overload_with` decorator.  The order of the arguments given corresponds to the order of the arguments in
        the function definition.  For example:

            >>> @overloaded
            ... def foo():
            ...     pass
            ...
            >>> @foo.overload_with(int, str)
            ... def foo(num, word):
            ...     return word * num
            ...
            >>> foo(3, 'hello')
            'hellohellohello'
            >>> foo('hello', 3)  # doctest: +ELLIPSIS
            Traceback (most recent call last):
                ...
            TypeError: invalid function signature ...

        If fewer types are specified than arguments, it is assumed that each of the remaining arguments can be any type.
        (specifying `None` for the type has the same effect).  Keyword arguments may also be given to `overload_with`,
        where the keyword must correspond to the argument name in the function definition.  Here is an example of this
        specification technique (which may be mixed with the previous technique), using the overloaded function `foo`
        from above:

            >>> @foo.overload_with(int, int, c=int, string=str)
            ... def foo(a, b, string, c=1):
            ...     return string*a*b*c
            ...
            >>> foo(2, 3, 'a')
            'aaaaaa'

        Finally, types may be specified by way of Python 3 function annotations (this is the prefered technique
        if the code is only to be used in Python 3) by using the `overload_with` decorator without arguments.

        The type of a given argument need not be an instance of `type`.  It can also be any callable that can
        be called with one argument, `None`, an `IterableOf` instance, a string, or a tuple of `type`
        any of these.  The way these work is as follows:

        * If the type is a callable that does not return `False` (or anything with a truth value
          of False) when called with the argument value, the argument is considered to be of the correct type.

        * If one of the types in a sequence is `None` and the argument is `None`, the argument is considered
          to have the correct type. [#f2]_

        * If the type is an instance if `IterableOf` (or one of its subtypes), the argument is considered to be
          the correct type if and only if all of the items in the container are considered the correct type when
          the criteria described here are applied with the `types` attribute of the `IterableOf` instance

        * If the type is a string, the string is evaluated in the context of the overloaded function's globals, and
          the return value of this evaluation is used as the type (which may, in turn, be any of the options described
          here except for a string).  This is particularly useful when the function to be overloaded is a
          method that takes as an argument an instance of that method's parent class.  Since the parent class
          will not be defined at the time of the function definition, using the parent class's name as an identifier
          will not work.

        * Finally, if a `tuple` (or any other `Iterable`) of type specifications is given, the argument is considered
          to have the correct type if any of the type specifications in the list lead to the correct type.

        For example:

            >>> def str_reversed(var):
            ...     return ''.join(reversed(var)) if isinstance(var, str) else NotImplemented
            ...
            >>> @foo.overload_with(
            ...     bar=lambda x: x == str_reversed(x),
            ...     n=(int, lambda x: isinstance(x, float) and round(x) == x)
            ... )
            ... def foo(bar, n):
            ...     return bar * int(n) + ' is a palendrome!'
            ...
            >>> @foo.overload_with(baz=str)
            ... def foo(baz, n=3):
            ...     return baz * int(n) + ' is not a palendrome.'
            ...
            >>> foo('racecar', 2.0)
            'racecarracecar is a palendrome!'
            >>> foo('asdf')
            'asdfasdfasdf is not a palendrome.'

        The above example also illustrates the order of precidence for overloaded functions:  if a call is
        allowed to multiple versions of the function, the first to be defined is used.  (Notice that the first
        call in the above example matches both overloaded versions of `foo` defined in the example).

        ..warning ::
            To allow for the use Python 3 function annotations, specifying a single argument to `overload_with` that
            is a callable will assume that the Python 3 type specification method is being used, as described above.
            This could cause problems if what is actually intended is that there be only one type-constrained
            argument which returns `True` for an arbitrary callable.  To get around this, simply specify the type
            callable for the single argument using the keyword form or as a single item tuple.  If the callable
            you want to use for this specification is a builtin such as `callable` or `int`, this shouldn't be
            an issue.

        .. rubric:: Footnotes

        .. [#f2] Note that if the type is `None` and it is not in a sequence, or a length one tuple `(None,)` is
                 given, this is considered a match for all types.  This is mostly to allow arguments to be left out
                 of the specification (in particular, the first argument of most methods, `self`)

        """
        # TODO check to make sure type specifications are valid
        if sys.version_info > (3, 0) \
                and len(kwargs) == 0 \
                and len(typespecs_or_func) == 1 \
                and inspect.isfunction(typespecs_or_func[0]) \
                and not inspect.isbuiltin(typespecs_or_func[0]):
            func = typespecs_or_func[0]
            if hasattr(func, 'func_annotations'):
                # Python 3 version with func_annotations:
                argspec = inspect.getargspec(func)
                # for now, ignore varargs and keywords annotations
                annotations = func.func_annotations
                typespec_dict = {}
                for arg in argspec:
                    typespec_dict[arg] = func.func_annotations[arg]
                self._versions.append(argtypespec(func, typespec_dict))
                return self
            else:
                raise TypeError("when not using Python 3 function annotations, "
                                "{}.overload_with must\nbe called with "
                                "type specifications as arguments".format(self.__name__))
        else:
            def _get_func_decorator(f):
                argspec = inspect.getargspec(f)
                typespec_dict = {}
                # remember zip always truncates at the length of the shortest iterator
                for spec, argname in zip(typespecs_or_func, argspec.args):
                    typespec_dict[argname] = spec
                for argname, spec in kwargs.items():
                    if argname in typespec_dict:
                        raise TypeError("multiple type specifications given for "
                                        "argument {} to overloaded function {}()".format(argname, self.__name__))
                    if argname not in argspec.args:
                        raise TypeError("type specifiation for unknown argument {} given to overloaded "
                                        "function {}()".format(argname, self.__name__))
                    typespec_dict[argname] = spec
                # fill in None for anything missing, indicating any type will do
                for argname in argspec.args:
                    if argname not in typespec_dict:
                        typespec_dict[argname] = None
                self._versions.append(argtypespec(f, typespec_dict))
                return self
            return _get_func_decorator
Exemplo n.º 4
0
    def overload_with(self, *typespecs_or_func, **kwargs):
        """ Add a overloaded version of the decorated function to the available options for a given overloaded function.
        Types can be specified several ways.  The simplest is to give the types as plain old arguments to
        the `overload_with` decorator.  The order of the arguments given corresponds to the order of the arguments in
        the function definition.  For example:

            >>> @overloaded
            ... def foo():
            ...     pass
            ...
            >>> @foo.overload_with(int, str)
            ... def foo(num, word):
            ...     return word * num
            ...
            >>> foo(3, 'hello')
            'hellohellohello'
            >>> foo('hello', 3)  # doctest: +ELLIPSIS
            Traceback (most recent call last):
                ...
            TypeError: invalid function signature ...

        If fewer types are specified than arguments, it is assumed that each of the remaining arguments can be any type.
        (specifying `None` for the type has the same effect).  Keyword arguments may also be given to `overload_with`,
        where the keyword must correspond to the argument name in the function definition.  Here is an example of this
        specification technique (which may be mixed with the previous technique), using the overloaded function `foo`
        from above:

            >>> @foo.overload_with(int, int, c=int, string=str)
            ... def foo(a, b, string, c=1):
            ...     return string*a*b*c
            ...
            >>> foo(2, 3, 'a')
            'aaaaaa'

        Finally, types may be specified by way of Python 3 function annotations (this is the prefered technique
        if the code is only to be used in Python 3) by using the `overload_with` decorator without arguments.

        The type of a given argument need not be an instance of `type`.  It can also be any callable that can
        be called with one argument, `None`, an `IterableOf` instance, a string, or a tuple of `type`
        any of these.  The way these work is as follows:

        * If the type is a callable that does not return `False` (or anything with a truth value
          of False) when called with the argument value, the argument is considered to be of the correct type.

        * If one of the types in a sequence is `None` and the argument is `None`, the argument is considered
          to have the correct type. [#f2]_

        * If the type is an instance if `IterableOf` (or one of its subtypes), the argument is considered to be
          the correct type if and only if all of the items in the container are considered the correct type when
          the criteria described here are applied with the `types` attribute of the `IterableOf` instance

        * If the type is a string, the string is evaluated in the context of the overloaded function's globals, and
          the return value of this evaluation is used as the type (which may, in turn, be any of the options described
          here except for a string).  This is particularly useful when the function to be overloaded is a
          method that takes as an argument an instance of that method's parent class.  Since the parent class
          will not be defined at the time of the function definition, using the parent class's name as an identifier
          will not work.

        * Finally, if a `tuple` (or any other `Iterable`) of type specifications is given, the argument is considered
          to have the correct type if any of the type specifications in the list lead to the correct type.

        For example:

            >>> def str_reversed(var):
            ...     return ''.join(reversed(var)) if isinstance(var, str) else NotImplemented
            ...
            >>> @foo.overload_with(
            ...     bar=lambda x: x == str_reversed(x),
            ...     n=(int, lambda x: isinstance(x, float) and round(x) == x)
            ... )
            ... def foo(bar, n):
            ...     return bar * int(n) + ' is a palendrome!'
            ...
            >>> @foo.overload_with(baz=str)
            ... def foo(baz, n=3):
            ...     return baz * int(n) + ' is not a palendrome.'
            ...
            >>> foo('racecar', 2.0)
            'racecarracecar is a palendrome!'
            >>> foo('asdf')
            'asdfasdfasdf is not a palendrome.'

        The above example also illustrates the order of precidence for overloaded functions:  if a call is
        allowed to multiple versions of the function, the first to be defined is used.  (Notice that the first
        call in the above example matches both overloaded versions of `foo` defined in the example).

        ..warning ::
            To allow for the use Python 3 function annotations, specifying a single argument to `overload_with` that
            is a callable will assume that the Python 3 type specification method is being used, as described above.
            This could cause problems if what is actually intended is that there be only one type-constrained
            argument which returns `True` for an arbitrary callable.  To get around this, simply specify the type
            callable for the single argument using the keyword form or as a single item tuple.  If the callable
            you want to use for this specification is a builtin such as `callable` or `int`, this shouldn't be
            an issue.

        .. rubric:: Footnotes

        .. [#f2] Note that if the type is `None` and it is not in a sequence, or a length one tuple `(None,)` is
                 given, this is considered a match for all types.  This is mostly to allow arguments to be left out
                 of the specification (in particular, the first argument of most methods, `self`)

        """
        # TODO check to make sure type specifications are valid
        if sys.version_info > (3, 0) \
                and len(kwargs) == 0 \
                and len(typespecs_or_func) == 1 \
                and inspect.isfunction(typespecs_or_func[0]) \
                and not inspect.isbuiltin(typespecs_or_func[0]):
            func = typespecs_or_func[0]
            if hasattr(func, 'func_annotations'):
                # Python 3 version with func_annotations:
                argspec = inspect.getargspec(func)
                # for now, ignore varargs and keywords annotations
                annotations = func.func_annotations
                typespec_dict = {}
                for arg in argspec:
                    typespec_dict[arg] = func.func_annotations[arg]
                self._versions.append(argtypespec(func, typespec_dict))
                return self
            else:
                raise TypeError(
                    "when not using Python 3 function annotations, "
                    "{}.overload_with must\nbe called with "
                    "type specifications as arguments".format(self.__name__))
        else:

            def _get_func_decorator(f):
                argspec = inspect.getargspec(f)
                typespec_dict = {}
                # remember zip always truncates at the length of the shortest iterator
                for spec, argname in zip(typespecs_or_func, argspec.args):
                    typespec_dict[argname] = spec
                for argname, spec in kwargs.items():
                    if argname in typespec_dict:
                        raise TypeError(
                            "multiple type specifications given for "
                            "argument {} to overloaded function {}()".format(
                                argname, self.__name__))
                    if argname not in argspec.args:
                        raise TypeError(
                            "type specifiation for unknown argument {} given to overloaded "
                            "function {}()".format(argname, self.__name__))
                    typespec_dict[argname] = spec
                # fill in None for anything missing, indicating any type will do
                for argname in argspec.args:
                    if argname not in typespec_dict:
                        typespec_dict[argname] = None
                self._versions.append(argtypespec(f, typespec_dict))
                return self

            return _get_func_decorator