Changing my brain (learning Python)

When we learn a new language , we have to retrain our brains to think in new ways.

Many of us that program forget how deeply counter-intuitive it is to write:

a=2
a=4
3=a

where the first two statements can both be valid in the same program, and not only is the third not a contradiction, it’s nonsense.  Thats because these aren’t equations.  Back in math, we learned in math that the = symbol is a comparison operator.  Computer programs have this, too, but it’s usually different, like .eq. or ==.

The equals sign in computer programming doesn’t mean “these two things are equal.” Instead, it means “the thing on the left is the address of a place in memory; erase whatever is in that part of memory and replace it with whatever the thing on the right evaluates to.”  Worse, in C the statement isn’t “true” if the two sides are equal, it’s true if the right hand side of the statement is true.

But after a while we get used to this sort of thing, and incorporate the totally different meanings for = into our brains without really realizing we’re doing it.

I’m learning Python now, and I’m having these sorts of major mental obstacles.  I know a smidgen of object oriented programming (some C++ and F90) but Python is requiring me to really reprogram my brain because it’s SO object oriented. Note that what follows is my very primitive attempt to wrap my brain around newfound knowledge, I’m sure I have the details wrong.

The first place I got hung up is that Python uses two different definitions for the = sign, and it’s almost never clear without context which is being used.  For instance, if I write:

a=2
b=a
b+=2
print(“a:”,a)
print(“b:”,b)

I get as output (from the print statements):

a: 2
b: 4

Things work basically like we expect. The variable b got the value 2 from a, I added 2 to that number “in place” (using the “+=” operator), then I printed out the two variables. Since I added 2 to b, and not to a, they have different values now. Just like we expect from C, FORTRAN, and most other languages.

But there’s another definition, too. If I write:

m=[2]
n=m
n+=[2]
print(“m=”,m)
print(“n=”,n)

things do not work this way. The syntax m=m+[2]” here means “add another element, 2, to the list m”. But what I get out is:

m: [2, 2]
n: [2, 2]

OK, so it didn’t do any array math, it appended another 2 to the end of the list. That’s fine, it’s just how operators work on lists, a choice the language creators made.

But why did m change?!

Because in this case, the statement “n=m” doesn’t mean “put the evaluated contents of m in the place in memory specified by n”. It means something very different: “from now until I redefine one of them, n and m are just different names for the same variable“. So, if I change either of them “in place” (by calling a function or using an operator like +=), the other one also changes.

So how is a user to know which = operator they are invoking when they type “a=b”? You have to know whether b is a “mutable” or “immutable” data type. Lists are “mutable”, so “=” here is an alias operator (and is commutative). Strings and floats are not, so “=” here is an assignment operator (and does not commute).

This will take getting used to.

The next thing I’ve gotten hung up on is that functions are just another kind of object, like variables, so they can be redefined just like variables. In other languages, I can write:

a=1.3
cos = cos(a)

and this means that I want my variable “cos” to refer to a place in memory that, for now, contains a floating point number equal to the mathematical cosine of the number 1.3.

But (one way to make) an equivalent statement in Python is:

from numpy import *
a=1.3
cos=cos(a)

(Python programmers are trying to stifle howls of protest right now…) The first line defines the mathematical function cos() (and many others) from the numpy module. The next two lines are equivalent to the above, with a major exception:

I no longer have access to the cosine function:

In: print(cos)
Out: 0.267498828625
In: cos=cos(a)
Out: TypeError
—-> cos=cos(a)
TypeError: ‘numpy.float64’ object is not callable

That’s because there’s a namespace collision: I have redefined cos to be a variable, so it’s no longer a function.

Again, it’s a choice Python made, and I’m sure it’s all very elegant and Pythonic. It shouldn’t be a problem, because my first statement that enabled this abomination, “from numpy import *” is B-A-D bad. One should instead import it as “import numpy as np” or some such, and call the cosine function as “np.cos()”

But the very fact that this is even possible is something I’m still wrapping my brain around.

n.b.: I’m not complaining about Python, attacking it, or saying it’s “wrong” or “bad”. I’m sure this will be a great, growing experience for me. I just find these choices in how the language works, especially the first one, hard to internalize. Why overload the = operator in a way that gives it two totally separate meanings like this? Why not use <- or <-> or something instead to distinguish?

3 thoughts on “Changing my brain (learning Python)

  1. mike b

    going along with marshall’s comment, i would explicity warn against using “from numpy import *”. namespaces should be explicitly defined in the code as “import numpy as np” and then “a = np.cos(np.pi)”. in idl, there is a single namespace, which means that you can completely invalidate large chunks of code by writing a new function. it also makes it essentially impossible to write large programs that are portable.

    the best part is you can write your own function libraries like “spectrograph_functions.py” and then use them in whatever program you like, with “import spectrograph_functions as sf”; “a = sf.reduce_spectrum()” etc.

    mike b

  2. Marshall

    Names and namespaces are the key concept to wrap your head around here; they’re deeply fundamental to how Python works under the hood. Many operations that seem unrelated at first glance actually turn out to be namespace manipulations under the hood.


    a = 5

    Define a local name ‘a’ which refers to the constant object 5


    b = some_object()

    Define a local name ‘b’ which refers to the object returned from that function call.


    from numpy import cos

    From the namespace ‘numpy’, take the object referred to by the name ‘cos’. Define a local name ‘cos’ that refers to the same object.


    from numpy import cos as cosine

    From the namespace ‘numpy’, take the object referred to by the name ‘cos’. Define a local name ‘cosine’ that refers to the same object.


    cos = cos(a)

    Call the object referenced by the local name ‘cos’, with the argument referenced by the local name ‘a’. Let the local name ‘cos’ now reference the result of that function call.


    somevar[4:6] = 16

    The string ‘somevar[4:6]’ is not a valid name, it’s clearly an subscript reference. Therefore find the object which is referred to by the name ‘somevar’, and ask it to perform that subscript lookup and assign the constant object 16 to the result of that lookup. In other words under the hood this actually turns into something more like: somevar.getitem(4, TO, 6).setvalue(16)

    The mutable/immutable distinction comes up because if you try to do that with an immutable object, it will cry out in pain “but you can’t setvalue() on any of my contents because they’re read-only!”

Leave a Reply

Your email address will not be published. Required fields are marked *