<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 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