def unquote(string, encoding='utf-8', errors='replace'):
    """Replace %xx escapes by their single-character equivalent. The optional
    encoding and errors parameters specify how to decode percent-encoded
    sequences into Unicode characters, as accepted by the bytes.decode()
    method.
    By default, percent-encoded sequences are decoded with UTF-8, and invalid
    sequences are replaced by a placeholder character.

    unquote('abc%20def') -> 'abc def'.
    """
    if encoding is None: encoding = 'utf-8'
    if errors is None: errors = 'replace'
    # pct_sequence: contiguous sequence of percent-encoded bytes, decoded
    # (list of single-byte bytes objects)
    pct_sequence = []
    res = string.split('%')
    for i in range(1, len(res)):
        item = res[i]
        try:
            if not item: raise ValueError
            pct_sequence.append(bytes.fromhex(item[:2]))
            rest = item[2:]
        except ValueError:
            rest = '%' + item
        if not rest:
            # This segment was just a single percent-encoded character.
            # May be part of a sequence of code units, so delay decoding.
            # (Stored in pct_sequence).
            res[i] = ''
        else:
            # Encountered non-percent-encoded characters. Flush the current
            # pct_sequence.
            res[i] = b''.join(pct_sequence).decode(encoding, errors) + rest
            pct_sequence = []
    if pct_sequence:
        # Flush the final pct_sequence
        # res[-1] will always be empty if pct_sequence != []
        assert not res[-1], "string=%r, res=%r" % (string, res)
        res[-1] = b''.join(pct_sequence).decode(encoding, errors)
    return ''.join(res)