蚊章汇 - Łukasz Langa
type: channel
link: https://linuxcn.bwsl.wang/author/933/
description: Łukasz Langa
items: 我们可以在同一个虚拟机中运行 Python 2 和 3 代码而不需要更改代码吗？

我们可以在同一个虚拟机中运行 Python 2 和 3 代码而不需要更改代码吗？
type: item
date: 2018-05-09T03:07:00+08:00
description: 从理论上来说，可以。Zed Shaw 说过一句著名的话，如果不行，那么 Python 3 一定不是图灵完备的。但在实践中，这是不现实的，我将通过给你们举几个例子来说明原因。对于字典（dict）来说，这意味着什么？让我们来想象一台拥有 Python 6 的虚拟机，它可以读取 Python 3.6 编写的 module3.py。但是在这个模块中，它可以导入 Python 2.7 编写的 module2.py，并成功使用它，没有问题。这显然是实验代码，但假设 module2.py 包含以下的功能：def update_config_from_dict(config_dict): items = config_dict.items() while items: k, v = items.pop() memcache.set(k, v) def config_to_dict(): result = {} for k, v in memcache.getall(): result[k] = v return result def update_in_place(config_dict): for k, v in config_dict.items(): new_value = memcache.get(k) if new_value is None: del config_dict[k] elif new_value != v: config_dict[k] = v 现在，当我们想从 module3 中调用这些函数时，我们遇到了一个问题：Python 3.6 中的字典类型与 Python 2.7 中的字典类型不同。在 Python 2 中，字典是无序的，它们的 .keys(), .values(), .items() 方法返回了正确的序列，这意味着调用 .items() 会在字典中创建状态的副本。在 Python 3 中，这些方法返回字典当前状态的动态视图。这意味着如果 module3 调用 module2.update_config_from_dict(some_dictionary)，它将无法运行，因为 Python 3 中 dict.items() 返回的值不是一个列表，并且没有 .pop() 方法。反过来也是如此。如果 module3 调用 module2.config_to_dict()，它可能会返回一个 Python 2 的字典。现在调用 .items() 突然返回一个列表，所以这段代码无法正常工作（这对 Python 3 字典来说工作正常）：def main(cmdline_options): d = module2.config_to_dict() items = d.items() for k, v in items: print(f'Config from memcache: {k}={v}') for k, v in cmdline_options: d[k] = v for k, v in items: print(f'Config with cmdline overrides: {k}={v}') 最后，使用 module2.update_in_place() 会失败，因为 Python 3 中 .items() 的值现在不允许在迭代过程中改变。对于字典来说，还有很多问题。Python 2 的字典在 Python 3 上使用 isinstance(d, dict) 应该返回 True 吗？如果是的话，这将是一个谎言。如果没有，代码将无法继续。Python 应该神奇地知道类型并会自动转换！为什么我们的 Python 6 的虚拟机无法识别 Python 3 的代码，在 Python 2 中调用 some_dict.keys() 时，我们还有别的意思吗？好吧，Python 不知道代码的作者在编写代码时，她所认为的 some_dict 应该是什么。代码中没有任何内容表明它是否是一个字典。在 Python 2 中没有类型注释，因为它们是可选的，即使在 Python 3 中，大多数代码也不会使用它们。在运行时，当你调用 some_dict.keys() 的时候，Python 只是简单地在对象上查找一个属性，该属性恰好隐藏在 some_dict 名下，并试图在该属性上运行 __call__()。这里有一些关于方法绑定，描述符，slots 等技术问题，但这是它的核心。我们称这种行为为“鸭子类型”。由于鸭子类型，Python 6 的虚拟机将无法做出编译时决定，以正确转换调用和属性查找。好的，让我们在运行时做出这个决定Python 6 的虚拟机可以标记每个属性，通过查找“来自 py2 的调用”或“来自 py3 的调用”的信息来实现这…