Pythonic means idiomatic and tasteful
Pythonic isn’t just idiomatic Python — it’s tasteful Python. It’s less an objective property of code, more a compliment bestowed onto especially nice Python code. The reason Pythonistas have their own word for this is because Python is a language that encourages good taste; Python programmers with poor taste tend to write un-Pythonic code.
This is highly subjective, but can be easily understood by Pythonistas who have been with the language for awhile.
Here’s some un-Pythonic code:
def xform(item): data = {} data["title"] = item["title"].encode("utf-8", "ignore") data["summary"] = item["summary"].encode("utf-8", "ignore") return data |
This code is both un-Pythonic and unidiomatic. There’s some code duplication which can very easily be factored out. The programmer hasn’t used concise, readability-enhancing facilities that are available to him by the language. Even lazy programmers will recognize this code’s clear downsides.
Here’s another version that is more idiomatic but is nonetheless still un-Pythonic:
class ItemTransformer(object): def __init__(self, item): self.item = item def encode(self, key): return self.item[key].encode("utf-8", "ignore") def transform(self): return dict( title=self.encode("title"), summary=self.encode("summary")) |
Nothing about this code is particularly unidiomatic. I might even see code like this in many popular open source projects. But it’s in poor taste. It’s un-Pythonic.
What is the code doing? It’s just taking an incoming dictionary, encoding its values using utf-8, and returning a new dictionary with those encoded values. There is no need to introduce an ItemTransformer object — it’s an extra abstraction and is just making the signal-to-noise ratio poorer. People coming from Java often write un-Pythonic code because Java is a language that does not reward good taste. The Pythonic view is: programming is hard enough — let’s not make it harder for ourselves.
Here’s a more Pythonic version:
def encode_news_item(item): def encoded(*keys): for key in keys: yield (key, item[key].encode("utf-8", "ignore")) return dict(encoded("title", "summary")) |
This code shows comfort with Python’s features, but does not abuse this comfort by obfuscating the code with mind-bending constructions. The programmer has reduced the problem to two comprehensible subproblems: creating a stream of tuples (key, encoded_value) and constructing the new dictionary from that stream. This leverages the elegant fact that in Python, dictionaries can be easily constructed from (key, value) tuples.
This version avoids code duplication while also making the last line (the return statement) a rough description of the entire function. “return a dictionary of the encoded values for the keys title and summary” Idiomatic, yes. But also tasteful, and thus Pythonic.
Even Pythonic code can be improved:
def encode_news_item(item): encoded = lambda key: item[key].encode("utf-8", "ignore") keys = ("title", "summary") return dict((key, encoded(key)) for key in keys) |
And though Pythonic code is often smaller than its un-Pythonic counterparts, the experienced Pythonista knows the road to hell is paved with good intentions:
def encode_news_item(item): keys = ("title", "summary") return dict(zip(keys, map(lambda key: item[key].encode("utf-8", "ignore"), keys))) |
Ick… time to hg revert this idiomatic Python code to the more Pythonic version
November 4th, 2010 at 3:17 pm
unless i’m missing something:
def encode_news_item(item):
return dict([(key, item[key].encode(“utf-8″, “ignore”)) for key in (“title”, “summary”)])
November 4th, 2010 at 3:27 pm
or how about implementing __iter__, so you can run dict on the item:
def __iter__(self): return iter([(key, item[key].encode(“utf-8″, “ignore”)) for key in (“title”, “summary”)])
November 5th, 2010 at 4:39 am
I’ll write something like this:
class Dict(dict):
def __setitem__(self, key, value):
super(Dict, self).__setitem__(key, value.encode(‘utf8′, ‘ignore’))
November 5th, 2010 at 4:47 am
yet another function version:
def encode_dict_items(dic, encoding=’utf8′, errors=’ignore’):
return dict((k, v.encode(encoding, errors)) for k, v in dic.items())
November 11th, 2010 at 1:15 pm
Sometimes it takes time to come up with elegant solutions, but in a real world environment with deadlines, the “ugly” solution gets the job done, and that’s enuff.
November 16th, 2010 at 6:44 am
It’s worth noting that the least Pythonic code example posted is in fact the fastest. It hurts, but sometimes it pays to leave well enough alone.