飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 认识Python

    • Python 历史与特点
    • Python 2 与 Python 3 的核心差异
    • 安装与运行 Python 2.7.18
    • 编码规范 PEP 8
  • 第2章 基础语法

    • 变量与对象
    • 数字类型
    • 字符串 str
    • Unicode 字符串
    • 运算符
    • 空值 None
  • 第3章 流程控制

    • if 条件语句
    • if-else 条件语句
    • if-elif-else 多分支
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 与 xrange
    • 循环控制:break、continue、pass
    • 循环 else 子句
  • 第4章 数据结构

    • 列表基础
    • 列表方法
    • 列表推导式
    • 元组
    • 字典基础
    • 字典方法
    • 字典循环技巧
    • 集合
    • 序列解包
    • 序列比较
  • 第5章 函数

    • 定义函数
    • 参数传递机制
    • 默认参数
    • 关键字参数
    • 可变参数
    • Lambda 表达式
    • 文档字符串
    • 函数对象
  • 第6章 模块与包

    • import 导入
    • 模块搜索路径
    • name 与主程序
    • 编译文件 .pyc 与 .pyo
    • 包结构
    • dir() 函数
  • 第7章 文件与IO

    • 打开与关闭文件
    • 文件读写方法
    • with 上下文管理器
    • 格式化输出:% 操作符
    • 格式化输出:str.format()
    • JSON 序列化
  • 第8章 面向对象

    • 类定义与实例化
    • init 构造方法
    • 类变量与实例变量
    • 方法调用与 self
    • 继承基础
    • 多重继承
    • 新式类与旧式类
    • 私有变量与名称改写
    • 属性装饰器 property
    • 类方法与静态方法
    • 魔术方法
    • 空类与数据记录
  • 第9章 异常处理

    • 异常类型
    • try-except
    • try-except-else-finally
    • 抛出异常 raise
    • 自定义异常
    • with 语句与上下文管理器
  • 第10章 迭代器与生成器

    • 迭代器协议
    • 生成器函数
    • 生成器表达式
    • itertools模块
  • 第11章 标准库精要

    • os模块
    • sys模块
    • datetime模块
    • re模块
    • json模块
    • collections模块
    • math与random模块
    • urllib2与网络请求
    • subprocess与命令执行
    • threading与并发
    • unittest与测试
    • 虚拟环境与包管理
  • 第12章 工程实践

    • 调试技巧
    • 性能分析
    • 文档与注释
    • 下一步学习

Python 2 与 Python 3 的核心差异

Python 3.0 于 2008 年发布,是一次不兼容的彻底重构。Guido van Rossum 和核心开发团队认为,Python 2 积累的一些设计缺陷(尤其是字符串处理)已经严重到必须"破釜沉舟"才能修正。理解这些差异,是阅读 Python 2 代码和向 Python 3 迁移的基础。

print:从语句到函数

在 Python 2 中,print 是一个语句,语法类似自然语言:

print "Hello, world"
print "The answer is", 42          # 多个对象用逗号分隔,自动加空格
print "No newline",                # 末尾逗号取消换行

Python 3 中 print 变成了函数,必须加括号:

print("Hello, world")
print("The answer is", 42)
print("No newline", end="")        # 用 end 参数控制换行

这个改动看似微小,实则影响深远。作为函数,print 可以接受关键字参数(sep、end、file),可以被赋值给变量、作为参数传递,甚至可以用 lambda 包装。作为语句,它不具备这些灵活性。

输入函数:raw_input 与 input

Python 2 有两个输入函数,这是初学者最容易混淆的地方:

name = raw_input("Enter your name: ")   # 始终返回字符串
age = input("Enter your age: ")          # 等价于 eval(raw_input()),会执行输入内容!

raw_input() 安全且直观:用户输入什么,就返回什么字符串。input() 则危险得多:它把用户的输入当作 Python 表达式执行。如果用户输入 __import__('os').system('rm -rf /'),程序真的会尝试执行这条命令。

Python 3 统一了这两个函数:input() 就是原来的 raw_input(),而旧的 input() 行为被彻底移除。

字符串:str 与 unicode 的分裂

Python 2 中有两种字符串类型,这是它最复杂也最致命的设计:

s = "hello"           # str 类型,存储字节序列(默认 ASCII 编码)
u = u"你好"           # unicode 类型,存储 Unicode 字符

