muhammed
muhammed

Reputation: 163

Could someone go through this Python code and explain it to me

months = ['January',
          'February',
          'March',
          'April',
          'May',
          'June',
          'July',
          'August',
          'September',
          'October',
          'November',
          'December']

def valid_month(month):
    if month:
        cap_month = month.capitalize()
        if cap_month in months:
            return cap_month

so above is python code that creates a list of months and basically prints what the user enters if it's valid month or not. the above code is nice.

below is another version similar to the above but I guess better or more user-friendly. but I'm not sure how this works exactly I haven't seen python dictionaries in this manner could someone go through this code and explain it to me thanks a lot I appreciate.

months_abbvs = dict((m[:3].lower(), m) for m in months)

def valid_month(month):
    if month:
        short_month = month[:3].lower()
        return month_abbvs.get(short_month)

Upvotes: 3

Views: 210

Answers (3)

abarnert
abarnert

Reputation: 366213

It may help to print out what you get from that extra step:

>>> months_abbvs
{'apr': 'April',
 'aug': 'August',
 'dec': 'December',
 'feb': 'February',
 'jan': 'January',
 'jul': 'July',
 'jun': 'June',
 'mar': 'March',
 'may': 'May',
 'nov': 'November',
 'oct': 'October',
 'sep': 'September'}

So, you have a mapping of lowercased abbreviations to full month names.

How does that work? Well, first, let's look at what the expression does:

>>> m = 'December'
>>> m[:3].lower()
'dec'
>>> m[:3].lower(), m
('dec', 'December')

So, the comprehension just does that for each month:

>>> [(m[:3].lower(), m) for m in months]
[('jan', 'January'),
 ('feb', 'February'),
 ('mar', 'March'),
 ('apr', 'April'),
 ('may', 'May'),
 ('jun', 'June'),
 ('jul', 'July'),
 ('aug', 'August'),
 ('sep', 'September'),
 ('oct', 'October'),
 ('nov', 'November'),
 ('dec', 'December')]

As explained in more detail in the tutorial, a comprehension is basically shorthand for a loop. In particular, this:

>>> m2 = [<expression with m> for m in months]

… is equivalent to:

>>> m2 = []
>>> for m in months:
...     m2.append(<expression with m>)

Using a generator expression instead of a list comprehension just means the sequence is built as a lazy iterator instead of a list.

And then passing the result (either way) to dict builds a dictionary mapping the first value of each tuple to the second.


You could write this all a bit more readably as a dictionary comprehension:

months_abbvs = {m[:3].lower(): m for m in months}

Even better, instead of writing m[:3].lower() repeatedly, give it a nice name and use that:

def abbreviate(m):
    return m[:3].lower()
months_abbvs = {abbreviate(m): m for m in months}

And then:

def valid_month(month):
    if month:
        short_month = abbreviate(month)
        return month_abbvs.get(short_month)

Now, what you do with your input in the new version is:

short_month = month[:3].lower()
return month_abbvs.get(short_month)

Since month_abbvs is a dict (you can tell that by printing it out, or just from the fact that it was created by calling dict on something), the get method is dict.get. So, as explained in the linked docs, month_abbvs.get(short_month) is the same as months_abbvs[short_month], except that if the key short_month is not found, you will get None, instead of raising an exception.

So, if given 'October', you'll set short_month to 'oct'. And then you look it up in the abbreviation dictionary, which returns 'October'. If given 'OCT' or 'october' or 'octal digit string', you'll also return the same thing. Since any non-empty string is truthy, if you've done something like if valid_month('October'):, it will be true.

But, if given, say, 'Muhammed', you'll set short_month to 'muh'. And then you look that up, and it isn't there. As explained above, the get method returns None for unknown keys, so you will return None. Since None is falsey, if you've done something like if valid_month('Muhammed'):, it will not be true.

In other words, it makes the function more lenient—which may be an improvement, or may be a bad thing (or may be a little of both—maybe you wanted 'OCT' to work, but not 'octal digit string').

Upvotes: 5

Marcin
Marcin

Reputation: 49886

The other two questions explain in detail how the code works, but I'd like to draw your attention to one particular facet of how the code is designed: it has tests to avoid exceptions, and if those tests fail, the code stops executing, such that execution falls off the end of the function text, and returns None.

A simpler way to write the code is to eliminate the checks, and handle the exceptions:

def valid_month(month):
    if month:
        # this will raise an exception if month is not a string
        short_month = month[:3].lower() 
        return month_abbvs.get(short_month)

becomes:

def valid_month(month):
    try:
        return month_abbvs.get(month[:3].lower())
    except KeyError, AttributeError: # or Exception to catch everything
        return None

Which gives us one main line to comprehend, which is a bit easier in this case. Obviously, the more checks, the more simplicity this gives us.


def valid_month(month):
    if month:
        cap_month = month.capitalize()
        if cap_month in months:
            return cap_month

becomes:

def valid_month(month):
    try:
        cap_month = month.capitalize()
        if cap_month and cap_month in months:
            return cap_month
    except AttributeError:
        return None

Which doesn't buy us very much in this case. It's good to be able to employ both styles.

Upvotes: 0

Nisan.H
Nisan.H

Reputation: 6352

months_abbvs = dict((m[:3].lower(), m) for m in months)
# months_abbvs = { 'jan':'January', 'feb':'February',... }
# the actual operation is two-step:
#     1. [(m[:3].lower(),m) for m in months] list comprehension over the "months" list which:
#     1.1. [m[:3].lower()] take first three letters of each item in the list, apply lowercase()
#     1.2. [(m[:3].lower,m)] return a tuple of (1.1, item)
#     2. [dict(...)] build a dictionary from the list comprehension
#     2.2. for each tuple returned from (1.2), create a key:value pair in the dict

def valid_month(month):
    if month:
        short_month = month[:3].lower()
        # get first three letters as lowercase string
        return month_abbvs.get(short_month)
        # 1. [month_abbvs.get(short_month)] perform dict.get(key)
        # 2. return result of 1 (None if no such key exists)

The reason this is more efficient is that dict uses a hashset for its internal representation, so finding whether a key exists in it or not is an (amortized) O(1) operation, whereas doing the same over a list is a worst-case-scenario O(n) operation for a list of size n.

As per @abarnert's comment, this also makes it more user-friendly to code, as you get away with simply doing a dict.get(key) and not worrying about iteration logic in your code. The condition becomes a "True/False" question, rather than a "Is it True for either of the cases in this set [...]?"

Upvotes: 2

Related Questions