Python 3.1, you shouldn’t have!

I highly appreciate the presents that the Python 3.1 team (unwittingly) got me for my birthday this year. This morning I wrote the following snippet to determine the day-frequency of birthday occurrences: [*]

#!/usr/bin/env python3.1
 
import collections
import datetime
from operator import itemgetter
 
 
birthday = {part: int(input('Enter birth {}: '.format(part)))
    for part in 'year month day'.split()}
now = datetime.datetime.now()
days = []
for year in range(birthday['year'], now.year + 1):
    iter_birthday = dict(birthday)
    iter_birthday['year'] = year
    date = datetime.date(**iter_birthday)
    days.append(date.strftime('%A'))
 
counter = collections.Counter(days)
fmt = '{:15} {:>3}'
for day, frequency in sorted(counter.items(), key=itemgetter(1),
        reverse=True):
    print(fmt.format(day, frequency))
print(fmt.format('Total', sum(counter.values())))

Check out the Python 3.1 goodness: automatically numbered format strings and collections.Counter. It’s also a relief that I no longer have to type iter, x, or raw — Python is even prettier without the 2.x cruft.

Turns out that my original day of birth is still in the lead!

Updates

  1. Added dict comprehensions per astute comments. :-)
  2. Replaced lambda argument to sorted with the more readable operator.itemgetter, which I didn’t realize exists! Thanks @xtian.

Footnotes

[*] Note that the script assumes your birthday has already occurred this year.

Tags: ,

8 Responses to “Python 3.1, you shouldn’t have!”

  1. Lennart Regebro Says:

    Ough, the obfuscation! It hurtz!

    There, I fixed it. ;-)

    #!/usr/bin/env python3.1
     
    import collections
    import datetime
     
     
    birthday = {}
    for part in ('year', 'month', 'day'):
        birthday[part] = int(input('Enter birth {}: '.format(part)))
     
    now = datetime.datetime.now()
    days = []
    for year in range(birthday['year'], now.year + 1):
        date = datetime.date(year, birthday['month'], birthday['day'])
        days.append(date.strftime('%A'))
     
    def frequency_key(t):
        return t[1]
     
    counter = collections.Counter(days)
    fmt = '{:15} {:>3}'
    for day, frequency in sorted(counter.items(), key=frequency_key, reverse=True):
        print(fmt.format(day, frequency))
    print(fmt.format('Total', sum(counter.values())))
  2. Roberto Bonvallet Says:

    You’re missing dict comprehensions :)

    birthday = {part: int(input('Enter birth {}: '.format(part)))
        for part in 'year month day'.split()}
  3. rgz Says:

    Instead of

        birthday = dict((part, int(input('Enter birth {}: '.format(part))))
            for part in 'year mbirthday = dict((part, int(input('Enter birth {}: '.format(part))))
            for part in 'year month day'.split())
        onth day'.split())

    what about

        birthday = {part: int(input('Enter birth {}: '.format(part)))
            for part in 'year mbirthday = dict((part, int(input('Enter birth {}: '.format(part))))
            for part in 'year month day'.split())
        onth day'.split()}
  4. Christopher Leary Says:

    @Lennart: I’m willing to accept that the dict comprehension may be better written as a for loop (due to stdin side effects), but I don’t agree with the elimination of the lambda. Really simple one-statement operations (like sequence indexing in a throw-away sort invocation) are the few cases where I’ll actually prefer an anonymous function — I’m pretty sure that’s the use case that caused lambdas to remain in the language in the transition to Py3k.

    @Roberto @rgz: You guys are totally right. Been writing too much 2.x compatible code lately. ;-) Updated to reflect your correction!

  5. xtian Says:

    itemgetter(1) is much nicer than lambda t: t[1] (it lives in the operator module).

  6. Malthe Says:

    Lennart’s version is much more readable, which is entirely what counts.

  7. Christopher Leary Says:

    @Malthe: I think it has distilled down to a difference of opinion. As I see it, frequency_key is a meaningless function outside of the context where you have a sequence with the second element being a frequency value. I personally find that function more objectionable because its name implies some sort of generality, whereas the anonymous function is intentionally throw-away in the (temporary) item-iteration context.

    Overall, I’m partial to an inline “itemgetter(1)“, as mentioned by @xtian — less ugly than a lambda, plus stdlib-approved and -documented.

  8. Malthe Says:

    I agree; either one of them. Certainly “itemgetter“ is more efficient, too.

Leave a Reply