这是一个非常好的问题,也是许多从 Python 2 迁移到 Python 3 的开发者最关心的问题。

答案是:不,Python 3 并不完全兼容 Python 2。
它们是两种不同的语言,尽管它们共享很多基础语法和理念,Python 的开发者们(以 Guido van Rossum 为首)认为 Python 2 已经走到了一个发展的死胡同,为了语言的长期健康发展,必须进行一次“不兼容”的升级。
下面我将详细解释为什么不兼容,以及主要的差异点,这对于理解两者的区别至关重要。
核心原因:为什么不追求完全兼容?
追求完全兼容意味着 Python 3 必须要保留 Python 2 的所有设计缺陷和过时的特性,这将阻碍语言的进化,Python 3 的设计目标之一就是修正 Python 2 中存在的设计缺陷和一致性问题,即使这意味着要破坏向后兼容性。

Python 2 vs Python 3 的主要差异
以下是导致两者不兼容的最关键和最常见的区别:
print 语句 vs print() 函数
这是最直观、也是最早被注意到的区别。
-
Python 2:
print是一个语句,不是函数。# Python 2 print "Hello, World!" # 直接打印 print "Hello", "World" # 打印两个值,用空格分隔,末尾不带换行符 print >> sys.stderr, "Error message" # 重定向输出
-
Python 3:
print是一个函数,必须使用括号。
(图片来源网络,侵删)# Python 3 print("Hello, World!") # 函数调用 # 实现旧版 "print a, b" 的功能 print("Hello", "World", end=' ') # 使用 end 参数指定结尾字符,默认是换行符 print() # 打印一个换行符 # 实现重定向功能 import sys print("Error message", file=sys.stderr)
影响: 这是最简单的区别,但也是所有代码都需要修改的地方,它使得 print 的行为更加灵活和一致,可以被用在更复杂的表达式和函数式编程中。
整数除法
这是在数值计算中一个非常经典且容易出错的陷阱。
-
Python 2: 两个整数相除,结果会截断小数部分,返回一个整数(
int)。# Python 2 >>> 5 / 2 2 # 结果是整数 2 >>> 5 / 2.0 2.5 # 如果其中有一个是浮点数,结果才是浮点数
-
Python 3: 两个整数相除,结果会返回一个浮点数(
float),除非你使用 运算符进行“地板除”。# Python 3 >>> 5 / 2 2.5 # 结果是浮点数 2.5 >>> 5 // 2 2 # // 运算符执行地板除,结果为整数
影响: 这个改动使得数学计算的结果更符合直觉,避免了大量的精度丢失 bug,但在从 Python 2 迁移旧代码时,必须仔细检查所有除法运算。
Unicode 支持
这是两者之间最根本、最重要的区别。
-
Python 2: 有两种字符串类型:
str: 字节串,是 Python 的默认字符串类型,它实际上是一串字节,处理非 ASCII 字符时会非常麻烦。unicode: 真正的 Unicode 字符串类型,你需要显式地创建它,u'你好'。
# Python 2 s = "你好" # 这是一个 str (字节串) u = u"你好" # 这是一个 unicode # 混合使用会导致 TypeError # s + u # 会报错
-
Python 3: 字符串模型被彻底简化:
str: 现在是 Unicode 字符串,是默认的字符串类型,可以无缝地存储和显示任何语言的字符。bytes: 对应 Python 2 的str,专门用于处理原始字节数据(如网络数据、文件读写)。
# Python 3 s = "你好" # 这是一个 str (Unicode 字符串) b = s.encode('utf-8') # 编码成 bytes u = s.decode('utf-8') # 从 bytes 解码成 str # 混合使用会导致 TypeError # s + b # 会报错
影响: Python 3 的 Unicode 模型让现代软件开发(特别是处理网页、数据库和多语言文本)变得无比简单和健壮,从 Python 2 迁移时,所有字符串编码/解码的逻辑都需要被审查和重写。
xrange vs range
-
Python 2:
range()函数会直接生成一个完整的列表,如果范围很大(如range(10000000)),会消耗大量内存。xrange()是一个“ xrange 对象”,它是一个惰性序列,只在需要时才生成下一个值,非常节省内存。 -
Python 3:
range()函数的行为已经和 Python 2 的xrange()一样了,它返回的是一个range对象(惰性序列),为了节省内存,Python 2 的xrange()被移除,range()成为了唯一的实现。# Python 2 # r = range(1000000) # 会创建一个包含100万个整数的列表,占用大量内存 # r = xrange(1000000) # 只创建一个 xrange 对象,占用很少内存 # Python 3 r = range(1000000) # 只创建一个 range 对象,占用很少内存 # 如果你需要一个列表,可以显式转换: list(r)
影响: 对于大多数现代循环 for i in range(...) 这个改动是透明的,但如果你曾经依赖 range() 来生成一个列表,那么在 Python 3 中需要使用 list() 函数来显式转换。
其他不兼容的改动
-
异常处理语法:
- Python 2:
except Exception, e:(逗号) - Python 3:
except Exception as e:(as 关键字)# Python 2 # try: # ... # except ValueError, e: # ...
Python 3
try: ... except ValueError as e: ...
- Python 2:
-
import语句:- Python 2: 可以相对导入,如
from . import module,但行为有时令人困惑。 - Python 3: 相对导入的语法被标准化和明确化,必须使用点号前缀,并且不能从主模块(
__main__)使用相对导入。
- Python 2: 可以相对导入,如
-
dict的.keys(),.values(),.items():- Python 2: 这些方法返回列表(
list)。 - Python 3: 为了节省内存,它们返回的是“视图对象”(
dict_keys,dict_values,dict_items),这些视图是动态的、可迭代的,如果你需要一个列表,需要用list()显式转换。
- Python 2: 这些方法返回列表(
如何处理兼容性问题?
既然不兼容,那么如何让代码同时运行在 Python 2 和 Python 3 上呢?主要有两种策略:
使用 __future__ 导入
对于 Python 3 中引入的新语法,但希望在 Python 2 中也能使用类似行为的情况,可以在 Python 2 文件的开头使用 from __future__ import ...。
# Python 2 文件
from __future__ import print_function # 使 print 成为函数
from __future__ import division # 使 / 执行真除法
from __future__ import absolute_import # 优化导入行为
# 之后就可以像在 Python 3 中一样写代码了
print("Hello")
print(5 / 2)
使用兼容性库 (如 six, future, 2to3)
-
2to3: 这是一个官方提供的工具,它可以自动分析你的 Python 2 代码,并将其转换为 Python 3 代码,它处理了大部分语法层面的不兼容问题,但对于像 Unicode 这样的逻辑问题,仍需人工干预。 -
six: 这是一个流行的第三方库,它提供了一系列函数和类,让你可以用同一套代码在 Python 2 和 Python 3 上运行,它在内部处理了版本差异。import six if six.PY2: # Python 2 特有的代码 u = u"unicode string" else: # Python 3 特有的代码
