如何编写适用于Python 2和Python 3的代码?

时间:2018-04-28 17:34:29

标签: python django python-3.x

我维护的Django网站目前使用的是Python 2.7,但我知道我必须在几个月内将其升级到Python 3。如果我现在正在编写必须在Python 2中工作的代码,是否有一种 Pythonic 方式来编写它,这样如果我知道语法是什么,它也可以在Python 3中工作而不需要任何更改将在Python 3中?理想情况下,我希望代码在升级后仍然可以继续工作而不更改它但是我很容易发现我在代码库中完成了这些操作,这样我就可以在有空的时候更改代码。这是我正在谈论的一个例子:

# Python 2 uses 'iteritems'
def log_dict(**kwargs):
    for key, value in kwargs.iteritems():
        log.info("{0}: {1}".format(key, value))

# Python 3 uses 'items'
def log_dict(**kwargs):
    for key, value in kwargs.items():
        log.info("{0}: {1}".format(key, value))

6 个答案:

答案 0 :(得分:8)

official documentation建议如何做到这一点。随着时间的变化,文档已经发生了变化,因此值得直接转到源代码(特别是如果您在编写完成后的一两年内阅读了这个答案)。

还值得阅读Conservative Python 3 Porting Guide并浏览Nick Coghlan的Python 3 Q& A,尤其是this section

从2018年初开始追溯:

futurize

