I’ve been thinking to write about something technical for a while. There are as I have mentioned here, two sides to a technical manager. The people side and the technical side. What better to show the technical side, than giving some insight into what tools I am using on a daily basis.
I have switched to python about a year ago. I still feel like a newb in the language and ecosystem. Albeit I am getting much warmer to the concepts and slowly becoming an admirer of how various problems were resolved in python.
It is very important to remember it’s not just about code nowadays. I am planning to cover the following:
- Python version conflicts
- Dependency management
- Project configuration
I have created a repository to follow along. I’ve been running this code on a Linux EC2 machine in AWS. It should work though on most UNIX-based systems. Not quite sure if pyenv will execute on Windows though.
Before we will go into details, lets just discuss what python is. Simply put, python is a programming language. It was named after the BBC sketch series Monty Python’s Flying Circus. Humour stands at the base of the language culture. Together with approaching programming to be as close as possible to writing in plain English.
Python is powerful… and fast; plays well with others; runs everywhere; is friendly & easy to learn; is Open.https://www.python.org/about/
There are multiple versions and releases of python, albeit for simplicity the main ones known about the world are Python2 and Python3. Python3 is the modern version, that is slowly going into super-ultra-performance space. Python2 is what you probably have, especially if you don’t have a proper maintenance strategy for EoL software. Python2 is not bad, just legacy. All of the code I produce is currently written in Python3, in version 3.8.
The first challenge when working with python, is to deal with potential complexities of managing python versions itself and all the library versions. What comes handy here is pyenv. With a simple installation, you get a virtual environment per project. This means the python code you run within your project scope is local and won’t impact any other setup you might have. Won’t override settings for legacy projects globally, etc. Once you are done with that, install poetry. Poetry again looks like maven (if you are familiar with it from Java). It’s a tool that primarily manages dependencies and packaging, but can do a lot more.
# install python version pyenv install 3.8.0 # set/activate local python version pyenv local 3.8.0 # install poetry pip install poetry # install poetry settings using pyproject.toml file poetry install
The next challenge when starting a new python project is what tools to choose, so that the project can be developed, ran and maintained reasonably well over its timespan. Thanks to PEP 518, you can now use pyproject.toml file to define all of the dependencies (and much more). I find it very similar to maven project configs in java. While not all tools support it yet, it is definitely a good idea to start leveraging a project file, over the previous solutions (like keeping dependencies in a simple .txt file).
Once you got your project file setup (example here), comes the time to avoid creating bash files. I’ve got convinced to using makefile. This with conjunction with toml and poetry can become very powerful, simplifying my run commands greatly. The one thing to remember is setting poetry scripts (as application entry points) in toml.
[tool.poetry.scripts] run_local = "src.main:run"
Then comes Flake8 for linting and black for code formatting. These both resolve the massive time-wasters on discussing style in pull requests and common import errors (imported, not used), as well as many more readability issues.
All that remains then is to write the code and execute it using make all.
Just an empty project wasn’t enough. For the so-called lesson-one, I have selected a few funny things from python. Just to follow the culture of humour. There is a place for the simple ‘Hello World!’ too.
Mutable default attributes
Using default arguments is a fairly common exercise in Python wherein you can set predefined values in the function and opt to change it at call time. This is useful when setting literals, numbers, or booleans as it helps you prevent a long list of parameters with redundant values.
But setting mutable values as default arguments can be dangerous and lead to nasty bugs. There is an example in the repo, though here is the code:
def _add_elements(element=): element.append("New") return element
What do you think is going to happen if I execute this method twice in the same execution context?
def mutable_default_attributes(): _add_elements() return _add_elements() # console result :) ['New', 'New']
Is Enumerator better optimized than range?
You will need an index sometimes. Not sure for what, as python deals well with index-less loops and provides massive flexibility. The gist here is to remember to reconsider using range vs enumerate. Using enumerator gives you the advantage of having a tuple that takes care of tracking the index value and element together. Besides being cleaner, it’s more optimized and also provides us with an optional second argument to set the starting count. The test results?
Case 1 for i in range(len(alphabet)): output += alphabet[i] Case 2 for i, letter in enumerate(alphabet): output += letter Case 3 for letter in alphabet: output += letter #console output of the code Enumerate Time: 4.76837158203125e-06s Range Time: 6.4373016357421875e-06s None Time: 2.86102294921875e-06s All on a simple alphabet array: abcdefghijklmnopqrstuvwxyz;
The code you will find in my repo might seem a bit complex for this case. I tried using a python library called timeit, though itself it was very slow, so it undermined my trust in the results.
This is where my article ends. To wrap up – remember about more than just running the code for code setup!