引言

在python的学习过程中我踩过不少的坑,我想许多像我一样的新人也可能遇到过这些问题,为此我总结了5个很经典的问题,并给出了我自己的解决办法,希望能对初学者有所帮助。

坑 1:‘is’ 与 ‘==’ 之间的区别

1. 示例代码:

a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)  # True
print(a is b)  # False

2. 原因解释:

  • 其中‘==’比较的是两个列表中的值是否相等。
  • 而‘is’则比较的是两个列表是否指向同一块内存。

3. 正确使用:多数情况下我们只需要使用‘==’即可,只有那种需要确认对象标识时才用‘is’。

坑 2:函数默认参数的陷阱

1. 错误示例代码:

def add_item(item, my_list=[]):
    my_list.append(item)
    return my_list

print(add_item(1))  # [1]
print(add_item(2))  # [1, 2] 不是 [2]

2. 原因解释:Python里定义函数时默认参数只会执行一次。

3. 正确写法:

def add_item(item):
    my_list = []
    my_list.append(item)
    # 可简写为一下形式(多步列表初始化可被替换为列表字面量)
    # my_list = [item] 
    return my_list

print(add_item(1))  # [1]
print(add_item(2))  # [2]

坑 3:深拷贝 vs 浅拷贝

1. 错误实例代码:

import copy

a = [[1, 2], [3, 4]]
b = copy.copy(a)
b[0][0] = 99
print(a)  # [[99, 2], [3, 4]]

2. 原因解释:以上写法为浅拷贝,浅拷贝只会复制外层对象,而内层对象仍然被共享使用,因为我们不想改变a中的数据,所以则需要使用一下深拷贝的写法。

3. 正确写法:

import copy

a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)
b[0][0] = 99
print(a)  # [[1, 2], [3, 4]]

4. 通俗理解: 引用当时讲师的白话就是:‘深拷贝拷贝的深,浅拷贝拷贝的浅’,仔细思考一下还是比较好理解的。

坑 4:浮点数精度问题

1. 错误实例代码:

print(0.1 + 0.2)  # 0.30000000000000004

2. 原因解释:浮点数在计算机中是二进制近似存储,存在精度问题,但python为我们提供了一个decimal模块用来以10进制方式精确计算小数。

3. 正确写法:

from decimal import Decimal

print(Decimal('0.1') + Decimal('0.2'))
# 注意: Decimal()方法中的数值一定要用''包裹.

坑 5:  循环变量的延迟绑定问题 (这个坑是困扰我比较久的我会详细解释一下)

1. 错误实例代码:

funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])   # 输出: [2, 2, 2]

2. 原因解释:

  • 简短解释:在python中闭包捕获的是变量的引用,而并非当时的值,也就是说lambda中的‘i’并不会在创建lambda的时候储存下来当数值,而是在lambda被调用时才取‘i’的当前值。
  • 关键点解释:
    • 定义阶段:每执行 ‘lambda:i’ 时创建一个函数对象,这个函数会引用外层作用域里的名字‘i’(不是复制‘i’的当前值),这些函数内部都持有对同一个名字 ‘i’ 的引用。
    • 循环结束后:外层的’i‘最终为2即最后循环的值。
    • 调用阶段:当 'f()' 时,lambda去读取‘i’的值,此时‘i’已经是2了,因此所有的lambda都返回2。
  • 总结:lambda捕获的是‘对名字i的引用’,而不是‘当时的数值’。

3. 解决方式:其中使用‘i=i’就是在函数定义时把 ‘i’ 的值当作默认值保存下来,这样‘i’就相当于被‘冻结’起来了。

funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])  # [0, 1, 2]

4. 内存图解释:

  • 错误写法:三个lambda都只存了1个指针,指向同一个变量‘i’,当循环结束后这个变量最终值为2,所以这个被三个lambda同时使用的指针指向的值就是这一个2.
第一次循环: i = 0
funcs[0] = lambda: i   ---> 引用同一个 i
第二次循环: i = 1
funcs[1] = lambda: i   ---> 还是引用同一个 i
第三次循环: i = 2
funcs[2] = lambda: i   ---> 还是引用同一个 i
      ┌─────────────┐
      │   funcs[0]  ├───┐
      └─────────────┘   │
      ┌─────────────┐   │    ┌─────────────┐
      │   funcs[1]  ├───┼──▶ │   i = 2     │
      └─────────────┘   │    └─────────────┘
      ┌─────────────┐   │
      │   funcs[2]  ├───┘
      └─────────────┘
  •  正确写法:每次循环都会生成一个新的‘i’,把当时的值‘冻结’下来,这时每个函数的内部都保留了自己的‘i’,所以输出就是[0, 1, 2]。
      ┌─────────────┐
      │   funcs[0]  ├──▶ i(默认参数)=0
      └─────────────┘

      ┌─────────────┐
      │   funcs[1]  ├──▶ i(默认参数)=1
      └─────────────┘

      ┌─────────────┐
      │   funcs[2]  ├──▶ i(默认参数)=2
      └─────────────┘

更多推荐