【问题标题】:Best way to write Python 2 and 3 compatible code using nothing but the standard library仅使用标准库编写 Python 2 和 3 兼容代码的最佳方法
【发布时间】:2015-08-06 00:02:16
【问题描述】:

我正在尝试使我的一些代码与 Python 2 和 3 兼容。

目前,我正在为range/xrange 之类的函数和dict.items/dict.iteritems 之类的方法而苦苦挣扎。理想情况下,我希望我的代码能够在 Python 3.x 中使用前者,而在 Python 2.x 中使用后者。

在我看来,使用if/else 是实现这一点的最简单方法:

if py >= 3:
    for item in array.items()
    ...
else:
    for item in array.iteritems()

但是,这样做会导致大量重复且丑陋的代码。有没有更好的方法仅使用标准库?我可以在代码开头的某处声明总是使用range/dict.items如果py >= 3xrange/dict.iteritems如果不是?

有可能做这样的事情吗?

if py < 3:
    use xrange as range

我环顾四周,我知道有几个库,比如 6 个 o futurize) 用于解决这个问题。但是,我正在使用仅运行 python 2.7 的服务器,并且不允许在其上安装任何额外的库。我有一些我想使用的 python3 代码,但我也想只维护一个版本的代码。

【问题讨论】:

  • 不要重新发明轮子。为什么限制使用标准库? pypi.python.org/pypi/six
  • 为什么对这个问题投反对票?还不够清楚吗?
  • 我建议使用six 模块。如果您不想安装它,您仍然可以查看它的代码,看看它是如何处理许多这些问题的,然后只需手动将它们选择性地添加到您自己的代码中。
  • 我想知道,为什么 ppl 试图强制 OP 使用 3d 零件库,facepalm。就像 jQuery 的孩子们......他们真的认为他们知道 JavaScript,在这里也是如此。对于 OP,在大多数情况下,您可以通过 dict 以其他方式循环 - 例如通过键或值。这不是迭代器,更“简单”的方式,但他支持所有版本的 Python,没有像“六”这样的自行车。最后,你不应该编写兼容的代码,这样做你将不会使用 py2 或 py3 的全部力量,结果将有一些平均的、未优化且难以调试的代码(由于兼容性层)
  • @alec_djinn Python3 正在/正在打破 python2 的向后兼容性。它总是这样,它就是这样设计的。这意味着如果你想同时支持两者,你需要有一个真正的兼容层,例如 six,它是受信任的、经过测试的,并且已经在大型项目(例如 Django)中得到证明。 不是你在几天内自己破解的。

标签: python python-2.7 python-3.x compatibility


【解决方案1】:
import sys

if sys.version_info.major > 2:
    xrange = range

但正如 Wim 所暗示的,这基本上是在自己重写six

你可以seesix 比处理range 做得更多。只是例如看看六种源代码中的_moved_attributes列表。

虽然 Python 带有“包含电池”,但它的标准库不是也不能包罗万象。也不是没有瑕疵。

有时市面上有更好的电池,不使用它们是一种浪费。只需将urllib2requests 进行比较。后者更好使用。

【讨论】:

  • 我非常喜欢这种方法,如果它解决了我需要解决的所有情况,我还在尝试..
  • 有什么类似的东西可以用于 items 和 iteritems 吗?
  • @alec_djinn 是的。它在六个源代码中处理。但我不会假装知道它是如何工作的。 :-)
  • Python.org 建议谨慎使用 six(即仅在必要时),请参阅 hereFuture 似乎是首选的解决方案。请参阅here 了解未来与六。
【解决方案2】:

我猜你在这种情况下混淆了arraydict

如果您因任何原因被限制使用 3-d 方库,那么为什么不这样:

def iterate_items(to_iterate):
    if py >= 3:
        return to_iterate.items()
    else:
        return to_iterate.iteritems()

然后使用它:

for item in iterate_items(your_dict):
    ...

【讨论】:

  • 不错的方法!但是我不喜欢这样,我需要用另一个函数调用来更改所有 items()。
  • 是的,感觉不太理想,但我认为当您还需要值时,这种解决方案比键循环解决方案要笨重得多,尤其是在列表/字典理解和生成器中。
【解决方案3】:

我建议在您的项目模块中编写 py2 或 py3,但不要将它们混合在一起,也不要包含任何类型的 2/3 检查。你的程序的逻辑不应该关心它的 python 版本,除了可能避免冲突的内置对象上的函数。

相反,从您自己的兼容性层导入 * 以修复您的框架之间的差异,并使用阴影使其对您的实际项目模块透明。

例如,在兼容性模块中,您可以编写 Roland Smith 的 range/xrange 替换,并在您的其他模块中添加“from compatibility import *”。这样做,每个模块都可以使用“xrange”,兼容层将管理 2/3 的差异。

不幸的是,它不能解决现有的对象函数,例如 dict.iteritems;通常你会对 dict 方法进行猴子补丁,但在内置类型上是不可能的(参见https://stackoverflow.com/a/192857/1741414)。我可以想象一些解决方法:

  • 函数包装器(基本上是 sobolevn 的答案)
  • 根本不要使用 .items() 函数;在键上使用简单的循环,然后使用这些键访问字典:
对于 my_dict 中的键: 值 = my_dict[键] # 剩下的代码在这里

【讨论】:

  • 摆脱 items() 可能是最好的策略,可以从根本上解决问题。感谢您的建议。
【解决方案4】:

简单的“别让我思考!”我使用的解决方案是启动简单的脚本:

#!/usr/bin/env python
# just make sure that Python 3 code runs fine with 2.7+ too ~98% of the time :)
from __future__ import (division, print_function, absolute_import,
                        unicode_literals)
from builtins import int
try:
    from future_builtins import ascii, filter, hex, map, oct, zip
except:
    pass
import sys
if sys.version_info.major > 2:
    xrange = range

(阻止大多数 pep8 linters 不必要地为此大喊大叫的额外提示:将最后 3 行移到上面的 try 块的内部和顶部)

但是我使用的唯一案例基本上是“shell脚本太大而且毛茸茸的,所以我很快将它们重写为Python,我只想让它们在Python 2和3下运行0依赖”。 请不要在真正的应用程序/库代码中使用它,直到您确切地知道以上所有行的后果是什么,以及它们是否足以满足您的用例。

此外,在这种情况下,.iteritems 的“解决方案”是“不要使用它”,忽略内存使用优化,而只是始终使用 .items - 如果这很重要,这意味着您不再编写“0 依赖项简单脚本”,因此只需选择 Python 3 并为其编写代码(如果您需要假装我们在 2008 年,则选择 Python 2)。

此外,请查看以下资源以正确理解:


(注意:我正在回答这个已经回答的问题,主要是因为接受的答案大致翻译为“你很愚蠢,这是愚蠢的”,我觉得这非常粗鲁 对于一个 SO 答案:无论这个问题多么愚蠢,以及实际回答它有多“错误”,一个问题都值得一个真正的答案。_

【讨论】:

  • "builtins" 不在 2.7 标准库中
【解决方案5】:
import sys
VERSION = float("{}.{}".format(sys.version_info.major, sys.version_info.minor))

通过使用它,我们可以为所需版本编写条件代码。

if VERSION >= 3.5:
     from subprocess import run as _run
else:
     from subprocess import call as _run

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-10-11
    • 1970-01-01
    • 2020-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多