DRY input-checking in python Let's say I want to write a super simple Python program to ask a user for their personal information and list it back out in a &quot;pretty&quot; format.
def get_info():
    first = input(&quot;enter your first name: &quot;)
    while not first.isalpha():
        first = input(&quot;enter your first name: &quot;)

    last = input(&quot;enter your last name: &quot;)
    while not last.isalpha():
        last = input(&quot;enter your last name: &quot;)

    age = input(&quot;enter your age: &quot;)
    while not age.isnumeric():
        age = input(&quot;enter your age: &quot;)
    age = int(age)

    has_pet = input(&quot;do you have a pet?: &quot;).lower()
    while has_pet not in [&quot;yes&quot;, &quot;no&quot;, &quot;y&quot;, &quot;n&quot;]:
        has_pet = input(&quot;do you have a pet?: &quot;).lower()

    if &quot;y&quot; in has_pet:
        has_pet = True
    else:
        has_pet = False
    
    return [first, last, age, has_pet]

def print_info(info):
    first, last, age, has_pet = info
    print(f&quot;Name: {first} {last}&quot;)
    print(f&quot;Age: {age}&quot;)
    print(f&quot;Has pet: {has_pet}&quot;)

print_info(get_info())

Clearly, I have violated DRY multiple times.
(1) Should I just throw the input() functions away and use arguments (with sys.argv)?
(2) The reason I can't use recursion is because I would have to ask the user for all of their information again if they mess up on, like, answering the pet question for example. Is there another way to clean up this code without sacrificing speed / resources?
Yes, abandon input(), which is usually a less powerful and flexible choice.
Here's an approximation of your code using
argparse. Notice several
things: (1) very little repetition in our code; (2) much less algorithmic code,
which means fewer chances for error; (3) greater simplicity overall
and thus higher readability;
(4) good validation and user help messages out of the box; (5) many other
possibilities (see the library's documentation); (6) less hassle for
users to run the code repeatedly or in any kind of automated context;
and (7) less hassle during development of the code, for the same reason.
# Usage examples:

$ python demo.py George Washington 289 --pet
Namespace(age=289, first_name='George', last_name='Washington', pet=True)

# The code:

import argparse
import sys

def main(args):
    ap, opts = parse_args(args)
    print(opts)

def parse_args(args):
    ap = argparse.ArgumentParser()
    ap.add_argument('first_name', type = personal_name)
    ap.add_argument('last_name', type = personal_name)
    ap.add_argument('age', type = int)
    ap.add_argument('--pet', action = 'store_true')
    opts = ap.parse_args(args)
    return (ap, opts)

def personal_name(x):
    if x.isalpha():
        return x
    else:
        raise ValueError(f'Invalid name: {x}')

if __name__ == '__main__':
    main(sys.argv[1:])

But if you cannot or do not want to take that route, the roll-your-own approach
looks similar in spirit: we need a general-purpose function to get input;
it needs to take some type of text or label to tell the user what we are asking
for; and it needs a converter/validator to check the reply.
Here's a rough sketch of the new or different parts:
def main(args):
    d = dict(
        first = get_input(label = 'first name', convert = personal_name),
        last = get_input(label = 'last name', convert = personal_name),
        age = get_input(label = 'age', convert = int),
        pet = get_input(label = 'pet status [y/n]', convert = yesno),
    )
    print(d)

def yesno(x):
    if x in ('yes', 'y'):
        return True
    elif x in ('no', 'n'):
        return False
    else:
        raise ValueError(f'Invalid yes-no: {x}')

def get_input(label, convert):
    while True:
        reply = input(f'Enter {label}: ')
        try:
            return convert(reply)
        except (ValueError, TypeError):
            print('Invalid reply')