第 2 课 变量与数据类型

2.1 变量:给数据起个名字

变量不是「装数据的盒子」,更准确的说法是贴在数据上的标签。

age = 18
name = "小明"

= 在这里不是数学的「等于」,而是赋值:把右边的值算出来,让左边的名字指向它。

>>> age = 18
>>> age
18
>>> age = age + 1     # 先算右边得 19,再让 age 指向 19
>>> age
19

因为变量只是标签,所以同一个名字可以先后指向不同类型的值:

x = 10
print(x)       # 10
x = "十"
print(x)       # 十

Python 是动态类型语言:不需要提前声明类型。灵活,但也意味着类型错误要到运行时才暴露。

用 id() 看见「标签」这件事

a = [1, 2, 3]
b = a              # b 和 a 指向同一个列表,不是复制
b.append(4)
print(a)           # [1, 2, 3, 4]   a 也变了

a = [1, 2, 3]
b = a.copy()       # 复制出一份新的
b.append(4)
print(a)           # [1, 2, 3]

这个区别在第 4 课还会重点讲,现在记住一句:= 不复制数据,只复制标签。

2.2 怎么给变量起名字

硬性规则(违反就报错):

  • 只能包含字母、数字、下划线,且不能以数字开头
  • 不能是关键字(if、for、class、def、True、None 等)
  • 区分大小写:age、Age、AGE 是三个不同的名字
  • 不能用中文标点、空格

软性规范(违反不报错,但会被同事嫌弃):

场景 推荐 不推荐
普通变量、函数 user_name、total_price(蛇形命名) userName、a、x1
常量 MAX_SIZE、PI max_size
类名 StudentInfo(大驼峰) student_info

好名字的标准:读代码时不需要看注释就知道它是什么。

# 差
d = 30
t = 8.5

# 好
days = 30
daily_hours = 8.5

2.3 五种基本类型

age = 18                # int      整数
price = 9.9             # float    浮点数(小数)
name = "小明"            # str      字符串
is_student = True       # bool     布尔值(True / False)
result = None           # NoneType 空值,表示「还没有结果」

用 type() 查看类型:

>>> type(18)
<class 'int'>
>>> type(9.9)
<class 'float'>
>>> type("18")
<class 'str'>
>>> type(True)
<class 'bool'>

isinstance() 判断是否属于某个类型,比 type() == 更常用:

>>> isinstance(18, int)
True
>>> isinstance(True, int)      # 注意:bool 是 int 的子类
True

⚠️ 常见坑:"18" 和 18 看起来像,但一个是文本一个是数字。"18" + 1 会直接报 TypeError。

2.4 数字与运算

四种数字类型相关运算

>>> 7 + 3       # 加
10
>>> 7 - 3       # 减
4
>>> 7 * 3       # 乘
21
>>> 7 / 3       # 除,结果永远是 float
2.3333333333333335
>>> 7 // 3      # 整除,向下取整,丢掉小数部分
2
>>> 7 % 3       # 取余,余数
1
>>> 7 ** 3      # 幂,7 的 3 次方
343

两个容易踩的点:

>>> -7 // 2     # 向下取整,不是向零取整
-4
>>> -7 % 3      # 余数的符号跟除数一致
2

// 和 % 组合起来可以拆数字,比如「137 分钟是几小时几分钟」:

