from typing import List
from typing import Callable
from contextlib import suppress


def get_expanded_str(string: str, lst: List[str], key: Callable[[str], str] = lambda x: x):
    """Get the first string of a list that starts with the most
    characters of a given string.

    If the string is empty, return the first item of the list. If the
    list is empty as well, raise a `ValueError`. A `ValueError` will be
    raised if the string is not in the list.

    Args:
        string (str): The string (or string-like object) to find
            characters in common with.
        lst (List[str]): The list of strings to test against. This list
            should contain `str`s or string-like objects.
        key (:obj:`Callable`, optional): This is called on each item of
            `lst` to get the string to use for that item's score. Should
            return a `str` or string-like object.

    Raises:
        ValueError: If no item of the list has any beginning characters
            in common with the string.

    Examples:
        >>> get_expanded_str('ro', ['rock', 'paper', 'scissors'])
        'rock'
        >>> get_expanded_str('', ['rock', 'paper', 'scissors'])
        'rock'
        >>> get_expanded_str('rock', ['rock', 'paper', 'scissors'])
        'rock'
        >>> get_expanded_str('egg', ['rock', 'paper', 'scissors'])
        Traceback (most recent call last):
        ...
        ValueError: string 'egg' not in list
        >>> class Human:
        ...     def __init__(self, name: str):
        ...         self.name = name
        ...     def __repr__(self):
        ...         return f'Human(name={self.name!r})'
        >>> humans = [Human('joe'), Human('liam'), Human('bob')]
        >>> get_expanded_str('li', humans, key=lambda x: x.name)
        Human(name='liam')
    """
    if lst:
        if not string:
            return lst[0]
    else:
        raise ValueError(f'string {string!r} not in list')
    scores = {i: 0 for i in lst}
    for original in lst:
        i = key(original)
        if i == string:
            return i
        score = 0
        with suppress(IndexError):
            for n, char in enumerate(i):
                if not char == string[n]:
                    break
                score += 1
        scores[original] = score
    guess = max(scores.items(), key=lambda i: i[1])
    if len(key(guess[0])) < len(string) or guess[1] == 0:
        raise ValueError(f'string {string!r} not in list')
    return guess[0]