Building a madlib generator I'm learning Python and I'm building a basic madlib generator. The code below works as expected. I'm just wondering if it uses best practice / can be made better.
def intro():
    print(&quot;Welcome to our madlib generator!&quot;)
    print(&quot;We'll ask you for a serious of words, you'll give us the words, and &quot;
          &quot;we'll make a dope ass story out of it!&quot;)
    print(&quot;Let's get some info from you first.&quot;)
    print(&quot;Select the type of madlib you want. &quot;)
    madlib_type = input(&quot;1. Short Story\n2. Poem\n&quot;)
    return madlib_type


def user_input():
    name = input(&quot;Enter a name: &quot;)
    color = input(&quot;Enter a color: &quot;)
    animal = input(&quot;Enter an animal: &quot;)
    food = input(&quot;Enter a food: &quot;)
    item = input(&quot;Enter an item: &quot;)
    adjective = input(&quot;Enter an adjective: &quot;)
    noun1 = input(&quot;Enter a plural noun: &quot;)
    noun2 = input(&quot;Enter any noun: &quot;)
    return [name, color, animal, food, item, adjective, noun1, noun2]


class MadlibGenerator:
    def __init__(self, *args):
        self.name = args[0].title()
        self.color = args[1]
        self.animal = args[2]
        self.food = args[3]
        self.item = args[4]
        self.adjective = args[5]
        self.noun1 = args[6]
        self.noun2 = args[7]

    def short_story_generator(self):
        print(f&quot;There once was a person named {self.name}. {self.name} LOVED &quot;
              f&quot;{self.animal}s. They were {self.name}'s favorite animal.&quot;)
        print(f&quot;One day, {self.name} was walking down the street wearing &quot;
              f&quot;{self.color} pants and a {self.color} shirt. In fact, all of &quot;
              f&quot;{self.name}'s &quot;)
        print(f&quot;clothes were {self.color}! In one of {self.name}'s hand, &quot;
              f&quot;there was a bright green {self.item}. In the other, there was &quot;
              f&quot;a big {self.food} that {self.name} was waiting to eat!&quot;)

    def poem_generator(self):
        print(f&quot;Roses are {self.adjective}&quot;)
        print(f&quot;{self.noun1} are blue&quot;)
        print(f&quot;I can't start my day without {self.noun2}&quot;)


madlib_type = intro()
madlib_input = user_input()
madlib = MadlibGenerator(madlib_input[0], madlib_input[1], madlib_input[2],
                         madlib_input[3], madlib_input[4], madlib_input[5],
                         madlib_input[6], madlib_input[7])
if madlib_type == &quot;1&quot;:
    madlib.short_story_generator()
if madlib_type == &quot;2&quot;:
    madlib.poem_generator()
You're asking for a lot of words you don't use. It might be nicer to only ask the user for relevant words. Maybe instead of user_input taking no arguments, it could take a list of descriptions like user_input([&quot;a name&quot;, &quot;a color&quot;, &quot;a different color&quot;]) and ask based on those?
I don't see why the two story generators live in the same class like that. Passing the same data to multiple generators is probably not very useful very often. The functions can just as easily take the words as arguments themselves instead. This doesn't make the idea of a MadlibsGenerator class inappropriate, but the two functions belong to different generators, and the functions' input doesn't need to be directly tied to a generator at all.
The generators also do two different things each - they both generate the stories and print them. Usually, it would be better to do those separately, in case you maybe want to do something else with the stories as well.
You don't check if the user provides valid input when selecting which generator to use. If I'm asked to pick 1 or 2 and enter 3, I'd probably expect an error message and maybe an opportunity to try again. Instead, the program just keeps going as usual, except I don't get a fun story at the end.
Some day, someone else might want to use these functions (or maybe you'll want to use them for something else). They'll probably try importing your file as a module. When they do that, the entire file is run, which right now means the entire madlibs program. That's probably not what they wanted. You can prevent that by placing the program logic inside an if __name__ == &quot;__main__&quot;: block, so that it doesn't execute when the file is imported.
Branching on a string to figure out which generator to use is a bit ugly. Just being able to look it up in a dictionary would be nice, and might also make it easier to add new ones, since we only need to update one place rather than having to worry about keeping intro and the ifs in sync.
But if we don't want to update intro each time we come up with a new generator we still have a problem - it needs to know what to call all the different generators when telling us what our options are. We could put that in the dictionary I mentioned above, or if we're already putting stuff in our generator objects we can add a name field in there as well. The latter is probably tidier.
Taken all together, we might get something like
class MadlibsGenerator:
    def __init__(self, name, descriptions, generator):
        self.name = name
        self.descriptions = descriptions
        self.generator = generator
    
    def __call__(self, *args, **kwargs):
        return self.generator(*args, **kwargs)

def intro(generators):
    print(&quot;Welcome to our madlib generator!&quot;)
    print(&quot;We'll ask you for a serious of words, you'll give us the words, and &quot;
          &quot;we'll make a dope ass story out of it!&quot;)
    print(&quot;Let's get some info from you first.&quot;)
    print(&quot;Select the type of madlib you want. &quot;)

    for key in generators:
        print(f&quot;{key}. {generators[key].name}&quot;)

    while True:
        selection = input()
        if selection in generators:
            break
        else:
            print(f&quot;No generator \&quot;{selection}\&quot;, please try again.&quot;)

    return generators[selection]

def user_input(descriptions):
    words = []
    for description in descriptions:
        words.append(input(f&quot;Enter {description}: &quot;))

    return words


def generate_short_story(name, animal, color, item, food):
    return (f&quot;There once was a person named {name}. {name} LOVED &quot;
        f&quot;{animal}s. They were {name}'s favorite animal.\n&quot;
        f&quot;One day, {name} was walking down the street wearing &quot;
        f&quot;{color} pants and a {color} shirt. In fact, all of &quot;
        f&quot;{name}'s\n&quot;
        f&quot;clothes were {color}! In one of {name}'s hand, &quot;
        f&quot;there was a bright green {item}. In the other, there was &quot;
        f&quot;a big {food} that {name} was waiting to eat!&quot;)



def generate_poem(adjective, noun1, noun2):
    return (f&quot;Roses are {adjective}\n&quot;
        f&quot;{noun1} are blue\n&quot;
        f&quot;I can't start my day without {noun2}&quot;)


if __name__ == &quot;__main__&quot;:
    generators = {
        &quot;1&quot;: MadlibsGenerator(&quot;Short Story&quot;,
            [&quot;a name&quot;, &quot;an animal&quot;, &quot;a color&quot;, &quot;an item&quot;, &quot;a food&quot;],
            generate_short_story),
        &quot;2&quot;: MadlibsGenerator(&quot;Poem&quot;,
            [&quot;an adjective&quot;, &quot;a plural noun&quot;, &quot;any noun&quot;],
            generate_poem)
    }

    generator = intro(generators)
    madlib_input = user_input(generator.descriptions)
    print(generator(*madlib_input))

Update
At this point, you might start to think: &quot;Hang on, each of those generator functions just puts strings into another string - creating a function each time we want to do that seems clumsy&quot;. And you have a point. While f-strings are nice, there are other options available that let us specify the template in one place and later use it in another. For example, strings already have a format method. We could take advantage of that and replace the generator functions with strings like
poem_template = &quot;&quot;&quot;
Roses are {0}
{1} are blue
I can't start my day without {2}
&quot;&quot;&quot;

And instead of having our generators call these functions, we can have them call .format on these strings like
class MadlibsGenerator:
    def __init__(self, name, descriptions, template):
        self.name = name
        self.descriptions = descriptions
        self.template = template.strip()

    def __call__(self, *args):
        return self.template.format(*args)

And now we can create generators like greeting_generator = MadlibsGenerator(&quot;Greeting&quot;, [&quot;a name&quot;], &quot;Hi there, {0}!&quot;) if we want to. Neat!