Calling the value of a dictionary using data

HI I have this code to get a definition(value) from a huge file comprised of a dictionary of words and definitions (keys and values).

import json
data = json.load(open("data.json"))

def translate(w):
    return data[w]

word = input("Enter word: ")

print(translate(word))

I don’t see where it calls for the value, it just calls for “data[w]”

How does that work?

Well, let’s suppose the data.json file contains:

 { "hello": "bonjour",
   "goodbye": "au revoir"
 }

json.load turns that into the corresponding Python dict, your
data, which looks just the same. A dict is a mapping associating keys
("hello", "goodbye") with values ("bonjour", "au revoir")
respectively.

If you call translate("hello"), then inside the translate function
the variable w has the value "hello". The variable data is not
local to translate, it is your global data dictionary. So
translate computes:

 data["hello"]

which returns the value associated with the key "hello". And that
value is "bonjour". And that is what translate("hello") returns.

2 Likes

Thanks so much, that was very clear.

Just a quick question: is there a benefit to putting a conditional like this in the function (check for non existent words) as opposed to in the main program where it prints the response?


import json
data = json.load(open("data.json"))

def translate(w):
    if w in data:
        return data[w]
    else:
        return "The word doesn't exist"
word = input("Enter word: ")

print(translate(word))

Like this:

import json
data = json.load(open("data.json"))

def translate(w):
    return data[w]

word = input("Enter word: ")

if word in data:
    print(translate(word))
else:
    print ("Word doesn't exist")

I’d put it in the main program, but there are several things to comment
on here.

First up is that your 2 approaches produce different outcomes.

The first flavour does this:

 if w in data:
     return data[w]
 else:
     return "The word doesn't exist"

In both cases the function returns a string. The calling function does
not know if the word was found or not unless it treats the string
"The word doesn't exist" as a special value. So we almost never do
exactly this.

If you’re taking this approach we usually return a “sentinel value”, a
special value which is clearly not in the range of the function i.e. a
value which is identifiably not a valid return value for a good
translation.

In Python that sentinel value is often the special value None, which
you test for like this:

 translated = translate(word)
 if translated is None:
     print("Word doesn't exist")
 else:
     print(translated)

The None value is not a string at all.

The code to produce it like look like:

 if w not in data:
     return None
 return data[w]

or even better:

 return data.get(w)

which as it happens will return None if w is not present.

However, returning a sentinel and checking for it is kind of tedious. If
you don’t test this value immediately perhaps it’s going to be stored.
Example:

 translated_words = []
 for word in words:
     translated_words.append(translate(word))

and maybe end up with a list like:

 ["bonjour", "au revoir", None]

You can identify the sentinel later when it’s important, perhaps. Or you
might just let it all run and come out with rubbish (incomplete
translation) later.

A more common approach in Python is to use exceptions. This is more like
your second approach:

 def translate(w):
     return data[w]

Here you unconditionally look up w in data. If it isn’t there, this
code will raise a KeyError exception.

In the calling code you test for this like this:

 try:
     translated_word = translate(word)
 except KeyError:
     # the word is unknown
     print("Word doesn't exist")
 else:
     print(translated_word)

Cumbersome, eh?

Howver, a primary convenience of an exception is that you don’t need to
check for it right where translate() is called. Maybe you’ve got code
like this:

 def translate(w):
     return data[w]

 def translate_sentence(sentence):
     translated_words = []
     for word in sentence.split():
         translated_words.append(translate(word))
     return translated_words

and in your main program:

 sentence = input("Enter a sentence: ")
 try:
     translated_words = translate_sentence(sentence)
 except KeyError:
     print("unknown words in the sentence")
 else:
     print(translated_words)

Even though the exception is raised 2 calls deep in translate() you
can defer handling it until all the way out in the main program, where
you have the high level view of what you’re doing and maybe do a higher
level response (such as complaining about the sentence as a whole
instead of complaining about each unknown word).

I often think if this kind of thing as “mechanism” versus “policy”.
Mechanism is things like translate(), which looks up a word in a
table. It does exactly that with no frills. Policy is deciding how to
respond to circumstances. That response often needs a high level view of
what’s going on. As such, policy tends to belong further out in the
program, eg in your main code.

Exceptions let us defer the response to a suitable outer layer of code
(about which the inner low level code like translate() knows
nothing).

1 Like

Fantastic thank you so much!