<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def replace_macros(string, spec=None):
 """Replace all macros in given string with corresponding values. For example: a string '%{name}-%{version}.tar.gz' will be transformed to 'foo-2.0.tar.gz'. :param string A string containing macros that you want to be replaced :param spec An optional spec file. If given, definitions in that spec file will be used to replace macros. :return A string where all macros in given input are substituted as good as possible. """

    if spec:
        assert isinstance(spec, Spec)

    def _is_conditional(macro: str) -> bool:
        return macro.startswith("?") or macro.startswith("!")

    def _test_conditional(macro: str) -> bool:
        if macro[0] == "?":
            return True
        if macro[0] == "!":
            return False
        raise Exception("Given string is not a conditional macro")

    def _macro_repl(match):
        macro_name = match.group(1)
        if _is_conditional(macro_name) and spec:
            parts = macro_name[1:].split(sep=":", maxsplit=1)
            assert parts
            if _test_conditional(macro_name):  # ?
                if hasattr(spec, parts[0]):
                    if len(parts) == 2:
                        return parts[1]

                    return getattr(spec, parts[0], None)

                return ""
            else:  # !
                if not hasattr(spec, parts[0]):
                    if len(parts) == 2:
                        return parts[1]

                    return getattr(spec, parts[0], None)

                return ""

        if spec:
            value = getattr(spec, macro_name, None)
            if value:
                return str(value)
        return match.string[match.start() : match.end()]

    # Recursively expand macros
    # Note: If macros are not defined in the spec file, this won't try to
    # expand them.
    while True:
        ret = re.sub(_macro_pattern, _macro_repl, string)
        if ret != string:
            string = ret
            continue
        return ret