12分钟讲解Python核心理念

本内容是对 Every Python Concept Explained in 12 Minutes 内容的翻译与整理。


Python之禅(The zen of Python)

当你在Python解释器中输入import this时,你会看到19条解释Python设计哲学的格言。

它描述了Python如何优先考虑优雅且富有美感的代码。

它阐明了代码应如何清晰地展示其功能,并避免"魔法"行为。

它提倡扁平优于嵌套,推崇使用空白和清晰的格式,并强调可读性。

它推崇有意处理错误、简单方案即是最佳方案的理念,并指出有时"聪明"的代码可能会导致糟糕的代码。

shell 复制代码
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
shell 复制代码
Python之禅,作者:蒂姆·彼得斯

优美胜于丑陋。
明确胜于隐晦。
简单胜于复杂。
复杂胜于繁复。
扁平胜于嵌套。
稀疏胜于密集。
可读性很重要。
特殊情况不足以违背这些原则。
尽管实用性胜过纯粹性。
错误绝不应该悄无声息地通过。
除非明确地加以抑制。
面对模糊不清时,拒绝猜测的诱惑。
应该有一种------而且最好只有一种------显而易见的方法来做事。
虽然这种方法一开始可能并不明显,除非你是荷兰人。
现在做胜过永不做。
尽管永不做往往胜过*现在立即*做。
如果实现很难解释,那就是个糟糕的想法。
如果实现很容易解释,那可能是个好想法。
命名空间是个绝妙的想法------让我们多多使用吧!

if __name__ == "__main__"

if __name__ == "__main__" 是一个至关重要的概念,也可能是Python中最广为人知的梗。

这是一个惯用语,用于判断一个程序是直接运行还是被作为模块导入。__name__是一个特殊变量,而"__main__"则代表主Python程序。

简单来说,当你双击并运行你的主程序时,它就是"__main__"

如果主程序正在被执行,那么名为__name__的这个变量将被设置为等于"__main__"

然而,如果你正在运行的代码或模块被导入到另一个Python项目中,那么__name__将被设置为你所导入的模块名,也就是不带.py后缀的Python文件名。if __name__ == "__main__"这个惯用语主要用于确保某些代码只在它作为主程序被直接运行时执行,而不是在被导入时执行。

if __name__ == "__main__" 的意思很简单:只有在直接运行这个文件时才执行某些代码,被导入时不执行

举个简单例子:

假设你有一个文件 my_module.py

python 复制代码
# my_module.py
def say_hello(name):
    print(f"你好,{name}!")

def add_numbers(a, b):
    return a + b

# 这里是关键部分
if __name__ == "__main__":
    # 只有直接运行这个文件时才会执行下面的代码
    print("正在直接运行 my_module.py")
    say_hello("小明")
    result = add_numbers(3, 5)
    print(f"3 + 5 = {result}")

两种运行方式的区别:

方式1:直接运行文件

bash 复制代码
python my_module.py

输出:

ini 复制代码
正在直接运行 my_module.py
你好,小明!
3 + 5 = 8

方式2:在另一个文件中导入

python 复制代码
# main.py
import my_module

my_module.say_hello("小红")  # 只调用函数

运行 python main.py 输出:

复制代码
你好,小红!

注意:导入时不会打印"正在直接运行 my_module.py"和其他测试代码。

为什么这样做?

这样可以让你的模块既能:

  • 作为独立程序运行(包含测试代码)
  • 被其他程序导入使用(只提供函数,不执行测试)

这就是Python之禅中"实用性胜过纯粹性"的体现!

万物皆对象

"万物皆对象"是Python设计哲学的一个基本方面。这意味着你在Python中使用的每一个实体都是某个类的实例,拥有属性和方法,可以被赋值给变量,可以作为参数传递,也可以从函数中返回。

数据类型是对象,函数是对象,类和模块是对象,甚至代码块也是对象。这就是Python中的对象层级结构。这使得Python极其灵活、一致且简单。

然而,这些对象可能会占用大量内存,从而影响性能。

空白与缩进

Python一个非常特别之处在于使用缩进和空白来定义代码块。

