More Help With Python
--
A thing to get about a computer language, is it comes with a cloud of jargon, and that’s not a bad thing (not a bug), is another feature to tune in, along with the style guide.
What may confuse the newcomer is how the different computer languages have jargons that overlap, often bigly, such that teasing apart the nuances requires the skills of a humanities reader. “But I thought this was engineering.” But with a drop of philosophy.
By “jargon” with regard to Python, lets start with “mutable versus immutable”, a key distinction.
In the Python namespace, this difference is typically introduced when talking about data types and which might be “changed in place” meaning their identity stays the same (see id( ) function) yet their content has mutated.
The list type is mutable in that if A = [2,], and then I go A[0] = 3, the id of A will remain unchanged. A is still A, just one of its elements (the only element in this case) was changed from 2 to 3.
On the other hand, if I go A = (2,), creating a tuple instead of a list, then A[0] = 3 is a TypeError. I’ve mistaken A for a mutable type. A tuple is more like a list “behind glass” or read-only.
But then (subtle point): A = ([ ]); A[0].append(1) does succeed in turning A into ([1]). Why? Because what’s immutable about A is it’s a tuple of one list element. Mutating said list element is not changing this basic fact about A, and said list remains mutable.
By “doodling and daydreaming in the REPL” (at the interactive Python console) we grow in our self-confidence, around our ability to interpret Python source code the same way Python does. REPL = Read, Evaluate, Print, Loop. Check Wikipedia.
The REPL (interactive prompt, like the “dot prompt” in xBase, or the customary $ prompt in bash) gives the Python student some traction, even with no teacher present (other than Python itself).
Turning to other languages, you’ll find the jargons surrounding Clojure and Rust likewise place emphasis on this mutability distinction.
Clojure takes the approach that the more you can freeze in place the better, reminiscent of Erlang, whereas Rust is all about not needing a garbage collector, such as Python provides, because the language itself prevents segfaults. You might need to be a humanities reader to parse all that.
Philosophy of language textbooks go way back with this thread on “identity”: when does swapping out parts of a ship constitute eventually having a new ship?
Every cell in “your” body gets replaced, but “you” don’t? What’s the grammar here? We might investigate further, as chief detective L. Wittgenstein suggested we do. Philosophy overlaps computer languages in that we stay self-conscious around grammar.
Human language defines the headwaters for the mutability concept, as one might expect. “Mutability” suggests flexibility, expandability, volatility. “Immutability” is likewise rich with connotations, such as “carved in stone”. Allow these associations to help guide you. Don’t dismiss imagery as “getting in the way” as that only impoverishes one’s mnemonic networks.
Another distinction: callable versus not.
The uncallables (all keywords are such) do not “eat” arguments, or “eat” at all, meaning no sideways lips like from emoticons :-( ).
If it makes sense to add sideways lips to a name in order to “call” it (trigger deep method __call__), then callable(said_name) should always return True (a keyword).
Putting square brackets after a name or object, as in this robot emoticon :-[ ], does not constitute “calling” (__call__-ing) and is rather a trigger for __getitem__ or __setitem__ (the deep method callables associated with bracket syntax).
So, for example, integers and strings are both immutable and uncallable.
If I say A = 3, and later A = 5, that’s considered binding “the same name A” to a different object. A link was broken, between a name and a number object, and a new link was made. id(3) != id(5).
Different integers are always different objects, ditto strings.
But remember A = [1]; A[0] = 2 makes sense, where A points to the same list object all along, with only the content of said object getting changed.
That’s a key distinction in human language as well, when defining “container”. An object is a container precisely if it contains content that might be swapped out for other content, without the container thereby changing its identity. An instance of the list type is like the bottle we keep refilling.
One would never write “hippopotamus”( ) as if string instances had a mouth. Strings do not eat, nor do numbers.
On the other hand, the types themselves, such as str, float, int, list, dict, tuple, property and more, all eat arguments, and aim to take in whatever might sensibly result in output “in the image” of said type. float(“2.1”) begets a float, from a string. str(2.1) begets a string, fed a float.
Indeed, str() will eat any number, simply by quoting its representation (returned by __repr__) unless __str__ is in the types hierarchy, in which case it runs that instead.
The types try to be omnivorous but know their own limits.
list( ) wants only an object it might sequence, like a string.
int( ) hates strings with a decimal in them, but will eat a float just fine.
I like that types are callables without being themselves functions, nor factories for functions. zip, enumerate and range are all types, not functions.
zip and enumerate are both iterators, meaning their instances may be fed to next( ), triggering __next__ (this isn’t Python 2.x).
We may categorize types by the APIs they support, what magic names.
Descriptors have __get__ and maybe __set__, for example.
Context managers have __enter__ and __exit__.
Likewise, instances of the function type are all about “having a mouth”. Even if a function takes no arguments, to “make it go” one must call it, by giving it “lips” ;-().
F alone will not do the work of F() or F.__call__().
Instances of the generic type (type) are optionally callable, depending on whether their designer decides to implement __call__ or not.
That’s one of your freedoms as a designer of types, through use of the class keyword.
Do you want your instances o1, o2, o3… to make sense of ~ (“invert”) or the keyword “in”? Simply implement __invert__ and/or __contains__ respectively in that case. A sense of style and aesthetic judgement kicks in, as to whether you have good taste in how you repurpose your operators. Go with the grain, of what these tend to mean, or be bold if you really need to be brilliant.
These deep methods (also known as “magic methods”) will be called in the presence of the specified syntax, even with only with the object itself as an argument (in the case of __invert__, a unary operator, only self is passed).
I hope this exercise in reading Python jargon was a good workout for you. In another story on Medium, I talk more about what I consider to be the five dimensions of Python: keywords & punctuation; builtins; special names (aka deep methods); standard library; 3rd party. Think for a ladder of five rungs.
If you approach Python in terms of these levels, you’ll have an easier time focusing your workouts in a specific “muscle group”.
You might also say this level of “jargon” (the surrounding namespace or cloud) is yet another dimension.
Or perhaps it’s part of the view you will get once you’ve climbed the five rungs enough times. Keep climbing them. The jargon keeps growing from here, embracing iterators versus iterables, descriptors, and context managers. Keep on learning. Let other languages inform you more. They all become clearer to the extent you allow them to drive your imagination with their similarities and differences.