K 的一隅

Python Python 语言核心

名字、缩进和类型:Python 基本语法从哪看起

缩进块、动态类型与基本值类型,变量即名字;if/for/while 控制流与 truthiness 直觉。

9 分钟阅读 更新于 2026-07-28
本文目录

下面这段代码,先别运行,凭直觉猜会打印什么:

python
x = 10
x = "ten"
print(x + 1)

如果你猜 11,说明还在用「变量是带类型的盒子」的模型。Python 里 x 是名字,不是盒子:赋值把名字绑到新对象上,第二次赋值后 x 指向字符串 "ten""ten" + 1 会触发 TypeError。搞清「名字 → 对象」这条线,后面容器、函数、类都会顺很多。

缩进不是排版,是语法

大多数语言用 {} 标记代码块;Python 用缩进。同一层级的语句对齐,块内多缩进一级(通常 4 个空格,不要混 Tab):

python
score: int = 85
if score >= 60:
    print("及格")
    if score >= 90:
        print("优秀")
else:
    print("不及格")

ifelifelse 末尾的冒号表示「后面跟缩进块」。缩进错了,IndentationError 在解析阶段就会报,程序还没开始跑逻辑。复制粘贴代码时,编辑器自动缩进不一致是常见坑——统一转成空格再保存。空块不能留空着,至少写 pass

python
if debug:
    pass  # 占位,稍后补逻辑

动态类型:绑在对象上,不绑在名字上

Python 是动态类型语言:名字在运行时指向什么类型的对象,取决于当时绑定了谁;名字本身没有「声明为 int」这一说(类型标注是提示,不改变运行时行为)。

同时它是强类型:对象有类型,运算尊重类型规则,"3" + 3 不会静默变成 6,而是报错。这和 JavaScript 的弱转换不同——Python 不会为了凑运算而偷偷改类型。

几种最常见的内置类型:

python
count: int = 42
price: float = 19.9
active: bool = True
label: str = "draft"
missing = None          # None 是独立单例,表示「没有值」
  • int / float:整数与浮点,3 / 2 在 Python 3 里是 1.5(真除法);3 // 2 才是整除得 1
  • boolTrue / False,是 int 的子类,但语义上当作逻辑值;True + True 合法得 2,只是别这么写业务逻辑。
  • str:文本,单引号双引号等价;三引号可跨行;f-string 格式化:f"{count} items"
  • None:函数没写 return 时默认返回它;可选字段常用 None 表示缺失。判空用 is None,不要用 == None

类型标注(如 count: int)给阅读者和静态检查器(mypy、Pyright)看,解释器运行时仍按实际对象类型走。标注错了,运行时未必立刻报错。

再猜一次:truthiness

python
values = [0, 1, "", "hi", [], [1], None]
for v in values:
    if v:
        print(repr(v), "→ 真")
    else:
        print(repr(v), "→ 假")

输出:

text
0 → 假
1 → 真
'' → 假
'hi' → 真
[] → 假
[1] → 真
None → 假

truthiness 规则直觉:数值零、空字符串、空容器、None 在布尔上下文里当 False;其余大多当 True。写 if items:if len(items) > 0: 更惯用,但前提是你确实想表达「非空」。注意:None 和空 list 都假,若业务要区分「未设置」与「空列表」,不能单靠 if data:,得显式 is None

控制流:ifforwhile

条件分支

python
role: str = "editor"
if role == "admin":
    level = 3
elif role == "editor":
    level = 2
else:
    level = 1

比较用 ==,身份同一性用 is(尤其和 None 比:if x is None)。链式比较 0 < n < 100 合法且可读。

for 遍历可迭代对象(字符串、列表等),不手动维护索引除非需要:

python
for ch in "abc":
    print(ch.upper())

for i, ch in enumerate("abc"):
    print(i, ch)

range(5) 生成 0..4 的整数序列,常配合 for:for i in range(5): ...

while 在条件为真时重复;要有能终止的更新,避免死循环:

python
n: int = 3
while n > 0:
    print(n)
    n -= 1