与其他使用花括号(如C或JavaScript)或关键字(如Ruby和Lua中的end)的语言不同,Python的这一设计特性确保了其最大程度的可读性。

在Python中,缩进通常用于ifforwhiledefclass代码块。不一致的缩进会导致IndentationError(缩进错误)。具有相同缩进级别的代码被视为在同一个代码块中,而嵌套代码则通过更多的缩进来实现。

循环中的else子句

循环中的else子句是一个经常让新用户感到惊讶的特性。这是一种开发者可以放在forwhile循环中的语句。它仅在循环正常完成而没有遇到break语句时才会执行。基本上,如果循环正常结束,else块就会执行;但如果循环被break语句终止,那么else块就会被跳过。

不要把它误认为是if-else语句的变体,它与条件逻辑无关,更像是一个"无中断"子句。一些实际用途包括检查质数或创建带有超时的循环。

列表推导式

列表推导式(List comprehensions)是一种独特的方式,可以用一行代码创建循环、列表和条件判断。一个普通的for循环是这样的,但通过Python的列表推导式,你可以把它变成紧凑的一行。

你甚至可以添加条件判断、编辑列表中的元素、执行嵌套循环和多个条件判断。虽然这是Python中一个有用的特性,但在所有情况下它未必是最佳选择。有时它会使事情变得非常复杂且难以阅读,在那些情况下,使用常规的语句会更好。

列表推导式就是用一行代码快速创建列表,比传统for循环更简洁。

基础例子

传统for循环:

python 复制代码
# 创建1到5的平方数列表
squares = []
for i in range(1, 6):
    squares.append(i ** 2)
print(squares)  # [1, 4, 9, 16, 25]

列表推导式(一行搞定):

python 复制代码
squares = [i ** 2 for i in range(1, 6)]
print(squares)  # [1, 4, 9, 16, 25]

加条件判断

传统方式:

python 复制代码
# 只要偶数的平方
even_squares = []
for i in range(1, 11):
    if i % 2 == 0:
        even_squares.append(i ** 2)
print(even_squares)  # [4, 16, 36, 64, 100]

列表推导式:

python 复制代码
even_squares = [i ** 2 for i in range(1, 11) if i % 2 == 0]
print(even_squares)  # [4, 16, 36, 64, 100]

处理现有列表

python 复制代码
names = ["alice", "bob", "charlie"]

# 传统方式
upper_names = []
for name in names:
    upper_names.append(name.upper())

# 列表推导式
upper_names = [name.upper() for name in names]
print(upper_names)  # ['ALICE', 'BOB', 'CHARLIE']

何时不要用?

太复杂时别用:

python 复制代码
# 这样太难读了!
result = [x*y for x in range(1,4) for y in range(1,4) if x != y and x+y > 3]

# 还是用传统for循环清晰
result = []
for x in range(1, 4):
    for y in range(1, 4):
        if x != y and x + y > 3:
            result.append(x * y)
  • 简单情况:用列表推导式,代码更pythonic
  • 复杂逻辑:用传统for循环,可读性更好
  • 这体现了Python之禅:"简单胜于复杂""可读性很重要"

多重赋值与元组解包

多重赋值与元组解包(Multiple assignment and tuple unpacking)是一种在单行代码中为多个变量赋值的方法。

你看,Python将右侧视为一个没有括号的元组,并按顺序将这些值赋给左侧的变量。顺便说一下,元组(tuple)是一种将多个值赋予一个变量的方式,有点像列表或数组,但一旦创建,就无法编辑或更改。这种赋值方式适用于任何可迭代的对象,比如列表、字符串或循环。

多重赋值与元组解包就是一次性给多个变量赋值,让代码更简洁。

基础例子

传统方式:

python 复制代码
# 分别赋值
name = "张三"
age = 25
city = "北京"

多重赋值(一行搞定):

python 复制代码
name, age, city = "张三", 25, "北京"
print(name)  # 张三
print(age)   # 25
print(city)  # 北京

交换变量值

传统方式(需要临时变量):

python 复制代码
a = 10
b = 20
temp = a
a = b
b = temp
print(a, b)  # 20 10

多重赋值(超级简洁):