total = 137
print(total // 60, "小时", total % 60, "分钟")   # 2 小时 17 分钟

优先级

** > 正负号 > * / // % > + -,同级从左往右。记不住就加括号,括号永远不嫌多。

>>> 2 + 3 * 4
14
>>> (2 + 3) * 4
20

浮点数精度

>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False

这不是 Python 的 bug,而是二进制无法精确表示 0.1(就像十进制写不出 1/3)。解决办法:

>>> round(0.1 + 0.2, 2) == 0.3
True
>>> abs((0.1 + 0.2) - 0.3) < 1e-9      # 更专业的写法:判断误差足够小
True

涉及金额计算时,用 decimal 模块而不是 float:

from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2"))   # 0.3

大数字和下划线

Python 的 int 没有溢出问题,可以算很大的数:

>>> 2 ** 100
1267650600228229401496703205376
>>> 10_000_000        # 下划线只是给人看的分隔,不影响数值
10000000

2.5 字符串

三种写法

s1 = '单引号'
s2 = "双引号"          # 和单引号完全等价,选一种风格坚持用
s3 = """三个引号可以
跨多行书写"""

索引与切片

字符串是「字符的序列」,可以用下标取单个字符,从 0 开始:

s = "Python"
#    012345
print(s[0])      # P
print(s[5])      # n
print(s[-1])     # n   负数从末尾数,-1 是最后一个
print(s[0:3])    # Pyt 左闭右开:含 0 不含 3
print(s[2:])     # thon
print(s[:3])     # Pyt
print(s[::2])    # Pto 每两个取一个
print(s[::-1])   # nohtyP 反转

常用方法

字符串方法都返回新字符串,原字符串不变(字符串不可变):

s = "  Hello, Python  "

s.strip()                 # 'Hello, Python'     去掉两端空白
s.lower()                 # '  hello, python  '  转小写
s.upper()                 # '  HELLO, PYTHON  '  转大写
s.replace("Python", "世界")  # '  Hello, 世界  '
"a,b,c".split(",")        # ['a', 'b', 'c']     切成列表
"-".join(["2026", "11"])  # '2026-11'           拼接
"Python".find("t")        # 2                   找不到返回 -1
"Python".startswith("Py") # True
len("Python")             # 6                   长度,注意 len 是函数不是方法

链式调用很常见,因为每一步都返回新字符串:

raw = "  XiaoMing@Example.COM  "
email = raw.strip().lower()
print(email)      # xiaoming@example.com

转义字符

print("第一行\n第二行")       # \n 换行
print("姓名\t年龄")           # \t 制表符(对齐用)
print("C:\\Users\\me")        # \\ 表示一个反斜杠
print("他说:\"你好\"")        # \" 在双引号字符串里表示引号

路径多的时候用原始字符串,r 前缀让反斜杠不再转义:

path = r"C:\Users\me\Documents"
print(path)      # C:\Users\me\Documents

2.6 f-string:拼字符串的最佳方式

Python 3.6 之后,格式化字符串首选 f-string:在引号前加 f,用 {} 嵌入变量或表达式。

name = "小明"
age = 18
print(f"{name} 今年 {age} 岁")          # 小明 今年 18 岁
print(f"明年他 {age + 1} 岁")           # 括号里可以写表达式
print(f"{name} 的名字有 {len(name)} 个字")

格式控制写在冒号后面:

pi = 3.14159
print(f"{pi:.2f}")        # 3.14      保留 2 位小数
print(f"{pi:10.3f}")      #      3.142 总宽 10,右对齐
print(f"{pi:<10.3f}|")    # 3.142     |  左对齐
print(f"{0.257:.1%}")     # 25.7%     百分比
print(f"{1234567:,}")     # 1,234,567 千分位
print(f"{42:05d}")        # 00042     补零到 5 位

调试时有个偷懒写法(3.8+),= 会同时打印变量名和值:

>>> x = 42
>>> print(f"{x=}")
x=42

2.7 类型转换

int("18")        # 18        字符串 → 整数
int(3.9)         # 3         浮点 → 整数,直接截断,不四舍五入
float("3.14")    # 3.14
str(18)          # '18'
bool(0)          # False

三个必知的陷阱

陷阱一:int("3.5") 会报错

>>> int("3.5")
ValueError: invalid literal for int() with base 10: '3.5'

因为 "3.5" 不是合法的整数字面量。要两步走:

int(float("3.5"))    # 3

陷阱二:bool 转换的规则和直觉不同

bool("0")      # True   非空字符串都是 True,哪怕内容是 "0" 或 "False"
bool("")       # False  空字符串才是 False
bool(0)        # False
bool([])       # False  空列表
bool(None)     # False

记法:0、""、[]、{}、None、False 为假,其它一切为真。

陷阱三:round() 是「银行家舍入」

>>> round(2.5)
2
>>> round(3.5)
4

遇到 .5 时舍入到最近的偶数。做「四舍五入」的业务逻辑时要留意,必要时用 decimal 的 ROUND_HALF_UP。

2.8 input():让程序接收输入

name = input("请输入你的名字:")
print(f"你好,{name}")

运行效果:

请输入你的名字:小明
你好,小明

关键点:input() 的返回值永远是字符串,哪怕用户输入的是 18。

age = input("年龄:")
print(type(age))       # <class 'str'>
print(age + 1)         # TypeError!

必须先转换:

age = int(input("年龄:"))
print(f"明年 {age + 1} 岁")

如果用户输入了非数字,int() 会抛 ValueError,程序崩溃。第 6 课会讲怎么优雅处理;现在先知道这件事会发生。

2.9 综合示例:BMI 计算器

height = float(input("请输入身高(米):"))
weight = float(input("请输入体重(公斤):"))

bmi = weight / height ** 2

print(f"你的 BMI 是 {bmi:.1f}")

if bmi < 18.5:
    print("偏瘦")
elif bmi < 24:
    print("正常")
else:
    print("偏胖")

这段代码已经用到了本课几乎所有知识点:input、类型转换、算术运算、f-string 格式化。if 分支下一课就讲。

2.10 小结

  • 变量是标签,= 是绑定不是复制
  • 五种基本类型:int、float、str、bool、None
  • / 永远得 float,// 向下取整,% 取余,** 是幂
  • 浮点数有精度误差,比较时用 round 或误差范围
  • 字符串不可变,切片左闭右开,方法都返回新字符串
  • 格式化优先用 f-string
  • input() 返回 str,用前必须转换
  • 假值集合:0、""、[]、{}、None、False

2.11 课后练习

练习 1 下面哪些变量名合法?

user_name   2name   _temp   my-age   class   userName   MAX_SIZE   名字

练习 2 预测输出,再运行验证:

print(7 / 2)
print(7 // 2)
print(7 % 2)
print(2 ** 3 ** 2)

练习 3 写程序:输入一个三位整数,分别输出百位、十位、个位。例如输入 365,输出 3 6 5。

练习 4 写程序:输入圆的半径,输出面积,保留两位小数(面积 = πr²,π 取 3.14159)。

练习 5 下面代码为什么报错?给出两种修改方式。

age = input("年龄:")
print("明年你 " + age + 1 + " 岁")

参考答案

练习 1

合法:user_name、_temp、userName、MAX_SIZE、名字(Python 3 支持中文变量名,但不推荐实际使用)。

非法:2name(数字开头)、my-age(含减号)、class(关键字)。

练习 2

3.5
3
1
512

2 ** 3 ** 2 是右结合的,等价于 2 ** (3 ** 2) = 2 ** 9 = 512。这是幂运算的特殊规则,和 + - * / 的左结合不同。

练习 3

num = int(input("输入三位整数:"))
hundreds = num // 100
tens = num // 10 % 10
ones = num % 10
print(hundreds, tens, ones)

用除法和取余「拆位」是最基础的数字处理技巧,后面处理时间、进制转换都靠它。

练习 4

r = float(input("半径:"))
area = 3.14159 * r ** 2
print(f"面积是 {area:.2f}")

练习 5

原因:age 是字符串,"明年你 " + age 得到字符串,再接 + 1(整数)时类型不匹配,抛 TypeError。

改法一,先转成数字再算:

age = int(input("年龄:"))
print(f"明年你 {age + 1} 岁")

改法二,保持字符串,先算再转:

age = input("年龄:")
next_age = int(age) + 1
print("明年你 " + str(next_age) + " 岁")

推荐第一种:尽早把数据转成正确的类型,而不是一路用字符串拼。

评论

评论区即 对应 issue 的讨论串,登录 GitHub 即可留言。