6
\$\begingroup\$

I do always search online English dictionaries for words that I know but want to get a better understanding of and/or to know their other meanings, or for words that I do not know at all. Therefore, I have written a simple program to help me search for English words faster and easier.

Here is my code:

import webbrowser

class OnlineDictionary:
    """Represents an online dictionary.

    Attributes:
      name: str
      url: str
    """
    def __init__(self, name, url, sep):
        """Initializes an OnlineDictionary object.

        name: str
        url: str
        sep: str
        """
        self.name = name
        self.url = url
        self.sep = sep

    def search(self, query):
        """Searches the dictionary for a given query.

        url: str
        query: str
        """
        query = self.sep.join(query.split())
        webbrowser.open(self.url + query)


def print_instructions(message, instructions, names, num_of_dics):
    """Prints the available dictionaries, their corresponding
    numerals and the instructions.

    message: str
    instructions: str
    names: list
    num_of_dics: int
    """    
    print(message)

    for numeral, name in zip(range(num_of_dics), names):
        print(numeral, name)

    print()
    print(instructions)     


def get_user_data(num_of_dics):
    """Asks the user for which dictionary 
    to use and for what to search for.

    num_of_dics: int

    Returns: tuple
    """
    while True:
        to_use = input('Specify which dictionary or dictionaries to use.\n')
        if to_use == '*':
            break

        try:
            to_use = to_use.replace(' ', '').split(',')
            numerals = []

            for num in to_use:
                num = int(num)
                if num not in range(num_of_dics):
                    raise
                numerals.append(num)

            to_use = set(numerals)  #set is used to get rid of any duplicates
            break

        except:
            continue

    query = input('Type something to search for.\n')

    return (to_use, query)


def __main__():  
    names = ('Merriam-Webster Dictionary', 'Oxford Dictionary', 'Camberidge Dictionary', 'Collins Dictionary',
             'Longman Dictionary of Contemporary English','The American Heritage Dictionary of the English Language')

    urls = ('https://www.merriam-webster.com/dictionary/', 'https://en.oxforddictionaries.com/definition/',
            'https://dictionary.cambridge.org/dictionary/english/', 'https://www.collinsdictionary.com/dictionary/english/',
            'https://www.ldoceonline.com/dictionary/', 'https://ahdictionary.com/word/search.html?q=')

    seps = ('%20', '_', '-', '-', '-', '%20')  #seperators are used if there are spaces in the query 

    collection = list(zip(names, urls, seps))

    num_of_dics = len(names)

    message = "These are the available dictionaries:\n"

    instructions = """To use a dictionary type its corresponding numeral.
To use more than one dictionary seperate the numerals with ",".
To use all of them type "*". Do not type the quotation marks.\n"""

    print_instructions(message, instructions, names, num_of_dics)

    while True:        
        to_use, query = get_user_data(num_of_dics)
        print()
        dics_to_use = collection if to_use == '*' else [collection[num] for num in to_use]
        dics = [OnlineDictionary(name, url, sep) for name, url, sep in dics_to_use]

        for dic in dics:
            print('Searching %s for "%s"...' % (dic.name, query))
            dic.search(query)

        print('Finished searching. Check your Web browser.\n')

if __name__ == '__main__':
    __main__()

I would appreciate any suggestions and comments.

\$\endgroup\$
6
\$\begingroup\$