python 复制代码
a = 10
b = 20
a, b = b, a  # 一行交换!
print(a, b)  # 20 10

解包列表/元组

python 复制代码
# 从列表解包
colors = ["红", "绿", "蓝"]
color1, color2, color3 = colors
print(color1)  # 红

# 从元组解包
point = (100, 200)
x, y = point
print(f"坐标:({x}, {y})")  # 坐标:(100, 200)

函数返回多个值

python 复制代码
def get_name_age():
    return "李四", 30  # 实际返回一个元组

# 一次接收多个返回值
name, age = get_name_age()
print(f"{name}今年{age}岁")  # 李四今年30岁

忽略不需要的值

python 复制代码
# 用下划线忽略不需要的值
data = ("王五", 28, "上海", "工程师")
name, age, _, job = data  # 忽略城市
print(f"{name}是{job},{age}岁")  # 王五是工程师,28岁

字符串解包

python 复制代码
word = "hello"
a, b, c, d, e = word
print(a)  # h
print(b)  # e
print(c)  # l

在循环中使用

python 复制代码
students = [("小明", 85), ("小红", 92), ("小刚", 78)]

for name, score in students:
    print(f"{name}的成绩是{score}分")

输出:

复制代码
小明的成绩是85分
小红的成绩是92分
小刚的成绩是78分

注意事项

python 复制代码
# 变量数量必须匹配
a, b = 1, 2, 3  # 错误!左边2个变量,右边3个值

# 可以用星号收集剩余值
a, b, *rest = 1, 2, 3, 4, 5
print(a)     # 1
print(b)     # 2  
print(rest)  # [3, 4, 5]

这体现了Python之禅中的**"优美胜于丑陋""简单胜于复杂"**!

动态与强类型

动态与强类型(Dynamic and strong typing)是Python的一个特性,它同时赋予了语言灵活性和安全性。

动态类型指的是在编写代码时无需显式声明变量的类型,变量的类型是在运行时确定的。

强类型指的是在运行时会强制执行严格的类型兼容性检查,类型不匹配将导致TypeError(类型错误)。这一特性使得函数更加灵活,并避免了因自动类型转换而导致的潜在错误。

鸭子类型

鸭子类型(Duck typing)是Python中的一个概念,它关注的是一个对象能做什么(即它的方法或属性),而不是它的类或类型。不需要进行类型检查。

这就像你玩一个游戏,需要猜哪个动物玩具会发出"嘎嘎"声。你首先拿起鸭子玩具,它发出了"嘎嘎"声。但如果你拿起另一个玩具,比如小狗玩具呢?你拿起它,惊讶地发现,它也发出了"嘎嘎"声。你后来发现,原来小狗玩具里放了一个鸭子叫声器。这大概是我能解释它的最好方式了。

基本上就是说,Python不关心这个东西"是什么",而更关心你希望它执行的"动作"。

pass语句

pass语句是一条告诉程序什么都不做的语句。它是"此页有意留白"的编程版本,但它确实有其用途。在开发过程中,你可以用它作为占位符。它也是构建类、头等函数和闭包的骨架的一个很好的构建块。

头等函数与闭包

头等函数(First-class functions,即函数作为头等公民)是一种让函数可以像字符串、变量或列表一样被使用的方式。这允许你在通常是面向对象的Python语言中使用函数式编程范式。函数可以被赋值给变量,用作其他函数的参数,也可以从其他函数中返回。

python 复制代码
# 函数可以赋值给变量
def greet(name):
    return f"你好,{name}!"

my_func = greet  # 函数赋值给变量

# 函数可以作为参数传递
def call_twice(func, arg):
    return func(arg) + " " + func(arg)

result = call_twice(greet, "小明")  # 传递函数作为参数

# 函数可以作为返回值
def make_multiplier(n):
    def multiplier(x):
        return x * n
    return multiplier  # 返回函数

double = make_multiplier(2)

闭包(closure)是一个函数对象,它能够使用来自其外层(包围它的)对象或函数的信息。

Dunder方法

Dunder方法(Dunder methods),是"双下划线方法"(double underscore methods)的简称,是一些特殊函数。

当你在Python代码中执行某个常见操作时,它们会自动运行。

