Python was the first language that I learned. I started dabbling with it long before I ever started at OSU. During my time here in the post-bacc program, I would say that the majority of my course work was completed in python. I was very comfortable in the language and I would go so far as to say that I used to think in python. However, I started working full time as a software developer several months ago and now code entirely in C# at work. Recently I have been learning go in my free time and even started a project at work in golang for a hackathon.
For our capstone project, we have agreed to implemenent all of our micro-services related to our machine learning project in python. This week I put together some of the scaffolding for the API that we will use to connect our neural network predictions to a web interface. I have to say that when I fired up neovim and opened up a new python file, I felt completely lost. At my place of employment, it is not uncommon that before implementing a large feature, or when starting on a new project, we are first required to open a PR for a detailed design. In C# this could entail writing interfaces which describe the methods that will be later implemented. Those methods will have both parameters and returns values with types that are clearly specified. This may also involve creating classes with typed data members which establish a data contract between services. These ideas do not exist in python since there is no concept of an interface and types are optional.
I was stuck when it came to creating a detailed design for a relatively simple API in python. After reading and writing so much in strongly types languages, the lack of types in python makes the code unintelligible. How do you indicate to another programmer reading your code what the function is suppose to take as parameters and what you expect it to return? Do you write comments? I know that optional typing is becoming more common in more recent versions of python3, but even so you have to rely on linters to see if you made a mistake. Typescript solves some of these problems by having to compile your code into javascript so you can see at ”compile” time type related errors.
I will include one example here from the go project that I am currently working on which shows a concept that does not exist in python.
In go (and in C#) you can decode json into defined types. I have a struct like this which holds options related to an azure service bus.
type Options struct {
Topic string `json:"topic"`
Subscription string `json:"subscription"`
Count int `json:"count"`
Start int64 `json:"start"`
Unzip bool `json:"unzip"`
EnqueuedAfter time.Time `json:"enqueuedAfter"`
EnqueuedBefore time.Time `json:"enqueuedBefore"`
Save bool `json:"save"`
Queue string `json:"queue"`
MetaData bool `json:"metaData"`
Query string `json:"query"`
}
I also have this method called ParseFromJson
which takes a pointer to an Options
struct, and sets the values by parsing json into the Options
struct type.
func (options *Options) ParseFromJson(filename string) {
file, err := os.Open(filename)
if err != nil {
exitError(fmt.Sprintf("Could not open config file %s\n", filename))
}
defer file.Close()
bytes, err := io.ReadAll(file)
json.Unmarshal(bytes, options)
options.Validate()
}
After the ParseFromJson
method executes on the Options
struct, the options struct is modified in place. In python, when you read content from a json file using the json package from the standard library, you get back a dictionary. There is no way to specify that shape of that dictionary. By that I mean you cannot say ahead of time that you are expecting to decode your json into an Options
class with a property of EnqueuedAfter which is of type time. You just get back a dictionary and the keys and values may be completely random.
By the end of this term, I will write another blog post where I will reflect again on my feelings towards python after finishing the project. Maybe I will become comfortable again with simple dynamic duck typing.