Supporting Python 3(支持python3)——2to3

2to3

虽然完全有可能直接在Python 3下跑你的Python 2代码并且当出现问题时马上修复它,但这很快就会变得非常乏味。你需要反每一个print语句改成print()函数,并且把每个一个“except Exception, e”改成新的“except Exception as esyntax”。这些变化用搜索和替换来做是很棘手的并且很快会推动兴奋感。幸运的是我们可以用2to3来自动执行这些无聊的变化。

使用2to3

2to3由几个部分组成。核心是lib2to3,一个包含重构Python代码支持的包。它可以分析代码并建立一个描述代码结构的解析树。你之后可以修改解析树并从它生成新的代码。在lib2to3里还有一个“固定剂(fixers)”的框架,这个框架是一个做像把print语句改成print()函数的特殊重构的模块。允许2to3转换Python 2成Python 3代码的固定剂(fixers)集合位于lib2to3.fixes。最后是可以你可以用来做转换的2to3脚本本身。

执行2to3是非常简单的。你给一个文件或者目录作为参数,它就会转换文件或者遍历目录寻找Python文件并转换它们。2to3会在输出里打印出所有的变化,但除非你使用-w标志否则它不会写到文件里。它会写进一个和原来文件名字相同的文件里并把旧文件保存在增加.bak后缀的文件里,所以如果你想要保持原来的文件名字不变你需要首先复制文件然后再转换副本。

如果在你的Python文件里有doctests,你还需要带-d选项执行第二2to3来转换doctests,并且如果你有本文doctests文件,你需要显示地在这些文件在执行。

