Today I watched Kenneth Reitz’s python for humans video. His statement that “API is all that matters” resonated with me because I sometimes struggle with that.
Then he mentioned that you should write the README first, before writing any code. This is something that I do sometimes, but not always thoroughly. So I decided to use this as an experiment to refactor my beammech module.
This is a module I wrote for myself to basically help me calculate deformations and stresses in structural members (beams) subject to external loads, something that happens quite regularly in my vocation as a mechanical engineer.
It basically evolved because I discovered I was often writing similar code to solve similar problems. Basically it concerns solving differential equations by integration. This is done by chopping the length of the beam up into a lot of small equal-sized parts, so that the integration can be done by summation over all these parts and then applying boundary conditions.
It works well enough but is somewhat cumbersome to use. This is probably due to the fact that it was a port of a port of a port. So I decided to start anew by writing a new README for this module.
In the beginning most of the variables defining the problem would be globals listed at the beginning of the script. This makes it easy to modify the calculation because all the definitions are in one place. But using globals can be dangerous because you can inadvertently change a parameter in a function without realizing it. And it makes it difficult to write test cases. So I prefer to use ‘pure’ functions wherever possible. That is, functions whose results depend solely on the parameters that they are supplied with. This makes it a lot easier to write test cases. But this problem needs a significant amount of data, and passing these all around separately is a pain. So for the new API I decided to gather all the necessary data in a Python dictionary (or dict). For those not familiar with Python, a dict is basically an array with strings as indices. For example;
beam['length'] = 2287 beam['supports'] = (6, 780)
Now the main API consists of a single function that takes a single argument. That is about as simple as it can be. This function updates the dictionary it was given with the results, so that the whole problem is contained in a single variable.
There are some classes in the API for defining the loads. It was most natural to write the constructors for these with keyword arguments.
So e.g. when defining a point load of -2000 N at the position 1000 mm when using standard arguments we write;
L = bm.PointLoad(-2000, 1000)
In this case the user would probably have to look into the documentation whether the arguments are force followed by position or the other way around.
If we use keyword arguments the code is:
L1 = bm.PointLoad(force=-2000, pos=1000)
When reading this it is immediately obvious what the arguments are. An added advantage is that e.g. different arguments can be used to specify the force. When the force keyword is used, its value is the force in Newtons. But when the kg argument is used it represents the gravitational force (on the Earth’s surface) of an object whose mass in kg is given, so -9.81*kg. This makes the API flexible.
With the API now much simpler I’m starting on adapting the implementation.