虽然Python 3于2008年发布,但许多项目仍然停留在Python 2上。
可以理解的是,将大型现有代码库移植到新版本是一个让许多开发人员感到震惊的前景。但代码不可避免地需要维护,因此当所有能够解决所有问题的新功能都在新版本中时,是否真的值得留在过去?
我们将向您介绍Python 2程序缺少的一些功能,不仅是从3.0到现在的版本(3.7)。
为什么Python 3发生了
在2008年之前,Python开发人员有点头疼。作为Guido van Rossum的宠物项目,1989年圣诞假期开始的语言现在正以快节奏的速度增长。功能已经堆积,项目现在足够大,以至于早期的设计决策阻碍了实施。因此,添加新功能的过程正在成为黑客攻击现有代码的一种练习。
解决方案是Python 3:唯一故意破坏向后兼容性的版本。当时,这个决定是有争议的。公开使用的开源项目是否可以故意破坏旧代码?尽管有这种强烈反应,但仍然做出了决定,让Guido和开发人员有机会清理冗余代码,解决常见问题并重新设计语言。目标是在Python 3中只有一种明显的做事方式。这证明了当时的设计选择,我们仍然在十年之后发布3.x版本。
在__future__是现在
该__future__ 进口是时间旅行魔法的片,它允许你从Python的未来版本的召唤选择功能。事实上,当前的Python版本3.7包含
__future__ 尚未编写的版本的导入!
好吧,所以它并不像那样宏伟,__future__ 导入只是一个明确的指示,用于切换与当前版本打包在一起的新语法。我们认为我们之所以提到它是因为 下面列出的一些Python 3特性可以
__future__ 在2.6和2.7中导入和使用,它们分别与3.0和3.1一致发布。话虽如此,当然仍然建议升级,因为新功能在过去的版本中被“冻结”,并且不会受益于当前版本的发展和维护。
在Python 3中你错过了什么......
打印是一个功能
是的,我们知道大多数人都知道这一点,但这是Pythonistas最常用的语句之一。print 从关键字转移到功能,这是有道理的。
这个Python 2代码
1 2 |
print "Fresh Hacks Every Day" print "Foo", "some more text on the same line as foo" |
将在Python 3中成为以下内容。
1 2 3 |
print("Fresh Hacks Every Day") print("Foo", end='') print("some more text on the same line as foo") |
加油打开包装
这里我们有一个包含一些数据的元组。在Python 2中,我们已经可以解压缩到不同的变量,如下所示:
1 2 |
person = ("Steve", "Hammond", 34, "England", "spaces") name, surname, age, country, indent_pref = person |
但是,假设我们只关心名字和缩进偏好。在Python 3中,我们现在可以像这样解压缩:
1 2 3 4 五 6 |
person = ("Steve", "Hammond", 34, "England", "spaces") name, *misc, indent_pref = person name, surname, age, *misc = person *misc, indent_pref = person |
这在解包时提供了更大的灵活性 - 如果处理元组的时间比本例中的元组长,则特别方便。
解包通常用于for循环,特别是在使用zip() 或类似的东西时
enumerate()。作为应用新语法的示例,我们现在有一个函数
get_people_data(),它返回一个元组列表,
person 如上例所示。
1 2 3 |
for name, *misc, indent_pref in get_people_data(): if indent_pref is not "spaces": print(f"You will feel the full wrath of PEP 8, {name}.") |
这很好用。但是,如果我们能够以比字符串更好的方式存储缩进首选项,那不是很好吗?在类似于enum C的方式?
枚举
Python中的枚举。多么好吃 由于受欢迎的需求,他们自3.4以来就一直在标准库中。
现在我们可以像这样定义缩进首选项:
1 2 3 4 五 6 7 8 9 10 11 12 13 14 |
from enum import Enum class Indent(Enum): SPACES = 'spaces' TABS = 'tabs' EITHER = 'either' person = ("Steve", "Hammond", 34, "England", Indent.SPACES) person = ("Steve", "Hammond", 34, "England", Indent("spaces")) person = ("Steve", "Hammond", 34, "England", Indent("invalid value")) |
语法似乎有点奇怪,但在处理数字常量或从字符串初始化时它可能很有用。
师
一个简单但重大的变化:当在Python 3中划分整数时,默认情况下我们得到真正的浮点除法(在Python 2中划分两个整数总是产生整数结果)。
这是Python 2的行为:
而在Python 3中,我们获得更高的准确性:
1 2 3 4 |
>>> 1 / 3 0.3333333333333333 >>> 5 / 2 2.5 |
// 如果需要,当然可以用于地板整数除法。
如果您将代码从2移植到3,则必须注意这一变化; 这是一个很容易被忽视的人,可能会导致程序逻辑中的重大问题。
链接例外
捕获一个异常然后引发另一个异常是很常见的。这可能是因为您的应用程序有一组已定义的自定义异常,或者仅仅是因为您希望提供有关出错的更多信息。
在这里,我们有一个程序,粗略地计算工作所需的天数,以赚取一定比例的年薪。
1 2 3 4 五 6 7 8 9 10 11 12 13 14 |
class InvalidSalaryError(Exception): pass def days_to_earn(annual_pay, amount): try: annual_frac = amount / annual_pay except ZeroDivisionError: raise InvalidSalaryError("Could not calculate number of days") return 365 * annual_frac if __name__ == '__main__': print(days_to_earn(0, 4500)) print(days_to_earn(20000, 4500)) |
我们可以看到,如果指定年薪为零并且ZeroDivisionError发生了,则会被捕获,并且
InvalidSalaryError会引发一个。
让我们尝试用Python 2运行它。
1 2 3 4 五 6 7 |
$ python days_calc.py Traceback (most recent call last): File "exception_chaining.py", line 13, in <module> print(days_to_earn(0, 4500)) File "exception_chaining.py", line 9, in days_to_earn raise InvalidSalaryError("Could not calculate number of days") __main__.InvalidSalaryError: Could not calculate number of days |
因为我们抓住了ZeroDivisionError它,它被吞噬了,所以只显示了
InvalidSalaryError追溯。
现在让我们用Python 3运行它。
1 2 3 4 五 6 7 8 9 10 11 12 13 14 |
$ python3 days_calc.py Traceback (most recent call last): File "exceptions_chaining.py", line 7, in days_to_earn annual_frac = amount / annual_pay ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "exceptions_chaining.py", line 13, in <module> print(days_to_earn(0, 4_500)) File "exceptions_chaining.py", line 9, in days_to_earn raise InvalidSalaryError("Could not calculate number of days") __main__.InvalidSalaryError: Could not calculate number of days |
我们得到了一个更详细的错误报告,解释了导致该问题的原因InvalidSalaryError,并完成了追溯
ZeroDivisionError。当您使用其他人编写的代码时,这尤其有用,这些代码可能没有最详细的错误消息。
我们不会在这里讨论它,但是还有新的raise ... from 语法允许显式异常链接。
旁注:处理Python中的大数字
在上面的程序中,我们不得不处理int成千上万的s。写长数字
20000 或者
0b001110100010是屏幕眯眼的配方。这个怎么样把它们分开:
20_000,
0b_0011_1010_0010?这是Python 3将使您的生活更轻松的另一种方式 - 数字文字中的可选下划线,这有助于使事物更具可读性。
Unicode和字节
在Python 2中,字节和字符串的概念几乎可以互换,并且都属于该str 类型。这导致了一些非常讨厌的转换后果和不可预测的行为。要记住的标题是在Python 3中 所有字符串都是unicode。在文本和字节之间非常谨慎地创建了区别,提供了更加明确的工作方式。
在下面的示例中,我们在Python 2和3的解释器中测试字节串的类型,“普通”字符串和unicode字符串。
Python 2.7:
1 2 3 4 五 6 |
>>> type(b'foo') <type 'str'> >>> type('foo') <type 'str'> >>> type(u'foo') <type 'unicode'> |
Python 3.7:
1 2 3 4 五 6 |
>>> type(b'foo') <class 'bytes'> >>> type('foo') <class 'str'> >>> type(u'foo') <class 'str'> |
这意味着在Python 3中处理编码更加清晰,即使它是以更多.encode()s 的滑动为代价的。有可能当您的程序与外部世界中的任何内容(例如文件或套接字)进行通信时,您必须对数据进行编码和解码。
例如,如果您正在使用pyserial读/写串行设备,则需要对消息进行显式编码和解码。
1 2 3 4 五 6 7 8 9 10 11 12 |
import serial PORT = '/dev/ttyACM0' BAUD = 115200 ENCODING = 'utf-8' if __name__ == '__main__': ser = serial.Serial(port=PORT, baudrate=BAUD) ser.write('hello'.encode(ENCODING)) response_bytes = ser.readline() response_str = response_bytes.decode(ENCODING) |
字符串格式
在Python 2中格式化字符串的方式与C类似:
1 2 3 4 五 |
author = "Guido van Rossum" date = 1989 foo = "%s started Python in %d" % (author, date) |
文档明确指出这种格式化方式“表现出各种怪癖,导致许多常见错误(例如无法正确显示元组和字典)”。它也是不灵活的,并且在处理长串时开始变得难看。
我们现在有两种格式化字符串的新方法。一个是超级方便的,一个是超级强大的。
格式化字符串文字
通常称为“f-strings”,它们提供了一种非常简单的方法来引用当前范围内的任何可用变量。
检查此代码的直观性和可读性:
1 2 3 4 五 |
author = "Guido van Rossum" date = 1989 foo = f"{author} started Python in {date}" |
花括号内的任何内容都将被计算为表达式,因此如果您愿意,可以在字符串中放置小的逻辑或语句。在字符串中包含程序逻辑可能不太可读,但如果它只是记录以提供额外的调试信息,那么它真的很方便。
1 2 3 4 五 |
author = "Guido van Rossum" date = 1989 foo = f"{author.split()[0]} started Python {2000 - date} years before the turn of the century" |
.format方法
在Python 3中,每个字符串都有一个.format 方法,它为格式化提供了一系列令人眼花缭乱的选项,覆盖了绝大多数用例。我们不会在这里详细介绍,因为它被反向移植到2.6和2.7,因此对于大多数Python 2用户来说不会是一个很大的升级。
这是我们最喜欢的参考指南之一。
进口
假设您使用的是Python 2,并具有以下文件层次结构:
<font><font>PKG</font></font><font></font><font><font>├──service.py</font></font><font></font><font><font>└──misc.py</font></font><font></font><font><font>run.py</font></font><font></font><font><font>utils.py</font></font>
run.py 只需从 包中的
service 模块导入并调用一些东西
pkg。
但service.py 依赖于包含的功能
utils.py。所以在最重要的是
service.py 我们有这个声明。
看起来相当不张扬,一切都会好起来的。但是,如果我们的文件夹结构现在改变了,pkg 获得一个新
utils 模块怎么办?
<font><font>PKG</font></font><font></font><font><font>├──service.py</font></font><font></font><font><font>├──misc.py</font></font><font></font><font><font>└──utils.py</font></font><font></font><font><font>run.py</font></font><font></font><font><font>utils.py</font></font>
混淆时间:我们的代码现在切换并使用其中的utils.py 文件
pkg。如果我们碰巧安装了一个名为的库,事情就会变得更加混乱
utils。这种方法定义不够,并且一直导致Python 2中出现不可预测的行为。
Python 3拯救:如果导入应该是绝对的还是相对的,那么它就不再受支持语法了。根据需要,顶部service.py 可以成为以下任何选项。
1 2 3 4 五 6 7 |
from . import utils from pkg import utils import utils |
在这个例子中,这个功能可能看起来有点精确,但在处理包含大型包/导入层次结构的大型代码库时,您会很高兴。
其他
与Python 2相比,还有很多其他功能可以提供改进和全新功能,即使有些功能仅适用于小众领域。这里仅仅是少数:
- 添加
asyncio 库使编写异步程序变得轻而易举。现代编程必不可少的。
- 其他主要标准库增加(如
concurrent.futures,
ipaddress和
pathlib)
- 在Python 2中访问父类在语法上是不必要的。在Python 3中,
super() 变得更加神奇。
- 许多内置 函数,例如
zip(),现在返回迭代器而不是列表。在许多情况下,这将节省大量内存,而您甚至不知道。
map()
filter()
工具
如果您决定从Python 2到3移植代码是可行的方法,那么现有的工具可以让您的生活更轻松。
- 2to3 - 官方自动代码翻译工具。是的,它将为您移植代码!不能保证抓住一切,但做了很多乏味的句法摆弄。
- caniusepython3 - 一个非常棒的实用程序,用于检测哪些项目依赖项阻止您实现跨越。
- tox 自动化并简化了Python代码的测试。它允许您轻松地在多个版本的Python上测试您的代码,这对于任何项目来说都非常棒,但是当您在不同版本上测试新移植的代码库的成功时,它会特别有用。
结论
升级到Python 3有无数的理由 - 不仅仅是因为新功能的便利性,而是因为Python开发人员定期修复语言中随机,模糊的错误,仅在Python 3中。只有经过最强大测试,永不需要改变的代码库才有借口留在Python 2.其他任何东西都应该享受Python开发人员为使语言成为现在而付出的奇妙努力。
来源: https://hackaday.com/2018/08/15/stop-using-python-2-what-you-need-to-know-about-python-3/