code: def signature(func, variadic=True, markup=True, safe=False):
      """get the input signature of a function
  
      func: the function to inspect
      variadic: if True, also return names of (*args, **kwds) used in func
      markup: if True, show a "!" before any 'unsettable' parameters
      safe: if True, return (None,None,None,None) instead of throwing an error
  
      Returns a tuple of variable names and a dict of keywords with defaults.
      If variadic=True, additionally return names of func's (*args, **kwds).
  
      Python functions, methods, lambdas, and partials can be inspected.
      If safe=False, non-python functions (e.g. builtins) will raise an error.
  
      For partials, 'fixed' args correspond to positional arguments given in
      when the partial was defined. Partials have 'unsettalble' parameters,
      where, these parameters may be given as input but will throw errors.
      If markup=True, 'unsettable' parameters are denoted by a prepended '!'.
  
      For example:
  
      >>> def bar(x,y,z,a=1,b=2,*args):
      ...   return x+y+z+a+b
      ...
      >>> signature(bar)
      (('x', 'y', 'z', 'a', 'b'), {'a': 1, 'b': 2}, 'args', '')
      >>>
      >>> # a partial with a 'fixed' x, thus x is 'unsettable' as a keyword
      >>> p = partial(bar, 0)
      >>> signature(p)
      (('y', 'z', 'a', 'b'), {'a': 1, '!x': 0, 'b': 2}, 'args', '')
      >>> p(0,1)
      4
      >>> p(0,1,2,3,4,5)
      6
      >>>
      >>> # a partial where y is 'unsettable' as a positional argument
      >>> p = partial(bar, y=10)
      >>> signature(p)
      (('x', '!y', 'z', 'a', 'b'), {'a': 1, 'y': 10, 'b': 2}, 'args', '')
      >>> p(0,1,2)
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      TypeError: bar() got multiple values for keyword argument 'y'
      >>> p(0,z=2)
      15
      >>> p(0,y=1,z=2)
      6
      >>>
      >>> # a partial with a 'fixed' x, and positionally 'unsettable' b
      >>> p = partial(bar, 0,b=10)
      >>> signature(p)
      (('y', 'z', 'a', '!b'), {'a': 1, '!x': 0, 'b': 10}, 'args', '')
      >>>
      >>> # apply some options that reduce information content
      >>> signature(p, markup=False)
      (('y', 'z', 'a', 'b'), {'a': 1, 'b': 10}, 'args', '')
      >>> signature(p, markup=False, variadic=False)
      (('y', 'z', 'a', 'b'), {'a': 1, 'b': 10})
      """
      TINY_FAIL = None, None  # XXX: or (),{} ?
      LONG_FAIL = None, None, None, None  # XXX: or (),{},'','' ?
      if safe and inspect.isbuiltin(func) and not IS_PYPY:
          return LONG_FAIL if variadic else TINY_FAIL
  
      # """fixed: if True, include any 'fixed' args in returned keywords"""
      # maybe it's less confusing to tie 'fixed' to 'markup'... so do that.
      fixed = markup
  
      identified = False
      if not inspect.ismethod(func) and not inspect.isfunction(func):
          try:  # then it could be a partial...
              p_args = func.args  # list of default arg values
              p_kwds = func.keywords or {}  # dict of default kwd values
              func = func.func
              identified = True
          except AttributeError:
              if hasattr(func, "__call__") and not hasattr(
                  func, "__name__"
              ):
                  func = (
                      func.__call__
                  )  # treat callable instance as __call__
              else:  # XXX: anything else to try? No? Give up.
                  pass
      if not identified:
          p_args = ()
          p_kwds = {}
  
      FULL_ARGS = hasattr(inspect, "getfullargspec")
      try:
          if FULL_ARGS:
              arg_spec = inspect.getfullargspec(func)
          else:
              arg_spec = inspect.getargspec(func)
      except TypeError:
          if safe:
              return LONG_FAIL if variadic else TINY_FAIL
          raise TypeError("%r is not a Python function" % func)
  
      if hasattr(arg_spec, "args"):
          arg_names = arg_spec.args  # list of input variable names
          arg_defaults = arg_spec.defaults  # list of kwd default values
          arg_varargs = arg_spec.varargs  # name of *args
          if FULL_ARGS:  # name of **kwds
              arg_keywords = getattr(arg_spec, "varkw") or {}
              arg_kwdefault = getattr(arg_spec, "kwonlydefaults") or {}
          else:
              arg_keywords = arg_spec.keywords
              arg_kwdefault = {}
      else:
          arg_names, arg_varargs, arg_keywords, arg_defaults = arg_spec
          arg_kwdefault = {}
  
      if not arg_defaults or not arg_names:
          defaults = {}
          explicit = tuple(arg_names) or ()
      else:
          defaults = dict(
              zip(arg_names[-len(arg_defaults) :], arg_defaults)
          )
          explicit = tuple(arg_names) or ()  # always return all names
      # explicit = tuple(arg_names[:-len(arg_defaults)]) # only return args
  
      # for a partial, the first p_args are now at fixed values
      _fixed = dict(zip(arg_names[: len(p_args)], p_args))
  
      # deal with the stupid case that the partial always fails
      errors = [i for i in _fixed if i in p_kwds]
      if errors:
          if safe:
              return LONG_FAIL if variadic else TINY_FAIL
          raise TypeError(
              "%s() got multiple values for keyword argument '%s'"
              % (func.__name__, errors[0])
          )
          # the above could fail if taking a partial of a partial
  
      # include any keyword-only defaults
      defaults.update(arg_kwdefault)
      # for a partial, arguments given in p_kwds have new defaults
      defaults.update(p_kwds)
      if markup:
          X = "!"
      else:
          X = ""
      # remove args 'fixed' by the partial; prepend 'unsettable' args with '!'
      explicit = tuple(
          X + i if i in p_kwds else i
          for i in explicit
          if i not in _fixed
      )
      if fixed:
          # defaults.update(_fixed)
          defaults = dict(
              (k, v) for (k, v) in defaults.items() if k not in _fixed
          )
          defaults.update(dict((X + k, v) for (k, v) in _fixed.items()))
  
      if inspect.ismethod(func) and func.__self__:
          # then it's a bound method
          explicit = explicit[1:]  # XXX: correct to remove 'self' ?
  
      if variadic:
          varargs = arg_varargs or ""
          varkwds = arg_keywords or ""
          return explicit, defaults, varargs, varkwds
      return explicit, defaults
  
file_path: klepto/klepto/_inspect.py
imports: import inspect
  from klepto.tools import IS_PYPY
  import sys
  from klepto.keymaps import keymap
  from klepto.rounding import simple_round, deep_round
  from copy import copy
label_desc: Use image node_id 1 for calling a global function with example usage: signature(func, variadic, markup, safe) and returns: explicit,  defaults, unknown, explicit,  defaults,  varargs,  varkwds, unknown, unknown