Dunder方法的例子有:__init__,它用于初始化一个类的新实例,当你的程序创建一个新对象时它会自动运行;__add____sub____mul__,每当你使用算术运算符时它们就会被调用;__str__,它定义了当对象被print()函数使用时应显示的字符串。

你通常不会直接使用Dunder方法,它们大多在底层工作。

*args**kwargs

*args**kwargs是Python中的两种特殊语法,它们允许用户在函数中放入任意数量的参数或输入。

*args或任意位置参数(arbitrary positional arguments),允许一个函数接受任意数量的输入。

**kwargs或任意关键字参数(arbitrary keyword arguments),与*args类似,但你可以为你的输入附加一个标签,并以键值对的形式传入它们。

海象运算符

海象运算符(The walrus operator),在Python 3.8中引入,正式名称为赋值表达式(assignment expression),它允许你在一个代码块内部为变量赋值,从而避免了为创建变量而单独写一行的需要。它们非常适合在循环、列表推导式中使用,并且常用于输入验证和数据解析。

海象运算符 := 就是在表达式中同时赋值和使用变量,避免重复计算或多写代码行。

基础例子

传统方式:

python 复制代码
# 需要先计算,再判断
text = input("请输入文字: ")
if len(text) > 5:
    print(f"文字长度是 {len(text)}")  # len()计算了两次

海象运算符:

python 复制代码
# 一次计算,同时赋值和使用
if (length := len(input("请输入文字: "))) > 5:
    print(f"文字长度是 {length}")  # 只计算一次

在循环中使用

传统方式:

python 复制代码
# 读取文件直到遇到空行
line = input("输入一行(空行结束): ")
while line != "":
    print(f"你输入了: {line}")
    line = input("输入一行(空行结束): ")  # 重复代码

海象运算符:

python 复制代码
# 避免重复代码
while (line := input("输入一行(空行结束): ")) != "":
    print(f"你输入了: {line}")

在列表推导式中使用

传统方式:

python 复制代码
# 处理数字,但要多次调用expensive_function
numbers = [1, 2, 3, 4, 5]

def expensive_function(x):
    print(f"计算 {x}...")  # 模拟耗时操作
    return x * x

# 问题:expensive_function被调用两次
results = [expensive_function(x) for x in numbers if expensive_function(x) > 10]

海象运算符:

python 复制代码
# 只计算一次
results = [result for x in numbers if (result := expensive_function(x)) > 10]

处理正则表达式匹配

传统方式:

python 复制代码
import re

text = "我的电话是 138-1234-5678"
pattern = r'\d{3}-\d{4}-\d{4}'

match = re.search(pattern, text)
if match:
    phone = match.group()
    print(f"找到电话号码: {phone}")

海象运算符:

python 复制代码
import re

text = "我的电话是 138-1234-5678"
pattern = r'\d{3}-\d{4}-\d{4}'

if (match := re.search(pattern, text)):
    print(f"找到电话号码: {match.group()}")

数据解析示例

传统方式:

python 复制代码
data = ["10", "abc", "25", "xyz", "30"]
numbers = []

for item in data:
    try:
        num = int(item)
        if num > 15:  # 需要重复使用num
            numbers.append(num)
    except ValueError:
        continue

海象运算符:

python 复制代码
data = ["10", "abc", "25", "xyz", "30"]
numbers = []

for item in data:
    try:
        if (num := int(item)) > 15:  # 赋值和判断一步完成
            numbers.append(num)
    except ValueError:
        continue

print(numbers)  # [25, 30]

实际应用:文件处理

python 复制代码
# 逐块读取大文件
with open("large_file.txt", "r") as file:
    while (chunk := file.read(1024)):  # 每次读1024字节
        process(chunk)  # 处理数据块

注意事项

python 复制代码
# 需要用括号包围赋值表达式
if (x := 10) > 5:  # 正确
    print(x)

# if x := 10 > 5:  # 错误!语法错误

海象运算符体现了Python之禅中的**"优美胜于丑陋",让代码更紧凑、避免重复计算,但要注意不要滥用,保持"可读性很重要"**!

装饰器