break 跳出循环,continue 跳过本次剩余体进入下一轮。else 也可挂在循环后:循环正常结束(未 break)时执行一次。

python
for item in [1, 2, 3]:
    if item == 2:
        break
else:
    print("没有遇到 2")  # 不会打印,因为 break 了

对空序列,for x in []: 体一次不执行,但 else 仍会跑——和「有没有 break」有关,和「有没有元素」无关,第一次见容易懵,记住「else 绑定的是循环是否正常跑完」即可。

名字重新绑定 vs 原地修改

python
nums = [1, 2, 3]
alias = nums
alias.append(4)
print(nums)   # [1, 2, 3, 4] — 同一个列表对象

nums = [9, 8] # 名字 nums 改绑到新列表,alias 仍指向旧列表
print(alias)  # [1, 2, 3, 4]

赋值永远只是「名字绑到对象」,不会复制对象本身(除非显式 copy)。mutable 对象通过多个名字共享同一实例——这是后面容器篇和默认参数陷阱的前奏。immutable 对象(如 int、str、tuple)「修改」其实是创建新对象再绑名字:

python
s = "hi"
s = s + "!"   # 新 str,旧 "hi" 若无人引用会被回收

表达式与语句

python
total = 1 + 2 * 3      # 7,运算符优先级照常
name = input("名字: ")  # input 返回 str

一行可以写多个简单语句用分号,但风格上少见。注释用 # 到行末。模块级字符串字面量若为首语句,会成为 docstring,这里先当普通字符串即可。

输入与输出

脚本与外界的接口往往就是读写文本:

python
name: str = input("你的名字: ")
print("你好,", name)

input 始终返回 str;要数字得再转。print 默认带换行,多个参数用空格分隔:print("a", "b", sep="-")a-b。格式化除了 f-string,还有 "{:.1f}".format(x),新代码优先 f-string。

比较与链式条件

python
age: int = 20
if 18 <= age < 65:
    print("劳动年龄段")  # 链式比较,中间不重复 age

成员测试:if "py" in "python":;容器篇会展开 in 在 list/dict/set 上的成本差异。身份与成员别混:None in [None, 0] 为 True,但判空仍用 is None

再猜:整数与布尔

python
print(bool(0), bool(1), bool(-1))
print(int(True), int(False))

输出 False True True1 0。布尔是 int 子类,和 C 里 0/1 当假真不同——Python 里只有 False 和零值、空容器等算假,负整数算真。

最后一题:顺序与短路

python
def side(tag: str) -> bool:
    print(tag)
    return True

if side("A") and side("B"):
    print("done")

因为 and 短路,左侧为假时右侧不再求值。若把第一个 return True 改成 return False,只会打印 A,不会打印 Bor 同理:左侧为真则跳过右侧。把函数调用放在条件里时,这种「是否会被执行到」的差异要心里有数——带副作用的调用别轻易塞进条件表达式。

再猜一次:is==

python
a = [1, 2]
b = [1, 2]
print(a == b)   # True,值相等
print(a is b)   # False,不是同一个对象

c = a
print(a is c)   # True,同一引用

== 比较值是否相等(可能调用 __eq__);is 比较是不是同一个对象(身份)。比较 None、单例、或「是不是这一份缓存」时用 is;比较内容用 ==。两个内容相同的 list 可以是不同对象,别混用。

字符串与数字的边界

python
n: int = int("42")       # 显式转换
text: str = str(3.14)
# int("3.14")  → ValueError,不能直接把带小数点的字符串转 int

动态类型不等于自动转换。从用户输入或 JSON 拿到的多是 str,参与算术前先 int() / float()。f-string 里 {price:.2f} 可以格式化浮点,但 {price} 本身不会把数字变字符串以外的类型。

多重赋值与交换

python
x, y = 1, 2
x, y = y, x          # 交换,无需临时变量
first, *rest = [10, 20, 30]  # first=10, rest=[20, 30]

解包在 for 循环、函数返回、容器操作里频繁出现。星号 *rest 收集「剩余部分」为 list,下一篇容器会用到更多。

运算符优先级速记