目前的官方建议是:

  • 只担心支持Python 2.7
  • 确保您拥有良好的测试覆盖率(coverage.py可以提供帮助; pip install coverage
  • 了解Python 2& 3
  • 使用Futurize(或Modernize)更新您的代码(例如pip install future
  • 使用Pylint来确保您不会在Python 3支持上退步(pip install pylint
  • 使用caniusepython3找出哪些依赖项阻止您使用Python 3(pip install caniusepython3
  • 一旦您的依赖项不再阻止您,请使用持续集成以确保您与Python 2& 3(tox可以帮助测试多个版本的Python; pip install tox
  • 考虑使用可选的静态类型检查,以确保您的类型用法适用于Python 2& 3(例如,使用mypy检查您在Python 2和Python 3下的输入。

注意最后一个建议。 Guido和另一个核心开发人员都大量参与领导大型团队将大型2.7代码库移植到3.x,并发现mypy非常有用(特别是在处理字节与unicode问题时)。事实上,这很大一部分原因是逐渐静态打字现在已成为该语言的官方部分。

您几乎肯定也想使用2.7中提供的所有future语句。这是显而易见的,他们似乎忘记了将其从文档中删除,但是,除了让您的生活更轻松(例如,您可以编写print函数调用),futurize和{{1} }(以及modernizesix)需要它。

6

该文档旨在让人们在不久的将来不可逆转地过渡到Python 3。如果您计划长时间坚持使用双版本代码,那么可能会更好地遵循之前的建议,这些建议主要围绕使用six而不是futurize。六个涵盖了两种语言之间的更多差异,并且还使您编写了明确关于双版本的代码,而不是尽可能接近Python 3,同时仍在2.7中运行。但缺点是你有效地做了两个端口 - 一个从2.7到六个双版本代码,然后,从3.x-只有六个代码到3.x-only"天然"代码。

2to3的

最初推荐的答案是使用2to3,这是一个可以自动将Python 2代码转换为Python 3代码的工具,或指导您这样做。如果您希望代码在中使用,则需要提供Python 2代码,然后在安装时运行sixer以将其移植到Python 3.这意味着您需要测试代码两种方式,通常修改它,使其仍然在2.7中工作,但也可以在2to3之后的3.x中工作,这并不总是很容易解决。事实证明这对于大多数非平凡的项目来说都是不可行的,因此核心开发人员不再推荐它 - 但它仍然内置在Python 2.7和3.x中,并且正在进行更新。

2to3还有两个变体:sixer自动将Python 2.7代码移植到使用六个代码的双版本代码,3to2允许您编写Python 3代码和自动端口在安装时回到2.7。这些都很受欢迎,但似乎不再使用了很多;现代化和未来化分别是他们的主要继承者。

针对您的具体问题,

  • 2to3如果您不介意2.7中的次要性能成本,则可以同时使用这两种方法。
  • 2to3可以在3.x的安装时自动将kwargs.items()更改为iteritems
  • futurize可用于执行上述任一操作。
  • 六将允许您编写items,其中six.iteritems(kwargs)位于2.7,iteritems位于3.x。
  • six还允许您编写items,这将在2.7中执行six.viewitems(kwargs)(这与viewitems在3.x中的操作相同,而不仅仅是类似的。)< / LI>
  • modernize和sixer会自动将items更改为kwargs.iteritems()
  • 3to2将允许您编写six.iteritems(kwargs)并在安装时自动将其转换为kwargs.items()
  • mypy可以验证您是否仅将结果用作一般可迭代(而不是专门用作迭代器),因此更改为viewitemsviewitems会使代码仍然正确输入。

答案 1 :(得分:2)

您可以导入未来的包

from future import ....

nested_scopes 2.1.0b1 2.2 PEP 227:静态嵌套范围

发电机2.2.0a1 2.3 PEP 255:简单发电机

division 2.2.0a2 3.0 PEP 238:更改分部操作员

absolute_import 2.5.0a1 3.0 PEP 328:进口:多线和绝对/相对

with_statement 2.5.0a1 2.6 PEP 343:“with”Statement

print_function 2.6.0a2 3.0 PEP 3105:打印功能

unicode_literals 2.6.0a2 3.0 PEP 3112:Python 3000中的字节文字

答案 2 :(得分:2)

让您的Django项目与两个Python版本兼容,包括以下步骤:

  1. 在每个模块的顶部添加from __future__ import unicode_literals,然后使用通常的引号,不带Unicode前缀用于Unicode字符串,b前缀用于字节串。

  2. 要确保值为bytestring,请使用django.utils.encoding.smart_bytes函数。要确保值为Unicode,请使用django.utils.encoding.smart_textdjango.utils.encoding.force_text函数。

  3. 在您的模型中使用__str__方法而不是__unicode__并添加python_2_unicode_compatible装饰器。

    # models.py
    # -*- coding: UTF-8 -*-
    from __future__ import unicode_literals
    from django.db import models
    from django.utils.translation import ugettext_lazy as _
    from django.utils.encoding import python_2_unicode_compatible
    
    @python_2_unicode_compatible
    class NewsArticle(models.Model):
        title = models.CharField(_("Title"), max_length=200)
         content = models.TextField(_("Content"))
    
        def __str__(self):
            return self.title
    
        class Meta:
            verbose_name = _("News Article")
            verbose_name_plural = _("News Articles")
    
  4. 要遍历字典,请使用django.utils.six中的iteritems(),iterkeys()和itervalues()。看看以下内容:

    from django.utils.six import iteritems
    
    d = {"imported": 25, "skipped": 12, "deleted": 3}
    for k, v in iteritems(d):
        print("{0}: {1}".format(k, v))
    
  5. 在捕获异常时,请使用as关键字,如下所示:

    try:
        article = NewsArticle.objects.get(slug="hello-world")
    except NewsArticle.DoesNotExist as exc:
        pass
    except NewsArticle.MultipleObjectsReturned as exc:
        pass
    
  6. 使用django.utils.six检查值的类型,如下所示:

    from django.utils import six
    
    isinstance(val, six.string_types) # previously basestring
    isinstance(val, six.text_type) # previously unicode
    isinstance(val, bytes) # previously str
    isinstance(val, six.integer_types) # previously (int, long)
    
  7. 使用django.utils.six.moves中的范围,而不是xrange,如下所示:

    from django.utils.six.moves import range
    
    for i in range(1, 11):
       print(i)
    
  8. <强> Source link

答案 3 :(得分:1)

除了从未来导入之外,还有六个项目旨在提供Python 2和Python 3之间的api兼容性:https://pypi.org/project/six/

您的示例代码可以在版本2和3之间兼容:

import six

for key, value in six.iteritems(dict):
    log.info("{0}: {1}".format(key, value))

仍有一些东西在2和3之间不兼容,就像f弦一样。

答案 4 :(得分:1)

有一些不同的工具可以帮助您确保编写python2 / 3兼容代码。

如果您有兴趣将python2代码移植到python3中,那么标准库附带的2to3程序将尝试将python 2程序转换为python 3。

https://docs.python.org/2/library/2to3.html

另一个很棒的工具是pylint。 pylint是一个python linter,可以在不修复问题的情况下向你描述问题。如果你在python3环境中pip install pylint,那么它将根据python 3的规则分析你的代码。如果你使用python 2安装pylint,它将使用python 2的规则执行相同的操作。

还有其他流行的和类似的工具,如flake8或autopep8,但我不熟悉它们足以宣传它们。

答案 5 :(得分:1)

六,未来是一条黄金法则,足以让即将到来的移民变得轻松

添加到每个python2文件,这是第一行:

from __future__ import absolute_import, unicode_literals

使用下面的字符串,迭代,元类,...

isinstance(sth, six.string_types)

six.iteritems(dict)

@six.add_metaclass(Meta)

依此类推six reference