Archives for: April 2009, 10
Python is not PHP
April 10th, 2009I cringe at a lot of API's these days, because I see designers making the same mistakes again and again. Perhaps the most pervasive mistake is the dreaded NBU design: Namespacing By Underscores. For example, imagine you have a "Thing" class with a "color" attribute:
t = Thing()
t.color = 'red'
One day, you decide to switch from color names to RGB triples. Why, oh, why is this your first thought?
t.color_r = 255
t.color_g = 0
t.color_b = 0
That's your PHP (or Javascript, or SQL, or other) experience poking its ugly head in. Yes, PHP 5.3 finally has namespaces, and you can use objects as namespaces in JS if you're diligent. But chances are, you won't.
In Python, namespaces are easy. Use them. Ask yourself what the clearest syntax is, and you might come up with something like this:
>>> t.color = RGB(255, 0, 0)
>>> t.color.red
255
This is not just a matter of clever delegation (replacing a str attribute with an RGB object)--it covers all manner of interface design decisions. Here's a recent example from python-dev regarding the email package's interface for Python 3:
message.headers['Subject']
message.bytes_headers['Subject']
Please don't do that--it makes it seem as if the "message" object has a set of headers and a distinct set of bytes_headers. At the least, you've elevated the rare case to be a peer of the common case. A new user of the email module shouldn't see anything about bytes in help(message) or dir(message). Instead, write this:
message.headers['Subject'] = 'A conversation'
message.headers['Subject'].encoding = 'utf-8'
message.headers['Subject'].encode()
Or, if you really prefer bytes over unicode as the canonical representation:
message.headers['Subject'] = b'A conversation'
message.headers['Subject'].encoding = 'utf-8'
message.headers['Subject'].decode()
If message.headers[x].encoding is given a sane default, and you expect the vast majority of users to only deal in unicode, they may never see the .encoding and .encode attributes. Good! We've made the common case easy and the rare cases possible.
In addition, we've embellished the Header object with a bytes representation using standard Python conventions: just like Python 3's str object has an encode method, so does our Header object. It's far easier to remember that such a convention applies, than to remember a brand-new name like "bytes_headers" or "decoded_headers".
Namespaces are one honking great idea -- let's do more of those! But please not faked via underscores.