⌨️ 第四章 流程图与算法
📄 一句话概括

参数进函数,是【复印一份】还是【把原件递进去】?——值传递给复印件(外面不受影响),引用传递递原件(外面看得见改动);Python 的规则一句话:不可变对象等效值传递、可变对象进去就能被改。

📝 本节和前面几节的关系

4-4 的参数怎么【进】机器,是新手最容易懵的一课。回收 3-14 的【房号与复印件】(b = a 拷的是房号)与 3-9 的【字符串不可变】——本节把这两课在函数门口合流。

一、两条传法:复印件 vs 原件

传法 进函数的是什么 函数内改动对外面
值传递 值的复印件 看不见
引用传递 对象的地址(房号) 看得见(直接改原件)

C 语言默认值传递(想要【能改外面】得亲手传指针,回收 3-14);C++ 提供引用形参两样都行;Python/Java 没有这两张选单——统一传【对象的引用】,但效果因对象可不可变而分岔。

二、Python 的分岔现场

def try_change(n, lst):
    n = n + 1          # int 不可变:这步造了个新值,函数内的 n 指向新值
    lst.append(99)     # list 可变:顺着房号直接改原件

x = 10
my_list = [1, 2]
try_change(x, my_list)
print(x, my_list)      # 10 [1, 2, 99]

x 没变、list 变了——因为:

还有一个更隐蔽的坑:函数内对参数重新赋值(lst = [9])只是把函数内的名字改指新房,不影响外面——【改房子】看得见,【换房号】看不见。

三、默认参数的大坑:可变默认值

def add_item(item, bag=[]):     # 危险!默认列表只创建一次
    bag.append(item)
    return bag

print(add_item("a"))    # ["a"]
print(add_item("b"))    # ["a", "b"]  —— 不是 ["b"]!

默认参数在函数定义时创建一次、之后所有调用共享同一个列表——这是 Python 面试第一题。正解:默认用 None,函数内再新建。

四、动手 Demo:三幕现场

def demo(n, lst):
    n = 100
    lst.append(100)
    lst = [7]             # 换房号:指向新列表
    lst.append(8)

x = 1
arr = [1]
demo(x, arr)
print(x, arr)             # 1 [1, 100]

看到什么算成功:输出 1 [1, 100]。

读结果:x 没变(int 不可变);arr 变了但只多了一个 100——append 生效(改原件),随后 lst = [7] 换房号、append(8) 都打在新房上,外面看不见。三行代码浓缩了本节全部要点。

五、30 秒实验

跑 def f(s): s = s + "x"\nt = "a"; f(t); print(t)——输出 a。字符串拼接造的是新字符串(不可变),函数内 s 的新指向与外面无关。再把参数换成列表试试 append——对比 30 秒,两条规则刻进脑子。

六、常见误区

  1. 【Python 一律值传递】或【一律引用传递】——都不对。它统一传引用,但不可变对象改不动原件(等效值传递)、可变对象改得动——按【可变/不可变】分岔,不按【传法】分岔。
  2. 【函数内赋值能影响外面的变量】——错。函数内对参数重新赋值只是换函数内那个名字的指向;要改外面得改对象本身(append、改字段)。
  3. 【可变默认参数很方便】——炸弹。默认对象全体调用共享,状态会【串场】;用 None 占位、函数内新建才是正解。
自测三问(点开对答案) 1. **为什么 Python 函数内改 int 不影响外面、改列表影响?** 答:int 不可变,函数内改是另起新值(外面名字仍指旧值);列表可变,函数持真房号、append 直接改原件。 2. **函数内 lst = [7] 后再 append,外面看得见吗?** 答:看不见——赋值是【换房号】(函数内名字指向新对象),只有【改对象本身】(append/改字段)才影响外面。 3. **可变默认参数为什么危险?怎么解?** 答:默认对象在定义时创建一次、所有调用共享,状态跨调用串场;正解是默认 None、函数内新建。

相关笔记