I encountered some surprising behavior in python’s list.sort() method. I was calling list.sort() with a function, then list.reverse(). This was silly - I forgot I could call list.sort(reverse=True). As it turns out, these are not the always the same, or even usually the same.
What’s happening here is that .sort() and sorted() in python are “stable sorts”, meaning that it’ll preserve the existing order when faced with a tie. This leads to a nice side effect, as described in the python wiki: you can sort by multiple criteria. So if we wanted to sort primarily by number (ascending), and secondarily by letter, we could do that quite easily. Just sort by the secondary key first, then by the primary key. Since the sort is stable, you know that a tie in the second (primary) sort will preserve the order you had going in.
I’m currently digging into the source of virtualenv, a python utility for managing environments. It’s incredibly handy, and I’d been using it for a while without really understanding how it worked. I’ll have more later on the details, but in the meantime, I hit a particularly puzzling bit of code:
123456789101112131415
if [ -z "$VIRTUAL_ENV_DISABLE_PROMPT" ] ; then
_OLD_VIRTUAL_PS1="$PS1"
if [ "x" != x ] ; then
PS1="$PS1"
else
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
else
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
fi
fi
export PS1
fi
This is largely intelligible - clearly we’re modifying the $PS1 variable, which sets the bash prompt. But what on earth is this line?
12
if [ "x" != x ] ; then
PS1="$PS1"
x is not a variable defined elsewhere in the script, and bash will interpret both "x" and x as string literals anyway. Is there some bizarre shell in which this test can possibly return true?
As it turns out, there isn’t. The reason this code looks so bizarre is that it’s automatically generated by virtualenv. The original line of code is this:
12
if [ "x__VIRTUAL_PROMPT__" != x ] ; then
PS1="__VIRTUAL_PROMPT__$PS1"
I recently started using git’s patch mode via git add -p. Patch mode makes my commits more granular, which means my commit messages are more accurate. Better still, patch mode allows you to abstract away from adding “files” - you’re adding changes to be committed. This is a closer mental model to what git is actually doing. I’m finding it particularly useful for code review: make several changes in a friend’s program, group the changes by concept, and commit one concept at a time.
That’s the why - here’s the how.
git add -p launches what git calls the “interactive hunk selector.” This leads me to my other favorite reason to use -p: besides being useful, all the docs sound like an episode of The Bachelorette. “Once you have decided the fate of all the hunks, if there is any hunk that is chosen, the index is updated with the selected hunks.” And if not, our bachelorette takes the million dollars!
In practice, I only end up using three of the interactive hunk selector options: [y]es, [n]o, and [s]plit. Split takes the current hunk and divides it into smaller hunks, then presents them for selection one at a time. (If you’re attempting to continue the Bachelorette metaphor, now is a good time to stop.)
Let’s look at an example. Suppose I’m working on a function that recursively calculates the sum of element of a list. My first draft looks like this:
123456
def sum(list_in):
""" Returns the sum of elements of a list."""
if list_in == []:
return 0
else:
return list_in.pop() + sum(list_in)
Not bad, but it’s a little verbose. Let’s take advantage of the fact that an empty list is falsey. And, oops, we’re overwriting the built-in sum function in python - we probably don’t want to do that.
123456
def summer(list_in):
""" Returns the sum of elements of a list."""
if not list_in:
return 0
else:
return list_in.pop() + summer(list_in)
Ok, these are two different thoughts, but we didn’t commit in between. Interactive mode to the rescue - let’s stage those hunks.
12345678910111213141516
sum [master]\ ⚲git add -p
diff --git a/sum.py b/sum.py
index fdccd3f..6d000f7 100644
--- a/sum.py
+++ b/sum.py
@@ -1,6 +1,6 @@
-def sum(list_in):
+def summer(list_in):
""" Returns the sum of elements of a list."""
- if list_in == []:
+ if not list_in:
return 0
else:
- return list_in.pop() + sum(list_in)
+ return list_in.pop() + summer(list_in)
Stage this hunk [y,n,q,a,d,/,s,e,?]?
Our changes aren’t in the right hunks, so we’ll pick s to split them.
1234567
Stage this hunk [y,n,q,a,d,/,s,e,?]? s
Split into 3 hunks.
@@ -1,2 +1,2 @@
-def sum(list_in):
+def summer(list_in):
""" Returns the sum of elements of a list."""
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?
Let’s deal with the naming issue first. y. Next up:
123456
@@ -2,4 +2,4 @@
""" Returns the sum of elements of a list."""
- if list_in == []:
+ if not list_in:
return 0
else:
sum [master *+]\ ⚲git commit -m "fix naming to not conflict with builtin"
[master caaa300] fix naming to not conflict with builtin
1 file changed, 2 insertions(+), 2 deletions(-)
Now we can repeat this process for our other change.
1234567891011121314
sum [master *]\ ⚲git add -p
diff --git a/sum.py b/sum.py
index 77676b1..6d000f7 100644
--- a/sum.py
+++ b/sum.py
@@ -1,6 +1,6 @@
def summer(list_in):
""" Returns the sum of elements of a list."""
- if list_in == []:
+ if not list_in:
return 0
else:
return list_in.pop() + summer(list_in)
Stage this hunk [y,n,q,a,d,/,e,?]?
This is our only hunk now, so y.
123
sum [master +]\ ⚲git commit -m "use falseyness of empty list"
[master 5209728] use falseyness of empty list
1 file changed, 1 insertion(+), 1 deletion(-)
Of course, you may not want your commits to be quite this granular - but hopefully the example has demonstrated the strength of git add -p in creating single-concept commits.