def lift(obj, memo=None):
    """Make a promise out of object `obj`, where `obj` may contain promises
    internally.

    :param obj: Any object.
    :param memo: used for internal caching (similar to :func:`deepcopy`).

    If the object is a :class:`PromisedObject`, or *pass-by-value*
    (:class:`str`, :class:`int`, :class:`float`, :class:`complex`) it is
    returned as is.

    If the object's `id` has an entry in `memo`, the value from `memo` is
    returned.

    If the object has a method `__lift__`, it is used to get the promise.
    `__lift__` should take one additional argument for the `memo` dictionary,
    entirely analogous to :func:`deepcopy`.

    If the object is an instance of one of the basic container types (list,
    dictionary, tuple and set), we use the analogous function
    (:func:`make_list`, :func:`make_dict`, :func:`make_tuple`, and
    :func:`make_set`) to promise their counterparts should these objects
    contain any promises.  First, we map all items in the container through
    :func:`lift`, then check the result for any promises.  Note that in the
    case of dictionaries, we lift all the items (i.e. the list of key/value
    tuples) and then construct a new dictionary.

    If the object is an instance of a subclass of any of the basic container
    types, the `__dict__` of the object is lifted as well as the object cast
    to its base type. We then use :func:`set_dict` to set the `__dict__` of
    the new promise. Again, if the object did not contain any promises,
    we return it without change.

    Otherwise, we lift the `__dict__` and create a promise of a new object of
    the same class as the input, using :func:`create_object`. This works fine
    for what we call *reasonable* objects. Since calling :func:`lift` is an
    explicit action, we do not require reasonable objects to be derived from
    :class:`Reasonable` as we do with serialisation, where such a default
    behaviour could lead to unexplicable bugs."""
    if memo is None:
        memo = {}

    if isinstance(obj, (PromisedObject, str, int, float, complex)):
        return obj

    if id(obj) in memo:
        return memo[id(obj)]

    if hasattr(obj, '__lift__'):
        rv = obj.__lift__(memo)
        memo[id(obj)] = rv
        return rv

    actions = {
        list:  (lambda x: x, make_list),
        dict:  (lambda x: list(x.items()), make_dict),
        tuple: (lambda x: x, make_tuple),
        set:   (lambda x: x, make_set)
    }

    if obj.__class__ in actions:
        items, construct = actions[obj.__class__]
        tmp = [lift(a, memo) for a in items(obj)]
        if any(isinstance(a, PromisedObject) for a in tmp):
            rv = construct(*tmp)
            memo[id(obj)] = rv
            return rv
        else:
            memo[id(obj)] = obj
            return obj

    subclass = next(filter(
        lambda x: issubclass(obj.__class__, x),
        actions.keys()), None)

    if subclass:
        members = lift(obj.__dict__, memo)
        internal = lift(subclass(obj), memo)

        if isinstance(internal, PromisedObject):
            internal = construct_object(obj.__class__, internal)
            rv = set_dict(internal, members)
        elif isinstance(members, PromisedObject):
            rv = set_dict(obj.__class__(internal), members)
        else:
            rv = obj

        memo[id(obj)] = rv
        return rv

    try:
        members = lift(obj.__dict__, memo)
        if isinstance(members, PromisedObject):
            rv = create_object(obj.__class__, members)
        else:
            rv = obj
    except AttributeError:
        memo[id(obj)] = obj
        return obj

    memo[id(obj)] = rv
    return rv
Make a promise out of object `obj`, where `obj` may contain promises
    internally.

    :param obj: Any object.
    :param memo: used for internal caching (similar to :func:`deepcopy`).

    If the object is a :class:`PromisedObject`, or *pass-by-value*
    (:class:`str`, :class:`int`, :class:`float`, :class:`complex`) it is
    returned as is.

    If the object's `id` has an entry in `memo`, the value from `memo` is
    returned.

    If the object has a method `__lift__`, it is used to get the promise.
    `__lift__` should take one additional argument for the `memo` dictionary,
    entirely analogous to :func:`deepcopy`.

    If the object is an instance of one of the basic container types (list,
    dictionary, tuple and set), we use the analogous function
    (:func:`make_list`, :func:`make_dict`, :func:`make_tuple`, and
    :func:`make_set`) to promise their counterparts should these objects
    contain any promises.  First, we map all items in the container through
    :func:`lift`, then check the result for any promises.  Note that in the
    case of dictionaries, we lift all the items (i.e. the list of key/value
    tuples) and then construct a new dictionary.

    If the object is an instance of a subclass of any of the basic container
    types, the `__dict__` of the object is lifted as well as the object cast
    to its base type. We then use :func:`set_dict` to set the `__dict__` of
    the new promise. Again, if the object did not contain any promises,
    we return it without change.

    Otherwise, we lift the `__dict__` and create a promise of a new object of
    the same class as the input, using :func:`create_object`. This works fine
    for what we call *reasonable* objects. Since calling :func:`lift` is an
    explicit action, we do not require reasonable objects to be derived from
    :class:`Reasonable` as we do with serialisation, where such a default
    behaviour could lead to unexplicable bugs.