Python's yield from keyword

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; both taught by Mike Kennedy. They're admittedly a little elementary for anyone not new to Python, but I'm still discovering some Pythonic fundamentals I missed as well as reinforcing good habits so it's been a sound investment of 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, inter alia, list comprehensions, generators, file IO, OOP (e.g., classes, inheritance, polymorphism), recursion, and lambdas—and highlighted the syntactic sugar Python provides for many common programming scenarios, markedly reducing the amount of code needed for the given task. Not only does this make source code more aesthetic and manageable, but less code improves bug susceptibility and simplifies code review so it's a win-win all around. In particular, I was enlightened to the yield from keyword that was introduced in Python 3.4 with PEP 380 and has many applications. It enables chaining generator responsibilities along a delegate pipeline of sub-generators till you're ready to terminate the task, return a value, process some data, or whatever it is your coroutine is designed to perform. Its implementation in some of the course apps was interesting enough to conduct further research, which led to David Beazley's brilliant presentation at PyCon 2014. The talk's profound insights showcased its powerful versatility that, unsurprisingly, forms much of the basis of asyncio. I encourage you to at least read through the slides and code examples—but the entire 3-hour talk is well worth your time.

The following snippet is excerpted from the course's keyword search app that traverses the file system from a given path, parsing each file for a given string:

def search(path, pattern):
   for root, _, files in os.walk(path):
      for file in files:
         filename = os.path.join(root, file)
         yield from parse(filename, 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

Notice that it saves appending each match to an iterable to be returned in its entirety to the calling function. Instead, only results from one file are held in memory at a given time, which saves resources, improves performance, and makes the code more manageable without sacrificing useability; there is still an iterable at the end of the chain sans the usual expense incurred by other implementations. This is a simple example of the benefit yield from confers, though, and David elucidates its utility in great detail.

Python equivalent of C's getch()

Days 1–3 of the second Python course I just started—100 Days of Code—covers the datetime module, during which I incidentally found a somewhat terse method to process keyboard input upon each individual keystroke. This enables, for example, program continuance with any key press—not exclusively return/enter—or controlling the movement of a simple robot with each key press. Given Python's famous concision, I was surpised to find most approaches verbose, which makes this a nice function to have on hand:

import os, sys, termios, time, tty

def getkey():
   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

This method is akin to the Python curses—which is actually a simple wrapper over the C functions provided by ncurses—module's getch() routine using cbreak mode as opposed to the usual buffered input mode—except without having to install the curses module.

As part of the datetime unit, the course suggests building a simple Pomodoro timer. To make it more practical, I thought about adding a pause and resume feature that could be activated by pressing a key during work or rest intervals. Subsequently, I discovered the above solution using only the standard library. You can pass the entered character to ord() and run it through either a series of if-elif statements or a dictionary to produce the desired response; such as (p)ause and (r)esume, for example. I've built the Pomodoro timer according to the unit specifications and learning outcomes (i.e., utilising datetime and not only time), but haven't yet added the pause and resume functionality; I might share it once I do. I use the Pomodoro technique in my own studies and have found it a highly effective tactic when used as part of a broader studying strategy.


Comments

comments powered by Disqus