失物报告单
LOST-001
报告人
旅行社职员 朴主任
丢了什么
团体纪念品清单。在“复制”出来备用的清单里加了一块冰箱贴,结果原来的清单里也多了一块冰箱贴。
结案条件
用代码证明两份清单从一开始就是同一个箱子,并做出一份真正的备用清单。
天空岛国际机场 行李中心
上班第一天的早上,都美兰班长在传送带前递给你一沓标签。“在这个中心,第一句要背下来的话是:箱子只有一个,标签可以有很多个。”
第一份报告来自一位旅行社职员。对方把纪念品清单多做了一份备用,可写进备用清单的东西,原来的清单里也会出现。班长看了看代码,笑了:“两份清单?是两个标签吧。”
=(9 分钟)报告人的代码是这样的:
souvenirs = ["postcard", "keyring"]
backup = souvenirs
backup.append("magnet")
print(souvenirs)
# ['postcard', 'keyring', 'magnet']backup = souvenirs 并不会复制列表。它是在挂着 souvenirs 标签的那个箱子上,再挂一个 backup 标签。箱子从一开始就只有一个,backup.append 把冰箱贴放进的,正是这唯一的箱子。
= 先计算右边,得到一个对象(箱子),再把左边的名字(标签)绑定到这个对象上。在下面的标签板上逐行推进看看。左栏是名牌,右栏是箱子。到第 3 行之前,你得先预测结果才能继续。
标签板
步骤 1 / 0
下一行将执行: 0
正在准备实验台…
刚刚变化的
封口的箱子(不可变)
每个对象都有三样东西。
| 怎么查看 | 放到机场里就是 | |
|---|---|---|
| 身份(identity) | id(obj)、a is b | 印在箱底的唯一编号 |
| 类型(type) | type(obj) | 箱子的种类(拉杆箱、背包……) |
| 值(value) | 用 == 比较 | 里面装的东西 |
x = ["postcard"]
y = ["postcard"]
print(x == y, x is y)
# True False
print(id(x) == id(y), id(souvenirs) == id(backup))
# False Truex 和 y 是内容完全相同的两个不同的箱子。== 为 True,is 为 False。souvenirs 和 backup 是同一个箱子,所以 id 也相同。id 的数字本身没有意义。在 CPython 里它是内存地址,但每次运行都不一样。只在比较时使用它。print(type(x), type(len))
# <class 'list'> <class 'builtin_function_or_method'>小钉提示
→ is 问的是“是不是同一个箱子”,== 问的是“装的东西一不一样”。比较值的时候一律用 ==。is 只用来确认世上独一无二的对象,比如 x is None。
不只是数字和列表才是对象。函数、类、模块,也都是绑定在名字上的对象。
shout = print
shout("boarding")
# boarding
print(shout is print)
# True
def greet():
return "hi"
print(type(greet), greet.__name__)
# <class 'function'> greet
alias = greet
del greet
print(alias())
# hidef greet(): 创建一个函数对象,并把名字 greet 绑定上去,本质上也是一种赋值。del greet 摘掉了这个名字,alias 仍指向同一个函数对象,所以函数照样存在。type(int) 和 type(type) 都是 <class 'type'>。这个话题第 7 课再展开。对一个名字能做的事只有两种。
a = ...。把名字重新绑定到另一个对象上。原来的箱子不受影响。a.append(...)、a[0] = ...。名字不动,箱子里的东西变了。所有指向这个箱子的名字都能看到变化。a = [1, 2]
b = a
a = a + [3]
print(a, b, a is b)
# [1, 2, 3] [1, 2] Falsea = [1, 2]
b = a
a.append(3)
print(a, b, a is b)
# [1, 2, 3] [1, 2, 3] Truea + [3] 会创建一个新列表,然后 a = 把标签 a 移到这个新列表上。b 还留在原来的箱子上。而 append 改动的是箱子本身,所以从 b 也能看到。在下面交替运行这两种写法,对比一下。
标签板
步骤 1 / 0
下一行将执行: 0
正在准备实验台…
刚刚变化的
封口的箱子(不可变)
1、2 是小值,所以直接写成了值,没有画箭头。其实它们也是对象,列表里装的是指向这些对象的引用(第 3 课)。a += [3] 属于哪一种?对列表来说是改动箱子。可对数字或字符串来说,它却变成了移动标签。这两副面孔是第 2 课的主题。del 摘掉的是标签,不是箱子(6 分钟)c = ["tag"]
d = c
del c
print(d)
# ['tag']
print(c)
# NameError: name 'c' is not defineddel c 只是去掉名字 c,并不会删除列表。d 还指着它,所以箱子还在。箱子真正消失,是在没有任何标签指向它的时候(第 11 课)。
交换两个名字的 p, q = q, p 也是移动标签。先用右边的 q, p 生成一个元组,再把左边的名字依次绑定到元组里的对象上。
p, q = "left", "right"
p, q = q, p
print(p, q)
# right left下面是报告人的两份清单其实是同一个箱子的证据,以及做出真正备用清单的方法。
souvenirs = ["postcard", "keyring"]
backup = souvenirs
backup.append("magnet")
print(souvenirs is backup, id(souvenirs) == id(backup))
# True True
safe = list(souvenirs) # 新做一个箱子,装进同样的东西
safe.append("mug")
print(souvenirs, safe, safe is souvenirs)
# ['postcard', 'keyring', 'magnet'] ['postcard', 'keyring', 'magnet', 'mug'] Falselist(souvenirs) 会创建一个新列表。现在往 safe 里放马克杯,原来的清单也不会变。(这种复制只“新建一层”,这一点第 3 课再讲。)
快下班时,小钉指向 13 号传送带。有报告说,两个一个标签都没有的箱子一直在上面转。班长只说了句“没有标签的话,早该被清走了”,就关掉了屏幕。
结案报告 — LOST-001。清单从一开始就只有一份,只是挂了两个标签。用 is 和 id 确认,用 list() 做出新清单解决。
x = [0]、y = x、x = [1] 之后,y 是什么?先画图,再运行确认。a = [1, 2],再创建 b = a[:] 和 c = a,预测 a is b、a is c、a == b 的结果。print 起一个别名,然后执行 del print,会怎样?内置名字住在哪里,第 5 课会学到。班长的工作手册
变量不是盒子,而是名牌。= 把左边的名字绑定到右边的对象上。
对象的三要素:身份(id、is)、类型(type)、值(==)。
is 看是不是同一个对象,== 看值是否相等。比较值一律用 ==。
函数、类、模块也是对象。def 也是一种绑定名字的赋值。
重新绑定(a = …)移动名字,修改(a.append …)改变对象。
del 只是摘掉名字;对象要等到没有任何名字指向它时才会消失。
订阅新课时和新课程通知
本课程新增课时或推出后续课程时,我们会发邮件通知你。
问答
关于本课有任何疑问都可以在这里留言。所有语言版本页面的提问会汇集在一起。