Porting Pattern to Python 3: DONE!
The final days of this year's Google Summer of Code have arrived and I am wrapping up my project. The last three months have been full of intense coding on the Pattern library and I'm happy to say that all milestones described in my project proposal are knocked off within the official coding period.
An exhaustive list of all my commits to the
clips/pattern repository can be found here. A very nice commit–based comparison is available here (full diff and full patch). The official commit graph can be seen here as soon as the changes have been merged into the
master branch. The Travis CI build for different branches can be looked at here together with the automated unit test coverage reports on coveralls.io. The last official GSoC commit is
ec95f97 on the
Overview & Synopsis
This is what the official project description reads:
The purpose of this GSoC project will be to modernize Pattern, a Python library for machine learning, natural language processing (NLP) and web mining. A substantial part of this undertaking is to port a majority of the code base to Python 3. This involves porting the individual modules and sub–modules piece by piece, where the whole process will be guided by unit tests. In the beginning, I will remove all tests from the pipeline that do not pass for Python 3 and take this pared–down code base as a starting point, porting parts of the code and putting the respective unit tests back in as I go along. Missing unit tests must be added before moving on. Since porting Python 2 code to Python 3 code is a standard problem for the Python community, there are many different tools available that can help in this regard. In addition to that, I'd like to extend this project to a bit of a Hausmeister project (housekeeping for Pattern), and optimize/modernize the code base in terms of execution speed, memory usage and documentation.
At the beginning of the project in May (launch time machine), Pattern was in a position where it wasn't actively maintained due to time constraints. Many unit tests were failing, some features were deprecated (e.g. in
pattern.web) but most importantly, it lacked Python 3 support which effectively made it unavailable for a large user base. Now, three months later, we are at a point where all of Pattern's modules (i.e.
pattern.metrics and the language modules
pattern.it) except for
pattern.server are fully ported to Python 3. This task included working on some other major milestones such as removing the bundled
PyWordNet in favor of NLTK's WordNet interface, transitioning to BeautifulSoup 4, removing
sgmllib etc.. However, the biggest challenge for a joint Python 2 / Python 3 code base is always to carefully deal with unicode handling in all parts of the library, which can sometimes be tedious. Whenever possible we attempted to write forward–compatible code, i.e. code that handles Python 3 as the default and Python 2 as the exception, which required some extra effort, but will hopefully make the code more readable in the long term and makes it easier to drop Python 2 support entirely at some point. The next release will deprecate Python 2.6- support in favor of Python 2.7 – which will be the last Python 2 version – and Python 3.6+.
Furthermore, several general maintenance tasks have been performed such as code cleanup, documentation, refactoring of duplicate code to an additional
pattern.helpers as well as general PEP 8 compliance.
Roadmap & Milestones
By far the largest chunk of work was dealing with the subtle differences between Python 2 and Python 3 to ensure that the code works identically regardless of the interpreter. Moving to a joint code base is a major undertaking, since there are many differences when it comes to strings (unicode vs. byte strings), generators and iterators, package import precedence, division and even fundamental data types such as
set. It is more or less hopeless to obtain a joint code base for Python 2.5- and Python 3, but fortunately it is possible to make it work for Python 2.6+ with some precautions, even without using
However, the following points on the roadmap were important milestones that don't necessarily have anything to do with the actual porting per se:
- The following bundled packages and (vendorized) libraries have been removed in favor of external dependencies:
PyWordNet. This also involved adapting Pattern's code to changes introduced in these external libraries.
- The removal of
PyWordNetcame with the need for a new interface for Pattern to wrap NLTK's WordNet interface. This was quite time–consuming, since there had of course been many incompatible changes over the years that needed to be dealt with.
- We set up Travis CI, a continuous integration platform to keep track of passing or failing unit tests on different branches / Python versions. This will run automatically for every PR and report changes in unit test coverage.
libLINEARhave been updated to the latest versions. The pre–compiled libraries have been removed for now because they were incompatible with the newer
- The unit tests were refactored to work with pytest. There is more work that can be done and it might be a good idea to leave the
unittestentirely behind at some point in the future.
- In the last days of the official coding period we went through a big PEP 8 (Style Guide for Python Code) cleanup which aims for a more consistent code base. However, we decided not to aggressively enforce all PEP 8 guidelines.
The new release will introduce the following external dependencies:
pdfminer.six for Python 3),
cherrypy. For a more in–depth discussion of each of these items, check out my detailed progress reports (phase #1, phase #2).
Let's play the numbers game: Over the course of the last three months, I have pushed > 403 commits to four different branches on the
clips/pattern repository. This affected 238 files with a total of 11129 insertions and 53735 deletions (
git diff --stat). My first commit was
1e17011 on the
python3 branch. My last commit was
ec95f97 on the
Here is what the contribution graph and the heat map on GitHub looks like:
The following panel shows deletions (left) and insertions (right) as a function of time:
This graph seems to reflect the roadmap pretty accurately. The majority of the deletions in the first period correspond to the removal of vendorized libraries. As the project progressed, more and more insertions took place and new or modified lines found their way into the code base.
Future Work & Next Steps
We will do some more testing and release the next major version of Pattern in autumn. The following items are predominantly independent of my particular project, but should be tackled before the next major release:
- The only Python 3 related issue currently remaining is a bug in
pattern.vectorthat affects the information gain tree classifier
IGTree. It's hard to debug but it looks to me like it has something to do with order differences when iterating over
dictobjects. In any case, someone needs to take a closer look. This issue will be tracked on GitHub in the near future.
pattern.servermodule has the major parts ported, but since there are no unit tests available for this module, it's hard to test it in a systematic way apart from running the examples. I believe it's currently not fully functional on Python 3.
pattern.webmodule contains code to access some popular web APIs. Some of the APIs are deprecated or changed in some other way that requires refactoring. Some APIs have moved to paid subscription models without free quotas.
- The current unit test coverage seems to be around 70%. This is okay for now, but there certainly is room for improvement.
The following resources proved to be invaluable during the porting, especially when it comes to the more subtle differences between Python 2 to Python 3:
- Easy, clean, reliable Python 2/3 compatibility (
- Supporting Python 3: An in-depth guide
- Porting Python 2 Code to Python 3
modernize: Automatic conversion to Py2/3
autopep8: A tool that automatically formats Python code to conform to the PEP 8 style guide
So this is it – the end of the official GSoC coding period, time to sign off for a couple days. Thank you to the Google Open Source Team for bringing this project to life, and of course special thanks to my mentors Tom and Guy for their valuable feedback and guidance throughout the entire project.
Altogether, this was a great experience and I will remain an active contributor for the foreseeable future. Happy coding!