Python简介

3 阅读17分钟

Python简介

Python发展历史

缘起:一个圣诞假期的“私活”项目

Python 的故事始于 1989 年的圣诞节。当时,荷兰程序员 Guido van Rossum 正在阿姆斯特丹的家中休假。为了打发无聊的假期时光,他决定启动一个个人项目:设计一门新的脚本解释语言。


初衷:继承 ABC 语言,填补 C 与 Shell 的空白

Guido van Rossum 的灵感并非凭空而来,而是源于他之前参与设计的一门名为 ABC 的教学语言。他认为 ABC 语言非常优美和强大,尤其适合非专业程序员,但其致命缺陷在于非开放性和扩展性差。因此,Guido van Rossum 创造 Python 的初衷非常明确:继承 ABC 语言简洁、易读、易用的核心特性;弥补其非开放、难扩展的缺陷,打造一门能与 C 语言交互的开放语言;填补 C 语言和 Shell 脚本之间的空白,既能快速编程,又具备强大功能。


命名:源自喜剧团体,而非“蟒蛇”

这是一个有趣的误会。很多人以为 Python 这个名字来源于「蟒蛇」,但实际上,Guido van Rossum 是一位英国喜剧团体 Monty Python 的忠实粉丝。为了体现这门新语言的趣味性和不严肃的风格,他直接从自己喜爱的喜剧《蒙提·派森的飞行马戏团》(Monty Python's Flying Circus)中汲取灵感,将这门语言命名为 「Python」 。所以,Python 的 Logo 虽然是条蟒蛇,但它的名字其实源于一个喜剧团体。


灵魂:《Python之禅》的哲学指引

Python 的灵魂,在于其独特的设计哲学——「Python之禅」(The Zen of Python)。这套哲学由 Python 核心开发者蒂姆·彼得斯(Tim Peters)于 2004 年撰写,它并非一门技术,而是 19 条指导代码编写的格言,凝聚了 Python 社区的价值观。你可以在任何 Python 环境中输入 import this 来一窥其全貌。

  • 优美胜于丑陋(Beautiful is better than ugly.): 代码不仅要功能正确,还要易于阅读和理解。优美的代码通常更易于维护和扩展。
  • 明确胜于隐晦(Explicit is better than implicit.): 代码应明确表达意图,避免隐藏的逻辑或“魔法”行为,确保每段代码的行为都可追溯、可预测。
  • 简单胜于复杂(Simple is better than complex.): 能用简单方法解决的问题,就不要引入不必要的复杂结构。
  • 可读性很重要(Readability counts.): 这是 Python 的核心。代码是写给人看的,其次才是机器。良好的命名、注释和格式是必需的。

「Python之禅」不仅适用于 Python ,也为所有软件开发提供了通用智慧,它倡导的是一种清晰、简洁、务实的编程美学。


发展:从 0.9.0 到 3.0 的演进之路

1991 年,Guido van Rossum 发布了 Python 的第一个公开发行版(0.9.0)。这个版本已经包含了类、函数、异常处理等核心特性,奠定了 Python 的基础。

  • 1994 年: Python 1.0 版本发布,引入了函数式编程工具(如 lambda、map、filter),标志着语言走向成熟。
  • 2000 年: Python 2.0 发布,增加了垃圾回收机制和对 Unicode 的支持,生态开始蓬勃发展。
  • 2008 年: Python 3.0 发布,这是一次不向后兼容的重大升级,旨在解决 Python 2.x 中的设计缺陷,为 Python 的未来发展铺平了道路。

从 Guido van Rossum 的个人项目到如今的全球主流编程语言,Python 的成功源于其简洁的设计哲学、强大的社区支持和在 Web 开发、数据分析、人工智能等领域的广泛应用。

Python 代码执行过程:

当你满怀期待地敲下 python my_script.py,然后按下回车键,屏幕上最终呈现出你期望的结果。这看似简单的瞬间,背后却上演着一场由 Python 解释器精心编排的复杂“魔术”。今天,就让我们一同揭开这层神秘的面纱,深入探索 Python 代码从文本到执行的完整旅程,并看看它与其他语言有何不同。


第一步:编译?是的,你没听错!

很多人认为 Python 是纯粹的“解释型语言”,代码被一行行直接执行。这其实是一个常见的误解。实际上,在 Python 代码真正开始“跑”起来之前,它首先要经历一个编译阶段。

这个编译过程并非像 C/C++ 那样生成可以直接在 CPU 上运行的机器码,而是将我们编写的人类可读的源代码(.py文件),转换成一种名为字节码(Bytecode) 的中间形态。

你可以把字节码想象成一种为 Python 虚拟机(PVM)量身定制的、与平台无关的低级指令集。它比源代码更接近机器,但又不依赖于任何特定的硬件或操作系统。

那么,这个编译过程是如何发生的呢?

  1. 词法分析(Tokenization) :解释器首先会像阅读文章一样,将你的源代码字符串分解成一个个有意义的“单词”,我们称之为“标记(Token)”。比如 x = 10 会被分解为标识符 x、赋值运算符 = 和数字常量 10
  2. 语法分析(Parsing) :接着,解释器会检查这些标记的排列组合是否符合 Python 的语法规则。它会构建一棵抽象语法树(Abstract Syntax Tree, AST) 。AST 是代码结构的树状表示,清晰地展现了代码的逻辑层次。如果代码中存在语法错误(比如漏了冒号或括号不匹配),程序就会在这一步戛然而止,并抛出 SyntaxError
  3. 生成字节码:一旦 AST 构建成功,Python 的编译器就会遍历这棵树,将其翻译成一系列的字节码指令。

一个有趣的细节:.pyc文件

你可能在项目目录中见过名为 __pycache__ 的文件夹,里面存放着 .pyc 文件。这些就是编译后生成的字节码文件!Python 解释器会缓存它们,当下次导入同一个模块时,如果源代码没有改变,解释器就会直接加载这些 .pyc 文件,跳过耗时的编译步骤,从而加快程序的启动速度。


第二步:Python 虚拟机(PVM)登场

字节码生成之后,真正的主角——Python 虚拟机(Python Virtual Machine, PVM) ——才正式登台。PVM 是一个用 C 语言实现的、基于栈的虚拟机。你可以把它想象成一个虚拟的、简化版的 CPU ,它的任务就是读取并执行字节码指令。

PVM 的执行过程是一个巨大的循环,它不断地从字节码中取出指令,然后根据指令的含义执行相应的操作。

  • 基于栈的操作:PVM 内部维护着一个“操作数栈”。大多数指令都会与这个栈交互。例如,LOAD_CONST 指令会将一个常量值压入栈顶,而 BINARY_ADD 指令则会从栈顶弹出两个值,将它们相加,再把结果压回栈中。

  • 逐条执行:PVM 就这样一条接一条地执行字节码,完成变量赋值、函数调用、循环控制等各种任务。当遇到函数调用时,PVM 会创建一个新的“栈帧(Frame)”来管理该函数的局部变量和执行上下文,函数执行完毕后,这个栈帧就会被销毁。


第三步:与操作系统握手

PVM 执行的字节码指令本身并不能直接操作硬件。当程序需要进行文件读写、网络通信或在屏幕上打印内容时,PVM 会调用底层的 C 库函数,这些 C 函数再与操作系统(如Windows、Linux、macOS)进行交互,最终由操作系统调度硬件资源来完成实际工作。

例如,当你执行 print("Hello, World!") 时,PVM会执行一系列字节码来准备字符串和调用 print 函数,最终这个调用会传递给操作系统的标准输出接口,将文本显示在你的终端上。


一个无法绕开的“角色”:全局解释器锁(GIL)

在讨论Python执行时,有一个话题总是绕不开,那就是全局解释器锁(Global Interpreter Lock, GIL)

GIL是CPython(最常用的Python解释器)中的一个机制,它确保在任何时刻,只有一个线程在执行Python字节码。这主要是为了简化CPython的内存管理(尤其是引用计数机制),避免多线程同时修改对象时出现数据竞争。

然而,GIL也带来了一个广为人知的“副作用”:在CPU密集型任务中,Python的多线程并不能真正利用多核CPU的优势,有时甚至可能比单线程更慢。这也是为什么在处理计算密集型任务时,我们更推荐使用多进程(multiprocessing)而非多线程。


Python vs. 其他语言:一场执行模式的较量

为了更深刻地理解Python的执行模式,我们不妨将它与编译型语言(如C/C++)和另一种混合型语言(如Java)做个对比。

Python vs. C/C++:编译型与“混合型”的碰撞

  • C/C++(纯编译型) :C/C++代码需要由编译器(如GCC)一次性全部翻译成特定平台的机器码,生成一个独立的可执行文件(如Windows下的.exe)。这个文件可以直接被CPU执行,因此速度极快。但缺点是,这个.exe文件无法在其他操作系统上运行,跨平台性差。这就像把一本英文书完整地翻译成了中文并印刷成册,读者(CPU)可以直接阅读,但这本书只能在特定地区发行。

  • Python(混合型) :Python则采用“先编译成字节码,再解释执行”的模式。字节码是平台无关的,可以在任何安装了Python解释器的系统上运行,实现了“一次编写,到处运行”。但代价是,PVM需要逐条解释字节码,执行效率通常低于C/C++。这就像同声传译,演讲者(代码)说一句,翻译官(PVM)就立刻翻译一句给听众(CPU)听,虽然灵活,但总有一点点延迟。

Python vs. Java:殊途同归的“中间路线”

有趣的是,Python和Java在执行模式上非常相似,都属于“编译+解释”的混合型。

  1. Java.java源代码首先被编译成.class字节码文件,然后由Java虚拟机(JVM)加载并执行。JVM同样可以选择解释执行,或者使用JIT(即时编译器)将频繁执行的“热点代码”编译成本地机器码,以提升性能。
  2. Python.py源代码被编译成.pyc字节码文件,然后由PVM解释执行。标准的CPython解释器主要是解释执行,但也有像PyPy这样的实现引入了JIT技术来加速。

可以说,Python和Java都巧妙地选择了在开发效率(跨平台、动态性)和执行效率之间取得平衡的“中间路线”。


总结

回顾一下,Python代码的执行并非一蹴而就,它经历了一场从高级到低级的转换之旅:

  1. 编译阶段:源代码(.py)经过词法分析和语法分析,生成抽象语法树(AST),再被编译成平台无关的字节码(.pyc)。
  2. 执行阶段:Python虚拟机(PVM)读取并逐条解释执行字节码,通过基于栈的模型完成各种操作。
  3. 系统交互:PVM在执行过程中,通过调用底层C库与操作系统交互,最终实现程序的各种功能。

理解这个过程,不仅能帮助我们写出更高效的Python代码,也能让我们在遇到性能瓶颈或奇怪的行为时,能够从更底层的视角去思考和分析。

Python 到底是“强类型”还是“弱类型”?

在编程世界里,关于 Python 的类型系统,流传着一个巨大的误解。很多初学者(甚至是有经验的开发者)常常因为 Python 变量不需要声明类型,就顺口把它和 JavaScript 或 PHP 归为一类,认为它是“弱类型”语言。

大错特错!

今天,我们就来彻底厘清这个概念。Python 不仅不是弱类型,反而是强类型语言的坚定捍卫者。而它所具备的“不声明类型”的特性,准确的说法叫做动态类型

这其中的区别,就像“自由”与“混乱”的区别一样重要。


内存的契约:为什么我们需要类型?

要理解强类型,我们得先回到计算机的底层——内存。

在计算机的内存中,数据并不是随意堆放的。为了高效地存取数据,系统必须明确三件事:

  1. 存储地址:数据放在哪里?(由变量名决定)
  2. 数据长度:数据占多少个字节?
  3. 处理方式:数据该如何运算和转换?

在 C 或 Java 这样的静态类型语言中,当你写下 int n = 10; 时,你实际上是在与编译器签订一份严格的契约:“嘿,编译器,给我预留 4 个字节的空间,并且我保证只往里面放整数。”

一旦你试图违背这份契约,比如把字符串塞进去,编译器就会在编译阶段直接报错,阻止程序运行。这种“先斩后奏”(先检查后运行)的机制,虽然繁琐,但极大地保证了程序的安全性。


动态类型:Python 的“随性”

Python 选择了另一条路。在 Python 中,变量更像是一个“标签”或“引用”,而不是一个固定的盒子。

当你写下 n = 10 时,你并没有告诉 Python n 是什么类型。Python 会创建一个整数对象 10,然后把标签 n 贴在这个对象上。下一秒,如果你写 n = "Hello",Python 会毫无怨言地把标签 n 撕下来,贴到字符串对象 "Hello"上。

这就是动态类型: * 灵活性极高:你不需要关心内存分配的细节,代码写起来飞快。 * 类型推断:类型是跟着值走的,而不是跟着变量走的。

但是,动态类型并不意味着弱类型。这是很多人最容易混淆的地方。


强类型:Python 的“原则”

那么,什么是强类型

简单来说,强类型语言是一种“眼里揉不得沙子”的语言。它一旦确定了某个数据的类型,就绝不允许在不进行显式转换的情况下,对其进行违背类型规则的操作。

让我们看一个经典的例子:

在 JavaScript(弱类型语言)中,你可以这样做:

let result = "5" + 2; 
// 结果: "52" (JavaScript 猜测你想做字符串拼接)
​
let math = "5" * 2; 
// 结果: 10 (JavaScript 猜测你想做数学运算)

JavaScript 会试图“帮助”你,它会自动猜测你的意图,并在后台悄悄进行类型转换。这虽然方便,但也埋下了巨大的隐患——你不知道它什么时候会猜错。

而在 Python(强类型语言)中:

result = "5" + 2
# 报错:TypeError: can only concatenate str (not "int") to str

Python 会直接抛出错误。它会告诉你:“嘿,你试图把一个字符串和一个整数相加,我不知道你想干什么,是想拼接还是想数学运算?请你明确告诉我!”

这就是强类型的核心:严谨。它不相信隐式的猜测,它要求程序员明确地表达意图。如果你想运算,你必须显式地转换:int("5") + 2


语言分类学:四大象限的博弈

为了让你更清晰地理解编程语言的版图,我们不能只看“强弱”,还要结合“动静”。这两个维度交叉,构成了编程语言的四大象限

我们可以把编程语言想象成一个坐标系: * 横轴(类型检查时机) :左边是静态(编译时检查),右边是动态(运行时检查)。 * 纵轴(类型转换严格度) :下边是强类型(严禁隐式转换),上边是弱类型(允许隐式转换)。

让我们看看 Python 和其他语言分别坐在哪个位置:

静态强类型:严谨的“学院派” * 代表语言:Java, C#, Go, Rust * 特点:这是企业级开发的最爱。它们要求你在使用变量前必须声明类型(静态),并且严禁乱搞类型转换(强类型)。 * 体验:写代码时虽然繁琐(要写很多类型定义),但编译器会帮你挡掉绝大多数错误。代码运行效率高,维护起来非常安心。

动态强类型:灵活的“实干派” * 代表语言Python, Ruby * 特点:这就是 Python 的舒适区。它们不需要你声明类型(动态),写起来飞快;但在运算时又很讲原则(强类型),绝不偷偷帮你转换类型。 * 体验:开发效率极高,代码简洁。虽然类型错误只能在运行时发现,但因为禁止了隐式转换,逻辑上的 Bug 反而比弱类型语言少得多。

静态弱类型:自由的“底层派” * 代表语言:C, C++ * 特点:这是系统编程的基石。类型在编译时确定(静态),但为了性能和灵活性,它们允许很多隐式转换(弱类型)。比如在 C 语言中,你可以把字符 'A' 直接当作整数 65 来运算。 * 体验:赋予程序员极大的权力,可以直接操作内存。但“能力越大,责任越大”,如果你不小心,这些隐式转换和指针操作可能会引发难以察觉的崩溃。

动态弱类型:随性的“脚本派” * 代表语言:JavaScript, PHP, Perl * 特点:这是 Web 开发的早期功臣。类型随值变(动态),而且为了让你少写代码,它会疯狂地帮你做类型转换(弱类型)。 * 体验:上手极快,代码极其灵活。但在大型项目中,"5" + 2 到底等于 "52" 还是 7,可能会成为团队争吵的源头。这也是为什么现代 JavaScript 开发越来越推崇 TypeScript(一种静态强类型的 JS 超集)的原因。

总结:Python 的独特定位

为了让你一目了然,我们整理了一个终极对比表:

语言类型代表语言核心特征优势劣势
静态强类型Java, C++编译期检查,严禁乱转性能高,极其安全代码冗长,开发慢
动态强类型Python, Ruby运行时检查,严禁乱转开发快,逻辑严谨运行速度相对较慢
静态弱类型C, C++ (部分)编译期检查,允许乱转极度灵活,性能高容易因隐式转换出 Bug
动态弱类型JS, PHP运行时检查,允许乱转极其灵活,上手快隐患多,难维护

所以,下次当有人问你 Python 是什么类型的语言时,你可以自信地回答:

Python 是一门动态强类型语言。它给了我们动态类型的自由,但保留了强类型的安全底线。 这正是 Python 既能快速开发原型,又能胜任大型系统开发的重要原因之一。

相关链接: (这里是我看到的有关这部分比较好的文章)

【Python 入门基础】—— 人工智能“超级引擎”,AI界的“瑞士军刀”