Thursday, November 1, 2012

Exploiting Python Features Vs. Employing Python Hacks


Every programming language has its quirks and dark alleys, which may be useful from time to time in order to achieve certain results. But when does using one of these features turns it into a hack?

A few days ago, I found myself having to solve a problem, and for some unexplained reason I went about it in a more 'interesting' way than one usually would (which doesn't mean more efficient). The way I solved this problem was such an amusement for me, that I felt the urge to go to the next room down the corridor, and tell Alon and Shahar all about it.

All hell broke loose.
At some point, Shahar became so horrified, he had to pull the "Zen of Python" manifesto on me (He was right, by the way).

Let me show you what I did, starting with the requirements: 
  1. Let's assume we have 2 functions that can return either True, False or None. 
  2. We want to write a new function that returns:
    • False if one these functions is False
    • True if both of these functions are True (or one of them is True and the other is None)
    • None if both of these functions are None.

For some weird reason, I chose to implement it something like this, with no documentation whatsoever (except for my unit-tests):


The main reason the guys freaked when I showed them this, is that it's not at all readable. Can you actually gather just by looking at this function, what might be the end result?

Let's break down the pieces of code that are the least readable, starting with the list comprehension:


This list comprehension filters out the elements which are None. What we get from that is a list that contains between 0 to 2 members that can hold either True or False. This part is pretty OK, I suppose, being a pythonic idiom of using a list comprehension for filtering out members of a list, creating a new one.

But now comes the evil part of what I did - After eliminating the scenario in which states is an empty list by using if not states, I wanted to find out at one fell swoop what would be the result of a logical operation on members of the states list, using the and operator. I had one problem to overcome though - I have no idea at this point how many members are in states. Could be one, could be two.

I could have used len() to find out, but that seemed tedious. So I did something much worse:


I used (some might argue exploited) a very nice Python feature - Negative indices for getting list members. states[0] is the first member of the states list, no doubt about it. But what about states[-1]? It most certainly is the last member, but in case we only have one member in states, states[-1] is also the first member, eliminating my need to know how many members states has!
Since True and True is True, and False and False is False, It simply works.

Now this got everybody real pissed off. And the point of this long, long blog post is - They were rightfully so.
What I did here was to abuse a very nice Python feature, turning it into an unreadable hack. A hack I was very proud of, and fell deeply in love with, and wanted to have little blond hacker children with. But a hack nonetheless.

I consider myself to sometimes be an extremely non-social person. But the code is no place for it. We sometimes fall so deeply in love with our hacks - because they're fun and ingenious, because they're different from everybody else's take on the matter - that we forget that other people will have to read and maintain this piece of code. What if someone had to read this code, or heaven forbid - add another member to that list?

At some point, someone suggested that if I documented what this piece of code is supposed to do in a comment, it would make this code more readable; Not so much in my opinion. The comment would be extremely readable, the code would certainly be not.

So what do you think is the best way to call what I did? Did I exploit a Python feature, or did I employ a Python hack?
















P.S.: What every normal person would do in this situation is probably write the following (or similar) piece of code:


Update: As @avivby, @oren_held, and Omry Tuval have correctly noticed, I had a bug in my requirements so I updated them. Thanks guys!

Should've known I couldn't get away with it so easily :)

2 comments:

  1. Awesome post man! I can rarely remember any other post discussing an important programming issue, teaching do's and don's, and all the while being amusing :)

    Keep'em coming!

    ReplyDelete
    Replies
    1. Thanks for the kind words, happy you enjoyed it :)

      Delete