Skip to content
Snippets Groups Projects
Python3.rst 4.9 KiB
Newer Older
========
Python 3
========

Python 2 has an `end-of-life date set for 2020 <http://legacy.python.org/dev/peps/pep-0373/>`_
and now that most third-party packages support both 2 and 3 we are starting to think about a
migration strategy for Mantid.

.. contents::
  :local:

Building Against Python 3
#########################

This is currently only possible on a Linux system with a pre-installed version of python 3. You need
to install some additional packages as shown below:

.. code-block:: sh

   apt-get install python3-sip-dev python3-pyqt4  python3-numpy  python3-scipy  python3-sphinx \
     python3-sphinx-bootstrap-theme  python3-dateutil python3-matplotlib ipython3-qtconsole \
     python3-h5py python3-yaml

or on fedora, with slightly different package names

.. code-block:: sh

   dnf install python3-sip-devel python3-PyQt4-devel python3-numpy python3-scipy python3-sphinx \
     python3-sphinx-theme-bootstrap python3-dateutil python3-matplotlib python3-ipython-gui \
     boost-python3-devel python3-h5py python3-yaml

then set ``-DPYTHON_EXECUTABLE=/usr/bin/python3`` when running cmake before building.

.. warning::
   If any of these packages are installed via pip, this could cause conflicts.
   Install as described here only.

Supporting Python 2 and 3
#########################

Python 3 introduces many exciting new features. For a full description see the official Python 3
changes document. For a shorter overview see
`here <https://asmeurer.github.io/python3-presentation/slides.html#1>`__ or
`here <http://python3porting.com/differences.html>`__.

Some features of Python 3 have been backported to Python 2.x within the
`__future__ <https://docs.python.org/2.7/library/__future__.html?highlight=future#module-__future__>`_
module. These make it easier to write code that is compatible with both versions.

This cheat sheet provides helpful examples of how to write code in a 2/3 compatible manner. Where an
option is given to use either the `six <https://pythonhosted.org/six/>`_ or
`future <https://pypi.python.org/pypi/future>`_ (not to be confused with ``__future__``!) modules
then ``six`` is used.

All new code should be written to be compatible with Python 2 & 3 and as a minimum the first import
line of the module should be:

.. code-block:: python

   from __future__ import (absolute_import, division, print_function)

It is quite common to also see ``unicode_literals`` in the above import list, however, when running
under Python 2 ``Boost.Python`` will not automatically convert a Python ``str`` to C++ ``std::string``
automatically if the string is unicode. When running with Python 3 ``Boost.Python`` will do this
conversion automatically for unicode strings so this is in fact not a huge issue going forward.

Migrating From Python 2 to 3
############################

One way to migrate a file from python 2 to 3 is as follows...

.. warning::
  | To perform the following procedure on windows:
  | 1. Git Bash or similar will be required.
  | 2. To run the ``2to3`` script you will need to start the command-prompt.bat in the build directory and run ``%PYTHONHOME%\Scripts\2to3``

Run the following script to run the python 2 to 3 translation tool and rename the file to ``filename.py.bak``

.. code-block:: sh

   2to3 --no-diffs -w filename.py
   mv filename.py{,.bak};


Run **one** of the following commands to append the import statement listed above.

.. code-block:: sh

  awk '/(from|import)/ && !x {print "from __future__ import (absolute_import, division, print_function)\n"; x=1} 1' \
      filename.py.bak > filename.py

**or**

.. code-block:: sh

  sed -i '0,/^import\|from.*/s/^import\|from.*/from __future__ import (absolute_import, division, print_function)\n&/' filename.py

Check each changed block,

- If any change has replaced ``xrange`` with ``range`` then add ``from six.moves import range``
  to the imports list
- If any change has replaced ``ifilterfalse`` with ``filterfalse`` from ``itertools`` then replace a
  statement like ``from itertools import filterfalse`` with ``from six.moves import filterfalse`` in the
  imports list. There are more cases like this documented `here <https://pythonhosted.org/six/#module-six.moves>`_.
- If any change has replaced ``for k, v in knights.iteritems()`` with ``for k, v in knights.items()``
  then add ``from six import iteritems`` to the import list and update the replacement to
  ``for k, v in iteritems(knights)``.

In some cases like ``range``, pylint will complain about `Replacing builtin 'range'` or similar.
Make sure to put the proper ignore statement on that line using ``#pylint: disable=redefined-builtin``.

Check the code still runs as expected in Python 2.

.. note::
   ``2to3`` will try to keep the type of the objects the same. So, for example ``range(5)`` will
   become ``list(range(5))``. This is not necessary if you use it just for iteration. Things like
   ``for i in range(5)`` will work in both versions of Python, you don't need to transform it into a
   list.