对序列使用+,+=,*,*=

# 对序列使用+和*
# 通常+两侧的序列由相同类型的数据所构成,在拼接的过程中,两个被操作的序列都不会被修改,Python会新建一个包含同样类型数据的序列来作为拼接的结果
# 如果想把一个序列复制几份然后再拼接起来,更快捷的做法是把这个序列乘以一个整数.同样,这个操作会产生一个新的序列:
l1 = [1, 2, 3]
print(l1 * 5)
# +和*都遵循这个规律,不修改原有的操作对象,而是构建一个全新的序列
# 注意:如果在a*n这个语句中,序列a里的元素是对其他可变对象的引用的话,这个式子的结果可能会出乎意料
l2 = [l1]
l3 = l2 * 3
print(l3)  # [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
l1[0] = 4
print(l3)  # [[4, 2, 3], [4, 2, 3], [4, 2, 3]]

# 建立由列表组成的列表
# 有时我们会需要初始化一个嵌套着几个列表的列表,譬如一个列表可能需要用来存放不同的学生名单,或者一个井字游戏板上的一行方块.
# 想要达成这些目的,最好的选择是使用列表推导式
board = [["_"] * 3 for i in range(3)]
print(board)  # [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
board[1][2] = "X"
print(board)  # [['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]

# 下面展示一种看起来很好,但是错误的方法
weird_board = [["_"] * 3] * 3
print(weird_board)  # [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
weird_board[1][2] = "O"
print(weird_board)  # [['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]
# 外面的列表其实包含3个指向同一个列表的引用,一旦我们视图标记第一行第二列的元素,就立马暴露了列表内3个引用指向同一个对象的事实


# 序列的增量赋值
# 增量运算符+=和*=的表现取决于它们的第一个操作对象.
# +=背后的特殊方法是__iadd__(用于"就地加法"),但是如果一个类没有实现这个方法,Python会退一步调用__add__
a = [1, 2, 3]
b = [4, 5, 6]
a += b
# 如果a实现了__iadd__方法,就会调用这个方法.同时对可变序列来说,a会就地该表.就像调用了a.extend(b)一样.
# 但是如果a没有实现__iadd__方法,a += b这个表达式的效果就变得跟a = a + b一样了.
# 也就是说,在这个表达式中,变量名会不会被关联到新的对象,完全取决于这个类型有没有实现__iadd__方法.
# 总的来说,可变序列一般都实现了__iadd__方法,因此+=是就地加法.
# 这个概念也适用于*=,不同的是,*=相对应是__imul__方法.

# 下面这个小例子,展示的是*=在可变和不可变序列上的作用
l4 = [1, 2, 2]
print(id(l4))  # 2534218951304
l4 *= 2
print(id(l4))  # 2534218951304
t = (1, 2, 3)
print(id(t))   # 2535959527360
t *= 2
print(id(t))   # 2534198634440
# 对不可变序列进行重复拼接操作的话,效率会低很多,因为每次都有一个新对象,而解释器需要把原来对象中的元素先复制到新的对象里,然后再追加新的元素.
# str是一个例外,因为对字符串做+=操作实在是太普遍了,所以CPython对它做了优化.为str初始化内存的时候,程序会为它留出额外的可拓展空间,因此进行增量操作的时候,
# 并不会涉及复制原有字符串到新位置这类操作.

# 一个关于+=的谜题
t = (1, 2, [30, 40])
t[2] += [50, 60]
# 这两个表达式到底会产生什么结果?
# A: t变成(1,2,[30,40,50,60])
# B: 因为tuple不支持对它的元素赋值,所以会排除TypeError异常
# C: 以上两个都不是
# D: a和b都是对的

posted @ 2020-06-16 16:16  怀心抱素  阅读(232)  评论(0编辑  收藏  举报