算术 → 比较 → 逻辑非 not → 与 and → 或 or。拿不准就加括号,可读性比省两个字符重要:

python
ready = logged_in and (role == "admin" or role == "editor")

和静态类型语言的交界

如果你从 Java、TypeScript 过来:Python 不要求写类型也能跑;写了类型标注也不强制运行时检查。缩进替代花括号;None 类似 null/undefined 的合体,但只能用 is None 判空才可靠。没有 switch 直到 3.10 的 match(后面遇到再学);日常 if/elif 足够表达分支。

综合小测:三段输出

段一

python
flag = None
if flag:
    print("A")
elif flag is None:
    print("B")
else:
    print("C")

None 为假,但第一个 if flag 不进;elif flag is None 成立,打印 B。判「有没有值」和判「是不是 None」是两件事。

段二

python
s = "42"
print(s * 2, int(s) + 1)

4242 43。str 乘整数是重复;int 才做算术。

段三

python
data = [1, 2, 3]
backup = data
data = data + [4]
print(len(backup), len(data))

3 4data + [4] 产生新 list 并绑回 databackup 仍指旧的三元素 list。若写成 data.append(4),则 backup 也会变成四个元素——改的是对象,不是名字。

缩进与块:再补一例

python
def show_status(code: int) -> None:
    if code == 200:
        message = "ok"
    elif code == 404:
        message = "missing"
    else:
        message = "error"
    print(code, message)

show_status(404)

函数体也是缩进块。message 在分支里赋值,在函数级缩进打印——只要每个分支都赋值,函数末尾就能用。若某分支漏赋,运行到未绑定名字会 UnboundLocalError。这类错误和「缩进层级错了」一样,属于写代码阶段就该抓住的结构问题。

while 与 input 的简单交互

python
total: int = 0
while True:
    line = input("数字(空行结束): ")
    if not line:
        break
    total += int(line)
print("合计", total)

while True 配合 break 很常见。not line 利用空字符串为假;若用户输入 "0",字符串非空,会正常转成整数累加。动态类型在这里体现在:line 先是 str,int(line) 后才参与加法。

类型标注读法

python
def clamp(value: int, low: int, high: int) -> int:
    if value < low:
        return low
    if value > high:
        return high
    return value

箭头右侧是返回类型注解。调用 clamp(1.5, 0, 10) 运行时不会自动截断成 int——标注不强制转换。静态检查器会警告类型不匹配;运行时仍按实际对象走。把标注当文档和工具提示,而不是运行时契约。

常见字面量与运算速览

python
# 算术
7 // 3    # 2 整除
7 % 3     # 1 取余
2 ** 10   # 1024 幂

# 字符串
"ab" + "c"     # "abc"
"=" * 40      # 重复,做分隔线

# 比较结果是 bool
(1 < 2) is True

除零抛 ZeroDivisionError;对 float 做 % 仍合法但少用。字符串和 bytes 是不同类型,网络读到的二进制别直接当 str 拼接。

None 参与比较时要小心:None == None 为真,但容器里「找不到」用 get 返回的默认值若也是 None,和「键存在且值为 None」在语义上可能不同——dict 的 key in d 可以区分键是否存在,这点留到容器篇对照。

读代码的三条习惯

第一,看见赋值先想「绑到哪个对象」,别默认复制。第二,看见 if x 先想 truthiness,必要时改成 is None 或显式比较。第三,看缩进对齐关系,块边界比关键字更可靠——尤其从网页或 PDF 复制的代码。

补一条:elif 链从上到下只走第一个为真的分支,后面的不再判断。把最特殊、最窄的条件放前面,宽泛的放后面,和多数语言的 if-else if 链同一逻辑。ifelif 之间不需要再写「否则如果」之外的任何连接符,冒号加缩进就是全部语法。写完分支建议 mentally 走一遍:每个分支是否互斥、是否覆盖所有情况。

这些差异点一两句带过即可,重点是把「名字绑定对象 + 缩进块 + truthiness」三条肌肉记忆建立起来。下一篇进入四种容器,看看数据在 Python 里通常怎么装。