Looks pretty good. I have a few suggestions though:

  1. There should be a way to exit (maybe if you don't type anything when it asks which dictionaries?)
  2. You should probably use urllib.parse.urljoin instead of +.
  3. You should probably use recursion instead of using while True then always breaking or continueing.
  4. You don't need parentheses when returning tuples.
  5. Bare raise statements are for reraising exceptions. Otherwise, it gives you RuntimeError: No active exception to reraise.
  6. Don't use bare except.
  7. I would probably use a list of tuples instead of using zip.
  8. You could pass around the whole list instead of its length.
  9. You can use enumerate(iterable) instead of zip(range(len(iterable)), iterable). What enumerate does is create an iterable that when iterated, yields tuples of increasing numbers starting with 0 and the next item from the iterable (in your case a list) you are enumerating. More info at the Python Docs.
  10. You should handle the logic for * in one place.
  11. Contained the logic for handling numerals within the get_user_data function.
  12. You should probably name your main function main instead of __main__.
  13. Don't combine multiline strings and '\n'.
  14. It might be good to have it display the result directly in the console instead of the web browser, but that would be a whole other project.
  15. You should probably return directly instead of using break.
  16. I might have made a few other minor changes.

Note that some of the changes are just personal preferences.

The result (also available at https://repl.it/@solly_ucko/DictionarySearchCodeReview):

import webbrowser
from urllib.parse import urljoin

class OnlineDictionary:
    """Represents an online dictionary.

    Attributes:
name: str
url: str
    """
    def __init__(self, name, url, sep):
        """Initializes an OnlineDictionary object.

        name: str
        url: str
        sep: str
        """
        self.name = name
        self.url = url
        self.sep = sep

    def search(self, query):
        """Searches the dictionary for a given query.

        url: str
        query: str
        """
        query = self.sep.join(query.split())
        webbrowser.open(urljoin(self.url, query))


def print_instructions(message, instructions, dictionaries):
    """Prints the available dictionaries, their corresponding
    numerals and the instructions.

    message: str
    instructions: str
    names: list
    num_of_dics: int
    """    
    print(message)

    for numeral, (name, _, _) in enumerate(dictionaries):
        print(numeral, name)

    print()
    print(instructions)     


def get_user_data(dictionaries):
    """Asks the user for which dictionary 
    to use and for what to search for.

    num_of_dics: int

    Returns: tuple
    """

    while True:
        to_use = input('Specify which dictionary or dictionaries to use.\n')

        if to_use == '':
            return (), ''
        elif to_use == '*':
            to_use = dictionaries
        else:
            try:
                to_use = to_use.replace(' ', '').split(',')
                numerals = []

                for num in to_use:
                    num = int(num)

                    if num not in range(len(dictionaries)):
                        raise ValueError

                    numerals.append(num)

                to_use = set(map(dictionaries.__getitem__, numerals)) # set is used to get rid of duplicates
                query = input('Type something to search for.\n')
                return to_use, query
            except ValueError:
                continue


def main():
    dictionaries = [
        ('Merriam-Webster Dictionary', 'https://www.merriam-webster.com/dictionary/', '%20'),
        ('Oxford Dictionary', 'https://en.oxforddictionaries.com/definition/', '_'),
        ('Camberidge Dictionary', 'https://dictionary.cambridge.org/dictionary/english/', '-'),
        ('Collins Dictionary', 'https://www.collinsdictionary.com/dictionary/english/', '-'),
        ('Longman Dictionary of Contemporary English', 'https://www.ldoceonline.com/dictionary/', '-'),
        ('The American Heritage Dictionary of the English Language', 'https://ahdictionary.com/word/search.html?q=', '%20')
    ]

    message = 'These are the available dictionaries:\n'

    instructions = """To use a dictionary type its corresponding numeral.
To use more than one dictionary separate the numerals with ",".
To use all of them type "*". Do not type the quotation marks.
"""

    print_instructions(message, instructions, dictionaries)

    while True:        
        to_use, query = get_user_data(dictionaries)

        if not to_use or not query:
            break

        print()
        chosen_dictionaries = [OnlineDictionary(name, url, sep) for name, url, sep in to_use]

        for dictionary in chosen_dictionaries:
            print('Searching %s for "%s"...' % (dictionary.name, query))
            dictionary.search(query)

        print('Finished searching. Check your Web browser.\n')

if __name__ == '__main__':
    main()
\$\endgroup\$
8
  • 1
    \$\begingroup\$ Thank you for your time and great review, Solomon! I am a beginner and would like to get a better understanding of the changes that you made, so could you please elaborate on the changes that are not personal preferences, and that help make the code better? \$\endgroup\$ – Mahmood Muhammad Nageeb Mar 14 '18 at 18:09
  • 2
    \$\begingroup\$ @MahmudMuhammadNaguib, I think the main things that I called personal preferences were #2, 4, 7, 12, and 13. As for the others, do you have any specific questions, or should I try to elaborate in general? \$\endgroup\$ – Solomon Ucko Mar 14 '18 at 19:51
  • 1
    \$\begingroup\$ Thanks! Things are clearer now. I am now refactoring the code according to your answer. It would be nice if you elaborate on using enumerate(iterable) instead of zip(len(iterable), iterable). \$\endgroup\$ – Mahmood Muhammad Nageeb Mar 14 '18 at 20:28
  • 2
    \$\begingroup\$ @MahmudMuhammadNaguib Elaborated in the answer: What enumerate does is create an iterable that when iterated, yields tuples of increasing numbers starting with 0 and the next item from the iterable (in your case a list) you are enumerating. More info at the Python Docs. \$\endgroup\$ – Solomon Ucko Mar 14 '18 at 22:23
  • \$\begingroup\$ Thanks again, Solomon! I have finished refactoring the code. The code is better now thanks to your review. Making get_user_data return the chosen dictionary/dictionaries directly instead of returning the symbol/symbols that represent them has enhanced the code's reusability a lot. \$\endgroup\$ – Mahmood Muhammad Nageeb Mar 15 '18 at 14:27

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.