作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Dario Bertini
Verified Expert in Engineering

Dario以编写可维护、简洁的后端代码为荣,并且喜欢自动化. 他喜欢用Haskell和Clojure工作.

Expertise

PREVIOUSLY AT

Google
Share

Python 3已经存在七年了, 然而,有些人仍然更喜欢使用Python 2而不是更新的版本. 对于第一次接触Python的初学者来说,这尤其是个问题. 我在之前的工作场所也意识到了这一点,同事们也有同样的情况. 他们不仅没有意识到两个版本之间的差异, 他们甚至不知道自己安装的是什么版本.

不可避免的是,不同的同事安装了不同版本的解释器. 如果他们试图盲目地在他们之间分享剧本,那将是一场灾难.

恰恰相反,这并不完全是他们的错. 需要更大的努力来记录和提高认识,以消除FUD(恐惧)的面纱, 不确定性和怀疑)有时会影响我们的选择. 这篇文章就是为他们而写的, 或者对于那些已经在使用Python 2但不确定是否要迁移到下一个版本的人, 也许是因为他们刚开始才尝试过版本3,当时版本3不够完善,对库的支持也更差.

Two Dialects, One Language

首先,Python 2和Python 3真的是不同的语言吗? 这不是一个微不足道的问题. 即使有些人会这样解决这个问题: “No, it’s not a new language”, as a matter of fact 一些可能会破坏兼容性而不会产生重要优势的提议被拒绝了.

Python 3是Python的新版本, 但它不一定向后兼容为Python 2编写的代码. 同时,编写与两个版本兼容的代码也是可能的, 这不是偶然,而是一个明确的承诺 Python developers 起草了几个PEP (Python扩展提案). 在语法不兼容的少数情况下, 由于Python是一种我们可以在运行时动态修改代码的语言, 我们可以解决这个问题,而不依赖于语法完全陌生于语言其余部分的预处理器.

因此,语法不是问题(特别是忽略Python 3之前的版本).3). 另一个很大的区别是代码的行为, 它的语义和大库的存在/缺失只适用于两个版本中的一个. 这确实是一个重大问题, 但对于那些已经有过其他编程语言经验的人来说,这并不是完全独特或新鲜的. 您可能已经获得了一个旧的代码库/库,无法使用最初使用的相同编译器的最新版本进行构建. 在这些情况下,编译器本身将帮助您(在Python中), 相反,帮助将来自您自己的测试套件).

为什么要让新版本有所不同呢? 这些变化会给我们带来什么好处呢?

A Concrete Example

假设我们想要编写一个程序来读取当前目录中文件/目录的所有者(在Unix系统上),并将它们打印到屏幕上.

# encoding: utf-8

from os import listdir, stat

为了使这个例子简单,我们不会使用' pwd '模块
names = {1000: 'dario',
         1001: u'олга'}

for node in listdir(b'.'):
    owner = names[stat(node).st_uid]
    print(owner + ': ' + node)

一切正常吗?? Apparently it does. 我们为包含源代码的文件指定了编码, 如果我们在目录中有一个由олга (uid 1001)创建的文件,那么它的名称将被正确地打印出来, 即使我们有非ascii名称的文件,这些也会正确打印.

还有一种情况我们还没有讲到:一个由олга创建的文件名中有非ascii字符的文件…

su олга -c "touch é"

让我们试着再次启动我们的小脚本,我们将得到一个:

UnicodeDecodeError: 'ascii'编解码器无法解码位置0的字节0xc3:序数不在范围内(128)

If you think about it, 类似的情况可能会很糟糕:您已经编写了程序(数千行而不是本例中的短短4行), 你开始收集一些用户, 其中一些甚至来自非英语国家,有着更奇特的名字. Everything is okay, 直到其中一个用户决定创建一个名称更普通的用户可以毫无问题地创建的文件. 现在您的代码将抛出一个错误, 服务器可能会以错误500回答来自该用户的每个请求, 您需要深入研究代码库,以理解为什么这些错误会突然出现.

Python 3如何帮助我们解决这个问题? 如果您尝试执行相同的脚本, 您将发现Python能够在您即将执行危险操作时立即检测到. 即使没有特殊名称和/或由特殊用户创建的文件, 您将立即收到如下异常:

