文章目录
P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。
前言
说实话啊,写Python这么多年,我算是看透了——这语言就是披着"简单易学"外衣的千层饼!表面看语法像白开水,真写起来才发现处处是坑。今天我就把这22年踩过的雷、掉过的坑,给你整一份《Python基础语法30个易错点》,看完至少能帮你避开90%的坑,少走弯路。
咱们不搞那些教科书式的八股文,直接上干货!我一条一条给你唠。
一、缩进那些破事儿
1. Tab和空格混用——隐形杀手!
说真的,这个坑我当年踩了不下20次。Python拿缩进当语法,不是排版!最绝的是,Tab和空格混用时,代码看着齐整,一运行直接IndentationError。
def hello():
print("第一行") # 用了4个空格
print("第二行") # 用了Tab键
错误提示还巨迷惑:“unindent does not match any outer indentation level”。啥意思?就是混用了!我后来学乖了,IDE里直接开"Tab自动转4个空格",一劳永逸。
2. 复制粘贴代码的缩进地狱
从CSDN、知乎、甚至微信聊天记录里复制代码?小心了!中文全角空格混进来,Python根本不认。看着明明是四个空格,其实里面藏着全角字符,报错位置还指不对…这酸爽,谁踩谁知道!
二、变量命名——别跟关键字抢地盘
3. 用保留字当变量名
if = 10、class = “语文”、list = [1,2,3]…这种代码写出来,Python直接给你个SyntaxError: invalid syntax。关键是list这种内置函数名,用了不报错,但后面想调list()转列表时,直接炸!因为list已经被你覆盖成变量了。
说真的,命名用user_name别用username,用item_count别用count,下划线隔开,既避开关键字又好看。
4. 变量名以数字开头
1st_name = “张三”?想都别想!Python变量名只能字母或下划线开头。这个坑新手犯的最多,因为想表达"第一名"这种语义,结果语法直接拍死。
三、数据类型——Python的"动态"是双刃剑
5. ==和is傻傻分不清
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True,值相等
print(a is b) # False,内存地址不同!
is比较的是内存地址(身份),比较的是值。判断None时用is None才对,用 None虽然结果对,但显得很不Pythonic。记住:None、True、False这种单例对象,必须用is判断!
6. 浮点数精度陷阱
0.1 + 0.2 == 0.3?结果是False!因为计算机二进制存浮点数有精度损失,0.1+0.2其实是0.30000000000000004。
真要做金融计算或精确比较,要么用Decimal模块,要么用round()或abs(a - b) < 1e-9这种误差范围判断。这坑不填,电商系统算钱能算出1分钱的差异,财务能追杀你三条街!
7. 整数除法/和//搞混
Python3里,5 / 2结果是2.5(浮点数),5 // 2才是2(整数除法)。从Java、C转过来的兄弟最容易踩,想着除法取整,结果拿到个小数,后面索引越界报错,找半天找不到原因…
四、可变对象的坑——Python最深的水
8. 列表乘法创建二维数组的惊天大坑
matrix = [[0] * 3] * 3
matrix[0][0] = 1
print(matrix) # [[1,1,1], [1,1,1], [1,1,1]] ???
卧槽!改一个元素,三行全变了!因为* 3复制的是引用,三行指向同一个内部列表!正确的姿势是列表推导式:[[0] * 3 for _ in range(3)]。
这个坑我印象太深了,当年写贪吃蛇游戏,蛇身移动逻辑因为这个bug,蛇头一动,蛇身全叠在一起, debug了整整一下午!
9. 字典的默认参数是"陷阱门"
def add_item(item, my_list=[]):
my_list.append(item)
return my_list
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] ???卧槽!
看到没?默认参数[]在函数定义时就被创建了,不是每次调用新建!第二次调用时,用的还是第一次那个列表。正确写法是默认None,函数内部判断初始化。
10. 循环中删列表元素
nums = [1, 2, 3, 4]
for n in nums:
if n % 2 == 0:
nums.remove(n) # 别这么干!
循环中改被迭代的列表,迭代器会乱套!结果是跳过某些元素删不干净。正确做法是倒序遍历或者遍历副本for n in nums[:]。
五、字符串和编码——一个头两个大
11. 字符串是不可变的!
s = "hello"
s[0] = "H" # TypeError!
Python字符串 Immutable(不可变),想改必须新建。频繁拼接字符串用+?性能爆炸!大数据量时用"".join(list)才是正解。
12. 中英文标点混用——新手噩梦
print(“hello”)(中文引号)、if a > 5:(中文冒号)…这种错误IDE都不一定能高亮出来,运行时报SyntaxError,位置指向还迷之不准。我现在的习惯是写代码时输入法强制切英文,宁可多按几次Shift。
13. encode和decode方向搞反
字符串转字节是encode(),字节转字符串是decode()。经常有人写成"hello".decode(),然后报错’str’ object has no attribute ‘decode’。记住:字符串是文本,字节是二进制,文本变二进制是编码(encode),二进制变文本是解码(decode)。
六、流程控制——逻辑没问题但写法有问题
14. else和elif的缩进层级
if x > 5:
print("大")
else: # 这里对齐if
print("小")
elif x == 5: # 缩进错了!应该在else层级
print("等于")
elif必须和if、else同级对齐。这种错误一般发生在多层嵌套后,眼睛看花的时候。
15. for循环的else子句
很多人不知道for可以带else!而且这else是在循环正常结束(没有被break)时执行。常用于找元素,没找到时给个提示:
for item in items:
if item == target:
print("找到了")
break
else:
print("没找到") # 循环跑完了才执行
16. while循环忘记更新条件
i = 0
while i < 5:
print(i) # 完了,i永远是0,死循环!
死循环还算好的,至少你能发现。最怕的是逻辑上该退出却没退出,CPU干满你还不知道哪出的问题…
七、函数参数——Python最灵活也最坑的地方
17. 可变参数*args是元组
def func(*args):
args[0] = 100 # TypeError!
*args收集参数变成元组tuple,元组不可变!想改要args = list(args)转一下。
18. 关键字参数kwargs的key必须是字符串
传func({1: “a”})会报错,因为字典展开成关键字参数时,key必须是合法的标识符(字符串)。数字当key不行。
19. 参数默认值的"延迟绑定"(闭包坑)
funcs = []
for i in range(3):
def f():
return i
funcs.append(f)
print(funcs) # 2,不是0!
循环里定义的函数,如果引用了循环变量,记住这变量是延迟绑定的!调用时i已经是2了。解决方法是默认参数绑定:def f(i=i): return i。
八、作用域——LEGB规则要记牢
20. 局部变量覆盖全局变量没报错
x = 10
def func():
x += 1 # UnboundLocalError!
函数内想改全局变量x,必须先声明global x,否则Python认为你在定义局部变量x,然后发现x还没赋值就+=,直接报错。
21. global和nonlocal搞混
global是模块级全局变量,nonlocal是外层函数(闭包)的变量。嵌套函数里想改外层非全局的变量,用nonlocal。
九、导入模块——循环导入是噩梦
22. 循环导入(Circular Import)
A.py里import B,B.py里import A,程序直接 ImportError 或者 AttributeError。解决方案:延迟导入(把import写在函数内部),或者重构代码把公共部分抽出来。
23. from module import *的坑
这种写法会导入模块里所有不以下划线开头的名字。如果模块里改了实现,加了新变量,可能覆盖你当前命名空间的变量!而且代码可读性巨差,根本不知道名字从哪来的。正经人都用import module或者from module import specific_name。
十、异常处理——别滥用try-except
24. 裸except catching everything
try:
do_something()
except: # 啥也不写,捕获所有包括KeyboardInterrupt!
pass
千万别这么写!这会捕获SystemExit、KeyboardInterrupt(Ctrl+C),导致程序无法正常退出。至少写except Exception。
25. try里用return,finally还会执行
def func():
try:
return 1
finally:
print("一定会执行")
即使try里有return,finally也一定会执行!如果finally里也有return,会覆盖try里的return值。这特性有时候能救命,有时候也能坑死人。
十一、类与面向对象——self别忘了
26. 实例方法忘记写self
class Dog:
def bark(): # 少了self!
print("汪汪")
调用dog.bark()时会报错,因为Python会自动把实例当第一个参数传进去,但你的函数定义没收。定义实例方法必须写def bark(self):。
27. 类属性 vs 实例属性
class Dog:
tricks = [] # 类属性!所有实例共享
def add_trick(self, trick):
self.tricks.append(trick)
这样写,所有狗的tricks都是同一个列表!应该用self.tricks = []在__init__里初始化实例属性。
十二、迭代器和生成器——用一次就没了
28. 迭代器只能遍历一次
it = iter([1, 2, 3])
print(list(it)) # [1,2,3]
print(list(it)) # [] 没了!
迭代器是一次性的!遍历完就 exhausted(耗尽)了。想重复遍历要么存成list,要么重新生成。
29. 生成器表达式 vs 列表推导式
列表推导式
squares = [x**2 for x in range(10)] # 立即生成整个列表
生成器表达式
squares = (x**2 for x in range(10)) # 惰性求值,省内存
圆括号是生成器,方括号是列表。大数据量时用生成器,小数据用列表。搞混了性能差几十倍!
三十、is None的判断
30. 别用==判断None!
虽然x == None大多数情况下结果对,但万一某个类自定义了__eq__方法返回True呢?严谨写法永远是x is None或x is not None。这是PEP 8强推的规范。
说真的啊,Python的坑远不止这30个,但这些绝对是最常见、最隐蔽、最让人血压飙升的。我写这篇文章的时候,好多坑的情景历历在目——有加班到半夜发现是缩进问题的,有上线前才发现浮点精度导致金额对不上的…
你现在卡在哪个坑里了?或者踩过啥更离谱的坑?评论区唠唠呗! 咱一起把这Python的雷区踩平,让后来者少走弯路。要是觉得有用,点个赞加个收藏,下次写代码前翻出来看看,保准少加班两小时!
P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。
267

被折叠的 条评论
为什么被折叠?



