def _get_type_name(target_thing):
    """
    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.
    """
    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__
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.