Days 1–3: Working with Python's datetime module

I'm two weeks into a protracted break from school—a 6-week long holiday before term 2 commences—so I picked up a free online MIT IAP 4-week course in C and C++, and finally started a couple Python courses I bought about a year ago from TalkPython taught by Mike Kennedy. They're admittedly a little basic for my level of Python now, but I'm still learning some Pythonic fundamentals I missed as well as reinforcing good habits so it's been a sound investment of my extra free time while on term break. I completed the first course in a week, which involved building ten apps that each focused on a core Python concept—such as list comprehensions, generators, file IO, OOP (e.g., classes, inheritance, polymorphism), recursion, lambdas, etc.—and highlighted the syntactic sugar Python provides for a lot of common programming scenarios that greatly reduce the amount of code needed. Not only does this make your source code look better, but less code reduces bug potential and simplifies code review so it's a win-win all around. In particular, it enlightened me to the yield from keyword—that was introduced in 3.4, I believe, with PEP 380—that opens up a lot of possibilities. It basically allows you to delegate generator responsibilities along a pipeline of chained sub-generators till you're ready to have your task terminate, return a value, process some data, or whatever it is your coroutine is designed to perform. I found the implementation in the course app interesting enough to do some further research which led me to David Beazley's presentation at PyCon 2014. It really is some mind blowing stuff! And unsurprisingly forms a lot of the basis of asyncio in Python. I encourage you to at least read through the slides and code examples—but the entire 3-hour talk is well worth the time.

The following code is excerpted from the keyword search app in the course that traverses the file system from a given path and parses each file for a given term:

def search(path, pattern):
   for root, _, files in os.walk(path):
      for file in files:
         file_path = os.path.join(root, file)
         yield from parse(file_path, pattern)

def parse(file, term):
   MatchFound = collections.namedtuple("MatchFound", "file, line, col,\
      text")
   with open(file) as f:
      for idx, line in enumerate(f, 1):
         if line.lower().find(term) >= 0:
            m = MatchFound(
                file=file,
                line=idx,
                col=line.lower().find(term) + 1,
                text=line.strip(),
            )
            yield m

You can see it saves appending each match to an iterable to be returned in its entirety to the calling function. Instead, only one file's worth of results are held in memory at a given time, which saves resources, improves performance, and makes the code more manageable without sacrificing useability; you still have an iterable at the end of the chain without the expense of a more common implementation. This is just the bare minimum that yield from provides, though, and David does an exemplary job of showcasing its utility.

As part of the second course that I just started, and the namesake of this blog post—100 Days of Code—while doing the unit on datetime, I incidentally found a somewhat terse method to process keyboard input upon each individual keystroke—so a program can continue from any key press without needing to enter return, for example, or a simple robot can be controlled with each key press. Given Python's notorious concision I was surpised there isn't something less verbose, but this is a handy function, nevertheless, to have on hand:

import os, sys, termios, time, tty

def charkey():
   fd = sys.stdin.fileno()
   oldset = termios.tcgetattr(fd)
   try:
      tty.setraw(fd)
      ck = sys.stdin.read(1)
   finally:
      termios.tcsetattr(fd, termios.TCSADRAIN, oldset)
      return ck

It's akin to the curses getch() routine using cbreak mode without having to install the curses module. As part of the datetime unit, the course suggests building a simple Pomodoro timer, and I thought about adding the feature to pause and resume the timer during a work interval with any key press, which led me to this solution using only the standard library. You can pass the returned character to ord() and run it through either a series of if-elif statements or a dictionary to produce the desired outcome. I've built the Pomodoro timer, at least as it pertains to the unit's learning outcomes—utilising datetime and not just time—but haven't yet added the pause and resume functionality; I'll share it once I do.


Comments

comments powered by Disqus