2
\$\begingroup\$

I did the following Excercise from Automate the boring stuff with Python Chapter 8:

Create a Mad Libs program that reads in text files and lets the user add their own text anywhere the word ADJECTIVE, NOUN, ADVERB, or VERB appears in the text file. For example, a text file may look like this:

The ADJECTIVE panda walked to the NOUN and then VERB. A nearby NOUN was unaffected by these events. The program would find these occurrences and prompt the user to replace them.

Enter an adjective:
silly
Enter a noun:
chandelier
Enter a verb:
screamed
Enter a noun:
pickup truck

The following text file would then be created:

The silly panda walked to the chandelier and then screamed. A nearby pickup truck was unaffected by these events. The results should be printed to the screen and saved to a new text file.

I decided to read from all the text files which are in the same folder as the python script. All new created files then end with _mad.txt

I would like to know if this is a good solution.

Are there any bad practices? Is the code easy to understand? What can be improved? Are there better approaches for some parts int the code?

Please feel free to comment on anything you can find.

Heres the code:

mad_libs.py

"""
Mad Libs
Scans for all .txt files in the working folder.
If a file is found the file is scanned for the keywords
-ADJECTIVE
-NOUN
-ADVERB
-VERB
Then the user is prompted to add a replacing word for the keyword.
A File with the name <source_name>_mad.txt is created in the folder.
In this file the keywords are replaced with the user input
"""
import os
import sys
import re

def filenames_in_script_folder():
    """
    Returns all the filenames which are located in the same folder
    as this running python script
    """
    os.chdir(os.path.dirname(sys.argv[0]))
    return os.listdir(os.getcwd())


def words_from_file(filename):
    """
    Reads text file and returns all the words in it
    """
    file = open(filename)
    file_content = file.read()
    file.close()
    return file_content.split()


def ask_for_replace_word(keyword):
    """
    Asks for a replace for mentioned keyword.
    Checks if keyword is a vowel to follow english grammar correct
    """
    vowel_regex = re.compile('^[aeiou]')
    if vowel_regex.search(keyword):
        return input("Enter an " + keyword.lower() + "\n")
    return input("Enter a " + keyword.lower() + "\n")


def replace_word(word, keywords):
    """
    Replaces provided word if it matches with one of the keywords.
    Any non alphabetical signs are ignored in keyword compare
    Otherwise returns provided word
    """
    no_char_regex = re.compile('[^a-zA-Z]')
    clean_word = no_char_regex.sub('', word)

    for keyword in keywords:
        if clean_word == keyword:
            new_word = ask_for_replace_word(keyword)
            return word.replace(keyword, new_word)
    return word


def write_data_to_file(data, filename):
    """
    Writes provided data to file.
    If no file exists a new one is created first
    """
    file = open(filename, 'w')
    file.write(data)
    file.close()


def mad_libs():
    """
    Main function reads from file, replaces keywords
    and writes to new file
    """
    for filename in filenames_in_script_folder():
        if filename.lower().endswith('.txt'):
            new_words = []
            replaced_a_word = False

            for word in words_from_file(filename):
                KEYWORDS = ('ADJECTIVE', 'NOUN', 'VERB', 'ADVERB')
                replace = replace_word(word, KEYWORDS)
                if replace != word:
                    replaced_a_word = True
                new_words.append(replace)

            if replaced_a_word:
                new_data = ' '.join(new_words)
                print(new_data)
                write_data_to_file(new_data, filename[:-4] + "_mad.txt")

mad_libs()

Bonus question:
I run Pylint over the code and in the definition of the Keywords in the mad_libs function it gives me:

Severity Code Description Project File Line Suppression State Message Variable name "KEYWORDS" doesn't conform to snake_case naming style

Shouldnt this be ALLCAPS like i did because these words are supposed to be constants?

edit: To see some results when run the programm you need to add a txt file like the example one in the same folder as the script.

I added a panda.txt with the text mentioned aboth and after aks for the words a panda_mad.txt is created in the folder.

\$\endgroup\$
  • 2
    \$\begingroup\$ "Shouldnt this be ALLCAPS like i did because these words are supposed to be constants?" Not true. You re-assign the values in a for loop. Should you take out the assignment to the top level, outside of any loops or function calls, yes, then it would be a pseudo-const. \$\endgroup\$ – Mast Nov 29 '18 at 21:01
  • \$\begingroup\$ so it would be better to declare it before the for loops. \$\endgroup\$ – Sandro4912 Dec 2 '18 at 8:55
1
\$\begingroup\$

Thanks for sharing your code!

Almost everything looks good to me.

  • You have documentation for the module :)
  • You have documentations for all your functions.
  • Naming seems good to me.
  • I did not see 'unpythonic' things in your code, maybe someone more experienced will have more to say on this?
  • You use a linter

The major things I would change is adding a __main__ to your script.

if __name__ == "__main__":
    mad_libs()

When you run you script with python mad_libs.py it will behave the same but if you import your module in another script, it will not execute the function upon importing, which is I think the desired behavior.

As for improvements:

  • you could use type annotations to do static validation with MyPy
  • write some tests with unittest, pytest, nose ...

Very nice code in my opinion.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.