mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-01 21:50:13 +08:00
Merge pull request #8626 from MjSeven/master
20180325 Could we run Python 2 and Python 3 code in the same VM with no code changes.md 翻译完毕
This commit is contained in:
commit
667ba35d23
@ -1,123 +0,0 @@
|
||||
Translating by MjSeven
|
||||
|
||||
|
||||
Could we run Python 2 and Python 3 code in the same VM with no code changes?
|
||||
======
|
||||
|
||||
Theoretically, yes. Zed Shaw famously jested that if this is impossible then Python 3 must not be Turing-complete. But in practice, this is unrealistic and I'll share why by giving you a few examples.
|
||||
|
||||
### What does it mean to be a dict?
|
||||
|
||||
Let’s imagine a Python 6 VM. It can read `module3.py` which was written for Python 3.6 but in this module it can import `module2.py` which was written for Python 2.7 and successfully use it with no issues. This is obviously toy code but let’s say that `module2.py` includes functions like:
|
||||
```
|
||||
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
|
||||
Now, when we want to use those functions from `module3`, we are faced with a problem: the dict type in Python 3.6 is different from the dict type in Python 2.7. In Python 2, dicts were unordered and their `.keys()`, `.values()`, `.items()` methods returned proper lists. That meant calling `.items()` created a copy of the state in the dictionary. In Python 3 those methods return dynamic views on the current state of the dictionary.
|
||||
|
||||
This means if `module3` called `module2.update_config_from_dict(some_dictionary)`, it would fail to run because the value returned by `dict.items()` in Python 3 isn’t a list and doesn’t have a `.pop()` method. The reverse is also true. If `module3` called `module2.config_to_dict()`, it would presumably return a Python 2 dictionary. Now calling `.items()` is suddenly returning a list so this code would not work correctly (which works fine with Python 3 dictionaries):
|
||||
```
|
||||
|
||||
|
||||
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}')
|
||||
|
||||
```
|
||||
|
||||
Finally, using `module2.update_in_place()` would fail because the value of `.items()` in Python 3 now doesn’t allow the dictionary to change during iteration.
|
||||
|
||||
There’s more issues with this dictionary situation. Should a Python 2 dictionary respond `True` to `isinstance(d, dict)` on Python 3? If it did, it’d be a lie. If it didn’t, code would break anyway.
|
||||
|
||||
### Python should magically know types and translate!
|
||||
|
||||
Why can’t our Python 6 VM recognize that in Python 3 code we mean something else when calling `some_dict.keys()` than in Python 2 code? Well, Python doesn’t know what the author of the code thought `some_dict` should be when she was writing that code. There is nothing in the code that signifies whether it’s a dictionary at all. Type annotations weren’t there in Python 2 and, since they’re optional, even in Python 3 most code doesn’t use them yet.
|
||||
|
||||
At runtime, when you call `some_dict.keys()`, Python simply looks up an attribute on the object that happens to hide under the `some_dict` name and tries to run `__call__()` on that attribute. There’s some technicalities with method binding, descriptors, slots, etc. but this is the gist of it. We call this behavior “duck typing”.
|
||||
|
||||
Because of duck typing, the Python 6 VM would not be able to make compile-time decisions to translate calls and attribute lookups correctly.
|
||||
|
||||
### OK, so let’s make this decision at runtime instead
|
||||
|
||||
The Python 6 VM could implement this by tagging every attribute lookup with information “call comes from py2” or “call comes from py3” and make the object on the other side dispatch the right attribute. That would slow things down a lot and use more memory, too. It would require us to keep both versions of the given type in memory with a proxy used by user code. We would need to sync the state of those objects behind the user’s back, doubling the work. After all, the memory representation of the new dictionary is different than in Python 2.
|
||||
|
||||
If your head spun thinking about the problems with dictionaries, think about all the issues with Unicode strings in Python 3 and the do-it-all byte strings in Python 2.
|
||||
|
||||
### Is everything lost? Can’t Python 3 run old code ever?
|
||||
|
||||
Everything is not lost. Projects get ported to Python 3 every day. The recommended way to port Python 2 code to work on both versions of Python is to run [Python-Modernize][1] on your code. It will catch code that wouldn’t work on Python 3 and translate it to use the [six][2] library instead so it runs on both Python 2 and Python 3 after. It’s an adaptation of `2to3` which was producing Python 3-only code. `Modernize` is preferred since it provides a more incremental migration route. All this is outlined very well in the [Porting Python 2 Code to Python 3][3] document in the Python documentation.
|
||||
|
||||
But wait, didn’t you say a Python 6 VM couldn’t do this automatically? Right. `Modernize` looks at your code and tries to guess what’s going to be safe. It will make some changes that are unnecessary and miss others that are necessary. Famously, it won’t help you with strings. That transformation is not trivial if your code didn’t keep the boundaries between “binary data from outside” and “text data within the process”.
|
||||
|
||||
So, migrating big projects cannot be done automatically and involves humans running tests, finding problems and fixing them. Does it work? Yes, I helped [moving a million lines of code to Python 3][4] and the switch caused no incidents. This particular move regained 1/3 of memory on our servers and made the code run 12% faster. That was on Python 3.5. But Python 3.6 got quite a bit faster and depending on your workload you could maybe even achieve [a 4X speedup][5] .
|
||||
|
||||
### Dear Zed
|
||||
|
||||
Hi, man. I follow your story for over 10 years now. I’ve been watching when you were upset you were getting no credit for Mongrel even though the Rails ecosystem pretty much ran all on it. I’ve been there when you reimagined it and started the Mongrel 2 project. I was following your surprising move to use Fossil for it. I’ve seen you abruptly depart from the Ruby community with your “Rails is a Ghetto” post. I was thrilled when you started working on “Learn Python The Hard Way” and have been recommending it ever since. I met you in 2013 at [DjangoCon Europe][6] and we talked quite a bit about painting, singing and burnout. [This photo of you][7] is one of my first posts on Instagram.
|
||||
|
||||
You almost pulled another “Is a Ghetto” move with your [“The Case Against Python 3”][8] post. I think your heart is in the right place but that post caused a lot of confusion, including many people seriously thinking you believe Python 3 is not Turing-complete. I spent quite a few hours convincing people that you said so in jest. But given your very valuable contribution of “Learn Python The Hard Way”, I think it was worth doing that. Especially that you did update your book for Python 3. Thank you for doing that work. If there really are people in our community that called for blacklisting you and your book on the grounds of your post alone, call them out. It’s a lose-lose situation and it’s wrong.
|
||||
|
||||
For the record, no core Python dev thinks that the Python 2 -> Python 3 transition was smooth and well planned, [including Guido van Rossum][9]. Seriously, watch that video. Hindsight is 20/20 of course. In this sense we are in fact aggressively agreeing with each other. If we went to do it all over again, it would look differently. But at this point, [on January 1st 2020 Python 2 will reach End Of Life][10]. Most third-party libraries already support Python 3 and even started releasing Python 3-only versions (see [Django][11] or the [Python 3 Statement of the scientific projects][12]).
|
||||
|
||||
We are also aggressively agreeing on another thing. Just like you with Mongrel, Python core devs are volunteers who aren’t compensated for their work. Most of us invested a lot of time and effort in this project, and so [we are naturally sensitive][13] to dismissive and aggressive comments against their contribution. Especially if the message is both attacking the current state of affairs and calling for more free labor.
|
||||
|
||||
I hoped that by 2018 you’d let your 2016 post go. There were a bunch of good rebuttals. [I especially like eevee’s][14]. It specifically addresses the “run Python 2 alongside Python 3” scenario as not realistic, just like running Ruby 1.8 and Ruby 2.x in the same VM, or like running Lua 5.1 alongside 5.3. You can’t even run C binaries compiled against libc.so.5 with libc.so.6. What I find most surprising though is that you claimed that Python core is “purposefully” creating broken tools like 2to3, created by Guido in whose best interest it is for everybody to migrate as smoothly and quickly as possible. I’m glad that you backed out of that claim in your post later but you have to realize you antagonized people who read the original version. Accusations of deliberate harm better be backed by strong evidence.
|
||||
|
||||
But it seems like you still do that. [Just today][15] you said that Python core “ignores” attempts to solve the API problem, specifically `six`. As I wrote above, `six` is covered by the official porting guide in the Python documentation. More importantly, `six` was written by Benjamin Peterson, the release manager of Python 2.7. A lot of people learned to program thanks to you and you have a large following online. People will read a tweet like this and they will believe it at face value. This is harmful.
|
||||
|
||||
I have a suggestion. Let’s put this “Python 3 was poorly managed” dispute behind us. Python 2 is dying, we are slow to kill it and the process was ugly and bloody but it’s a one way street. Arguing about it is not actionable anymore. Instead, let’s focus on what we can do now to make Python 3.8 better than any other Python release. Maybe you prefer the role of an outsider looking in, but you would be much more impactful as a member of this community. Saying “we” instead of “they”.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: http://lukasz.langa.pl/13/could-we-run-python-2-and-python-3-code-same-vm/
|
||||
|
||||
作者:[Łukasz Langa][a]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:http://lukasz.langa.pl
|
||||
[1]:https://python-modernize.readthedocs.io/
|
||||
[2]:http://pypi.python.org/pypi/six
|
||||
[3]:https://docs.python.org/3/howto/pyporting.html
|
||||
[4]:https://www.youtube.com/watch?v=66XoCk79kjM
|
||||
[5]:https://twitter.com/llanga/status/963834977745022976
|
||||
[6]:https://www.instagram.com/p/ZVC9CwH7G1/
|
||||
[7]:https://www.instagram.com/p/ZXtdtUn7Gk/
|
||||
[8]:https://learnpythonthehardway.org/book/nopython3.html
|
||||
[9]:https://www.youtube.com/watch?v=Oiw23yfqQy8
|
||||
[10]:https://mail.python.org/pipermail/python-dev/2018-March/152348.html
|
||||
[11]:https://pypi.python.org/pypi/Django/2.0.3
|
||||
[12]:http://python3statement.org/
|
||||
[13]:https://www.youtube.com/watch?v=-Nk-8fSJM6I
|
||||
[14]:https://eev.ee/blog/2016/11/23/a-rebuttal-for-python-3/
|
||||
[15]:https://twitter.com/zedshaw/status/977909970795745281
|
@ -0,0 +1,120 @@
|
||||
我们可以在同一个虚拟机中运行 Python 2 和 Python 3 代码而不需要更改代码吗?
|
||||
=====
|
||||
|
||||
从理论上来说,可以。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 中,dicts 是无序的,它们的 `.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 的调用”,并使对象发送正确的属性。这会让事情变得很慢,并且使用更多的内存。这将要求我们使用用户代码使用的代理将两种版本的给定类型保留在内存中。我们需要将这些对象的状态同步到用户背后,使工作加倍。毕竟,新字典的内存表示与 Python 2 不同。
|
||||
|
||||
如果你在思考字典问题,考虑 Python 3 中的 Unicode 字符串和 Python 2 中的字节(byte)字符串的所有问题。
|
||||
|
||||
### 一切都会丢失吗?Python 3 不能运行旧代码?
|
||||
|
||||
一切都不会丢失。每天都会有项目移植到 Python 3。将 Python 2 代码移植到两个版本的 Python 上推荐方法是在代码上运行 [Python-Modernize][1]。它会捕获那些在 Python 3 上不起作用的代码,并使用 [six][2] 库将其替换,以便它在 Python 2 和 Python 3 上运行。这是对 `2to3` 的改编,它正在生成 Python 3-only 代码。`Modernize` 是首选,因为它提供了更多的增量迁移路线。所有的这些在 Python 文档中的 [Porting Python 2 Code to Python 3][3]文档中都有很好的概述。
|
||||
|
||||
但是,等一等,你不是说 Python 6 的虚拟机不能自动执行此操作吗?对。`Modernize` 查看你的代码,并试图猜测哪些是安全的。它会做出一些不必要的改变,还会错过其他必要的改变。但是,它不会帮助你处理字符串。如果你的代码没有保留“来自外部的二进制数据”和“流程中的文本数据”之间的界限,那么这种转换并非微不足道。
|
||||
|
||||
因此,迁移大项目不能自动完成,并且涉及人类进行测试,发现问题并修复它们。它工作吗?是的,我曾帮助[将一百万行代码迁移到 Python 3][4],并且交换没有造成事故。这一举措重获了我们服务器内存的 1/3,并使代码运行速度提高了 12%。那是在 Python 3.5 上,但是 Python 3.6 的速度要快得多,根据你的工作量,你甚至可以达到 [4 倍加速][5]。
|
||||
|
||||
### 亲爱的 Zed
|
||||
|
||||
hi,伙计,我关注你已经超过 10 年了。我一直在观察,当你感到沮丧的时候,你对 Mongrel 没有任何信任,尽管 Rails 生态系统几乎全部都在上面运行。当你重新设计它并开始 Mongrel 2 项目时,我一直在观察。我一直在关注你使用 Fossil 这一令人惊讶的举动。随着你发布 “Rails 是一个贫民窟”的帖子,我看到你突然离开了 Ruby 社区。当你开始编写 “Learn Python The Hard Way” 并且开始推荐它时,我感到非常兴奋。2013 年我在 [DjangoCon Europe][6] 见过你,我们谈了很多关于绘画,唱歌和倦怠的内容。关于[这张你的照片][7]是我在 Instagram 上的第一篇文章。
|
||||
|
||||
你几乎把另一个“贫民区”的行动与 [“反对 Python 3” 案例][8] 文章拉到一起。我认为你本意是好的,但是这篇文章引起了很多混乱,包括许多人认为你认为 Python 3 不是图灵完整的。我花了好几个小时让人们相信,你是在开玩笑。但是,鉴于你对 Python 学习方式的重大贡献,我认为这是值得的。特别是你为 Python 3 更新了你的书。感谢你做这件事。如果我们社区中真的有人要求因你的帖子为由将你和你的书列入黑名单,把他们请出去。这是一个双输的局面,这是错误的。
|
||||
|
||||
在记录中,没有一个核心 Python 开发人员认为 Python 2 到 Python 3 的转换过程会顺利而且计划得当,[包括 Guido van Rossum][9]。真的,可以看那个视频,这有点事后诸葛亮的意思了。从这个意义上说,我们实际上是积极地相互认同的。如果我们再做一次,它会看起来不一样。但在这一点上,[在 2020 年 1 月 1 日,Python 2 将会到达终结][10]。大多数第三方库已经支持 Python 3,甚至开始发布只支持 Python 3 版本(参见[Django][11]或 [科学项目关于 Python 3 的声明][12])。
|
||||
|
||||
我们也积极地就另一件事达成一致。就像你于 Mongrel 一样,Python 核心开发人员是志愿者,他们的工作没有得到报酬。我们大多数人在这个项目上投入了大量的时间和精力,因此[我们自然而然敏感][13]于那些对他们的贡献不屑一顾和激烈的评论。特别是如果这个信息既攻击目前的事态,又要求更多的自由劳动。
|
||||
|
||||
我希望到 2018 年你会让忘记 2016 发布的帖子,有一堆好的反驳。[我特别喜欢 eevee][14](译注:eevee 是一个为 Blender 设计的渲染器)。它特别针对“一起运行 Python 2 和 Python 3 ”的场景,这是不现实的,就像在同一个虚拟机中运行 Ruby 1.8 和 Ruby 2.x 一样,或者像 Lua 5.3 和 Lua 5.1 同时运行一样。你甚至不能用 libc.so.6 运行针对 libc.so.5 编译的 C 二进制文件。然而,我发现最令人惊讶的是,你声称 Python 核心开发者是“有目的地”创造诸如 2to3 之类的破坏工具,这些由 Guido 创建,其最大利益就是让每个人尽可能顺利,快速地迁移。我很高兴你在之后的帖子中放弃了这个说法,但是你必须意识到你会激怒那些阅读原始版本的人。对蓄意伤害的指控最好有强有力的证据支持。
|
||||
|
||||
但看起来你仍然会这样做。[就在今天][15]你说 Python 核心开发者“忽略”尝试解决 API 的问题,特别是 `six`。正如我上面写的那样,Python 文档中的官方移植指南涵盖了 “six”。更重要的是,`six` 是由 Python 2.7 的发布管理者 Benjamin Peterson 编写。很多人学会了编程,这要归功于你,而且由于你在网上有大量的粉丝,人们会阅读这样的推文,他们会相信它的价值,这是有害的。
|
||||
|
||||
我有一个建议,让我们把 “Python 3 管理不善”的争议搁置起来。Python 2 正在死亡,这个过程会很慢,并且它是丑陋而血腥的,但它是一条单行道。争论那些没有用。相反,让我们专注于我们现在可以做什么来使 Python 3.8 比其他任何 Python 版本更好。也许你更喜欢看外面的角色,但作为这个社区的成员,你会更有影响力。请说“我们”而不是“他们”。
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: http://lukasz.langa.pl/13/could-we-run-python-2-and-python-3-code-same-vm/
|
||||
|
||||
作者:[Łukasz Langa][a]
|
||||
译者:[MjSeven](https://github.com/MjSeven)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:http://lukasz.langa.pl
|
||||
[1]:https://python-modernize.readthedocs.io/
|
||||
[2]:http://pypi.python.org/pypi/six
|
||||
[3]:https://docs.python.org/3/howto/pyporting.html
|
||||
[4]:https://www.youtube.com/watch?v=66XoCk79kjM
|
||||
[5]:https://twitter.com/llanga/status/963834977745022976
|
||||
[6]:https://www.instagram.com/p/ZVC9CwH7G1/
|
||||
[7]:https://www.instagram.com/p/ZXtdtUn7Gk/
|
||||
[8]:https://learnpythonthehardway.org/book/nopython3.html
|
||||
[9]:https://www.youtube.com/watch?v=Oiw23yfqQy8
|
||||
[10]:https://mail.python.org/pipermail/python-dev/2018-March/152348.html
|
||||
[11]:https://pypi.python.org/pypi/Django/2.0.3
|
||||
[12]:http://python3statement.org/
|
||||
[13]:https://www.youtube.com/watch?v=-Nk-8fSJM6I
|
||||
[14]:https://eev.ee/blog/2016/11/23/a-rebuttal-for-python-3/
|
||||
[15]:https://twitter.com/zedshaw/status/977909970795745281
|
Loading…
Reference in New Issue
Block a user