Python: The value of “with”

I never learned to code with Python; my first forays into development were with batch files (I kid you not) and then Visual Basic (which taught me many things I spent years unlearning).

Python is, in many respects, a great language for learners (which I’m not going to discuss today).

There is however, a great deal of… less than intuitive show-off code that can be, nay is encouraged to be, written using the language. Solutions are deemed to be “Pythonic”, a term as nebulous as “elegant” and often resulting in code just as unreadable to the casual observer or Python learner.

In some respects, this is about reducing the number of lines written – something fraught with peril for someone new to coding.

But there is a seductive element to Pythonic solutions – they don’t require you to twist yourself up in lines of boilerplate just to overcome a (often commonly encountered) problem.

The “with” keyword is not especially unique to Python, but is actively encouraged, and with good reason. This is one case where fewer lines is definitely better.

Why? Because with encompasses all the nonsense that developers are used to having to think about even though it’s 2016, we’ve learned that people suck at thinking like machines and it’s much safer to just let them do that for us.

We have the technology to not have to do the busywork ourselves.

But I didn’t grow up with that syntactic sugar, and so I have to retrain myself to use it whenever I write new code.

Let’s take a look at this particular bug that I spent more time staring at than I should have:

domainList = open(DOMAINS_TXT, "w")
for parent, sites in domains.iteritems():
  for site in sites:
    print >> domainList, site,
  print >> domainList

certResult = subprocess.Popen([LE_SH_LOCATION + "letsencrypt.sh", "--cron"], stdout=subprocess.PIPE)

You can probably spot the issue a mile away. But in the context of the whole script, and with my eyes firmly set on the “thing I hadn’t done before” (using Popen to run a shell script and capture the output) as the culprit, I missed the obvious for far too long.

Get it yet?

I’ve opened a file, overwritten it with content, forgotten to close it and then called a script which needs to read that very same, still locked, unflushed file.

So the script had a perfectly reasonable response – it tried to read the contents of the file, found “nothing”, shrugged and quit.

But to me, looking at the file after the python script had run (or even manually running the shell script after the fact), the file was there, content was present and everything should have run well.

What I should have done from the get go is use with – which handles, among other things, closing the file. It also handily forces you to indent the code that pertains to the file – which aids readability and reduces the chances of programmer error (is there any other kind?) relating to sprawling file access code.

with open(DOMAINS_TXT, "w") as domainList:
  for parent, sites in domains.iteritems():
    for site in sites:
      print >> domainList, site,
    print >> domainList

certResult = subprocess.Popen([LE_SH_LOCATION + "letsencrypt.sh", "--cron"], stdout=subprocess.PIPE)

Much better.

This is one instance where the “Pythonic” solution is superior in pretty much every aspect. Learners of the language (myself included) need to take note of constructs like this and make sure we take advantage of them – the same way developers needed to learn to work with and trust features like garbage collection back in the day.

Leave a Reply

Your email address will not be published. Required fields are marked *