装饰器(Decorators)是一种在不永久改变函数代码的情况下增强其功能的方式。它们接受函数作为输入,并输出一个功能被修改过的函数。

这是一种让函数们做"融合之舞"并创造出一个新的"超级函数"的酷炫方式。装饰器也适用于类和方法,你甚至可以堆叠使用多个装饰器。它通常用于计时、缓存和验证。

with语句与上下文管理器

with语句与上下文管理器(The with statement and context managers)是用于资源管理和文件清理的工具,并且省去了编写try...finally语句的需要。

上下文管理器实现了两个特殊方法:__enter__,它负责设置并返回资源;以及__exit__,它负责处理清理工作。它在文件处理中非常常用。

__slots__优化

__slots__优化是Python中一个用于优化内存使用的独特功能。通常,一个类的属性存储在一个字典中,这是Python中的一种键值对结构。你可以把字典想象成电话簿,名字是键,电话号码是值。

然而,如果你有大量的对象实例,这会占用大量内存。这就是__slots__发挥作用的地方。它使用一种更紧凑的内部结构,比如一个引用数组,来存储属性。它最适合用于内存密集型应用。但要小心,它可能会破坏依赖于字典或vars()函数的其他代码。

错误处理中的else语句

错误处理中的else语句是一个帮助开发者编写更具意图性的错误处理代码的特性。它仅在try块中没有发生错误或异常时才会执行。它用于创建一个"成功路径"而非"错误路径",并使错误处理更加精确。错误处理中的else语句非常适用于API请求和数据库事务。

可变默认参数

可变默认参数(Mutable default arguments)是Python中一个对于初学者来说可能容易混淆的方面,通常被称为一个"坑"(gotcha)。可变默认参数是指你可以将一个可编辑的数据结构(如列表或字典)作为函数的输入或参数。但棘手的部分是,无论你为这个可编辑输入传入什么值,它都会在你每次调用该函数时持续存在,并被设置为其默认值。即使你改变了输入,这个可变默认参数仍将保持不变。对此,一个常见的解决方案是使用None作为默认值,或使用不可编辑的默认值。

全局解释器锁

全局解释器锁(Global Interpreter Lock,简称GIL)是Python设计中一个非常有争议的方面。它基本上是一个互斥锁(mutex或mutual exclusion lock),用于保护对Python对象的访问。互斥锁是Python中的一条规则,规定在同一时间只有一个线程可以访问程序的某个部分。

更简单地说,想象一下你和朋友们一起玩视频游戏。如果多个人共享一个手柄,它可能会被损坏甚至弄坏。互斥锁就是一种规定,即同一时间只有一个人可以使用一个手柄。全局解释器锁则是一条包罗万象的规则,规定在任何单个时刻只有一个CPU线程在运行一段代码。这就像之前的手柄规则一样,但这次是用于一个单人游戏。

表面上看,这似乎效率低下。那么为什么Python要这样设计呢?这在某种程度上是一种安全措施。Python会追踪它的对象------记住,在Python中,几乎所有东西都是对象。它以一种特殊的方式进行追踪,如果允许多个线程同时访问这些对象,就会破坏Python内部的追踪方式。全局解释器锁确保了这种情况不会发生。

相关推荐
绝无仅有3 小时前
Java技术复试面试:全面解析
后端·面试·github
用户297994363793 小时前
门户功能技术方案实战:搞定动态更新与高频访问的核心玩法
后端
我不是混子3 小时前
为什么不建议使用SELECT * ?
后端·mysql
NightDW3 小时前
amqp-client源码解析1:数据格式
java·后端·rabbitmq
程序员清风3 小时前
美团二面:KAFKA能保证顺序读顺序写吗?
java·后端·面试
风象南4 小时前
SpringBoot的零配置API文档工具的设计与实现
spring boot·后端
程序员爱钓鱼4 小时前
Go语言实战案例-项目实战篇:开发一个 IP 归属地查询接口
后端·google·go
追逐时光者4 小时前
C#/.NET/.NET Core推荐学习书籍(25年9月更新)
后端·.net
IT_陈寒4 小时前
Vue3性能优化:掌握这5个Composition API技巧让你的应用快30%
前端·人工智能·后端