Recently, I gave a training session called “Introduction to Computational Thinking”. The materials are available through The Carpentries. This was the first time that we offered it. Our usual offerings covered an introduction to Linux, an introduction to shell scripting, and then an introduction to the usual starter programming languages, like Python and R. We realized, however, that we were getting attendees who had never been exposed to computing, as opposed to simply using computers. They are two different things. There was a segment of our clients that were not prepared to get the most out of the rest of the training that we offered.
This first time of offering this session was done online. While it went fine, I think that this particular session would work much better as an in person session. Being able to interact in person is so important for the fundamental sessions. Once you have the core ideas down, you are in a much better place to absorb the material of later sessions.
Because Python uses a massive community of modules in order to do work, you can quickly end up with a huge mess of installed modules, with no clear idea of what project uses which modules. They way around this problem is to use virtual environments. In this way, you can corral the installed modules for a specific project into a single environment. There are several options available for managing virtual environments. The default standard one is venv. It should already be available with Python. There are modules available to work with venv that you might want to explore.
Creating a new virtual environment can be done with
python -m venv /path/to/directory
The last parameter is the path to the directory name that you want to use for this virtual environment. You probably want to use it in the current directory, giving the directory name as your project name.
You have the option of having symlinks to Python and libraries or having them copied into your virtual environment. The default is different in different environments. My personal preference is to copy, so that there is no chance of changes when you do system updates. You can do this with the command
python -m venv --copies ./my_venv
Once you are ready to use the virtual environment, you can activate it with one of the following commands, based on your operating system.
Windows.\my_venv\Scripts\activate.bat
PowerShell.\my_venv\Scripts\activate.ps1
Bash. ./my_venv/Scripts/activate
With the environment active, Python uses your folder to hold and search for modules. pip installs new modules here in this location. While you can install modules one at a time, it is a good practice to keep a list of everything that you need for your project. While the filename of this text file doesn’t matter, the usual filename used is “requirements.txt”. You can install all required modules in one step with
pip install -r requirements.txt
Once you are all done, you can deactivate your virtual environment with one of the following commands:
Windowsdeactivate.bat
PowerShelldeactivate
Bashdeactivate Note you don’t need to add the path to the deactivate script, since activation added it to your path.
Plotting on a globe
The main way to do plotting on Python is through matplotlib. This library has a lot of functionality available. There are also lots of extra modules that build on the core of matplotlib.In this example, we will use basemap to manage the globe part of the plot.
The first step is installation. You can simply use pip,
pip install matplotlib
pip install basemap
For our example plot, we have some initial imports to get the code that we will need.
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
The next step is to initialize the map. We set up the map projection that we want to use, and also set the coordinates above which the camera will be positioned.
# set up orthographic map projection with
# perspective of satellite looking down at 45N, 100W.
# use low resolution coastlines.
map = Basemap(projection='ortho',lat_0=45,lon_0=-100,resolution='l')
The enxt step is to create the map components that we want to display for our plot.
# draw coastlines, country boundaries, fill continents.
map.drawcoastlines(linewidth=0.25)
map.drawcountries(linewidth=0.25)
map.fillcontinents(color='coral',lake_color='aqua')
Along with major components, there also map details that can be configured for your plot.
# draw the edge of the map projection region (the projection limb)
map.drawmapboundary(fill_color='aqua')
# draw lat/lon grid lines every 30 degrees.
map.drawmeridians(np.arange(0,360,30))
map.drawparallels(np.arange(-90,90,30))
Now that we have our map ready, we can get data ready to plot (in this case, it is made up data).
# make up some data on a regular lat/lon grid.
nlats = 73; nlons = 145; delta = 2.*np.pi/(nlons-1)
lats = (0.5*np.pi-delta*np.indices((nlats,nlons))[0,:,:])
lons = (delta*np.indices((nlats,nlons))[1,:,:])
wave = 0.75*(np.sin(2.*lats)**8*np.cos(4.*lons))
mean = 0.5*np.cos(2.*lats)*((np.sin(2.*lats))**2 + 2.)
The last step is to plot your data and to show the resultant plot.
# compute native map projection coordinates of lat/lon grid.
x, y = map(lons*180./np.pi, lats*180./np.pi)
# contour data over the map.
cs = map.contour(x,y,wave+mean,15,linewidths=1.5)
plt.title('contour lines over filled continent background')
plt.show()
You should get something like the following:
Hopefully this short piece will give you the encouragement to go see what you can do with matplotlib. You can find a huge gallery of examples at https://matplotlib.org/stable/gallery/index.html.
Welcome to the first of what will be a series of articles talking about what is great about Python, and what is not so great. Even though I am a fan of the language, I am not so enamored as to be blind to its weaknesses. So over the coming months we will not only learn about how to do certain tasks in Python, but also what pitfalls you may run into during your travels. Once we have some Python basics under our collective belts, we will start looking at ways to apply this new found knowledge and code some programs up for the Raspberry Pi. The Raspberry Pi is one of the low cost educational boards out there that you can experiment on. The first subject we will look at is using objects in Python. According to the official documentation, objects are Python’s abstraction for data. All data in a Python program is represented by objects or by relations between objects. Actually, almost everything in Python is an object. So learning a bit more about how they behave is important to developing code of any real value. More specifically, we will look at classes and how to work with objects of your own design.
Classes in Python support all of the most common concepts in Object Oriented Programming. The mechanisms of classes in Python were inspired by C++ and Modula-3. A class can inherit from multiple base classes and can override any methods in the base classes. All of the methods in the base classes are also available to be called from the inheriting class. As for data, objects can contain arbitrary amounts and types. And because Python is a dynamic language, all of this is modifiable at run-time. As an example, let’s say that you want to develop a program that will do some kind of geometric processing. One of the core objects that you will want to use is likely something that represents a point. Looking at two dimensional geometry first, you will need to store two values, an x and a y. In code, this would look like
class Point:
pass
This gives us a blank class called Point. The first line is how you define a new class. It is very similar to how you define a new function, except that you exchange the keyword ‘def’ with the keyword ‘class’. A class always needs to have at least one statement. If you don’t want your new class to do anything yet, you can use the keyword ‘pass’. This essentially tells Python, “don’t do anything here”. You can now use this class to create new objects of type Point. You can do this with
my_point = Point()
This is not really all that useful yet. There is nowhere to store our point values. Or is there? One of the really cool things about Python objects is that they are fully dynamic. You can add x and y values with the statements
my_point.x = 10.0
my_point.y = 20.0
This particular instance of the Point class now has an x and y value. Unfortunately, we can’t really use them effectively. It would be better to have them as part of the definition of the class so that we can write methods that know how to use this data. It many cases, it also makes sense to initialize these variables. One way to do this is to simply write statements within the class definition directly.
class Point:
x = 0.0
y = 0.0
The other way to initialize values is to use the init method in classes. This method gets automatically called when a new object gets instantiated. This looks like
class Point:
def __init__(self):
x = 0.0
y = 0.0
You can then set your values for x and y after creating a new instance with
While this works well, one thing to remember is that programmers are inherently lazy and do not want to type more than absolutely necessary. Following this idea, it would be great if you could assign your x and y values at the same time you are creating a new object. In Python, you can. The init function can be written to take parameters.
Isn’t that much easier? Now that you have a point defined, you can add methods to work on this data. One of the first methods for a point is its absolute value. To add this to your class, you can just add the function definition to the class definition with
Now that you have a basic two dimensional point defined, you may want to create objects that can handle three dimensional points. Code reuse is something that should be aimed for, and easy to do in Python. You can create a new class that builds on the existing code for two dimensional points. This would look like
Written this way, the initialization method has been overridden to take three parameters rather than two. But, nothing has been done to the method abs_val, inherited from the Point subclass. So when you call this from an instantiated ThreeDPoint object, you will get the length of the x and y parts of this point. If you want a proper three dimensional absolute value, you will need to override the abs_val function.
Now that you’ve seen how to create your own objects, next month we’ll look at some details around how Python manages these objects in memory. On most desktop systems, memory is not really a concern. But the Raspberry Pi is relatively constrained, so knowing how memory is handled will become more important for your code.