Base 16

p
def base16_encode(data: bytes) -> str:
    """
    Encodes the given bytes into base16.

    >>> base16_encode(b'Hello World!')
    '48656C6C6F20576F726C6421'
    >>> base16_encode(b'HELLO WORLD!')
    '48454C4C4F20574F524C4421'
    >>> base16_encode(b'')
    ''
    """
    # Turn the data into a list of integers (where each integer is a byte),
    # Then turn each byte into its hexadecimal representation, make sure
    # it is uppercase, and then join everything together and return it.
    return "".join([hex(byte)[2:].zfill(2).upper() for byte in list(data)])


def base16_decode(data: str) -> bytes:
    """
    Decodes the given base16 encoded data into bytes.

    >>> base16_decode('48656C6C6F20576F726C6421')
    b'Hello World!'
    >>> base16_decode('48454C4C4F20574F524C4421')
    b'HELLO WORLD!'
    >>> base16_decode('')
    b''
    >>> base16_decode('486')
    Traceback (most recent call last):
      ...
    ValueError: Base16 encoded data is invalid:
    Data does not have an even number of hex digits.
    >>> base16_decode('48656c6c6f20576f726c6421')
    Traceback (most recent call last):
      ...
    ValueError: Base16 encoded data is invalid:
    Data is not uppercase hex or it contains invalid characters.
    >>> base16_decode('This is not base64 encoded data.')
    Traceback (most recent call last):
      ...
    ValueError: Base16 encoded data is invalid:
    Data is not uppercase hex or it contains invalid characters.
    """
    # Check data validity, following RFC3548
    # https://www.ietf.org/rfc/rfc3548.txt
    if (len(data) % 2) != 0:
        raise ValueError(
            """Base16 encoded data is invalid:
Data does not have an even number of hex digits."""
        )
    # Check the character set - the standard base16 alphabet
    # is uppercase according to RFC3548 section 6
    if not set(data) <= set("0123456789ABCDEF"):
        raise ValueError(
            """Base16 encoded data is invalid:
Data is not uppercase hex or it contains invalid characters."""
        )
    # For every two hexadecimal digits (= a byte), turn it into an integer.
    # Then, string the result together into bytes, and return it.
    return bytes(int(data[i] + data[i + 1], 16) for i in range(0, len(data), 2))


if __name__ == "__main__":
    import doctest

    doctest.testmod()