Python for Bruce IDL Users

Python and IDL are both high-level, interpreted languages, with essentially feature-parity across the board, and so are actually quite similar in many respects. However, there are some fundamental differences that we will quickly highlight here for new users. There also exist countless cheat sheets online comparing Python and IDL. One version (focusing primarily on the numpy package for Python) is: http://mathesaurus.sourceforge.net/idl-numpy.html

Some important similarities

  • Both use 0-based indexing (in Python this applies to all enumerated iterable types such as lists, tuples, and numpy arrays; but see below for caveats)

  • Both cache bytecode to speed up load times, meaning that code changes on disk need to be recompiled/reloaded in any active session to take effect (in Python this is facilitated by the importlib module).

  • Both allow for multi-line statements to be executed via the interpreter (but see below for caveats)

  • Both use square brackets for indexing (exclusively in Python) and parentheses for function calls

  • Both support positional arguments and keyword arguments to functions (but see below for caveats)

  • Both have a huge ecosystem of 3rd party tools.

And Now the Differences

Most importantly, Python uses whitespace for flow control. In particular, indentation levels denote stepping into a new scope or block (a method, the contents of a loop, etc.). Some people absolutely hate this, but it is what it is. This means that it’s incredibly important to be mindful of whitespace when copying/pasting blocks of code. The native Python shell isn’t very forgiving (and isn’t very user friendly) so we will be using a better shell, called ipython (https://ipython.org/). Much of the rest of this section is specific to ipython. Probably the best thing about ipython is that it will ignore any leading tabs/spaces when they are uniformly applied to a whole block of code. So, let’s say you want to pull a loop that’s itself several indentation levels deep in some code. As long as you copy all of the leading space in the first line, and paste the whole block as one into your ipython session, it will execute properly.

Everything in Python is an Object

This includes basic literals such as strings and floats. The upshot is that you can call the builtin dir() function on any variable and instantly get a listing of every attribute and method of that object. This makes exploration of new codes particularly easy via the interpreter - just instantiate the relevant objects (in gpilib2, that’s gpi) and take a look inside. This is particularly useful when coupled with ipython’s autocomplete (start typing and hit tab).

Python Only Supports Functions

There are no procedures in Python. You can execute a series of commands in a script file, but a named method is always a function and must always be called with trailing () even if it has no arguments.

Importing Modules

Modules (both Python internal and 3rd party) must be explicitly imported before they can be used. This applies both to Python code files and to interactive shell sessions. The import system is described here: https://docs.python.org/3/reference/import.html

Getting Help

In any ipython shell, appending a question mark (?) to any method will automatically print out its full docstring and syntax declaration.

Crashing

By default, any thrown exception in Python (that is not explicitly caught by your code) is automatically raised to the main scope of the interpreter and causes any running methods to instantly return. You can, however, get IDL-like behavior of remaining in the scope where the error occurred by toggling on the Python debugger. In ipython just type pdb to toggle the debugger on and off. The debugger supports pretty much the same commands as the IDL one. See here for a full list: https://docs.python.org/3/library/pdb.html#debugger-commands

Strictness

Python (in some respects) is stricter than IDL. Most importantly, function keywords are case sensitive and must be exact matches. Thus, if a function has a keyword input apodizer, using a shortened version like apod will not work. Python also does not include the slash syntax for setting boolean keywords. Any boolean keywords must be explicitly set (i.e., keyword=True).

Indexing and Iteration

Much like MATLAB, numpy arrays can be indexed with arrays of integers (representing 0-based indices) or equally sized arrays of booleans. In the former case, the returned array is the size of the indexing array (exactly the same as in IDL). In the latter case, the resulting array will be the entries of the indexed array where the indexing array is True. The data type of the indexing array must be strictly integer or boolean (indexing with floats will throw exceptions). Lists can only be indexed by integers or integer ranges (called slices in Python-speak). Slices are represented as start:end:step and are inclusive of the starting index and exclusive of the ending index. If the step is omitted, it is automatically set to 1. Thus:

mylist = [0, 1, 2, 3, 4]
print(mylist[0])
print(mylist[1:3])

will print out 0 and [1, 2], respectively. The argument to the second print statement is returning everything between the 2nd element (index 1, inclusive) and 4th element (index 3, exclusive), meaning just the second and third elements of the list (indices 1 and 2). Ranges can also be open ended, and negative numbers represent counting from the end. mylist[1:] will return [1, 2, 3, 4] (everything after the second entry, inclusive). mylist[-1] will return the last entry (4), and mylist[1:-1] will return [1, 2, 3]. Finally you can play fun games with omitted slice inputs. mylist[::-1] represents ‘step through the full list backwards’ and thus returns the list reversed ([4, 3, 2, 1, 0]).

When iterating (as in the context of a for loop), you can use a numerical iterator (same as in IDL), or absolutely anything else that Python recognizes as iterable. In the former case, the range method provides IDL-like behavior:

for j in range(5): print(j)

will print out 0 through 4.

for j in range(1, 6, 2): print(j)

will print out 1, 3, 5 - that is, start at 1, count to 6 (exclusive) by 2. Alternatively, we can loop through a list (or tuple or array, or dict, etc.):

for j in {'a':1, 'b':2, 'c':3}: print(j)
for j in ['a', 'b', 'c']: print(j)
for j in ('a', 'b', 'c'): print(j)

All of these will print out ‘a’, ‘b’, ‘c’. An insanely powerful aspect of Python is its built-in list comprehension capability, which is explained here: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions