10 个让我豁然开朗的Python概念
编程中的理解,有时只需一个好例子
当我初次接触Python时,我以为一年时间就能掌握它。简单的语法、友好的社区、无限的教程——我似乎什么都不缺。但随着时间推移,我意识到自己在一些基础概念上存在误解。
最困难的是什么?这些并非元类或编写Python编译器那样高大上的主题,而是我多年来一直理解错误或遗忘的基础知识。每次正确理解它们后,头脑中的障碍就消失了。
今天,我将分享花了数年才理解的10个Python概念,以及最终让我豁然开朗的简单示例。
1. 可变对象 vs 不可变对象
这是我遇到的第一个经典Python陷阱。我曾经无法理解为什么我的列表在函数内部会神奇地改变。
defadd_item(items, value):
items.append(value)
return items
my_list = [1, 2, 3]
add_item(my_list, 4)
print(my_list) # [1, 2, 3, 4] - 原列表被修改了!
关键在于:
不可变类型(int、float、str、tuple)→ 修改时会创建新对象 可变类型(list、dict、set)→ 修改时会改变原对象
理解这个区别后,调试代码变得容易多了。
2. 默认可变参数
这个问题我记了很多年。可变默认参数会导致"奇怪"的行为。
defadd_to_list(value, items=[]):
items.append(value)
return items
print(add_to_list(1)) # [1]
print(add_to_list(2)) # [1, 2] - 不是新的列表!
解决方法:
defadd_to_list(value, items=None):
if items isNone:
items = []
items.append(value)
return items
解释是:默认参数在函数定义时被计算,而不是每次调用时。
3. Python的对象引用传递
我曾经长期争论Python是"传引用"还是"传值"。实际上是按对象引用传递。
变量只是对象的标签 函数参数是同一对象的新名字
defmodify(num):
num += 1
print("Inside:", num)
x = 5
modify(x)
print("Outside:", x) # 仍然是5
对于不可变值,表现像传值;对于可变对象,表现像传引用。这种细微差别让我困扰了很久。
4. is 与 == 的区别
我曾互换使用这两个操作符,直到因此出错。
a = [1, 2]
b = [1, 2]
print(a == b) # True (值相等)
print(a is b) # False (不同对象)
==→ 比较值is→ 比较对象标识
这在处理None时尤其重要。**始终使用is None,而不是== None**。
5. 迭代器和生成器
我使用Python多年后才真正理解迭代器。关键在于__next__和__iter__方法。
my_iter = iter([1, 2, 3])
print(next(my_iter)) # 1
print(next(my_iter)) # 2
生成器简化了这一过程:
defcountdown(n):
while n > 0:
yield n
n -= 1
for i in countdown(3):
print(i) # 输出 3, 2, 1
理解了生成器后,我才明白为什么它们不将所有内容都保留在内存中。
6. 列表推导式 vs 生成器表达式
我最初以为它们是一样的,其实不是。
# 列表推导式:在内存中构建完整列表
squares = [x*x for x in range(5)]
# 生成器表达式:惰性计算,一次只产生一个值
squares_gen = (x*x for x in range(5))
处理大型数据集时,内存使用至关重要。
7. 上下文管理器(with语句)
我曾经这样写:
f = open("data.txt")
data = f.read()
f.close()
直到有人告诉我这很危险。如果出现错误,文件可能永远不会关闭。上下文管理器解决了这个问题:
with open("data.txt") as f:
data = f.read()
好处是:即使发生错误,with语句也能保证执行清理工作。你还可以使用__enter__和__exit__创建自己的上下文管理器。
8. *args 和 **kwargs 的强大之处
我曾经因为觉得吓人而避免使用它们。但它们其实就是动态参数解包。
defdemo(a, *args, **kwargs):
print("a:", a)
print("args:", args)
print("kwargs:", kwargs)
demo(1, 2, 3, x=4, y=5)
输出:
a: 1
args: (2, 3)
kwargs: {'x': 4, 'y': 5}
一旦理解了这个概念,编写可重用函数就变得更容易了。
9. 装饰器
我长期模仿使用装饰器却不理解其原理,直到看到这个示例:
deflog(func):
defwrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log
defgreet(name):
print(f"Hello, {name}")
greet("Python")
装饰器只是接受函数并返回函数的函数。理解了这一点,你就不再会觉得装饰器是"魔法"了。
10. __name__ == "__main__"的用途
我曾以为这行代码只是样板文件,直到理解了它的目的:
defmain():
print("Running as script!")
if __name__ == "__main__":
main()
它让你能够将文件同时作为模块和脚本来运行。因此,任何严肃的Python项目都会包含这行代码。
写在最后
这些概念一旦理解后似乎很明显,但我保证——它们最初对我来说并不明显。每一个都代表了我Python经验中的"顿悟时刻",突然间对事物有了更深层次的理解。
如果你仍然对其中一些概念感到困惑,别担心——这很正常。关键是不断尝试示例,直到理解为止。因为在编程中,一个好的示例可以改变你一生的编码方式。
Python激发好奇心。你探索的特性越多,你的代码就越优雅。不要死记硬背语法——要去实验、破坏它并从中学到东西。
你还遇到过哪些让你豁然开朗的Python概念?欢迎在评论区分享!
🏴☠️宝藏级🏴☠️ 原创公众号『数据STUDIO』内容超级硬核。公众号以Python为核心语言,垂直于数据科学领域,包括可戳👉Python|MySQL|数据分析|数据可视化|机器学习与数据挖掘|爬虫等,从入门到进阶!
长按👇关注- 数据STUDIO -设为星标,干货速递