It has been some time since I wrote an installment of On Coding. It’s time to address one of my more recent programming adventures: Python. I started learning Python about two-and-a-half years ago when I began teaching at the University of San Francisco.
One of my colleagues introduced me to the Sage environment (now going by “CoCalc”) as a place to do Mathematica-like calculations, albeit at a smaller scale. Four features were worthy of note to me: 1) you could do graphics; 2) you could write code (in Python); 3) you could run the environment in your browser without downloading anything; and 4) it was open source.
For me, this was (at the time) the perfect environment to develop tools for creating digital art which I could freely share. Yes, I had thousands of lines of Mathematica code, but Mathematica is fairly expensive. I wanted an environment which would be easily accessible to students (and my blog followers!), and Sage fit the bill.
So that’s why I started learning Python — it was the language I needed to learn in order to use Sage.
For me, two things were a challenge. The first was how heavily typed Python is. In Mathematica, the essential data structure is a list, just like in LISP. For example,
{1, 2, 3}
is a list. But that list may also represent a vector in three-dimensional space — even though it would look exactly the same. It may also represent a set of numbers, so you could calculate
Intersection[{1, 2, 3}, {3, 4, 5}].
In Python you can create a list, tuple, or set, as follows:
list([1, 2, 3]), tuple([1, 2, 3]), set([1, 2, 3]).
And in Python, these are three different objects, none equal to any other. I don’t necessarily want to start a discussion of typed vs. untyped languages, but when you’re so used to using an untyped language, like Mathematica, you are constantly wondering if the argument to some random Python function is a list, tuple, or….
Second, Python has a “return” statement. In languages like LISP and Mathematica, the value of the last statement executed is automatically returned. In Python, you have to specify that you want a value returned by using a return statement. I forget this all the time.
And while not a huge obstacle, it does take a little while to get used to integer division. In Python, 3/4 = 0, since when you divide integers, the value of the fraction is the quotient when considered as integer division. But 3/4. = 0.75, since adding the decimal point after the 4 indicates the number is a floating point number, and so floating-point arithmetic is performed.
Of course, if you’ve been reading recent posts, you know I’ve moved from Sage entirely to Processing in my Mathematics and Digital Art course. You can read more about that decision here — but one key feature of Processing is that there’s a Python mode, so I was able to take work already done in Sage and adapt it for Processing.
It turns out that this was not as easy as I had hoped. The essential difficulty is that in Sage, the bounding box of your image is computed for you, and your image is appropriately scaled and displayed on the screen. In Processing, you’ve got to do that on your own, as well as work in a space where x– and y-coordinates are in units of pixels, which is definitely not how I am used to thinking about geometry.
I am finding out, however — much to my delight and surprise — that there are quite a few functional programming aspects built into Python. I suspect there are many more than I’m familiar with, but I’m learning them a little at a time.
For example, I am very fond of using maps and function application in Mathematica to do some calculations efficiently. Rather than use a loop to, say, add the squares of the numbers 1– 10, in Mathematica, you would say
Plus @@ (#^2& /@ Range[10])
The “#^2&” is a pure function, and the “/@” applies the function to the numbers 1–10 and puts them in a list. Then the function “Plus” is applied, which adds the numbers together.
There is a similar construct in Python. The same sum of 385 can be computed by using
sum([(n + 1)**2 for n in range(10)])
OK, this looks a little different, but it’s just the syntax. Rather than the “#” character for the variable in the pure function, you provide the variable name. The “for” here is called list comprehension in Python, though it is just a map. Of course you need “(n + 1),” since Python always starts at 0, so that “range(10)” actually refers to the numbers 0–9. And the “sum” function can take a list of numbers as well. But from a conceptual level, the same thing is going on.
The inherent “return” in any Mathematica function does find its way into Python as well. Let’s take a look at a simple example: we’ll write a function which computes the maximum of two numbers.
Now you’d probably think to write:
This is the usual way of defining “max.” But there’s another way do to this in Python.
If you ask Python to
print 3 > 2,
you’ll see “True.” But you can also tell Python to
print (3 > 2) + 7
and get “8.” What’s going on here is that depending on the context, “3 > 2” can take on the value “True” or “1.” Likewise, “3 < 2” can take on either the value “False” or the value “0.”
This allows you to completely sidestep making Boolean checks. Consider the following definition.
This also works! If in fact a >= b, you return the value 1 * a + 0 * b, which gives you a — the maximum value when a >= b. And when a < b, you return b. Note that when a = b, both are the maximum, so we could just as well have written
I think this is a neat feature of Python, which does not have a direct analogue in Mathematica. I am hoping to learn many other intriguing features like this as I dive deeper into Python.
Python is my newest language, and I have yet to become “fluent.” I still sometimes ask the internet how to do simple things which would be at my fingertips in Mathematica. But I do love learning new languages! Maybe in a year or so I’ll update my On Coding entry on Python with a flurry of new and interesting things I’ve learned….
A student and I are learning Python as we go through the book Coding the Matrix. Here are three examples of unexpected behavior (for someone used to Mathematica) which came up while working with my student.
Unlike Mathematica, Python does not issue a warning when I try to redefine a built-in function:
>>> list = [1,2,3]
>>> list({7,8})
Traceback (most recent call last):
File “”, line 1, in
TypeError: ‘list’ object is not callable
This particular error was tricky for me to trace because I implicitly assumed that we cannot casually redefine a built-in function.
I cannot define a set of sets they way I do it in Mathematica, which by the way is also consistent with standard mathematical notation.
>>> {{1}, {2,3}}
Traceback (most recent call last):
File “”, line 1, in
TypeError: unhashable type: ‘set’
I eventually learned that set is implemented as a hash table. As a consequence, we cannot have mutable elements in a set. It did get me to read up on hashing though.
Speaking about mutable objects, there are methods in Python which works via its side effect instead of returning a value. This can cause problems when we use a list comprehension.
>>> [1,2].append(3)
>>> [x.append(3) for x in [[1],[2]]]
[None, None]
>>> lst = [1,2]
>>> lst.append(3)
>>> lst
[1, 2, 3]
We eventually figured out that we should have used + instead of append.
>>> [x+[3] for x in [[1],[2]]]
[[1, 3], [2, 3]]
I’m sure we’ll find more interesting examples as the semester progresses.
LikeLike
Michael,
Thanks for the comments! Yes, when your brain is predisposed to think a certain way, it is sometimes difficult to suddenly shift perspectives. I suspect we’ll both keep finding more glitches in our thinking. Feel free to post about other interesting things you discover with your student.
Vince
LikeLike