2015-01-30
I've come to believe Django's way of defining database tables is wrong
Django defines both database tables and HTML forms in the same way, a way that seems to be extremely common in web frameworks across several languages (and which I think first surfaced in Rails, although I may well be wrong there):
class AForm(...): login = forms.CharField(...) email = forms.EmailField(...) ...
This is very appealing initially and Django goes well out of its
way to make it all work. But over time I've
come around to feeling that this is in fact the wrong way to do
forms, database tables, and so on in Python. Why not boils down to
one famous sentence from 'import this' (aka the zen of Python):
Explicit is better than implicit.
Django form classes and database classes are full to the brim of implicit magic. They're essentially an illusion. Worse, they're not really a very Pythonic illusion. We accept the illusion because it's convenient and this way of defining forms and tables has become more or less standard, but that doesn't mean that it's right in Python.
(My view is that the initial Rails version was a reasonably natural looking DSL that happened to also be valid Ruby code with the right mangling. The Python version is clearly not something you can read as a DSL, so I think that the seems show much more; it looks like Python but it's kind of bizarre Python.)
Given the Tim Peters remark I led with, I think a more Pythonic way
would make explicit various things that are currently implicit. I
don't have a good handle on what that would look like, though.
Doing the setup in class __init__? Defining the table or form
layout by calling code instead of defining a class? Either would
be (or at least could be) more explicit and less magical.
(Part of the issue is that Python has decided that structures with named fields are only really available as classes. Once you have classes, all sorts of temptations start materializing and looking at least partly natural.)
PS: It's my view that the magical, illusory nature of Django form and table definitions starts showing through very distinctly once you want to do more complex things with them. Like much magic, it works best if you don't touch it at all.
2015-01-19
A gotcha with Python tuples
Here's a little somewhat subtle Python syntax issue that I recently got to relearn (or be reminded of) by stubbing my toe on it. Let's start with an example, taken from our Django configuration:
# Tuple of directories to find templates
TEMPLATE_DIRS = (
"/some/project/directory"
)
This looks good (and used to be accepted by Django), but it's wrong.
I'm being tripped up by the critical difference in Python between
'(A)' and '(A,)'. While I intended to define a one-element
tuple, what I've actually done is set TEMPLATE_DIRS to a single
string, which I happened to write in parentheses for no good reason
(as far as the Python language is concerned, at least). This is
still the case even though I've split the parenthesized expression
over three lines; Python doesn't care about how many lines I use
(or even how I indent them).
(Although it is not defined explicitly in the not a specification, this behavior is embedded in CPython; CPython silently ignores almost all newlines and whitespace inside ('s, ['s, and {'s.)
I used to be very conscious of this difference and very careful
about putting a , at the end of my single-element tuples. I think
I got into the habit of doing so when I at least thought that the
% string formatting operation only took a tuple and would die if
given a single element. At some point % started accepting bare
single elements (or at least I noticed it did) and after that I got
increasing casual about "..." % (a,) versus "..." % (a) (which
I soon changed to "..." % a, of course). Somewhere along this the
reflexive add-a-comma behavior fell out of my habits and, well, I
wound up writing the example above.
(And Django accepted it for years, probably because any number of people wrote it like I did so why not be a bit friendly and magically assume things. Note that I don't blame Django for tightening up their rules here; it's probably a good idea as well as being clearly correct. Django already has enough intrinsic magic without adding more.)
As a side note, I think Python really has to do things this way. Given
that () is used for two purposes, '(A)' for a plain A value is at
least ambiguous. Adopting a heuristic that people really wanted a single
element tuple instead of a uselessly parenthesized expression strikes me
as too much magic for a predictable language, especially when you can
force the tuple behavior with a ','.