Reputation: 163
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
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
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
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