[conspire] Why I loves me the Python.
Nick Moffitt
nick at zork.net
Mon Sep 21 16:56:44 PDT 2009
Rick Moen:
> > [1] There is actually a "rational subset"[2] of perl that's actually
> > quite readable.
>
> Yes, indeed. Nick Moffitt (also on this mailing list) was actually the
> first Perlista whose code I could consistently read, because he writes
> C-like Perl, bless him. Before I saw his scripting, I was worried I'd
> never learn to understand the damned stuff.
Eep! You're showing off the coding equivalent of embarrassing disco
outfit photos here :)
I haven't written a lick of Perl in nearly a decade, now (I think my
last Perl was for a consulting gig doing GNU Bayonne scripting). About
ten years ago I spent a while trying to wrap my head around why Python
was a worthwhile language to code in, and that simmered on the back
burner while I did ridiculous things in GNU make[1] and awk[2] and such.
I tend to work almost exclusively in bash, AWK, and Python these days.
I'm not impressed by much of the "line noise" snark at Perl, since I
think that most of that originally came from people who had never worked
with regular expressions before. The symbolic decoration on variable
types is something that probably seemed less odd to me because of my
experience with 6502 and 8086 assemblers that used # and $ for somewhat
similar things. Still, I can see how some folks see the punctuation as
helpful visual landmarks while others see it as indication of
complexity. It's definitely an emotional reaction more than anything.
I do appreciate the sentiment that a disciplined or naturally clear
programmer can write good code in nearly any language[3], but I find
that this little bromide often gets trotted out to silence critical
analysis of a language's flaws. It's not interesting to know that good
programmers can do good things with poor tools. The fact that Mozart
could write grand opera in German doesn't necessarily make it the ideal
language for the task.
I tend to prickle up at the Python party line that once you get used to
the invisible syntax, you'll believe it to be an asset. I find myself
emotionally drifting toward this position, but intellectually I still
feel like it's more true to say that Python's success *despite* this
wart shows off its other strengths incredibly well. Discussions of how
Python encourages you to be a good coder often get dragged down into
bikeshedding about a syntactic feature that I'm still not convinced is
as helpful as they say.
This distracting debate causes people to ignore a few things that I
love most about the language:
1: Namespaces.
Namespaces can feel like esoteric constraints in other
languages, like you're just artificially walling off
symbols from each other in confusing and arbitrary ways.
Java seems to force lots of them on you, and early
jabs at Java were all about the
very.long.paths.through.modules.to.functions().
By contrast, Python namespaces are a simple mapping onto
the filesystem, at the root, and actual data structures
in your own code toward the leaves. If most other
language implementations just adopted Python's namespace
handling and import syntax, I'd give them a closer look.
Net effect: if you have gronk/spoot/fooble.py (with
__init__.py files in the gronk and spoot dirs), and say
a hork object with a zorch method inside fooble.py, you
could call it in python by doing:
import gronk
gronk.spoot.fooble.hork.zorch()
-or-
from gronk.spoot import fooble as wut
wut.hork.zorch()
-or-
from gronk.spoot.fooble import *
hork.zorch()
...and so on. I'm amazed that people manage to code
anything useful at all in PHP, with a polluted root
namespace like this:
http://www.php.net/quickref.php
It's like keeping all your files in / without any
subdirectories! They adopted underscore conventions,
but that doesn't get you "relative path" operations. I
hear newer PHP releases have namespaces, but it sounds
like it will be ages before they can reorganize their
standard library.
2: Generators/Iterators.
A common pattern in programming is to generate a list of
items in memory, and then iterate over that list to
perform some operation on each element within. The
trouble with this is that if your list is large and you
don't need to keep it around for anything but the walk
straight down it, you're pretty much wasting time and
memory building it up in the first place.
Python gives you a great tool for making your functions
return something that is as good as a list for
iteration's sake, but which lazily only creates the next
item when it's asked for.
Consider the old way:
def stuff():
i = 0
l = []
while not_done_yet():
l.append(get_a_thing(i))
i += 1
return l
for thing in stuff():
print thing
If not_done_yet() doesn't return False until you've done
zillions of get_a_thing() calls, this would fail at user
interaction. You'd wait for hours, maybe, and then your
screen would whoosh by with far too much text to deal
with.
So we change it all:
def stuff():
i = 0
while not_done_yet():
yield get_a_thing(i)
i += 1
for thing in stuff():
print thing
Because our function uses "yield" instead of "return",
it immediately returns a funky "generator object" that
the for loop can iterate over. With each iteration the
yield basically pauses stuff() and "returns" the value,
and on the next iteration it un-pauses and picks up
right on the next line after the yield.
I avoided this feature for a long time after it entered
the language because it seemed really mysterious (after
all, it's kind of like scheme's call/cc), but boy am I
glad I went back to look into it! I've found that
inserting this sort of lazy evaluation into my code in
specific circumstances has been a fantastic way to
improve performance and responsiveness--often with a net
reduction in code length (as in the above admittedly
pathological example).
3: Duck Typing Gone Mad
Python's a dynamically-typed language, which basically
just means you don't have a type checker scolding you
for writing a for loop and not caring if the thing
you're iterating over is a proper list or if it's just a
funky generator function that uses "yield". The basic
type system is often called "duck typing" after the old
saw about something that looks like a duck and quacks
like a duck.
This sounds like an invitation to chaos, and I think
programming language theorists are still slightly
baffled at why it isn't a catastrophic failure. One
interesting trend is that some of the sorts of things we
used to rely on type checking for are now handled by
unit testing. But things work well in Python because
if you implement some basic methods in your classes,
your objects get to take advantage of core language
syntax.
So if you implement the necessary method to become a
"callable", hey presto your object can behave like a
simple function. If you implement the __getitem__()
method, you can do "x = myobject[key]" just as if it
were a dictionary! You can pretend to be a string, an
integer, or any number of core data types. If you want
to get really crazy you can use this stuff to
dynamically present namespaces to other code.
So this takes the straitjacket off the 'EVERYTHING IS AN
OBJECT' philosophy. Sure everything's an object, but
objects can also trivially become anything.
4: Really Powerful Function Arguments
Man, I really love named function arguments.
def foo(thingy, length=20, width=42):
...
squarethingy = foo(mythingy, width=20)
The above just accepted the default value for length,
which is 20. The call is also clearer, you're passing
in a thingy which is obvious, but passing in lists of
integers gets eye-glazing after a while. This saves you
the step of digging through for the definition of the
function to see whether width or length came first in
the argument list.
But variable arguments are also great:
def bar(thingy, *args, **kwargs):
In this case thingy is the first parameter, then "args"
is a read-only list of all the arguments that were
passed in without the =, and then kwargs is a
*dictionary* of the foo=bar arguments that were passed
in, so:
bar(mything, 1, 2, 3, 4, buckle="my shoe",
shut="the door")
...would call bar with thingy set to mything, args would
be the tuple (1, 2, 3, 4) and kwargs would be a
dictionary where kwargs['buckle'] is "my shoe" and
kwargs['shut'] is "the door".
For bonus points, you can reverse it:
y = baz(somefink, *my_list, **a_nice_dictionary)
...and that will unpack the list and put each element
into the argument list, and then unpack all the
dictionary entries to form keyword arguments.
I've kind of spazzed all over, explaining this, but it's
just an example of how something as simple as namespaces
or passing arguments to functions can be designed the
right way and provide you lots of opportunities to write
clear and useful code.
Tying most of them together, namespaces are largely implemented as (or
at least presented as) ordinary dictionaries[4]! So thanks to duck
typing, obj.member is usually the same as obj.__dict__['member'],
similar to the way keyword arguments and dictionaries can swap back and
forth.
...Of course all of these things would be the usual sort of useless
gilding on esoteric never-see-production-use languages if it weren't for
the fact that the Python developers went to the trouble of actually
integrating smoothly with OS services, made the namespaces map onto
grubby filesystem details, and spent time writing libraries of neat
stuff similar to Perl's collection.
Does this make Python magically spew forth good code from bad
programmers? No, in the same way that Perl can't completely knock the
legs out from under good programmers. I find that one distressing sign
of a bad interface between Python and a C library of some sort will be
a scarcity of duck typing tricks: you may find yourself with a bunch of
"get_first_foo()" and "get_next_foo()" functions instead of an object
that implements the methods necessary to behave as a Python sequence or
iterator.
Python's "list comprehension" syntax, while it's a useful shortcut,
really looks more like the haskell it derived from than true Python.
Likewise, that yield thing can really confuse someone who isn't familiar
with it, and I'll admit that I am responsible for some flow-control head
scratching now and again. Still, good programmers can write good code
in any language, isn't that right brainfu--I mean, isn't that right
folks?
--
"Here is the memo if you didn't receive it: GNOME and
Free Software is all about *SAVING THE WORLD* not
drawing pissy little buttons on the screen."
-- Jeff Waugh
1: http://en.wikipedia.org/wiki/GARNOME
2: http://www.firthworks.com/roger/cloak/ (search for AWK)
3: That said, ain't nobody can make an asterisk config I can follow
without headaches the next morning.
4: This can get wasteful, so you can use the 'slots' system to fix a
class's member variables in a tuple, which *really* helps the
JIT accelerators like psyco!
5: Yeah look at that Date header. I should get to sleep.
More information about the conspire
mailing list