Generic user input obtainer Earlier today I answered Timetable viewer, where one of my recommendations was to make a get_input function.
However, this hasn't been the first time I've recommended making a bespoke get_input function.

This function should get user input, until it is valid. This may be any input, or any number in a specific range.
The arguments customize how it works, such as working with strings or integers.
The function should repeatedly prompt the user for specific input, until a valid input is entered.
In the case case of valid input we should return that data, so the end user can use it.

Since it's quite a common function, with a lot of uses, I decided to make a generic version of the function, that should cover most use cases.
I decided to make two functions, as strings don't need to be converted, however when changing to numbers, you'd have to duplicate a lot of arguments.

def build_input(
    prompt=None,
    *,
    errors=None,
    values=None,
    process=None,
    wanted=None,
    end=None
  ):
    prompt = prompt or ''
    process = process or (lambda v: v)
    errors = errors or tuple()
    if values is None:
        wanted = wanted or (lambda v: True)
    else:
        values = list(values)
        prompt += '[{}] '.format('/'.join(str(i) for i in values))
        wanted = wanted or (lambda v: v in values)
        values = set(values)

    if end is not None:
        prompt += end
    elif values:
        prompt += '\n&gt; '

    def ask():
        while True:
            value = input(prompt)
            try:
                value = process(value)
            except errors:
                continue
            if wanted(value):
                return value
    return ask

def build_number_input(
    prompt=None,
    *,
    errors=ValueError,
    values=None,
    process=int,
    wanted=None,
    end=None
  ):
    return build_input(
        prompt,
        errors=errors,
        values=values,
        process=process,
        wanted=wanted,
        end=end
    )


I have a couple of concerns:


build_number_input spans 17 lines, for a function definition and a function call. Is there a better way to define this?
Are there a better way to style my function definitions? I think they may not be PEP8 compliant, but couldn't think of anything nicer to read.
Are there any common options that you think should be added?


Below are a couple of ways to use the above functions, and a sample run:

name_input = build_input('What is your name? ')
age_input = build_number_input('What is your age? ')
day_input = build_number_input('Day: ', values=[1, 2, 3, 4, 5])
again_input = build_input('Again? ', values='yn', process=lambda v:v.lower())

print('Hello, {}! You are {}.'.format(name_input(), age_input()))
print('You want day {}'.format(day_input()))
print('You {}want to go again.'.format('' if again_input() == 'y' else "don't "))


What is your name? Peilonrayz
What is your age? 20.5
What is your age? 20a
What is your age? 20
Hello, Peilonrayz! You are 20.
Day: [1/2/3/4/5] 
&gt; 6
Day: [1/2/3/4/5] 
&gt; 0
Day: [1/2/3/4/5] 
&gt; 55
Day: [1/2/3/4/5] 
&gt; five
Day: [1/2/3/4/5] 
&gt; 5
You want day 5
Again? [y/n] 
&gt; yeah
Again? [y/n] 
&gt; Nope?
Again? [y/n] 
&gt; N
You don't want to go again.
I do not like the functional approach of this, especially since you are "inheriting" the user_input by number_input.
Here's an object-oriented version:

#! /usr/bin/env python3

class UserInput:

    def __init__(self, prompt=None, *, errors=None, values=None, process=None,
                 wanted=None, default=None, end=None):
        self.prompt = prompt
        self.errors = errors
        self.values = values
        self.process = process
        self.wanted = wanted
        self.default = default
        self.end = end

    def __call__(self):
        return self.get()

    @property
    def prompt(self):
        """Returns the prompt."""
        prompt = self._prompt or ''

        if self.values:
            prompt += '[{}] '.format('/'.join(str(i) for i in self.values))

        if self.end is not None:
            prompt += self.end
        elif self.values:
            prompt += '\n&gt; '

        return prompt

    @prompt.setter
    def prompt(self, prompt):
        self._prompt = prompt

    @property
    def errors(self):
        return self._errors or tuple()

    @errors.setter
    def errors(self, errors):
        self._errors = errors

    @property
    def values(self):
        return self._values

    @values.setter
    def values(self, values):
        self._values = set(values) if values is not None else None

    @property
    def process(self):
        return self._process or (lambda v: v)

    @process.setter
    def process(self, process):
        self._process = process

    @property
    def wanted(self):
        """Returns the wanted callback."""
        if self._wanted:
            return self._wanted
        elif self.values is None:
            return lambda _: True

        return lambda value: value in self.values

    @wanted.setter
    def wanted(self, wanted):
        self._wanted = wanted

    def get(self):
        """Retrieves the user input."""
        while True:
            try:
                value = input(self.prompt)
            except (EOFError, KeyboardInterrupt):
                if self.default is not None:
                    return self.default

                raise

            try:
                value = self.process(value)
            except self.errors:
                continue

            if self.wanted(value):
                return value


