mirror of https://github.com/python/cpython
Merge for the porting HOWTO update
This commit is contained in:
commit
ec38bcb66e
|
@ -17,7 +17,8 @@ Porting Python 2 Code to Python 3
|
||||||
please see :ref:`cporting-howto`.
|
please see :ref:`cporting-howto`.
|
||||||
|
|
||||||
If you would like to read one core Python developer's take on why Python 3
|
If you would like to read one core Python developer's take on why Python 3
|
||||||
came into existence, you can read Nick Coghlan's `Python 3 Q & A`_.
|
came into existence, you can read Nick Coghlan's `Python 3 Q & A`_ or
|
||||||
|
Brett Cannon's `Why Python 3 exists`_.
|
||||||
|
|
||||||
For help with porting, you can email the python-porting_ mailing list with
|
For help with porting, you can email the python-porting_ mailing list with
|
||||||
questions.
|
questions.
|
||||||
|
@ -32,8 +33,7 @@ are:
|
||||||
#. Make sure you have good test coverage (coverage.py_ can help;
|
#. Make sure you have good test coverage (coverage.py_ can help;
|
||||||
``pip install coverage``)
|
``pip install coverage``)
|
||||||
#. Learn the differences between Python 2 & 3
|
#. Learn the differences between Python 2 & 3
|
||||||
#. Use Modernize_ or Futurize_ to update your code (``pip install modernize`` or
|
#. Use Futurize_ (or Modernize_) to update your code (e.g. ``pip install future``)
|
||||||
``pip install future``, respectively)
|
|
||||||
#. Use Pylint_ to help make sure you don't regress on your Python 3 support
|
#. Use Pylint_ to help make sure you don't regress on your Python 3 support
|
||||||
(``pip install pylint``)
|
(``pip install pylint``)
|
||||||
#. Use caniusepython3_ to find out which of your dependencies are blocking your
|
#. Use caniusepython3_ to find out which of your dependencies are blocking your
|
||||||
|
@ -41,10 +41,9 @@ are:
|
||||||
#. Once your dependencies are no longer blocking you, use continuous integration
|
#. Once your dependencies are no longer blocking you, use continuous integration
|
||||||
to make sure you stay compatible with Python 2 & 3 (tox_ can help test
|
to make sure you stay compatible with Python 2 & 3 (tox_ can help test
|
||||||
against multiple versions of Python; ``pip install tox``)
|
against multiple versions of Python; ``pip install tox``)
|
||||||
|
#. Consider using optional static type checking to make sure your type usage
|
||||||
If you are dropping support for Python 2 entirely, then after you learn the
|
works in both Python 2 & 3 (e.g. use mypy_ to check your typing under both
|
||||||
differences between Python 2 & 3 you can run 2to3_ over your code and skip the
|
Python 2 & Python 3).
|
||||||
rest of the steps outlined above.
|
|
||||||
|
|
||||||
|
|
||||||
Details
|
Details
|
||||||
|
@ -54,7 +53,7 @@ A key point about supporting Python 2 & 3 simultaneously is that you can start
|
||||||
**today**! Even if your dependencies are not supporting Python 3 yet that does
|
**today**! Even if your dependencies are not supporting Python 3 yet that does
|
||||||
not mean you can't modernize your code **now** to support Python 3. Most changes
|
not mean you can't modernize your code **now** to support Python 3. Most changes
|
||||||
required to support Python 3 lead to cleaner code using newer practices even in
|
required to support Python 3 lead to cleaner code using newer practices even in
|
||||||
Python 2.
|
Python 2 code.
|
||||||
|
|
||||||
Another key point is that modernizing your Python 2 code to also support
|
Another key point is that modernizing your Python 2 code to also support
|
||||||
Python 3 is largely automated for you. While you might have to make some API
|
Python 3 is largely automated for you. While you might have to make some API
|
||||||
|
@ -82,12 +81,13 @@ have to import a function instead of using a built-in one, but otherwise the
|
||||||
overall transformation should not feel foreign to you.
|
overall transformation should not feel foreign to you.
|
||||||
|
|
||||||
But you should aim for only supporting Python 2.7. Python 2.6 is no longer
|
But you should aim for only supporting Python 2.7. Python 2.6 is no longer
|
||||||
supported and thus is not receiving bugfixes. This means **you** will have to
|
freely upported and thus is not receiving bugfixes. This means **you** will have
|
||||||
work around any issues you come across with Python 2.6. There are also some
|
to work around any issues you come across with Python 2.6. There are also some
|
||||||
tools mentioned in this HOWTO which do not support Python 2.6 (e.g., Pylint_),
|
tools mentioned in this HOWTO which do not support Python 2.6 (e.g., Pylint_),
|
||||||
and this will become more commonplace as time goes on. It will simply be easier
|
and this will become more commonplace as time goes on. It will simply be easier
|
||||||
for you if you only support the versions of Python that you have to support.
|
for you if you only support the versions of Python that you have to support.
|
||||||
|
|
||||||
|
|
||||||
Make sure you specify the proper version support in your ``setup.py`` file
|
Make sure you specify the proper version support in your ``setup.py`` file
|
||||||
--------------------------------------------------------------------------
|
--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -98,6 +98,7 @@ Python 3 yet you should at least have
|
||||||
also specify each major/minor version of Python that you do support, e.g.
|
also specify each major/minor version of Python that you do support, e.g.
|
||||||
``Programming Language :: Python :: 2.7``.
|
``Programming Language :: Python :: 2.7``.
|
||||||
|
|
||||||
|
|
||||||
Have good test coverage
|
Have good test coverage
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
@ -106,10 +107,11 @@ to, you will want to make sure your test suite has good coverage. A good rule of
|
||||||
thumb is that if you want to be confident enough in your test suite that any
|
thumb is that if you want to be confident enough in your test suite that any
|
||||||
failures that appear after having tools rewrite your code are actual bugs in the
|
failures that appear after having tools rewrite your code are actual bugs in the
|
||||||
tools and not in your code. If you want a number to aim for, try to get over 80%
|
tools and not in your code. If you want a number to aim for, try to get over 80%
|
||||||
coverage (and don't feel bad if you can't easily get past 90%). If you
|
coverage (and don't feel bad if you can't easily get passed 90%). If you
|
||||||
don't already have a tool to measure test coverage then coverage.py_ is
|
don't already have a tool to measure test coverage then coverage.py_ is
|
||||||
recommended.
|
recommended.
|
||||||
|
|
||||||
|
|
||||||
Learn the differences between Python 2 & 3
|
Learn the differences between Python 2 & 3
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
|
@ -127,13 +129,15 @@ Update your code
|
||||||
|
|
||||||
Once you feel like you know what is different in Python 3 compared to Python 2,
|
Once you feel like you know what is different in Python 3 compared to Python 2,
|
||||||
it's time to update your code! You have a choice between two tools in porting
|
it's time to update your code! You have a choice between two tools in porting
|
||||||
your code automatically: Modernize_ and Futurize_. Which tool you choose will
|
your code automatically: Futurize_ and Modernize_. Which tool you choose will
|
||||||
depend on how much like Python 3 you want your code to be. Futurize_ does its
|
depend on how much like Python 3 you want your code to be. Futurize_ does its
|
||||||
best to make Python 3 idioms and practices exist in Python 2, e.g. backporting
|
best to make Python 3 idioms and practices exist in Python 2, e.g. backporting
|
||||||
the ``bytes`` type from Python 3 so that you have semantic parity between the
|
the ``bytes`` type from Python 3 so that you have semantic parity between the
|
||||||
major versions of Python. Modernize_,
|
major versions of Python. Modernize_,
|
||||||
on the other hand, is more conservative and targets a Python 2/3 subset of
|
on the other hand, is more conservative and targets a Python 2/3 subset of
|
||||||
Python, relying on six_ to help provide compatibility.
|
Python, directly relying on six_ to help provide compatibility. As Python 3 is
|
||||||
|
the future, it might be best to consider Futurize to begin adjusting to any new
|
||||||
|
practices that Python 3 introduces which you are not accustomed to yet.
|
||||||
|
|
||||||
Regardless of which tool you choose, they will update your code to run under
|
Regardless of which tool you choose, they will update your code to run under
|
||||||
Python 3 while staying compatible with the version of Python 2 you started with.
|
Python 3 while staying compatible with the version of Python 2 you started with.
|
||||||
|
@ -153,6 +157,7 @@ the built-in ``open()`` function is off by default in Modernize). Luckily,
|
||||||
though, there are only a couple of things to watch out for which can be
|
though, there are only a couple of things to watch out for which can be
|
||||||
considered large issues that may be hard to debug if not watched for.
|
considered large issues that may be hard to debug if not watched for.
|
||||||
|
|
||||||
|
|
||||||
Division
|
Division
|
||||||
++++++++
|
++++++++
|
||||||
|
|
||||||
|
@ -173,6 +178,7 @@ an object defines a ``__truediv__`` method but not ``__floordiv__`` then your
|
||||||
code would begin to fail (e.g. a user-defined class that uses ``/`` to
|
code would begin to fail (e.g. a user-defined class that uses ``/`` to
|
||||||
signify some operation but not ``//`` for the same thing or at all).
|
signify some operation but not ``//`` for the same thing or at all).
|
||||||
|
|
||||||
|
|
||||||
Text versus binary data
|
Text versus binary data
|
||||||
+++++++++++++++++++++++
|
+++++++++++++++++++++++
|
||||||
|
|
||||||
|
@ -189,7 +195,7 @@ To make the distinction between text and binary data clearer and more
|
||||||
pronounced, Python 3 did what most languages created in the age of the internet
|
pronounced, Python 3 did what most languages created in the age of the internet
|
||||||
have done and made text and binary data distinct types that cannot blindly be
|
have done and made text and binary data distinct types that cannot blindly be
|
||||||
mixed together (Python predates widespread access to the internet). For any code
|
mixed together (Python predates widespread access to the internet). For any code
|
||||||
that only deals with text or only binary data, this separation doesn't pose an
|
that deals only with text or only binary data, this separation doesn't pose an
|
||||||
issue. But for code that has to deal with both, it does mean you might have to
|
issue. But for code that has to deal with both, it does mean you might have to
|
||||||
now care about when you are using text compared to binary data, which is why
|
now care about when you are using text compared to binary data, which is why
|
||||||
this cannot be entirely automated.
|
this cannot be entirely automated.
|
||||||
|
@ -198,15 +204,15 @@ To start, you will need to decide which APIs take text and which take binary
|
||||||
(it is **highly** recommended you don't design APIs that can take both due to
|
(it is **highly** recommended you don't design APIs that can take both due to
|
||||||
the difficulty of keeping the code working; as stated earlier it is difficult to
|
the difficulty of keeping the code working; as stated earlier it is difficult to
|
||||||
do well). In Python 2 this means making sure the APIs that take text can work
|
do well). In Python 2 this means making sure the APIs that take text can work
|
||||||
with ``unicode`` in Python 2 and those that work with binary data work with the
|
with ``unicode`` and those that work with binary data work with the
|
||||||
``bytes`` type from Python 3 and thus a subset of ``str`` in Python 2 (which the
|
``bytes`` type from Python 3 (which is a subset of ``str`` in Python 2 and acts
|
||||||
``bytes`` type in Python 2 is an alias for). Usually the biggest issue is
|
as an alias for ``bytes`` type in Python 2). Usually the biggest issue is
|
||||||
realizing which methods exist for which types in Python 2 & 3 simultaneously
|
realizing which methods exist on which types in Python 2 & 3 simultaneously
|
||||||
(for text that's ``unicode`` in Python 2 and ``str`` in Python 3, for binary
|
(for text that's ``unicode`` in Python 2 and ``str`` in Python 3, for binary
|
||||||
that's ``str``/``bytes`` in Python 2 and ``bytes`` in Python 3). The following
|
that's ``str``/``bytes`` in Python 2 and ``bytes`` in Python 3). The following
|
||||||
table lists the **unique** methods of each data type across Python 2 & 3
|
table lists the **unique** methods of each data type across Python 2 & 3
|
||||||
(e.g., the ``decode()`` method is usable on the equivalent binary data type in
|
(e.g., the ``decode()`` method is usable on the equivalent binary data type in
|
||||||
either Python 2 or 3, but it can't be used by the text data type consistently
|
either Python 2 or 3, but it can't be used by the textual data type consistently
|
||||||
between Python 2 and 3 because ``str`` in Python 3 doesn't have the method). Do
|
between Python 2 and 3 because ``str`` in Python 3 doesn't have the method). Do
|
||||||
note that as of Python 3.5 the ``__mod__`` method was added to the bytes type.
|
note that as of Python 3.5 the ``__mod__`` method was added to the bytes type.
|
||||||
|
|
||||||
|
@ -232,10 +238,11 @@ This allows your code to work with only text internally and thus eliminates
|
||||||
having to keep track of what type of data you are working with.
|
having to keep track of what type of data you are working with.
|
||||||
|
|
||||||
The next issue is making sure you know whether the string literals in your code
|
The next issue is making sure you know whether the string literals in your code
|
||||||
represent text or binary data. At minimum you should add a ``b`` prefix to any
|
represent text or binary data. You should add a ``b`` prefix to any
|
||||||
literal that presents binary data. For text you should either use the
|
literal that presents binary data. For text you should add a ``u`` prefix to
|
||||||
``from __future__ import unicode_literals`` statement or add a ``u`` prefix to
|
the text literal. (there is a :mod:`__future__` import to force all unspecified
|
||||||
the text literal.
|
literals to be Unicode, but usage has shown it isn't as effective as adding a
|
||||||
|
``b`` or ``u`` prefix to all literals explicitly)
|
||||||
|
|
||||||
As part of this dichotomy you also need to be careful about opening files.
|
As part of this dichotomy you also need to be careful about opening files.
|
||||||
Unless you have been working on Windows, there is a chance you have not always
|
Unless you have been working on Windows, there is a chance you have not always
|
||||||
|
@ -243,11 +250,13 @@ bothered to add the ``b`` mode when opening a binary file (e.g., ``rb`` for
|
||||||
binary reading). Under Python 3, binary files and text files are clearly
|
binary reading). Under Python 3, binary files and text files are clearly
|
||||||
distinct and mutually incompatible; see the :mod:`io` module for details.
|
distinct and mutually incompatible; see the :mod:`io` module for details.
|
||||||
Therefore, you **must** make a decision of whether a file will be used for
|
Therefore, you **must** make a decision of whether a file will be used for
|
||||||
binary access (allowing binary data to be read and/or written) or text access
|
binary access (allowing binary data to be read and/or written) or textual access
|
||||||
(allowing text data to be read and/or written). You should also use :func:`io.open`
|
(allowing text data to be read and/or written). You should also use :func:`io.open`
|
||||||
for opening files instead of the built-in :func:`open` function as the :mod:`io`
|
for opening files instead of the built-in :func:`open` function as the :mod:`io`
|
||||||
module is consistent from Python 2 to 3 while the built-in :func:`open` function
|
module is consistent from Python 2 to 3 while the built-in :func:`open` function
|
||||||
is not (in Python 3 it's actually :func:`io.open`).
|
is not (in Python 3 it's actually :func:`io.open`). Do not bother with the
|
||||||
|
outdated practice of using :func:`codecs.open` as that's only necessary for
|
||||||
|
keeping compatibility with Python 2.5.
|
||||||
|
|
||||||
The constructors of both ``str`` and ``bytes`` have different semantics for the
|
The constructors of both ``str`` and ``bytes`` have different semantics for the
|
||||||
same arguments between Python 2 & 3. Passing an integer to ``bytes`` in Python 2
|
same arguments between Python 2 & 3. Passing an integer to ``bytes`` in Python 2
|
||||||
|
@ -274,21 +283,22 @@ To summarize:
|
||||||
#. Make sure that your code that works with text also works with ``unicode`` and
|
#. Make sure that your code that works with text also works with ``unicode`` and
|
||||||
code for binary data works with ``bytes`` in Python 2 (see the table above
|
code for binary data works with ``bytes`` in Python 2 (see the table above
|
||||||
for what methods you cannot use for each type)
|
for what methods you cannot use for each type)
|
||||||
#. Mark all binary literals with a ``b`` prefix, use a ``u`` prefix or
|
#. Mark all binary literals with a ``b`` prefix, textual literals with a ``u``
|
||||||
:mod:`__future__` import statement for text literals
|
prefix
|
||||||
#. Decode binary data to text as soon as possible, encode text as binary data as
|
#. Decode binary data to text as soon as possible, encode text as binary data as
|
||||||
late as possible
|
late as possible
|
||||||
#. Open files using :func:`io.open` and make sure to specify the ``b`` mode when
|
#. Open files using :func:`io.open` and make sure to specify the ``b`` mode when
|
||||||
appropriate
|
appropriate
|
||||||
#. Be careful when indexing binary data
|
#. Be careful when indexing into binary data
|
||||||
|
|
||||||
|
|
||||||
Use feature detection instead of version detection
|
Use feature detection instead of version detection
|
||||||
++++++++++++++++++++++++++++++++++++++++++++++++++
|
++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
Inevitably you will have code that has to choose what to do based on what
|
Inevitably you will have code that has to choose what to do based on what
|
||||||
version of Python is running. The best way to do this is with feature detection
|
version of Python is running. The best way to do this is with feature detection
|
||||||
of whether the version of Python you're running under supports what you need.
|
of whether the version of Python you're running under supports what you need.
|
||||||
If for some reason that doesn't work then you should make the version check is
|
If for some reason that doesn't work then you should make the version check be
|
||||||
against Python 2 and not Python 3. To help explain this, let's look at an
|
against Python 2 and not Python 3. To help explain this, let's look at an
|
||||||
example.
|
example.
|
||||||
|
|
||||||
|
@ -340,14 +350,12 @@ at least the following block of code at the top of it::
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
You can also run Python 2 with the ``-3`` flag to be warned about various
|
You can also run Python 2 with the ``-3`` flag to be warned about various
|
||||||
compatibility issues your code triggers during execution. If you turn warnings
|
compatibility issues your code triggers during execution. If you turn warnings
|
||||||
into errors with ``-Werror`` then you can make sure that you don't accidentally
|
into errors with ``-Werror`` then you can make sure that you don't accidentally
|
||||||
miss a warning.
|
miss a warning.
|
||||||
|
|
||||||
|
|
||||||
You can also use the Pylint_ project and its ``--py3k`` flag to lint your code
|
You can also use the Pylint_ project and its ``--py3k`` flag to lint your code
|
||||||
to receive warnings when your code begins to deviate from Python 3
|
to receive warnings when your code begins to deviate from Python 3
|
||||||
compatibility. This also prevents you from having to run Modernize_ or Futurize_
|
compatibility. This also prevents you from having to run Modernize_ or Futurize_
|
||||||
|
@ -364,22 +372,23 @@ care about whether your dependencies have also been ported. The caniusepython3_
|
||||||
project was created to help you determine which projects
|
project was created to help you determine which projects
|
||||||
-- directly or indirectly -- are blocking you from supporting Python 3. There
|
-- directly or indirectly -- are blocking you from supporting Python 3. There
|
||||||
is both a command-line tool as well as a web interface at
|
is both a command-line tool as well as a web interface at
|
||||||
https://caniusepython3.com .
|
https://caniusepython3.com.
|
||||||
|
|
||||||
The project also provides code which you can integrate into your test suite so
|
The project also provides code which you can integrate into your test suite so
|
||||||
that you will have a failing test when you no longer have dependencies blocking
|
that you will have a failing test when you no longer have dependencies blocking
|
||||||
you from using Python 3. This allows you to avoid having to manually check your
|
you from using Python 3. This allows you to avoid having to manually check your
|
||||||
dependencies and to be notified quickly when you can start running on Python 3.
|
dependencies and to be notified quickly when you can start running on Python 3.
|
||||||
|
|
||||||
|
|
||||||
Update your ``setup.py`` file to denote Python 3 compatibility
|
Update your ``setup.py`` file to denote Python 3 compatibility
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
|
|
||||||
Once your code works under Python 3, you should update the classifiers in
|
Once your code works under Python 3, you should update the classifiers in
|
||||||
your ``setup.py`` to contain ``Programming Language :: Python :: 3`` and to not
|
your ``setup.py`` to contain ``Programming Language :: Python :: 3`` and to not
|
||||||
specify sole Python 2 support. This will tell
|
specify sole Python 2 support. This will tell anyone using your code that you
|
||||||
anyone using your code that you support Python 2 **and** 3. Ideally you will
|
support Python 2 **and** 3. Ideally you will also want to add classifiers for
|
||||||
also want to add classifiers for each major/minor version of Python you now
|
each major/minor version of Python you now support.
|
||||||
support.
|
|
||||||
|
|
||||||
Use continuous integration to stay compatible
|
Use continuous integration to stay compatible
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
@ -404,20 +413,17 @@ don't accidentally break Python 2 or 3 compatibility regardless of which version
|
||||||
you typically run your tests under while developing.
|
you typically run your tests under while developing.
|
||||||
|
|
||||||
|
|
||||||
Dropping Python 2 support completely
|
Consider using optional static type checking
|
||||||
====================================
|
--------------------------------------------
|
||||||
|
|
||||||
If you are able to fully drop support for Python 2, then the steps required
|
Another way to help port your code is to use a static type checker like
|
||||||
to transition to Python 3 simplify greatly.
|
mypy_ or pytype_ on your code. These tools can be used to analyze your code as
|
||||||
|
if it's being run under Python 2, then you can run the tool a second time as if
|
||||||
#. Update your code to only support Python 2.7
|
your code is running under Python 3. By running a static type checker twice like
|
||||||
#. Make sure you have good test coverage (coverage.py_ can help)
|
this you can discover if you're e.g. misusing binary data type in one version
|
||||||
#. Learn the differences between Python 2 & 3
|
of Python compared to another. If you add optional type hints to your code you
|
||||||
#. Use 2to3_ to rewrite your code to run only under Python 3
|
can also explicitly state whether your APIs use textual or binary data, helping
|
||||||
|
to make sure everything functions as expected in both versions of Python.
|
||||||
After this your code will be fully Python 3 compliant but in a way that is not
|
|
||||||
supported by Python 2. You should also update the classifiers in your
|
|
||||||
``setup.py`` to contain ``Programming Language :: Python :: 3 :: Only``.
|
|
||||||
|
|
||||||
|
|
||||||
.. _2to3: https://docs.python.org/3/library/2to3.html
|
.. _2to3: https://docs.python.org/3/library/2to3.html
|
||||||
|
@ -428,13 +434,19 @@ supported by Python 2. You should also update the classifiers in your
|
||||||
.. _importlib: https://docs.python.org/3/library/importlib.html#module-importlib
|
.. _importlib: https://docs.python.org/3/library/importlib.html#module-importlib
|
||||||
.. _importlib2: https://pypi.python.org/pypi/importlib2
|
.. _importlib2: https://pypi.python.org/pypi/importlib2
|
||||||
.. _Modernize: https://python-modernize.readthedocs.org/en/latest/
|
.. _Modernize: https://python-modernize.readthedocs.org/en/latest/
|
||||||
|
.. _mypy: http://mypy-lang.org/
|
||||||
.. _Porting to Python 3: http://python3porting.com/
|
.. _Porting to Python 3: http://python3porting.com/
|
||||||
.. _Pylint: https://pypi.python.org/pypi/pylint
|
.. _Pylint: https://pypi.python.org/pypi/pylint
|
||||||
|
|
||||||
.. _Python 3 Q & A: https://ncoghlan-devs-python-notes.readthedocs.org/en/latest/python3/questions_and_answers.html
|
.. _Python 3 Q & A: https://ncoghlan-devs-python-notes.readthedocs.org/en/latest/python3/questions_and_answers.html
|
||||||
|
|
||||||
|
.. _pytype: https://github.com/google/pytype
|
||||||
.. _python-future: http://python-future.org/
|
.. _python-future: http://python-future.org/
|
||||||
.. _python-porting: https://mail.python.org/mailman/listinfo/python-porting
|
.. _python-porting: https://mail.python.org/mailman/listinfo/python-porting
|
||||||
.. _six: https://pypi.python.org/pypi/six
|
.. _six: https://pypi.python.org/pypi/six
|
||||||
.. _tox: https://pypi.python.org/pypi/tox
|
.. _tox: https://pypi.python.org/pypi/tox
|
||||||
.. _trove classifier: https://pypi.python.org/pypi?%3Aaction=list_classifiers
|
.. _trove classifier: https://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||||
|
|
||||||
.. _"What's New": https://docs.python.org/3/whatsnew/index.html
|
.. _"What's New": https://docs.python.org/3/whatsnew/index.html
|
||||||
|
|
||||||
|
.. _Why Python 3 exists: http://www.snarky.ca/why-python-3-exists
|
||||||
|
|
Loading…
Reference in New Issue