TypeError:无法将'bytes'对象隐式转换为str '

Related to line:

print(owner + ': ' + node)

在我看来,错误信息甚至更容易理解. The str object is owner, and node is a bytes object. 知道了这一点,很明显,这个问题是由于 listdir 返回给我们的是一个字节对象列表吗.

不是所有人都知道的一个细节是 listdir 根据用作输入的对象的类型返回一个字节对象或unicode字符串的列表. I avoided using listdir('.') 完全可以在Python 2和Python 3上获得相同的行为, 否则在Python 3上,这将是一个unicode字符串,将使错误消失.

如果我们试图改变一个字符,从 listdir(b'.') to listdir(u'.') 我们将能够看到代码现在是如何在Python 3和Python 2上工作的. 为了完整起见,我们还应该进行更改 'dario' to u'dario'.

然而,Python 2和Python 3之间的这种行为差异是由两个版本处理字符串类型的根本差异所支持的, 这种差异主要是在从一个版本移植到另一个版本时发现的.

In my opinion, 这种情况象征着一句格言:“拆分者比拆分者更容易被合并”。. 在Python 2中,什么被集中在一起(unicode字符串和默认的字节字符串?, 它们可以自由地组合在一起)在Python 3中被拆分了.

自动转换工具

For this reason tools like 2to3, 即使写得很好,对自动转换其他差异也非常有用, have some limitations. 通过字节/unicode分割,行为的差异在运行时显现出来, 如果你有一个巨大的Python 2代码库,混合了这两种类型,那么一个只能做解析/静态分析的工具将无法拯救你. 你必须卷起袖子,正确地设计你的API,以决定到目前为止不加区分地接受任何类型的字符串的函数现在是否只适用于其中的一些(以及哪些)。. Conversely, 尽管用得越来越少了, 从Python 3到Python 2的转换工具更容易使用. Let’s see an example:

Sometime ago, I wrote a toy HTTP server (only dependency: python-magic), 这是Python 2的版本(自动从Python 3转换而无需任何手动更改): http://gist.github.com/berdario/8abfd9020894e72b310a

现在,如果你想,你可以直接看一下 code converted to Python 3 使用2to3,或者您可以直接在系统上转换它. 当尝试执行它时,您将意识到您可以尝试手工修复的每个错误都与字节/unicode分割有关.

您可以像这样手动应用更改:http://gist.github.com/berdario/34370a8bc39895cae139/revisions

这样,你的程序就可以在python3上运行了. 这些变化并不复杂, 但是它们仍然需要推断函数所处理的数据类型, and on the control flow. 120行改动了13行, 这个比例不太容易处理:需要移植数千行代码, 很容易就会有数百个需要修改.

If you’re curious, 然后你可以试着把这段代码从python3转换回python2. Using 3to2 you’d obtain this: http://gist.github.com/berdario/cbccaf7f36d61840e0ed. 其中唯一必须手动应用的更改是 .encode('utf-8') at line 55.

从Python 3开始(如果您需要将其转换回Python 2),会容易得多. 但如果你需要让你的代码在另一个版本上工作, 像这样的完全转换并不是最好的选择. 的两个版本保持兼容性要好得多 Python. 要做到这一点,你可以依靠像 futurize.

Python 3不只是关于Unicode

即使你没有机会在生产环境中使用Python 3(也许你正在使用的某个库体积庞大,只与Python 2兼容), 我建议您保持代码与Python 3兼容. You could even stub/mock 排除不兼容的库,以便您可以连续运行 your tests on both versions. 这将使您在将来最终准备迁移到Python 3时更容易, 更不用说它如何帮助你更好地设计API了, 或者像本文开头的例子那样识别错误.

所有这些都是关于移植和字节/unicode差异的讨论, 即使你最初对使用/从Python 3开始持怀疑态度, 可能会让您认为这是一种较小的罪恶,而不是在将来处理移植. 但如果移植是大棒,那么胡萝卜在哪里呢? 是添加到语言及其标准库中的新特性吗?