$ 2to3 -w .
$ 2to3 -w -d .
$ 2to3 -w -d src/mypackage/README.txt
$ 2to3 -w -d src/mypackage/tests/*.txt

在Linux和OS X下2to3脚本安装Python执行文件相同的目录下。在Windows下它被作为2to3.py安装在你的Python安装目录下的Tools\Scripts目录,并且你需要给出完整的路径:

C:\projects\mypackage> C:\Python3.3\Tools\Scripts\2to3.py -w .

如果你经常执行2to3,或者你需要把Scripts目录加进系统路径里。

明确的固定器(fixers)

默认情况下,转换会使用在lib2to3.fixers包里除了buffer、idioms、set_literal和 ws_comma外所有的固定器(fixers)。这些只有在明确地在命令行里加上时才会被使用。在这种情况下你还是需要用all来指定默认的固定器(fixers)集合:

$ 2to3 -f all -f buffer .

buffer固定器(fixers)会用memoryview类型来替换buffer类型。buffer类型在Python已经消失了并且memoryview类型不是完全兼容。所以当你做普通变化时buffer固定器(fixers)没有被包含进默认里。

另外三个固定器(fixers)会做一个格式上的变化但这不是是真正必需的。

idioms固定器(fixsers)会更新过时的风格。它会把type(x)== SomeType及其他的类型测试改成使用isinstance(),它会把在Python 1中用的旧式while 1: 改成while True:,并且它会把一些.sort()的使用改成sorted()(见使用sorted()代替 .sort())。

set_literal固定器(fixers)会把set()构造器的调用改成使用新的集合文字(Set literals)。见集合文字(Set literals

ws_comma固定器(fixers)会修复代码中空白逗号及冒号。

有可能你会自己写固定器(fixers),虽然你极不可能需要这么做。更多的信息在使用你自己的固定器(fixers)扩展2to3 with

当在执行默认的固定器(fixers)集合时你也可以排除掉一个固定器(fixers):

$ 2to3 -x print .

如果你不打算去支持Python 2,那么这就是所有你需要知道的2to3的内容了。你只需要执行一次2to3,然后到在常见的迁移问题里描述的修复迁移问题这个有趣的部分了。

分发包

当你写一个其他开发者在用的包或者模块时,你可能会想要继续支持Python 2。那么你需要确保Python 2和Python 3用户得到对应版本的包。如果你自己建包的网页的话,这可以很简单地在下载页记录。

大多数包通用使用distutils并上传到CheeseShop[1],人们经常使用像easy_install或者pip这样工具从那里安装包。这些工具会下载包的最后一个版本并安装,如果你上传了Python 2和Python 3两个版本的包到CheeseShop,那么一些用户会下载错误的包并且不能安装你的包。

一个通常的解决办法是用两个独立的包名,就像mymodule和mymodule3这样,但是这样的话你就要维护两个包并且做两个发布版本。一个更好的解决办法是在一个分发文档中同时包含各版本的源树,例如src2给Python 2,src3是在Python 3下的。然后你可以在你的setup.py里根据Python的版本选择要安装哪个源树:

import sysfrom distutils.core import setup
if sys.version_info < (3,):
      package_dir = {'': 'src2'}
else:
      package_dir = {'': 'src3'}
      
setup(name='foo',
      version='1.0',
      package_dir = package_dir,
      )

这种方式所有你的模块包的用户都会下载相同的代码并且安装脚本会安装正确版本的代码。你的setup.py需要能同时在Python 2和Python 3下工作,这通常不是一个问题。看不用转换同时支持Python 2 和3来获得如何去做的帮助。

如果你有一个非常复杂的setup.py,你或许需要为每个Python版本准备一个,一个Python 2下调用setup2.py,一个Python 3下调用setup3.py。然后你写一个setup.py来根据Python版本选择正确的安装安装文件:I

import sys
if sys.version_info < (3,):
    import setup2
else:
    import setup3

在安装时执行2to3

官方的同时支持Python 2和Python 3的方式是修护一个Python 2版本的代码并用2to3工具转换成Python 3的。如果你这样做的话,似乎可以在安装转换你的发布版本。这种方式你不需要单独包甚至在发布两份代码的副本。

Distutils使用自定义的构建命令可以支持这个。在Python 3下用build_py_2to3替换build_py命令类: 

try:
   from distutils.command.build_py import build_py_2to3 \
        as build_py
except ImportError:
   from distutils.command.build_py import build_py

setup(
   ...
   cmdclass = {'build_py': build_py}
   )

然而,如果你想要用这个解决办法,你可以要从Distutils切换到Distribute,Distribute进一步扩展了这个理念并且紧密地整合进了开发过程中。

使用Distribute支持多个版本的Python

如果你想要用2to3同时支持Python 2和Python 3,那么你会发现Distribute[2]是很有帮助的。它是Distutils扩展了Phillip J. Eby的流行的Setuptools包的Python 3兼容分支。Distribute像Distutils做的那样加入了2to3的整合,所以它能为你解决发布问题,但是在你开发和测试包时它同样可以提供帮助。

当你使用2to3来同时支持Python 2和Python 3时,你需要在每一次修改时在运行测试前运行2to3。Distribute把这个过程整合进了test命令,这意味着你更新过的文件会被复制进构建并在构建目录里运行测试前使用2to3进行转换,所有的这些只要一个命令。在你修改代码后,只需要为每一个你需要支持Python版本执行python setup.py test来确保测试能跑。这使得能在一个舒服的环境里添加Python 3支持并能继续支持Python 2。

安装Distribute你需要从http://python-distribute.org/distribute_setup.py获得Distribute安装脚本并执行。然后要在所有你想要安装Distribute的Python版本下执行distribute_setup.py。Distribute主要兼容于Setuptools,所以在Python 2下你可以使用Setuptools来代替Distribute,但在Python 2下使用Distribute也一样并且这可能会更好的保持一致。

如果你在使用Distutils或者Setuptools来安装你的软件,你已经有一个setup.py。从Setuptools切换到Distribute你不需要做任何事。从Distutils切换到Distribute你需要修改导入setup()函数的地方。在Distutils里你从distutils.core导入,但在Setuptools和Distribute是从setuptools导入。

如果你没有setup.py,那么你需要创建一个。一个典型的setup.py例子就像这样:

from setuptools import setup, find_packages

readme = open('docs/README.txt', 'rt').read()
changes = open('docs/CHANGES.txt', 'rt').read()

setup(name='Supporting Python 3 examples',
      version="1.0",
      description="An example project for Supporting Python 3",
      long_description=readme + '\n' + changes,
      classifiers=[
          "Programming Language :: Python :: 2",
          "Topic :: Software Development :: Documentation"],
      keywords='python3 porting documentation examples',
      author='Lennart Regebro',
      author_email='regebro@gmail.com',
      license='GPL',
      packages=find_packages(exclude=['ez_setup']),
      include_package_data=True)

解释Distribute的所有复杂和可能的使用就超出了本书的范围了。Distribute的完整文档在http://packages.python.org/distribute

使用Distribute运行测试

一旦你使用Distribute来打包你的模块,你就需要使用Distribute来运行你的测试。你可以添加test_suite参数到setup()调用的地方来告诉Distribute在哪能找到测试脚本。你也可以指定一个返回TestSuite类的模块、测试类或者函数来执行。经常地你可以把它设置成和基本包的名字相同。这将会让Distribute在那个包里寻找测试来运行。如果你有一个用于作为DocTests运行的单独文件,那么Distribute不会自动找到他们。在这些情况下,写一个使用所有测试返回TestSuite的函数是很容易的。

import unittest, doctest, StringIO

class TestCase1(unittest.TestCase):
    
    def test_2to3(self):
        assert True
        
def test_suite():
    suite = unittest.makeSuite(TestCase1)
    return suite

然后我们在setup()里指定这个函数:

from setuptools import setup, find_packages

readme = open('docs/README.txt', 'rt').read()
changes = open('docs/CHANGES.txt', 'rt').read()


setup(name='Supporting Python 3 examples',
      version="1.0",
      description="An example project for Supporting Python 3",
      long_description=readme + '\n' + changes,
      classifiers=[
          "Programming Language :: Python :: 2",
          "Topic :: Software Development :: Documentation"],
      keywords='python3 porting documentation examples',
      author='Lennart Regebro',
      author_email='regebro@gmail.com',
      license='GPL',
      packages=find_packages(exclude=['ez_setup']),
      include_package_data=True,
      test_suite='py3example.tests.test_suite')

现在你就可以使用python setup.py test来运行测试了。

使用Distribute运行2to3

一旦你在Python 2下做测试,你就可以把use_2to3关键字选项加进setup()里并且开始用Python 3开运行测试。也可以添加“Programming Language :: Python :: 3”到classifiers的列表里。这会告诉CheeseShop及你的用户,你支持Python 3。

from setuptools import setup, find_packages

readme = open('docs/README.txt', 'rt').read()
changes = open('docs/CHANGES.txt', 'rt').read()

setup(name='Supporting Python 3 examples',
      version="1.0",
      description="An example project for Supporting Python 3",
      long_description=readme + '\n' + changes,
      classifiers=[
          "Programming Language :: Python :: 2",
          "Programming Language :: Python :: 3",
          "Topic :: Software Development :: Documentation"],
      keywords='python3 porting documentation examples',
      author='Lennart Regebro',
      author_email='regebro@gmail.com',
      license='GPL',
      packages=find_packages(exclude=['ez_setup']),
      include_package_data=True,
      test_suite='py3example.tests.test_suite',
      use_2to3=True)

在Python 3下,测试命令现在会首先把文件复制进构建目录然后在他们上面运行2to3。之后它会从构建目录执行测试。在Python 2下use_2to3选项会被乎略。

Distribute会转换所有的Python文件及Python文件中的所有doctests,如果你的dctests位于单独的文本文件,它们就将不会被自动转换。通过把它们加进convert_2to3_doctests选项,Distribute将同样会转换它们。

使用额外的固定器(fixers),use_2to3_fixers可以设置一个包含固定器(fixers)的包的列表。这可以同时被包含于2to3的明确的固定器(fixers)及外部的固定器(fixers)使用,就像每一次你使用Zope Component Architecture时需要的固定器(fixers)。

from setuptools import setup, find_packages

readme = open('docs/README.txt', 'rt').read()
changes = open('docs/CHANGES.txt', 'rt').read()

setup(name='Supporting Python 3 examples',
      version="1.0",
      description="An example project for Supporting Python 3",
      long_description=readme + '\n' + changes,
      classifiers=[
          "Programming Language :: Python :: 2",
          "Programming Language :: Python :: 3",
          "Topic :: Software Development :: Documentation"],
      keywords='python3 porting documentation examples',
      author='Lennart Regebro',
      author_email='regebro@gmail.com',
      license='GPL',
      packages=find_packages(exclude=['ez_setup']),
      include_package_data=True,
      test_suite='py3example.tests.test_suite',
      use_2to3=True,
      convert_2to3_doctests=['doc/README.txt'],
      install_requires=['zope.fixers'],
      use_2to3_fixers=['zope.fixers'])

注意

当你修改setup.py时,可能会修改被转换的文件。最后一次运行时转换过程将会不知道哪些文件被修改了,所以不知道哪些文件在最后一次运行时被复制的文件现在需要复制并且转换。因些经常需要在修改setup.py后删除整个构建目录。


你现在应该已经准备好去偿试使用python3 setup.py test来在Python 3下执行测试了。很有可能一些测试会失败,但是只要2to3过程是工作的并测试在执行,甚至连它们也失败,那么你也走进了向支持Python 3前进的长路。现在是时候看看怎么修复这些失败的测试了,这将引导我们讨论觉的迁移问题。

附注:

[1]http://pypi.python.org/
[2]http://pypi.python.org/pypi/distribute


在湖闻樟注:

原文http://python3porting.com/2to3.html

引导页Supporting Python 3:(支持Python3):深入指南

目录Supporting Python 3(支持Python 3)——目录


转载于:https://my.oschina.net/soarwilldo/blog/509030

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值