import functools


def single_or_multiple(f):
  """
  Method wrapper, converts first positional argument to tuple: tuples/lists
  are passed on as tuples, other objects are turned into tuple singleton.
  Return values should match the length of the argument list, and are unpacked
  if the original argument was not a tuple/list.

  >>> class Test:
  ...   @single_or_multiple
  ...   def square(self, args):
  ...     return [v**2 for v in args]
  ...
  >>> T = Test()
  >>> T.square(2)
  4
  >>> T.square([2,3])
  (4, 9)

  Args
  ----
  f: :any:`callable`
      Method that expects a tuple as first positional argument, and that
      returns a list/tuple of the same length.

  Returns
  -------
  :
      Wrapped method.
  """

  @functools.wraps(f)
  def wrapped(*args, **kwargs):
    if len(args) <= 1:
      raise TypeError('{} requires at least 1 positional argument'.format(f.__name__))
    ismultiple = isinstance(args[1], (list,tuple))
    retvals = tuple(f(args[0], tuple(args[1]) if ismultiple else args[1:2], *args[2:], **kwargs))
    if not ismultiple:
      retvals, = retvals
    return retvals
  return wrapped