diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..5789bcc --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,18 @@ +name: Run pytest + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.6' + - name: Install dependencies + run: python -m pip install -r test-requirements.txt + - name: Run tests + run: python -m pytest -v diff --git a/.gitignore b/.gitignore index 84c048a..4ea31d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /build/ +__pycache__/ diff --git a/LICENCE.CC-BY-SA b/LICENCE.CC-BY-SA new file mode 100644 index 0000000..2b56468 --- /dev/null +++ b/LICENCE.CC-BY-SA @@ -0,0 +1,372 @@ +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/LICENCE.CC0 b/LICENCE.CC0 new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENCE.CC0 @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/README b/README index 7008508..b5be14a 100644 --- a/README +++ b/README @@ -3,3 +3,44 @@ The Conservative Python 3 Porting Guide A guide for people for whom Python 3 is a necessary nuisance, written for large, conservative codebases that need to start supporting Python 3. + + +Building +-------- + +To build the documentation locally, use a Python 3 virtualenv, +install sphinx: + + $ python -m pip install sphinx + +... and run it: + + $ make html + + +Testing +------- + +We have a few automatic tests. To run them, install dependencies: + + $ python -m pip install -r test-requirements.txt + +... and run: + + $ python -m pytest -v + + +Licence +------- + +This guide is released under the [CC BY-SA 4.0] licence. + +Additionally, all code in this repository (including code samples in the text, +tests, build scripts) is dedicated to the public domain under the +[CC0 1.0] dedication. + +[CC BY-SA 4.0]: https://creativecommons.org/licenses/by-sa/4.0/ +[CC0 1.0]: https://creativecommons.org/publicdomain/zero/1.0/ + +See the files `LICENCE.CC-BY-SA` and `LICENCE.CC0`, respectively, for the +license text. diff --git a/source/builtins.rst b/source/builtins.rst index 3445fdf..4145bb4 100644 --- a/source/builtins.rst +++ b/source/builtins.rst @@ -105,12 +105,13 @@ aren't of the ``file`` type. If type-checking for files is necessary, we recommend using a tuple of types that includes :class:`io.IOBase` and, under Python 2, ``file``:: - import six import io - if six.PY2: + try: + # Python 2: "file" is built-in file_types = file, io.IOBase - else: + except NameError: + # Python 3: "file" fully replaced with IOBase file_types = (io.IOBase,) ... @@ -123,7 +124,7 @@ that includes :class:`io.IOBase` and, under Python 2, ``file``:: Removed ``apply()`` ~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf libmodernize.fixes.fix_apply`` (but see below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_apply`` (but see below) * Prevalence: Common In Python 2, the function :func:`apply` was built in. @@ -151,7 +152,7 @@ in some of your modules, revert the fixer's changes in that module. Moved ``reduce()`` ~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf libmodernize.fixes.fix_reduce`` +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_reduce`` * Prevalence: Uncommon In Python 2, the function :func:`reduce` was built in. @@ -174,7 +175,7 @@ The recommended fixer will add this import automatically. The ``exec()`` function ~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_exec`` +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_exec`` * Prevalence: Rare In Python 2, :func:`exec` was a statement. In Python 3, it is a function. @@ -213,10 +214,12 @@ The call:: execfile(filename) -was equivalent to:: +was roughly equivalent to:: + + from io import open def compile_file(filename): - with open(filename) as f: + with open(filename, encoding='utf-8') as f: return compile(f.read(), filename, 'exec') exec(compile_file(filename)) @@ -228,8 +231,21 @@ as above. Although :ref:`python-modernize` has an ``execfile`` fixer, we don't recommend using it, as it doesn't close the file correctly. +Note that the above hard-codes the ``utf-8`` encoding (which also works if your +code uses ASCII). +If your code uses a different encoding, substitute that. +If you don't know the encoding in advance, you will need to honor `PEP 263`_ +special comments: on Python 3 use the above with :func:`py3:tokenize.open` +instead of :func:`py3:open`, and on Python 2 fall back to the old +:func:`py2:execfile`. + +The `io.open()` function is discussed in this guide's +:ref:`section on strings `. + .. XXX: file an issue in python-modernize +.. _PEP 263: https://www.python.org/dev/peps/pep-0263/ + .. index:: reload .. index:: NameError; reload @@ -246,11 +262,13 @@ In Python 3, it is moved to the ``importlib`` module. Python 2.7 included an ``importlib`` module, but without a ``reload`` function. Python 2.6 and below didn't have an ``importlib`` module. -If your code uses ``reload()``, import it conditionally on Python 3:: - - import six +If your code uses ``reload()``, import it conditionally if it doesn't exist +(using `feature detection`_):: - if not six.PY2: + try: + # Python 2: "reload" is built-in + reload + except NameError: from importlib import reload @@ -266,11 +284,13 @@ Moved ``intern()`` The :func:`~sys.intern` function was built-in in Python 2. In Python 3, it is moved to the ``sys`` module. -If your code uses ``intern()``, import it conditionally on Python 3:: +If your code uses ``intern()``, import it conditionally if it doesn't exist +(using `feature detection`_):: - import six - - if not six.PY2: + try: + # Python 2: "intern" is built-in + intern + except NameError: from sys import intern @@ -292,3 +312,9 @@ If any of your classes defines the special method ``__coerce__``, remove that as well, and test that the removal did not break semantics. .. XXX: I've never seen serious use of ``coerce``, so the advice is limited. + + +.. Common links + ------------ + +.. _feature detection: https://docs.python.org/3/howto/pyporting.html#use-feature-detection-instead-of-version-detection diff --git a/source/comprehensions.rst b/source/comprehensions.rst index 23fb8fe..4a69677 100644 --- a/source/comprehensions.rst +++ b/source/comprehensions.rst @@ -1,7 +1,7 @@ Comprehensions -------------- -List comprehensions, a shrtcut for creating lists, have been in Python +List comprehensions, a shortcut for creating lists, have been in Python since version 2.0. Python 2.4 added a similar feature – generator expressions; then 2.7 (and 3.0) introduced set and dict comprehensions. @@ -47,7 +47,7 @@ To fix this, either rewrite the code to not use the iteration variable after a list comprehension, or convert the comprehension to a ``for`` loop:: powers = [] - for i in for i in range(10): + for i in range(10): powers.append(2**i) In some cases, the change might silently cause different behavior. @@ -75,7 +75,7 @@ Unfortunately, you will need to find and fix these cases manually. Comprehensions over Tuples ~~~~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_paren`` +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_paren`` * Prevalence: Rare Python 2 allowed list comprehensions over bare, non-parenthesized tuples: diff --git a/source/core-obj-misc.rst b/source/core-obj-misc.rst index 41892b2..97e1f2c 100644 --- a/source/core-obj-misc.rst +++ b/source/core-obj-misc.rst @@ -17,7 +17,7 @@ classes. Function Attributes ~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_funcattrs`` (but see below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_funcattrs`` (but see below) * Prevalence: Rare In Python, functions are mutable objects that support custom attributes. @@ -194,8 +194,8 @@ Do this change in all classes that implement ``__nonzero__``. Unbound Methods ~~~~~~~~~~~~~~~ -Python 2 had two kinds of methods: *bound* methods, which you could retreive -from a class object, and *unbound* methods, which were retreived from +Python 2 had two kinds of methods: *unbound* methods, which you could retreive +from a class object, and *bound* methods, which were retreived from an instance:: >>> class Hello(object): diff --git a/source/dicts.rst b/source/dicts.rst index e7c2f72..1d1d3de 100644 --- a/source/dicts.rst +++ b/source/dicts.rst @@ -10,7 +10,7 @@ There are three most significant changes related to dictionaries in Python 3. Removed ``dict.has_key()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.has_key`` (See caveat below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_has_key`` (See caveat below) * Prevalence: Common The ``dict.has_key()`` method, long deprecated in favor of the ``in`` operator, @@ -27,9 +27,9 @@ you should use:: Note that the recommended fixer replaces all calls to *any* ``has_key`` method; it does not check that its object is actually a dictionary. -If you use a third-party non-dict-like class, it should implement ``in`` +If you use a third-party dict-like class, it should implement ``in`` already. -If not, complain to its author: it should have been added as part of adding +If not, notify its author: it should have been added as part of adding Python 3 support. If your own codebase contains a custom dict-like class, add @@ -101,8 +101,8 @@ In Python 3.3+, this setting is the default:: [('c', 3), ('e', 5), ('d', 4), ('a', 1), ('b', 2)] Additionally, CPython 3.6+ uses a new implementation of dictionaries, -which makes them appear sorted by insertion order (though you shouldn't rely -on this behavior):: +which makes them appear sorted by insertion order (though you can only rely +on this behavior in Python 3.7+):: $ python3 order.py [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)] diff --git a/source/exceptions.rst b/source/exceptions.rst index 58e421f..850aaa9 100644 --- a/source/exceptions.rst +++ b/source/exceptions.rst @@ -26,7 +26,7 @@ supporting Python 3. The new ``except`` syntax ~~~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_except`` +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_except`` * Prevalence: Very common In Python 2, the syntax for catching exceptions was @@ -137,7 +137,7 @@ to the exception to use it outside the ``except`` clause. Iterating Exceptions ~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf libmodernize.fixes.fix_except`` (but see caveat below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_except`` (but see caveat below) * Prevalence: Rare In Python 2, exceptions were *iterable*, so it was possible to “unpack” the @@ -196,7 +196,7 @@ Otherwise, switch to using a dedicated Exception class. The Removed ``StandardError`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf libmodernize.fixes.fix_standarderror`` (but see caveat below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_standarderror`` (but see caveat below) * Prevalence: Rare The :class:`py2:StandardError` class is removed in Python 3. diff --git a/source/imports.rst b/source/imports.rst index 27b1d8a..91a80db 100644 --- a/source/imports.rst +++ b/source/imports.rst @@ -10,9 +10,9 @@ Developer-visible changes are summarised below. Absolute imports ~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_import`` (See caveat below) +* :ref:`Fixer `: ``python-modernize -wnf libmodernize.fixes.fix_import`` (See caveat below) * Prevalence: Common -* Future import: ``from __future__ import absolute_imports`` +* Future import: ``from __future__ import absolute_import`` * Specification: `PEP 328 `_ Under Python 2, when importing from inside a package, the package's own modules @@ -43,7 +43,7 @@ Given the structure above, these statements would be equivalent Additionally, a *future import* was added to make all imports absolute (unless explicitly relative):: - from __future__ import absolute_imports + from __future__ import absolute_import Using this feature, ``from collections import deque`` will import from the standard library's ``collections`` module. @@ -103,7 +103,7 @@ Import Cycles * :ref:`Fixer `: None * Prevalence: Rare -Python 3 introduced a reworked importmentation of ``import`` in the form +Python 3 introduced a reworked implementation of ``import`` in the form of the :py:mod:`importlib` module. The new machinery is backwards-compatible in practice, except that some import cycles, especially those involving submodules, now raise diff --git a/source/index.rst b/source/index.rst index 9a51efb..13182ce 100644 --- a/source/index.rst +++ b/source/index.rst @@ -49,6 +49,7 @@ Still with us? Let's dive in! classes comprehensions core-obj-misc + invoking-python etc diff --git a/source/invoking-python.rst b/source/invoking-python.rst new file mode 100644 index 0000000..e307c05 --- /dev/null +++ b/source/invoking-python.rst @@ -0,0 +1,56 @@ +Invoking Python +--------------- + +While this is not a change in Python 3, the transition increased the number of +systems that have more than one Python interpreter installed: it is not +uncommon for ``python``, ``python2``, ``python3``, ``python3.6`` and +``python3.9`` to all be valid system commands; other interpreters may be +installed in non-standard locations. + +This makes it important to use the correct command for each situation. + + +Current interpreter +~~~~~~~~~~~~~~~~~~~ + +The current Python interpreter should be invoked via ``sys.executable``. + +Python provides the path of the currently running interpreter as +:data:`sys.executable`. +This variable should be preferred over ``python`` or other hard-coded commands. + +For example, rather than:: + + subprocess.Popen('python', 'somescript.py') + +use:: + + subprocess.Popen(sys.executable, 'somescript.py') + +The assumption that ``'python'`` is correct is only valid in tightly controlled +environments; however, even in those environments ``sys.executable`` is likely +to be correct. + +The documentation does include a warning: + + If Python is unable to retrieve the real path to its executable, + ``sys.executable`` will be an empty string or ``None``. + +In practice, this does not apply to mainstream platforms. +If ``sys.executable`` is unusable, then either your platform's concept of +launching a process via filename is somehow unusual (and in this +case you should know what to do), or there's an issue in Python itself. + + +Unix shebangs +~~~~~~~~~~~~~ + +On Unix, executables written in Python must have a shebang line identifying +the interpreter. +The correct shebang to use will depend on the environment you are targeting +and on the version compatibility of the project. + +General recommendations for Python shebangs are listed in +the `For Python script publishers`_ section of PEP 394. + +.. _For Python script publishers: https://www.python.org/dev/peps/pep-0394/#for-python-script-publishers diff --git a/source/iterators.rst b/source/iterators.rst index 57a5e89..4052a4e 100644 --- a/source/iterators.rst +++ b/source/iterators.rst @@ -255,3 +255,31 @@ fixer's output and revert the changes for objects of this class. The fixer will not add a ``__next__`` method to your classes. You will need to do this manually. + +Generators cannot raise ``StopIteration`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* :ref:`Fixer `: None +* Prevalence: Rare + +Since Python 3.7, generators cannot raise ``StopIteration`` directly, +but must stop with ``return`` (or at the end of the function). +This change was done to prevent subtle errors when a ``StopIteration`` +exception “leaks” between unrelated generators. + +For example, the following generator is considered a programming error, +and in Python 3.7+ it raises ``RuntimeError``:: + + def count_to(maximum): + i = 0 + while True: + yield i + i += 1 + if i >= maximum: + raise StopIteration() + +Convert the ``raise StopIteration()`` to ``return``. + +If your code uses a helper function that can raise ``StopIteration`` to +end the generator that calls it, you will need to move the returning logic +to the generator itself. diff --git a/source/numbers.rst b/source/numbers.rst index fd9b647..3370884 100644 --- a/source/numbers.rst +++ b/source/numbers.rst @@ -27,7 +27,7 @@ In Python 2, dividing two integers resulted in an integer:: This *truncating division* was inherited from C-based languages, but confused people who don't know those languages, -such as those coming from Javascript or pure math. +such as those coming from JavaScript or pure math. In Python 3, dividing two integers results in a float:: @@ -37,7 +37,7 @@ In Python 3, dividing two integers results in a float:: The ``//`` operator, which was added all the way back in Python 2.2, always performs truncating division:: - while_minutes = seconds // 60 + whole_minutes = seconds // 60 The ``from __future__ import division`` directive causes the ``/`` operator to behave the same in Python 2 as it does in Python 3. @@ -100,7 +100,7 @@ This change has several consequences. Removal of the ``long`` type ............................ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_long`` +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_long`` * Prevalence: Common The ``long`` builtin no longer exists. @@ -130,7 +130,7 @@ The recommended fixer will do this. The ``L`` suffix not allowed in numeric literals ................................................ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_numliterals`` (but see below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_numliterals`` (but see below) * Prevalence: Very common In Python 2, ``12345L`` designated a ``long`` literal. @@ -175,7 +175,7 @@ Call ``str()`` instead of ``repr()`` when the result might be a (long) integer. Octal Literals ~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_numliterals`` (but see below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_numliterals`` (but see below) * Prevalence: Uncommon Python 2's other holdover from C-based languages is the syntax of octal diff --git a/source/process.rst b/source/process.rst index 8e22000..57ed0f7 100644 --- a/source/process.rst +++ b/source/process.rst @@ -33,7 +33,7 @@ unmaintained, start looking for a replacement. For projects in Fedora, the `portingdb`_ project lists known alternatives for dropped packages. -.. _portingdb: https://fedora.portingdb.xyz +.. _portingdb: http://fedora.portingdb.xyz .. index:: tests @@ -83,8 +83,8 @@ Porting by introducing specific workarounds and helpers. We don't recommend separating these phases. For larger projects, -it it much better to separate the work by modules – port low-level -code first, then move on to things that depends on what's already ported. +it is much better to separate the work by modules – port low-level +code first, then move on to things that depend on what's already ported. We provide some general porting tips below: @@ -128,7 +128,7 @@ Even more importantly, do not combine large automated changes with manual fixups. It is much easier to review two patches: one done by a tool (which the reviewer can potentially re-run to verify the commit), and another that -fixes up places where human care is nedeed. +fixes up places where human care is needed. The descriptions of individual items in this guide are written so that you can use them in commit messages to explain why each change is necessary @@ -148,6 +148,17 @@ tackled in a typical project. We recommend that you skim the introduction of each of the chapters, so that you know what you're up against before you start. +Note that while the guide is fairly comprehensive, there are changes it does +not cover. +Be prepared to find a few issues specific to your code base that you'll need +to figure out independently. + +Also note that the guide was written for Python 3.6. +It includes several updates for newer versions, but we recommend skimming +[What's New lists](https://docs.python.org/3/whatsnew/index.html) +in the Python documentation to familiarize yourself with +the changes in newer versions of Python. + .. index:: dropping Python 2 @@ -160,5 +171,5 @@ Python 3-compatible version. For less conservative projects, dropping Python 2 support will include removing compatibility workarounds. -Targeting Python 3 only will enable you start using all the new +Targeting Python 3 only will enable you to start using all the new features in the new major version – but those are for another guide. diff --git a/source/strings.rst b/source/strings.rst index 6b2c699..8bc3bdb 100644 --- a/source/strings.rst +++ b/source/strings.rst @@ -14,15 +14,15 @@ In Python 2, the ``str`` type was used for two different kinds of values – **Text** contains human-readable messages, represented as a sequence of Unicode codepoints. - Usually, it does not contain unprintable control characters such as NULL. + Usually, it does not contain unprintable control characters such as ``\0``. This type is available as ``str`` in Python 3, and ``unicode`` in Python 2. In code, we will refer to this type as ``unicode`` – a short, unambiguous name, although one that is not built-in in Python 3. - Some projects refer to it as ``six.text_type`` (from the :ref:`six` - library). + Some projects refer to it as ``six.text_type`` + (from the :ref:`six library `). * @@ -58,24 +58,25 @@ can use what is conceptually a third type: The **native string** (``str``) – text in Python 3, bytes in Python 2 -Custom ``__str__`` and ``__repr__`` methods, and code that deals with +Custom ``__str__`` and ``__repr__`` methods and code that deals with Python language objects (such as attribute/function names) will always need to use the native string, because that is what each version of Python uses for internal text-like data. Developer-oriented texts, such as exception messages, could also be native strings. -For other data, you can use the native string in these circumstances: +For other data, you should only use the native string if all of the following +hold: - * You are working with textual data - * Under Python 2, each “native string” value has a single well-defined - encoding (such as ``UTF-8`` or :func:`py2:locale.getpreferredencoding`) - * You do not mix native strings with either bytes or text – always - encode/decode diligently when converting to these types. +* you are working with textual data, +* Under Python 2, each “native string” value has a single well-defined + encoding (e.g. ``UTF-8`` or :func:`py2:locale.getpreferredencoding`), and +* you do not mix native strings with either bytes or text – always + encode/decode diligently when converting to these types. Native strings affect the semantics under Python 2 as little as possible, while not requiring the resulting Python 3 API to feel bad. But, having -a third incompatible type makes porting process harder. +a third incompatible type makes the porting process harder. Native strings are suitable mostly for conservative projects, where ensuring stability under Python 2 justifies extra porting effort. @@ -97,13 +98,19 @@ trying all alternatives. Unlike images, one bytestring can often be successfully decoded using more than one encoding. -So, never assume an encoding without consulting relevant documentation + +Encodings +......... + +Never assume a text encoding without consulting relevant documentation and/or researching a string's use cases. If an encoding for a particular use case is determined, document it. -For example, a docstring can specify that some argument is “a bytestring -holding UTF-8-encoded text data”. +For example, a function docstring can specify that some argument is +“a bytestring holding UTF-8-encoded text data”, or module documentation may +clarify that „as per RFC 4514, LDAP attribute names are encoded to UTF-8 +for transmission“. -Some common encodings are: +Some common text encodings are: * ``UTF-8``: A widely used encoding that can encode any Unicode text, using one to four bytes per character. @@ -118,23 +125,31 @@ Some common encodings are: * ``locale.getpreferredencoding()``: The “preferred encoding” for command-line arguments, environment variables, and terminal input/output. +If *you* are choosing an encoding to use – for example, your application +*defines* a file format rather than using a format standardized externally – +consider ``UTF-8``. +And whatever your choice is, explicitly document it. + Conversion to text ------------------- +.................. There is no built-in function that converts to text in both Python versions. -The :ref:`six` library provides ``six.text_type``, which is fine if it appears -once or twice in uncomplicated code. +The :ref:`six library ` provides ``six.text_type``, which is fine if it +appears once or twice in uncomplicated code. For better readability, we recommend using ``unicode``, which is unambiguous and clear, but it needs to be introduced with the following code at the beginning of a file:: - if not six.PY2: + try: + # Python 2: "unicode" is built-in + unicode + except NameError: unicode = str Conversion to bytes -------------------- +................... There is no good function that converts an arbitrary object to bytes, as this operation does not make sense on arbitrary objects. @@ -147,13 +162,51 @@ Depending on what you need, explicitly use a serialization function String Literals --------------- +* :ref:`Fixer `: None +* Prevalence: Very common + Quoted string literals can be prefixed with ``b`` or ``u`` to get bytes or text, respectively. These prefixes work both in Python 2 (2.6+) and 3 (3.3+). Literals without these prefixes result in native strings. +In Python 3, the ``u`` prefix does nothing; it is only allowed for backwards +compatibility. +Likewise, the ``b`` prefix does nothing in Python 2. + Add a ``b`` or ``u`` prefix to all strings, unless a native string is desired. +Unfortunately, the choice between text and bytes cannot generally be automated. + +Raw Unicode strings +................... + +* :ref:`Fixer `: None +* Prevalence: Rare + +In Python 2, the ``r`` prefix could be combined with ``u`` to avoid processing +backslash escapes. +However, this *did not* turn off processing Unicode escapes (``\u....`` or +``\U........``), as the ``u`` prefix took precedence over ``r``:: + + >>> print u"\x23☺\u2744" # Python 2 with Encoding: UTF-8 + #☺❄ + >>> print ur"\x23☺\u2744" # Python 2 with Encoding: UTF-8 + \x23☺❄ + +This may be confusing at first. +Keeping this would be even more confusing in Python 3, where the ``u`` prefix +is a no-op with backwards-compatible behavior. +Python 3 avoids the choice between confusing or backwards-incompatible +semantics by forbidding ``ur`` altogether. + +Avoid the ``ur`` prefix in string literals. + +The most straightforward way to do this is to use plain ``u`` literals +with ``\\`` for a literal backslash:: + + >>> print(u"\\x23☺\u2744") + \x23☺❄ .. index:: TypeError; mixing text and bytes @@ -170,7 +223,9 @@ For example, these are all illegal:: import re pattern = re.compile(b'a+') - pattern.patch('aaaaaa') + pattern.match('aaaaaa') + +Encode or decode the data to make the types match. Type checking @@ -191,7 +246,7 @@ In Python 3, the concept of ``basestring`` makes no sense: text is only represented by ``str``. For type-checking text strings in code compatible with both versions, the -:ref:`six` library offers ``string_types``, which is ``(basestring,)`` +:ref:`six library ` offers ``string_types``, which is ``(basestring,)`` in Python 2 and ``(str,)`` in Python 3. The above code can be replaced by:: @@ -204,12 +259,12 @@ The recommended fixer will import ``six`` and replace any uses of ``basestring`` by ``string_types``. -.. index:: file I/O +.. index:: file I/O, open .. _str-file-io: File I/O -~~~~~~~~ +-------- * :ref:`Fixer `: ``python-modernize -wnf libmodernize.fixes.fix_open`` * Prevalence: Common @@ -262,7 +317,7 @@ We recommend adding them manually if the encoding is known. .. _testing-str: Testing Strings -~~~~~~~~~~~~~~~ +--------------- When everything is ported and tests are passing, it is a good idea to make sure your code handles strings correctly – even in unusual situations. @@ -300,7 +355,7 @@ If your software handles multiple text encodings, or handles user-specified encodings, make sure this capability is well-tested. Under Linux, run your software with the ``LC_ALL`` environment variable -set to ``C`` and ``tr_TR.UTF-8``, and check handling of any command-line +set to ``C`` and to ``tr_TR.utf8``. Check handling of any command-line arguments and environment variables that may contain non-ASCII characters. diff --git a/source/syntax.rst b/source/syntax.rst index 75031fa..feb7953 100644 --- a/source/syntax.rst +++ b/source/syntax.rst @@ -39,7 +39,7 @@ You can use the following Bash command for this:: Tuple Unpacking in Parameter Lists ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_tuple_params`` (fixup needed) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_tuple_params`` (fixup needed) * Prevalence: Common Python 3 requires that each argument of a ``def`` function has a name. @@ -79,7 +79,7 @@ named function would be an improvement. Backticks ~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_repr`` (with caveat) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_repr`` (with caveat) * Prevalence: Common The backtick (`````) operator was removed in Python 3. @@ -91,12 +91,12 @@ The recommended fixer does a good job, though it doesn't catch the case where the name ``repr`` is redefined, as in:: repr = None - print(`1+_2`) + print(`1+2`) which becomes:: repr = None - print(repr(1+_2)) + print(repr(1+2)) Re-defining built-in functions is usually considered bad style, but it never hurts to check if the code does it. @@ -108,7 +108,7 @@ hurts to check if the code does it. The Inequality Operator ~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_ne`` +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_ne`` * Prevalence: Rare In the spirit of “There's only one way to do it”, Python 3 removes the @@ -126,6 +126,9 @@ New Reserved Words * :ref:`Fixer `: None * Prevalence: Rare +Constants +......... + In Python 3, ``None``, ``True`` and ``False`` are syntactically keywords, not variable names, and cannot be assigned to. This was partially the case with ``None`` even in Python 2.6. @@ -133,6 +136,64 @@ This was partially the case with ``None`` even in Python 2.6. Hopefully, production code does not assign to ``True`` or ``False``. If yours does, figure a way to do it differently. +``async`` and ``await`` +....................... + +Since Python 3.7, ``async`` and ``await`` are also keywords. + +If your code uses these names, rename it. +If other code depends on the names, keep the old name available for +old Python versions. +The way to do this will be different in each case, but generally +you'll need to take advantage of the fact that in Python's various namespaces +the strings ``'async'`` and ``'await'`` are still valid keys, even if they +are not accesible usual with the syntax. + +For module-level functions, classes and constants, also assign the original +name using :py:func:`globals()`. +For example, a function previously named ``async`` could look like this:: + + def asynchronous(): + """... + + This function used to be called `async`. + It is still available under old name. + """ + + globals()['async'] = asynchronous + +For methods, and class-level constants, assign the original name using +``setattr``:: + + class MyClass: + def asynchronous(self): + """... + + This method used to be called `async`. + It is still available under old name. + """ + + setattr(MyClass, 'async', MyClass.asynchronous) + +For function parameters, more work is required. The result will depend on +whether the argument is optional and whether ``None`` is a valid value for it. +Here is a general starting point:: + + def process_something(asynchronous=None, **kwargs): + if asynchronous is None: + asynchronous = kwargs.get('async', None) + else: + if 'async' in kwargs: + raise TypeError('Both `asynchronous` and `async` specified') + if asynchronous is None: + raise TypeError('The argument `asynchronous` is required') + +For function arguments, if the parameter cannot be renamed as above, +use “double star” syntax that allows you to pass arbitrary argument names:: + + process_something(**{'async': True}) + + Other Syntax Changes ~~~~~~~~~~~~~~~~~~~~ diff --git a/source/tools.rst b/source/tools.rst index 8fa3552..cb256fb 100644 --- a/source/tools.rst +++ b/source/tools.rst @@ -52,10 +52,15 @@ These are best handled by the ``python-modernize`` tool – a code-to-code translator that takes a Python 2 codebase and updates it to be compatible with both Python 2 and 3. -The tool builds on top of ``2to3``, a library that comes with Python. ``2to3`` -was once intended as the main porting tool. It turned out inadequate for that -task, but ``python-modernize`` (among others) successfully reuses its general -infrastructure. +.. note:: + + ``python-modernize`` was built on top of ``2to3`` from of Python's + standard library. ``2to3`` was once intended as the main porting tool. + It turned out inadequate for that task, but ``python-modernize`` + (among others) successfully reuses its general infrastructure. + Because ``2to3`` itself is built into Python and thus missing improvements + newer than the the Python that runs it, ``python-modernize`` now uses a + fork of ``2to3`` called ``fissix``. Assuming code is in version control, you'll generally want to run ``python-modernize`` with the ``-wn`` flags: ``-w`` flag causes the tool to @@ -98,7 +103,7 @@ For these, we have two pieces of advice: * Even though this is a conservative guide, we encourage you to try porting - C extensions away from the Cython C API. For wrappers to external libraries + C extensions away from the Python C API. For wrappers to external libraries we recommend `CFFI`_; for code that needs to be fast there's `Cython`_. While this is relatively disruptive, the result will very likely be more diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..3bdf818 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,2 @@ +pytest +modernize diff --git a/test_portingguide/test_fixer_library.py b/test_portingguide/test_fixer_library.py new file mode 100644 index 0000000..f012db7 --- /dev/null +++ b/test_portingguide/test_fixer_library.py @@ -0,0 +1,45 @@ +from pathlib import Path +import subprocess +import re +import sys + +import pytest + +basepath = Path(__file__).parent.parent / 'source' +all_rst_files = [str(p.relative_to(basepath)) + for p in basepath.glob('**/*.rst')] + +# Fixer names are in the format ".fixes.fix_" but we test also +# for typos like ".fixes.ifx_". +FIXER_RE = re.compile(r'\w+\.fixes\.\w+') + + +@pytest.fixture(scope='module') +def fixer_names(): + """Get list of available fixers""" + # use subprocess -- modernize doesn't have public fixer list API + proc = subprocess.run([sys.executable, '-m', 'modernize', '-l'], + stdout=subprocess.PIPE, encoding='ascii', check=True) + + # In modernize 0.6.1, the output has changed to list additional information + # This takes the first part only and works with older modernize as well + lines = [l.strip().split()[0] for l in proc.stdout.splitlines() if l] + + # first line is a header, all others should be fixers + assert not FIXER_RE.fullmatch(lines[0]) + fixers = lines[1:] + assert all([FIXER_RE.fullmatch(f) for f in fixers]) + + return fixers + + +@pytest.mark.parametrize('filename', all_rst_files) +def test_fixers_exist(filename, fixer_names): + """Test that all mentioned fixers are correctly named and available""" + path = basepath / filename + with path.open() as f: + for lineno, line in enumerate(f, start=1): + for match in FIXER_RE.finditer(line): + found_text = match.group(0) + print(f'{filename}:{lineno}:{line.strip()}') + assert found_text in fixer_names diff --git a/test_portingguide/test_future_imports.py b/test_portingguide/test_future_imports.py new file mode 100644 index 0000000..14bd1dd --- /dev/null +++ b/test_portingguide/test_future_imports.py @@ -0,0 +1,30 @@ +from pathlib import Path +import subprocess +import re +import sys + +import pytest + +basepath = Path(__file__).parent.parent / 'source' +all_rst_files = [str(p.relative_to(basepath)) + for p in basepath.glob('**/*.rst')] + +# All future imports mentioned +FUTURE_RE = re.compile(r'from __future__ import [^\s`]+') + + +def try_import(line): + cp = subprocess.run([sys.executable, '-c', line]) + return cp.returncode + + +@pytest.mark.parametrize('filename', all_rst_files) +def test_future_imports_work(filename): + """Test that all mentioned future imports work""" + path = basepath / filename + with path.open() as f: + for lineno, line in enumerate(f, start=1): + for match in FUTURE_RE.finditer(line): + found_text = match.group(0) + print(f'{filename}:{lineno}:{found_text}') + assert try_import(found_text) == 0