数据与变量
数据是所有编程语言的核心,程序的本质是将一些数据(输入)转换成另一些数据(输出)。在 Python 中,数据至关重要,数据处理、机器学习等高度依赖数据的任务是 Python 的主要应用领域。
在语句 print("Hello, World!") 中,"Hello, World!" 就是一个数据。
Python 常用的数据类型
数据通常被划分为不同的类型,因为不同类型的数据需要不同的处理方式。例如,数值数据可以进行加减乘除运算;字符串则更多用于截取、拼接等操作。下文列出了 Python 中最常用的数据类型。在这里,只需要对它们有个大致印象,后续章节会详细解释这些数据类型的使用方法。
-
数值型:
- 整型(int): 代表整数,例如:1, 100, -33。
- 浮点型(float): 代表实数,例如:3.14, -0.001。
- 复数型(complex): 代表复数,例如:3 + 4j。
-
布尔型(bool): 只有两个值:True 和 False。通常用于条件测试。
-
序列类型:
-
集合类型:
- 集合(set): 一个无序不重复元素集,基本功能包括关系测试和消除重复元素。例如:
{1, 2, 3}。 - 冻结集合(frozenset): 与集合类似,但是它是不可变的。
- 集合(set): 一个无序不重复元素集,基本功能包括关系测试和消除重复元素。例如:
-
映射类型:
- 字典(dict): 无序的键值对集合,且键必须是唯一的。例如:
{'name': 'John', 'age': 30}。
- 字典(dict): 无序的键值对集合,且键必须是唯一的。例如:
-
- NoneType:只有一个值 None。用于表示一个值的缺失或空。
-
二进制类型:
- 字节序列(bytes): 包含字节的不可变序列,例如:b'hello'。
- 字节数组(bytearray): 包含字节的可变序列。
- 内存视图(memoryview): 通过 memoryview 对象访问其他数据结构的内存。
-
文件对象: 用于文件操作,例如:读取或写入文件。
-
其他数据类型: 许多 Python 模块和库都对数据类型进行了扩展。在使用了这些模块的程序中,很可能会遇到其他数据类型,如
numpy库中定义的ndarray或pandas库中定义的DataFrame等。
后文将对这些类型数据的用法再做详解。在继续介绍数据前,我们先要介绍一下“变量”(Variable)。
变量
在编程语言里,通常不是直接使用原始数据本身,而是要给数据起个名字,程序中通过名字来使用数据。这些数据的名称,或者叫标签,这就是“变量”。变量使我们能方便地对数据进行标记、存储和操作。
初学者常把变量与代数方程中的变量(如 )进行类比,二者确实都有“占位符”的作用。但需要注意的是,编程中的变量更像是一个容器或指针。x = x + 1 在数学中并不成立,但在编程中它表示“计算 x + 1 的结果,并将其重新赋值给 x”。
比如下面这个打印函数的参数直接使用了数据本身:
print(5 + 5 + 5 + 5 + 5)
如果我们想让程序改而打印 3 + 3 + 3 + 3 + 3 的结果呢?那就需要把程序中每个数据 5 都替换成 3,非常麻烦。但如果给数据起个名字,比如叫做 x:
x = 5
print(x + x + x + x + x)
同时,在函数使用数据时,可以使用数据的名字 x。程序运行的结果是一样的,但当我们再次需要打印 3 + 3 + 3 + 3 + 3 时,只要把 x 的值改为 3 即可,无须修改每一处数据。
以下是关于 Python 中变量的几个基本概念和特点:
声明和赋值
赋值语句
在 Python 中,不需要显式地声明变量或其类型。当为变量赋值时,Python 会自动创建变量。
变量的值可以是数值、字符串或任何其他类型的数据。
变量的名字是一种标识符,遵循标识符的定义规范。它必须是一串大小写英文字母、数字和下划线 _ 的组合,并且变量名不能以数字开头。比如,name_1 是一个合法的变量名,而 1_name 不是。
Python 的变量命名区分大小写,这意味着 Variable 和 variable 是两个不同的变量。习惯上,变量名中不应该包含任何大写字母,Variable 不是一个符合 Python 习惯的变量名。
变量名不能包含空格,但可使用下划线来分隔其中的单词。比如,is_student 是一个合法的变量名,但 is student 不是。
应当避免使用 Python 中已有特定用途的名称作为变量名。例如,print 是 Python 中打印函数的名字,就不适合再用作变量名了。
虽然在语法上我们可以用任何单词作为变量名称,但为每个变量赋予具有描述性、恰当的名称,对于程序是否易读、易懂至关重要。
作为对比,有些语言,比如 C 语言,变量必须预先声明,然后才可以使用;有些语言,比如 JavaScript 还有专门的关键字 “var” 来定义变量;还有一些语言,比如 PHP 要求每个变量的名字都必须以一个特殊字符 “$” 开头。与这些语言相比,Python 对于变量的限制是最宽松的了。
下面的程序创建了一个数值为 5 的变量:
x = 5 # 创建一个名为 x 的整数变量,并赋值 5
上面这段程序,x 是变量名,5 是数据。等号 = 表示赋值操作,运行这段程序后,就会得到一个值为 5 的变量。这种为变量设值的操作被称为赋值语句。井号 # 表示注释,在程序中,井号后面的文字都只是说明文字,不参与程序运行。
类型提示
创建变量的时候,也可以为其添加类型提示(Type Hints),比如:
name: str = "谷货载"
age: int = 30
height: float = 5.9
is_student: bool = False
上面程序,变量名之后跟了一个冒号,冒号后面的文字指明了变量的数据类型。大多数编程语言,一旦指定了变量的数据类型,编译器或解释器就会自动检查变量的数据类型是否正确,如果有错误会报错。但是 Python 并不会施加任何真正的数据类型限制,这些类型提示仅仅是提示了预期的 变量类型。运行下面的程序,Python 不会报告任何错误提示:
age: int = "Qizhen Ruan"
虽然 Python 自身不会在运行时检查变量的数据类型,但是类型提示对于代码的可读性非常有帮助。很多代码检查工具(如 mypy)也依赖于类型定义对代码进行类型检查。在 Pythora 星球上,程序中的变量通常都带有类型提示。
多变量赋值
Python 可以使用一个等号为多个变量赋值,变量名和变量名之间,数据 and 数据之间用逗号分隔。这在很多时候可以简化程序,比如说,在定义二维平面上位置的时候,总是需要一个 X 轴数据和一个 Y 轴数据,这两个数据可以同时定义:
x, y = 3, 5
lat, lon = 33.4, 77.98
一个常用的操作是把两个变量中的数据进行交换,多数语言都需要引入一个临时变量来解决这样的问题,但在 Python 中,这个操作可以更简洁:
x, y = 3, 5
x, y = y, x # x 和 y 中的数据被交换了,现在 x == 5, y == 3
如果给单个变量赋值多个数据,Python 会自动将所有数据打包成一个元组:
z = 3, 5
print(z) # 输出: (3, 5)
如果多个变量的值相同,也可以 使用链式赋值,比如:
x = y = z = 4
上面的代码就相当于下面这三行代码:
x = 4
y = x
z = y
请读者分析一下下面这个程序的输出是什么:
x, y = y, x = z = 3, 5
print(x, y, z)
此代码涉及多重赋值,输出可能出乎意料,应谨慎使用。
链式赋值一般用于不可变数据类型(下文会详细解释)。链式赋值会导致所有变量都指向同一个数据,如果该数据是可变的,一旦其中一个变量修改了数据,其他所有指向该数据的变量的值也会随之改变,这可能会引发一些意想不到的问题。
动态类型
Python 是动态类型的语言。这意味着可以在程序运行时更改变量的类型。与之对比,多数语言中,变量的数据类型都是固定的,一旦声明,就不能再改变了。
x = 5 # x 是整数
x = "hi" # 现在 x 是字符串
动态数据类型的优点是简洁灵活,非常适合编写小型、临时使用的程序。许多编程语言最初为开发小型程序设计,因此倾向于采用动态类型。其中一些语言后来的应用场景已经远远超出了最初的设计,用其开发的程序规模越来越大。然而在大型程序中,动态数据类型带来的麻烦却远超过其优点。比如,它会导致无法在程序运 行前检查数据类型错误、降低代码可读性、影响运行效率,并难以优化。所以,很多语言,如 JavaScript 和 PHP,后来都演化出了支持静态类型或禁用动态类型的超集/版本(如 TypeScript),以便更好地支持大型程序开发。
变量的“变”
解释程序的时候,我们经常会说:“把变量 x 中的数据变成 5”之类的话。但是这样的说法非常不严谨,它没有说清楚:到底是把变量指向一个不同的数据;还是让变量仍然指向同一个数据,但是改变这个数据的值。
在 Python 中,使用赋值语句,改变的一定是变量的指向。比如,我们先让 x = 5,然后再让 x = 3,那么这时候,一定是 x 指向了不同的数据,而不是原来那个数据从 5 变成了 3。
x = 5 # x 指向 5
x = 3 # 现在 x 指向了另一个数据
不同的变量是可以指向同一个数据的,如果赋值语句的两边分别是两个变量,那么就表示左边的变量将也指向右边变量指向的数据。
x = [1,2] # x 指向 [1,2]
y = [1,2] # x 和 y 指向了不同的数据,但是他们的值都是 [1,2]
x = [1,2] # x 指向 [1,2]
y = x # y 也指向了 x 指向的那个数据
数据的可变性
既然赋值语句改变的总是变量的指向,那么数据本身可以被改变吗?
在一些编程语言中,比如 C 语言中,所有的数据都是可变的(除非特别用 const 声明),比如,用户可以把一个整数的数值从 1 改为 2。但在 Python 中,许多类型的数据(如整数、浮点数和字符串等)是不可变的,这意味着数据创建后,其值无法修改。当然,Python 中也有许多其他类型的数据是可变的。
在下面的示例程序中,列表数据是一个典型的可变数据,但字符串是不可变数据。
a = [1, 2, 3]
a[0] = 5
print(a) # 打印输出: [5, 2, 3]
b = "Tom"
b = "Jerry"
print(b) # 打印输出: "Jerry"
一个列表数据可能是由多个其它的数据组成的,比如上面程序中的变量 a 是一个由三个整数组成的列表。语句 a[0] = 5 表示把这个列表的第 0 个元素替换成 5。
我们生活中,给物品排序计数,总是从 1 开始的,第 1 个、第 2 个...这样数下去。然而,绝大多数的编程语言,包括 Python,都是从 0 开始计数的。一堆数据,最前面的总是第 0 个,然后后面才是第 1 个、第 2 个...
所以,当程序把列表的第 0 个元素替换成 5 之后,变量 a 所指向的数据就变成了 [5, 2, 3]。
Python 中的字符串数据是不可变的,比如,我们没办法把字符串 "Tom" 的第二个字母修改为其他字母。但我们仍可以让变量 b 指向另一个字符串。
需要格外注意的是,上面讨论的是数据本身是否可 变,而不是变量是否可变。变量总是可以指向一个新的数据的。
引用型变量
在 Python 中,变量存储的是数据的引用,而不是数据本身。这意味着,当你为一个变量赋值为另一个变量时,两者实际上都指向同一个数据,而不是具有相同数值的两个数据。我们怎么证明这一点呢?看下面的程序:
a = [1, 2, 3]
b = a
b[0] = 5
print(a) # 输出: [5, 2, 3]
程序中,运行语句 b = a,表示变量 b 也将指向变量 a 所指向的数据,它们都指向了同一个数据。如果后续的程序改变了变量 b 的数据,那么变量 a 也会发生相同的变化。
请读者考虑一下,运行下面程序的输出结果是什么?
a = [1, 2, 3]
b = [1, 2, 3]
b[0] = 5
print(a)
使用链式赋值的时候,尤其需要注意这个问题,比如:
x = y = []
x.append(10)
print(x, y) # 输出:[10] [10] 因为 x 和 y 指向同一个列表
作用域
除了上面提到的这几点,作用域也是变量的一个重要属性,也就是变量在什么地方有效、哪些范围内可变。关于这个问题,我们将在介绍了函数之后再来讨论,参考章节函数和变量的作用域。
删除变量
我们可以使用 del 语句删除一个变量。变量被删除之后,再尝试访问该变量就会出错:
x = 10
del x
print(x) # x 已经不存在了,运行这一句会出错
删除变量其实并不常用,可以说在一般的小程序中基本用不到。它的用途主要有两点:一是在处理海量数据时,可以帮助及时释放内存;二是出于安全考虑,确保某些变量在后续程序中无法被访问。
有些编程语言,比如 C 语言,自身没有回收内存的功能,程序中的内存管理完全依赖编程人员自己实现。在这些语言中,所有为 数据开辟的内存,都必须在使用后得到释放,否则就会产生内存泄露。当然,也不能过早释放,否则可能出现野指针(即指向无效内存的指针)、数据混乱等问题。现在主流的编程语言大多具有一定的内存垃圾回收机制了,它们可以自动释放那些程序中不会再用到的内存。程序员不必再担心内存分配,只需专注于程序逻辑本身。系统会在适当的时候自动释放和回收程序占用的内存。比如,比较具有代表性的是 Java 的内存垃圾回收机制。
Python 也有自动的垃圾回收机制。与 Java 主要采用“可达性分析”不同,Python 主要采用引用计数(Reference Counting)机制。原理很简单:Python 会实时记录每个数据被多少个变量所引用。当一个数据的引用计数降为 0 时(意味着没有任何变量指向这个数据了),Python 就会立即回收这块内存。
虽然 Python 的机制在处理循环引用时需要额外的辅助算法(标记-清除),但在大多数情况下,它的内存管理是高效且及时的。作为程序员,在 Python 中通常不需要像 C 语言那样手动管理内存,也不必过分担心内存泄漏问题。显式使用 del 删除变量通常只在处理极占用内存的大型数据(如加载了几个 GB 的数据集)且急需释放空间时才使用。
常量
很多语言中,除了变量,还允许用户定义常量。常量可以理解为某个特定数据的名称、标签。这些名称一旦定义就不能再指向其他数据了,多数情况下,这些常量数据本身也不允许被改变。比如,很多语言中会定义一个常量 Pi = 3.1415926。
在 Python 中,并没有内置的方式来定义真正的常量,但是,我们可以采用一些约定俗成的规则和技巧来模拟常量的行为。最常见的方式是使用大写字母作为标识符:按照 Python 的命名约定,全大写的变量名通常被视为常量。尽管这并不会阻止在程序中修改变量的值,但它向其他开发者发出信号,表明这个变量的值不应被修改。比如下面定义的变量,在程序运行过程中不应修改其值:
PI = 3.14159
MAX_SIZE = 100
有时候多个常量会被包装成一组常量,比如代表每种特定颜色常量:红、黄、蓝可以被包装成一组名为 color 的常量,这种就是枚举类型,我们会在后面对其做详细介绍。
此外,为了防止常量中的数据被改动,还可以考虑使用类对数据进行包装,并控制数据的访问。相关的方法会在属性装饰器一节做详细介绍。