str 本质上是字节串,每个字符对应一个字节(在 ASCII 范围内)。一旦涉及中文、日文等非 ASCII 字符,就必须显式使用 u"..." 前缀创建 unicode 对象。混用两者会引发著名的 UnicodeDecodeError:

s = "你好"            # 如果文件头没有 # -*- coding: utf-8 -*-,这里就会报错
u = u"你好"
print s + u           # UnicodeDecodeError: ascii codec can't decode...

Python 3 彻底解决了这个问题:str 就是 Unicode 字符串,而字节串用独立的 bytes 类型表示。两者不能隐式混用,必须显式编码/解码:

# Python 3
s = "你好"            # str,Unicode
b = s.encode('utf-8') # bytes,显式编码
s2 = b.decode('utf-8') # str,显式解码

整数除法:地板除的陷阱

Python 2 中,/ 运算符的行为取决于操作数的类型:

print 17 / 3          # 5,两个整数相除,结果截断为整数
print 17 / 3.0        # 5.66666666667,只要有一个浮点数,结果就是浮点数
print 17 // 3         # 5,地板除(// 在 Python 2 和 3 中行为一致)

这种"根据操作数类型改变行为"的设计极易引发 bug。Python 3 中 / 始终执行真除法(返回浮点数),// 始终执行地板除。Python 2.7 提供了 from __future__ import division 来提前体验 Python 3 的行为:

from __future__ import division
print 17 / 3          # 5.66666666667
print 17 // 3         # 5

range 与 xrange

Python 2 中 range(1000000) 会立刻创建一个包含一百万个整数的列表,消耗大量内存。xrange(1000000) 则返回一个惰性迭代器,只在需要时生成下一个数字:

for i in range(1000000):     # 先分配一大块内存,再遍历
    pass

for i in xrange(1000000):    # 几乎不占用额外内存
    pass

Python 3 中 range() 就是 Python 2 的 xrange(),而 xrange 这个名字被移除了。

字典方法:items 与 iteritems

Python 2 的字典提供了两套方法,分别返回列表和迭代器:

d = {'a': 1, 'b': 2}
print d.items()       # [('a', 1), ('b', 2)],返回完整列表
print d.iteritems()   # <dictionary-itemiterator>,返回迭代器

遍历大字典时,iteritems() 更省内存。Python 3 中 items() 直接返回视图(类似迭代器),iteritems() 被移除。

异常语法:逗号与 as

Python 2 支持两种捕获异常的语法:

try:
    1 / 0
except ZeroDivisionError, e:    # 传统语法,逗号分隔
    print "Error:", e

except ZeroDivisionError as e:  # Python 2.6+ 引入,与 Python 3 一致
    print "Error:", e

逗号语法在 Python 3 中被移除,因为它容易与元组字面量混淆:except (A, B), e 看起来像是捕获两个异常并赋值给 e,实际上在旧语法中确实如此,但可读性极差。

类:旧式类与新式类

Python 2 中,不继承任何类的定义是"旧式类":

class OldStyle:       # 旧式类,不继承 object
    pass

class NewStyle(object):  # 新式类,显式继承 object
    pass

旧式类的方法解析顺序(MRO)是深度优先,不支持 super()、property、描述符等现代特性。Python 3 中所有类都是新式类,object 继承是隐式的。

其他差异速查

特性Python 2.7Python 3.x
内置模块名__builtin__builtins
编译文件.pyc/.pyo 与源文件同目录__pycache__/*.pyc
整数类型int(有限)/ long(无限)统一为 int(无限精度)
比较不同类型3 < "a" 合法(按类型名比较)抛出 TypeError
next 方法.next().__next__()
列表推导式变量泄漏for x in ... 的 x 泄漏到外部作用域不泄漏
编码声明必须写 # -*- coding: utf-8 -*-默认 UTF-8

迁移工具:2to3

Python 官方提供了 2to3 脚本,可以自动将大部分 Python 2 代码转换为 Python 3:

$ 2to3 -w myscript.py       # -w 表示直接写入文件
$ 2to3 -w mypackage/        # 可以处理整个目录

但它不能处理所有情况,尤其是涉及字节/字符串边界、元类、C 扩展的代码。理解上述差异,才能判断 2to3 的转换是否正确,并手动修复它遗漏的问题。

上一页
Python 历史与特点
下一页
安装与运行 Python 2.7.18