Well, 从Python 2的最后一个小版本发布到现在已经过去了5年, 有很多有趣的花边新闻正在堆积. 例如,我发现自己经常依赖于新事物 keyword-only arguments.

Optional Keyword Arguments

当我想写一个函数来合并任意数量的字典时(类似于 dict.update does, 但不修改输入)我发现添加一个函数参数让调用者自定义逻辑是很自然的. 这样就可以像下面这样调用这个函数,通过保留最右边的字典中的值来合并多个字典.

merge_dicts({“a”:1、“c”:3},{a: 4 b: 2}, {b: 1})
# {'b': -1, 'a': 4, 'c': 3}

同样地,通过加值来合并:

from operator import add
merge_dicts({“a”:1、“c”:3},{a: 4 b: 2}, {b: 1}, withf =添加)
# {'b': 1, 'a': 5, 'c': 3}

在Python 2中实现这样的API需要定义一个 **kwargs input and look for the withf argument. 如果调用者将参数输入错误为(e.g.) withfun 但是,错误将被静默地忽略. 在Python 3中,在变量参数之后添加一个可选参数是完全可以的(并且它只会与它的关键字一起使用):

def second(a, b):
    return b

Def merge_dicts(*dicts, withf=second):
    newdict = {}
    for d in dicts:
        shared_keys = newdict.keys() & d.keys()
        newdict.update({k: d[k] for k in d.keys() - newdict.keys()})
        newdict.更新({k: withf(newdict[k], d[k]) for k in shared_keys})
    return newdict

Unpacking Operator

Since Python 3.5、天真归并其实可以用 new unpacking operator. But even before 3.Python获得了一种改进的解包形式:

a, b, *rest = [1, 2, 3, 4, 5]
rest
# [3, 4, 5]

我们从3月开始就有这个了.0. Akin to destructuring, 这种解包是一种有限的/特别的模式匹配形式,通常用于函数式语言(它也用于流控制),它是Ruby和Javascript等动态语言(其中支持EcmaScript 2015)的常见功能。.

Simpler APIs for Iterables

In Python 2, 很多处理可迭代对象的api都是重复的, 默认的有严格的语义. 现在,所有东西都将根据需要生成值: zip(), dict.items(), map(), range(). 你想写你自己版本的吗 enumerate? 在Python 3中,它就像将标准库中的函数组合在一起一样简单:

zip(itertools.count(1), 'abc')

Is equivalent to enumerate('abc', 1).

Function Annotations

难道您不希望像这样简单地定义HTTP api吗?

@get('/balance')
def balance(user_id: int):
    pass
    
from decimal import Decimal

@post('/pay')
def pay(user_id: int, amount: Decimal):
    pass

No more '' Ad-hoc语法,以及使用任何类型/构造函数(如 Decimal),而不必定义自己的转换器.

Something like this has already been implemented, 你看到的是有效的Python语法, 利用新的注释使编写具有自文档性的api更加方便.

Wrapping Up

这只是几个简单的例子, 但是这些改进是深远的,并最终帮助您编写更健壮的代码. 一个例子是默认情况下启用的异常链回溯,在适当命名的文章中展示 “Python 3中最被低估的特性” 作者是Ionel Cristian m riecov,这本书也有涉及 other post 由Aaron Maxwell编写,加上Python 3中更严格的比较语义,以及新的 super behavior.

This is not all. There are plenty of other improvements,这些是我觉得每天影响最大的:

可以获得更全面的全景图 “What’s New” 文档的页面,或者对于更改的另一个概述,我也建议使用这个 post by Aaron Maxwell and these slides from Brett Cannon.

Python 2.7 will be supported until 2020,但不要等到2020年才转向新的(更好的)版本!

聘请Toptal这方面的专家.
Hire Now
Dario Bertini

Dario Bertini

Verified Expert in Engineering

London, United Kingdom

Member since August 28, 2015

About the author

Dario以编写可维护、简洁的后端代码为荣,并且喜欢自动化. 他喜欢用Haskell和Clojure工作.

作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Expertise

PREVIOUSLY AT

Google

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 privacy policy.

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 privacy policy.

Toptal Developers

Join the Toptal® community.