Python基础语法30个易错点

文章目录

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的朋友,否则看看零散的博文就够了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值