code: def _get_type_name(target_thing):
      
      thing = target_thing
      if hasattr(thing, 'im_class'):
          # only works in Python 2
          return thing.im_class.__name__
      if inspect.ismethod(thing):
          for cls in inspect.getmro(thing.__self__.__class__):
              if cls.__dict__.get(thing.__name__) is thing:
                  return cls.__name__
          thing = thing.__func__
      if inspect.isfunction(thing) and hasattr(thing, '__qualname__'):
          qualifier = thing.__qualname__
          if LOCALS_TOKEN in qualifier:
              return _get_local_type_name(thing)
          return _get_external_type_name(thing)
      return inspect.getmodule(target_thing).__name__
docs: Functions, bound methods and unbound methods change significantly in Python 3.
  
      For instance:
  
      class SomeObject(object):
          def method():
              pass
  
      In Python 2:
      - Unbound method inspect.ismethod(SomeObject.method) returns True
      - Unbound inspect.isfunction(SomeObject.method) returns False
      - Unbound hasattr(SomeObject.method, 'im_class') returns True
      - Bound method inspect.ismethod(SomeObject().method) returns True
      - Bound method inspect.isfunction(SomeObject().method) returns False
      - Bound hasattr(SomeObject().method, 'im_class') returns True
  
      In Python 3:
      - Unbound method inspect.ismethod(SomeObject.method) returns False
      - Unbound inspect.isfunction(SomeObject.method) returns True
      - Unbound hasattr(SomeObject.method, 'im_class') returns False
      - Bound method inspect.ismethod(SomeObject().method) returns True
      - Bound method inspect.isfunction(SomeObject().method) returns False
      - Bound hasattr(SomeObject().method, 'im_class') returns False
  
      This method tries to consolidate the approach for retrieving the
      enclosing type of a bound/unbound method and functions.