class NumberInput(UserInput):

    def __init__(self, prompt=None, *, errors=ValueError, values=None,
                 process=int, wanted=None, end=None):
        super().__init__(
            prompt, errors=errors, values=values, process=process,
            wanted=wanted, end=end)


def main():

    name_input = UserInput('What is your name? ')
    age_input = NumberInput('What is your age? ')
    day_input = NumberInput('Day: ', values=[1, 2, 3, 4, 5])
    again_input = UserInput('Again? ', values='yn', process=lambda v:v.lower())

    print('Hello, {}! You are {}.'.format(name_input(), age_input()))
    print('You want day {}'.format(day_input()))
    print('You {}want to go again.'.format('' if again_input() == 'y' else "don't "))


if __name__ == '__main__':
    main()


I also added an additional keyword argument default the value of which will be returned on KeyboardInterrupts and EOFErrors (which occur on pressing [Ctrl]+[D]) iff specified.

Regarding the function of the code itself, I think that it is trying to be a jack-of-all-trades-device and doing a bad job at it while violating the YAGNI principle.
User input is highly variable and dynamic. If you think of a simple yes/no question where yes should be default (if one just hits enter) and the prompt should highlight the default value like in the pacman prompt below, this is really hard to implement using your functions (or classes).

$ LANG=en_US sudo pacman -S kate
resolving dependencies...
:: There are 2 providers available for phonon-qt5-backend:
:: Repository extra
   1) phonon-qt5-gstreamer  2) phonon-qt5-vlc

Enter a number (default=1):   # Just hit [Return] here.
looking for conflicting packages...
warning: dependency cycle detected:
warning: phonon-qt5-gstreamer will be installed before its phonon-qt5 dependency

Packages (43) attica-qt5-5.38.0-1  editorconfig-core-c-0.12.1-2  kactivities-5.38.0-1  karchive-5.38.0-1  kauth-5.38.0-1  kbookmarks-5.38.0-1  kcodecs-5.38.0-1  kcompletion-5.38.0-1  kconfig-5.38.0-1
              kconfigwidgets-5.38.0-1  kcoreaddons-5.38.0-1  kcrash-5.38.0-1  kdbusaddons-5.38.0-1  kglobalaccel-5.38.1-1  kguiaddons-5.38.0-1  ki18n-5.38.0-1  kiconthemes-5.38.0-1  kio-5.38.0-1
              kitemmodels-5.38.0-1  kitemviews-5.38.0-1  kjobwidgets-5.38.0-1  knewstuff-5.38.0-1  knotifications-5.38.0-1  kparts-5.38.0-1  kservice-5.38.0-1  ktexteditor-5.38.0-1  ktextwidgets-5.38.0-1
              kwallet-5.38.0-1  kwidgetsaddons-5.38.0-1  kwindowsystem-5.38.0-1  kxmlgui-5.38.0-1  libdbusmenu-qt5-0.9.3+16.04.20160218-1  media-player-info-22-2  phonon-qt5-4.9.1-4
              phonon-qt5-gstreamer-4.9.0-3  polkit-qt5-0.112.0+git20160226-1  qt5-multimedia-5.9.1-2  qt5-speech-5.9.1-2  solid-5.38.0-1  sonnet-5.38.0-1  syntax-highlighting-5.38.0-1  threadweaver-5.38.0-1
              kate-17.08.1-1

Total Download Size:    37.95 MiB
Total Installed Size:  146.67 MiB

:: Proceed with installation? [Y/n]  # Default option is capitalized.