目录
0. 前言:为什么是Python?
1. 准备工作:安装与环境
1.1 安装Python
1.2 选择代码编辑器或IDE
1.3 第一个程序:Hello, World!
2. 基础语法与核心概念
2.1 变量与数据类型
2.2 基本运算符
2.3 字符串操作
2.4 列表 (Lists)
2.5 元组 (Tuples)
2.6 字典 (Dictionaries)
2.7 集合 (Sets)
3. 流程控制
3.1 条件语句 (if, elif, else)
3.2 循环语句 (for, while)
3.2.1 for 循环
3.2.2 while 循环
3.2.3 循环控制语句 (break, continue, else)
4. 函数
4.1 定义和调用函数
4.2 参数传递
4.3 参数类型
4.4 返回值 (return)
4.5 变量的作用域 (Scope)
4.6 Lambda 表达式 (匿名函数)
5. 模块和包
5.1 模块 (Module)
5.2 包 (Package)
5.3 标准库模块 (The Standard Library)
6. 文件操作
6.1 打开文件 (open())
6.2 读写文件内容
7. 异常处理 (Exception Handling)
7.1 异常类型
7.2 捕获和处理异常 (try...except)
7.3 抛出异常 (raise)
8. 面向对象编程 (OOP) 基础
8.1 定义类和创建对象
8.2 继承 (Inheritance)
8.3 封装 (Encapsulation)
8.4 多态 (Polymorphism)
9. 接下来学什么?
目标读者: 想正经学Python编程,不想被太多花哨比喻干扰,能接受一点技术性冷幽默的人。
核心原则: 直接了当讲概念、语法和为什么。少点“想象一下”,多点“看代码”。
字数: 妥妥超过10000字。
简单说:语法相对简单,用途极其广泛(Web开发、数据分析、人工智能、自动化脚本、科学计算...),社区庞大(意味着遇到问题容易找到答案),库多到离谱(别人造好的轮子,拿来就用)。
缺点?运行速度可能没C/C++快(但对大多数入门级应用和脚本来说,够用了),某些设计哲学(比如缩进)让从其他语言转来的人一开始有点懵。但总的来说,它是目前最友好的入门语言之一,也是生产环境的强大工具。
一点玩笑: 学Python的最大好处?你可以跟人说“我会编程”,而不需要先花几年时间搞清楚C++的指针到底指向了谁的灵魂。当然,如果你想深入,该学的计算机基础一样跑不掉。
去官网: 打开 https://www.python.org (别输错了,网上骗子多)。
下载: 找 “Downloads” 部分。通常网站会自动推荐你操作系统的最新稳定版(比如 Python 3.12.x)。关键:下载 Python 3.x!Python 2.x 已经在2020年正式退休了,别去考古。
安装:
Windows: 运行下载的 .exe
安装程序。超级重要: 勾选 Add Python 3.x to PATH
这个选项!这决定了你能否在命令行里直接敲 python
命令。然后点 “Install Now”。如果没勾选PATH,后面会很麻烦,可能需要手动配置环境变量(新手劝退点之一)。
macOS: 运行下载的 .pkg
安装程序。安装过程通常很顺畅。
Linux: 大概率系统已经预装了 Python 3。打开终端 (Terminal),输入 python3 --version
或 python --version
看看。如果没装或者版本太旧,用系统自带的包管理器安装(如 Ubuntu/Debian: sudo apt update && sudo apt install python3
)。
验证安装:
打开命令行 (Windows: cmd 或 PowerShell; macOS/Linux: Terminal)。
输入 python --version
(或 python3 --version
)。如果看到像 Python 3.12.1
这样的输出,恭喜,安装成功。如果提示 'python' is not recognized...
或者 command not found
,大概率是 PATH 没设置好,回去检查安装步骤。
写代码需要个趁手的工具。记事本能写,但效率极低。推荐几个:
初学者友好:
IDLE: Python 自带的简易 IDE。够用,但功能简陋。适合体验。
Thonny: 专为教学设计的轻量级 IDE。对理解变量、执行流程很有帮助。
主流强大:
Visual Studio Code (VS Code): 微软出品,免费开源,插件生态极其丰富(包括强大的 Python 支持),轻量且强大。强烈推荐。需要单独安装 Python 扩展。
PyCharm (Community Edition): JetBrains 出品。专为 Python 设计,功能非常全面(代码补全、调试、版本控制集成等),社区版免费。功能全但也相对重一些。
其他选择: Sublime Text, Atom, Vim/Emacs (需要配置,高手向)。
建议: 新手从 VS Code 或 PyCharm Community 开始。它们能帮你避免很多低级错误(比如拼写错误),并提供良好的代码提示。
这是编程界的传统仪式。打开你选择的编辑器,新建一个文件,保存为 hello.py
(.py
是 Python 文件的标准后缀)。
在文件里输入:
# 这是我的第一个Python程序 print("Hello, World!")
保存文件。
运行方法:
命令行运行: 打开命令行,导航到 hello.py
文件所在的目录 (用 cd
命令),输入 python hello.py
(或 python3 hello.py
),回车。
在 IDE 中运行: 通常在编辑器里有个“运行”按钮 (像播放键 ▶️) 或菜单选项 (Run -> Run Module)。
如果一切顺利,你会在命令行窗口或 IDE 的输出区域看到 Hello, World!
。
解释:
#
开头的是注释。Python 解释器会忽略它。注释是用来给人看的,解释代码是干嘛的或者为什么这么写。多写注释是美德(虽然经常懒得写)。
print(...)
是一个内置函数。它的作用就是把括号里的内容输出到屏幕上(标准输出)。这里我们让它输出一个字符串 "Hello, World!"
。字符串要用单引号 '
或双引号 "
括起来。
一点玩笑: 恭喜!你刚刚完成了程序员职业生涯中99%的“Hello, World!”任务。剩下的就是不断复制粘贴和改bug了(误)。
变量 (Variables): 想象成一个个贴了标签的盒子。你把数据(值)放进去,通过标签(变量名)就能找到它。Python 中创建变量极其简单:直接给一个名字赋值就行。
message = "Hello, Python!" # 创建一个叫 message 的变量,存字符串 count = 10 # 创建一个叫 count 的变量,存整数 price = 19.99 # 创建一个叫 price 的变量,存浮点数 (小数) is_active = True # 创建一个叫 is_active 的变量,存布尔值 (True/False)
变量命名规则:
只能包含字母 (a-z, A-Z)、数字 (0-9) 和下划线 (_
)。
不能以数字开头。 (2nd_place
是错的,second_place
是对的)。
区分大小写 (myvar
, myVar
, MYVAR
是三个不同的变量)。
避免使用 Python 的关键字 (如 if
, for
, while
, def
, class
等,这些词有特殊含义)。
起有意义的名字! user_name
比 un
好得多,total_price
比 tp
清晰。代码是给人读的,包括未来的你。x
, y
, tmp
这种名字只在非常短的、作用域很小的代码里勉强可以接受。
数据类型 (Data Types): 变量里放的东西是有类型的。Python 是动态类型语言:你不需要在创建变量时声明它的类型,解释器会根据你赋的值自动确定。但类型本身是存在的,而且很重要!
整数 (int): 没有小数点的数。10
, -5
, 0
, 1000000
。
浮点数 (float): 带小数点的数。3.14
, -0.001
, 2.0
。注意:计算机处理浮点数有精度问题,例如 0.1 + 0.2
可能不等于 0.3
(非常接近但不是绝对相等),这是二进制浮点数的通病,不是 Python 的 bug。
字符串 (str): 文本数据。用单引号 '...'
或双引号 "..."
括起来。"Hello"
, 'Python'
, "It's a nice day"
(字符串里包含单引号时,用双引号括起来方便)。三引号 '''...'''
或 """..."""
用于多行字符串。
布尔值 (bool): 只有两个值:True
和 False
(注意首字母大写)。用于逻辑判断。
空值 (NoneType): 只有一个值 None
。表示“什么都没有”或“空”。常用于初始化变量或表示函数没有返回值。
查看类型: 用 type()
函数。
print(type(message)) # 输出:print(type(count)) # 输出: print(type(price)) # 输出: print(type(is_active)) # 输出: print(type(None)) # 输出:
类型转换: 有时需要把一个类型转换成另一个类型。使用内置函数:
int(x)
:将 x
转换为整数。如果 x
是浮点数,会截断小数部分(不是四舍五入)。字符串 x
必须是数字格式(如 "123"
)。
float(x)
:将 x
转换为浮点数。字符串 x
必须是数字格式。
str(x)
:将 x
转换为字符串。几乎任何类型都能转成字符串。
bool(x)
:将 x
转换为布尔值。规则:0
, 0.0
, 空字符串 ""
, 空列表 []
, None
等被视为 False
;其他大多数值被视为 True
。
num_str = "123" num_int = int(num_str) # 转成整数 123 num_float = float(num_str) # 转成浮点数 123.0 price = 19.99 price_str = str(price) # 转成字符串 "19.99" result = bool(0) # False result = bool("hello") # True
一点玩笑: 动态类型一时爽,类型错误调试火葬场。写代码时心里得清楚变量现在是什么类型,不然 "10" + 5
会报错(字符串不能直接加整数),而 10 + "5"
同样报错。
用来对变量和值进行计算或比较。
算术运算符:
+
:加 (5 + 3
-> 8
)
-
:减 (5 - 3
-> 2
)
*
:乘 (5 * 3
-> 15
)
/
:真除 (总是返回浮点数) (5 / 2
-> 2.5
)
//
:整除 (向下取整) (5 // 2
-> 2
, -5 // 2
-> -3
)
%
:取模 (求余数) (5 % 2
-> 1
, 10 % 3
-> 1
)
**
:幂 (2 ** 3
-> 8
(2的3次方))
赋值运算符: =
是最基本的。还有复合赋值:
+=
(x += 3
等价于 x = x + 3
)
-=
, *=
, /=
, //=
, %=
, **=
同理。
比较运算符 (返回布尔值 True/False):
==
:等于 (5 == 5
-> True
)
!=
:不等于 (5 != 3
-> True
)
>
:大于 (5 > 3
-> True
)
<
:小于 (5 < 3
-> False
)
>=
:大于等于 (5 >= 5
-> True
)
<=
:小于等于 (5 <= 3
-> False
)
逻辑运算符 (操作布尔值):
and
:逻辑与。两边都为 True 才返回 True。
or
:逻辑或。只要有一边为 True 就返回 True。
not
:逻辑非。取反。 (not True
-> False
, not False
-> True
)
注意短路行为: and
如果左边是 False
,右边就不计算了;or
如果左边是 True
,右边就不计算了。
a = 10 b = 20 c = 10 print(a == c) # True print(a != b) # True print(a > b) # False x = True y = False print(x and y) # False print(x or y) # True print(not x) # False # 短路示例 def some_function(): print("我被调用了!") return True if False and some_function(): # some_function 不会被调用 pass if True or some_function(): # some_function 不会被调用 pass
一点玩笑: =
和 ==
是新手永恒的敌人。写 if x = 5:
(这是赋值) 会报错,正确的是 if x == 5:
(这是比较)。编译器/解释器通常会揪出这个错误,但人眼有时会忽略。
字符串是编程中处理最频繁的类型之一。Python 提供了丰富的字符串操作方法。
拼接 (Concatenation): 用 +
连接字符串。
first_name = "张" last_name = "三" full_name = first_name + " " + last_name # "张 三"
重复 (Repetition): 用 *
重复字符串。
separator = "-" * 20 # "--------------------"
索引 (Indexing): 访问字符串中的单个字符。索引从 0 开始。可以用 负数索引 从右边开始数(-1 是最后一个字符)。
s = "Python" print(s[0]) # 'P' print(s[2]) # 't' print(s[-1]) # 'n' # print(s[6]) # 错误!索引越界 (IndexError)
切片 (Slicing): 获取字符串的一部分(子串)。语法:[start:end:step]
start
: 起始索引 (包含)。
end
: 结束索引 (不包含)。
step
: 步长 (默认为1)。负数步长表示反向切片。
可以省略 start
(默认为0), end
(默认为末尾), step
(默认为1)。
s = "Hello, World!" print(s[0:5]) # "Hello" (索引0到4) print(s[7:12]) # "World" (索引7到11) print(s[:5]) # "Hello" (从开始到索引4) print(s[7:]) # "World!" (从索引7到末尾) print(s[-6:-1]) # "World" (倒数第6个到倒数第2个) print(s[::2]) # "Hlo ol!" (步长为2) print(s[::-1]) # "!dlroW ,olleH" (反转字符串)
常用方法 (Methods): 字符串是对象,有很多内置方法(使用点 .
语法调用)。这些方法返回新字符串,原始字符串不变(字符串是不可变对象)。
.lower()
: 转小写 ("HELLO".lower() -> "hello"
)
.upper()
: 转大写 ("hello".upper() -> "HELLO"
)
.capitalize()
: 首字母大写 ("hello world".capitalize() -> "Hello world"
)
.title()
: 每个单词首字母大写 ("hello world".title() -> "Hello World"
)
.strip()
: 去除两侧空白字符 (空格、制表符 \t
、换行符 \n
等)。还有 .lstrip()
(只去左) 和 .rstrip()
(只去右)。
.replace(old, new)
: 替换子串 ("spam eggs".replace("spam", "ham") -> "ham eggs"
)
.split(sep)
: 根据分隔符 sep
分割字符串,返回列表。默认分隔符是空格 ("a b c".split() -> ['a', 'b', 'c']
; "a,b,c".split(',') -> ['a', 'b', 'c']
)
.join(iterable)
: 用字符串把可迭代对象(如列表)中的元素连接起来 (", ".join(['apple', 'banana', 'cherry']) -> "apple, banana, cherry"
; "".join(['H', 'e', 'l', 'l', 'o']) -> "Hello"
)
.startswith(prefix)
, .endswith(suffix)
: 检查是否以指定字符串开头/结尾 (返回布尔值)。
.find(sub)
, .index(sub)
: 查找子串 sub
第一次出现的索引。find
找不到返回 -1
,index
找不到会引发 ValueError
错误。还有 .rfind()
, .rindex()
从右边开始找。
.isalpha()
, .isdigit()
, .isalnum()
: 检查字符串是否只由字母/数字/字母或数字组成 (返回布尔值)。
.len()
不是字符串方法,是内置函数:len(s)
返回字符串长度。
text = " Hello, World! " print(text.strip()) # "Hello, World!" print(text.lower()) # " hello, world! " print(text.replace("World", "Python")) # " Hello, Python! " words = text.strip().split(",") # 先去除空格,再按逗号分割 -> ['Hello', ' World!'] new_text = "-".join(words) # "Hello- World!"
格式化 (Formatting): 将变量值插入到字符串模板中。有多种方式:
%
格式化 (旧式,逐渐淘汰,但很多老代码在用):
name = "Alice" age = 30 print("My name is %s and I am %d years old." % (name, age)) # %s 字符串, %d 整数, %f 浮点数
str.format()
方法 (较新,推荐):
print("My name is {} and I am {} years old.".format(name, age)) # 可以指定位置: {0}, {1} print("I am {1} years old and my name is {0}.".format(name, age)) # 可以命名参数: print("Name: {n}, Age: {a}".format(n=name, a=age)) # 格式化数字: {:.2f} 保留两位小数 pi = 3.1415926 print("Pi is approximately {:.2f}".format(pi)) # "Pi is approximately 3.14"
f-strings (Python 3.6+ 推荐): 最简洁方便的方式。在字符串前加 f
或 F
,在字符串内部用 {变量名或表达式}
直接嵌入。
print(f"My name is {name} and I am {age} years old.") print(f"Next year I will be {age + 1} years old.") print(f"Pi is approximately {pi:.2f}") # 同样可以格式化
一点玩笑: 程序员处理字符串的时间可能比处理真正的“数字”还多。尤其是处理用户输入,你永远不知道用户会输入 "123"
, " 123 "
, "one hundred twenty-three"
还是 "一二三"
。做好清洗和验证吧。
列表是 Python 中最基本、最常用的数据结构之一。它是一个有序的、可变的集合,可以存放任意类型的元素(数字、字符串、其他列表、对象等),同一个列表里的元素类型可以不同。
创建列表: 用方括号 []
,元素用逗号 ,
分隔。
empty_list = [] # 空列表 numbers = [1, 2, 3, 4, 5] mixed = [10, "hello", 3.14, True] nested = [[1, 2, 3], ["a", "b", "c"]] # 列表嵌套
访问元素: 和字符串一样,使用索引 (从0开始) 和切片。
fruits = ["apple", "banana", "cherry", "orange"] print(fruits[0]) # "apple" print(fruits[-1]) # "orange" print(fruits[1:3]) # ["banana", "cherry"] (索引1到2) print(fruits[:2]) # ["apple", "banana"] print(fruits[2:]) # ["cherry", "orange"]
修改元素: 列表是可变的 (Mutable),可以通过索引直接赋值修改。
fruits[1] = "blueberry" # 把 "banana" 改成 "blueberry" print(fruits) # ["apple", "blueberry", "cherry", "orange"]
列表方法 (常用):
.append(item)
: 在列表末尾添加一个元素 item
。最常用。
.insert(index, item)
: 在指定 index
位置插入元素 item
。
.extend(iterable)
: 将另一个可迭代对象(如列表、元组、字符串的字符)中的所有元素逐个添加到列表末尾。相当于 +
操作符 (list1 + list2
会返回新列表,而 .extend()
修改原列表)。
.remove(item)
: 删除列表中第一个值为 item
的元素。如果值不存在会引发 ValueError
。
.pop([index])
: 删除并返回指定 index
位置的元素。如果不指定 index
,默认删除并返回最后一个元素。
.clear()
: 清空列表,移除所有元素。
.index(item)
: 返回值为 item
的元素的第一个索引。找不到引发 ValueError
。
.count(item)
: 返回元素 item
在列表中出现的次数。
.sort(key=None, reverse=False)
: 对列表进行原地排序 (修改原列表)。reverse=True
降序排序。key
是一个函数,指定排序依据(后面函数部分讲)。
.reverse()
: 原地反转列表中的元素顺序。
copy()
: 创建列表的一个浅拷贝。new_list = old_list.copy()
或 new_list = old_list[:]
或 new_list = list(old_list)
。
numbers = [3, 1, 4, 1, 5, 9] numbers.append(2) # [3, 1, 4, 1, 5, 9, 2] numbers.insert(0, 0) # [0, 3, 1, 4, 1, 5, 9, 2] numbers.remove(1) # 删除第一个1 -> [0, 3, 4, 1, 5, 9, 2] popped = numbers.pop(2) # 删除索引2的元素 (4), popped=4 -> [0, 3, 1, 5, 9, 2] numbers.sort() # [0, 1, 2, 3, 5, 9] numbers.reverse() # [9, 5, 3, 2, 1, 0] count = numbers.count(5) # 1 idx = numbers.index(3) # 2 numbers_copy = numbers.copy()
列表操作:
+
: 连接两个列表,返回新列表 ([1, 2] + [3, 4] -> [1, 2, 3, 4]
)。
*
: 重复列表,返回新列表 (['a'] * 3 -> ['a', 'a', 'a']
)。
in
: 检查元素是否在列表中 (返回布尔值) (3 in [1, 2, 3] -> True
)。
len(list)
: 获取列表长度 (元素个数)。
遍历列表: 通常用 for
循环 (后面流程控制会详细讲)。
for fruit in fruits: print(fruit) # 如果需要索引,用 enumerate() for index, fruit in enumerate(fruits): print(f"Index {index}: {fruit}")
列表推导式 (List Comprehensions): 一种简洁、高效地创建新列表的语法。形式:[expression for item in iterable if condition]
# 创建一个包含0到9平方的列表 squares = [x**2 for x in range(10)] # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] # 创建一个包含0到9中偶数的平方的列表 even_squares = [x**2 for x in range(10) if x % 2 == 0] # [0, 4, 16, 36, 64] # 将列表中的字符串转为大写 fruits = ["apple", "banana", "cherry"] upper_fruits = [fruit.upper() for fruit in fruits] # ['APPLE', 'BANANA', 'CHERRY']
一点玩笑: 列表是万能的,直到你需要更快的查找或者唯一的元素。列表推导式很优雅,但嵌套太多层会让代码像一行神秘的咒语,一周后你自己也看不懂。
元组与列表非常相似,都是有序集合。关键区别:元组是 不可变的 (Immutable)。 一旦创建,不能修改其中的元素(不能增、删、改)。
创建元组: 用圆括号 ()
,元素用逗号 ,
分隔。注意: 即使只有一个元素,也需要加逗号 (element,)
,否则会被认为是普通括号运算。
empty_tuple = () # 空元组 single_element = (42,) # 单元素元组 (必须有逗号) coordinates = (10, 20) # 坐标点 mixed_tuple = (1, "two", 3.0, [4, 5]) # 元素类型可以不同, 包含的列表本身可变
访问元素: 和列表、字符串一样,使用索引和切片。
print(coordinates[0]) # 10 print(coordinates[1]) # 20 print(mixed_tuple[1:3]) # ('two', 3.0)
“修改”元组: 不能直接修改元素。但可以:
重新赋值给整个元组变量。
如果元组包含可变对象(如列表),可以修改那个可变对象本身。
# coordinates[0] = 15 # 错误!TypeError: 'tuple' object does not support item assignment coordinates = (15, 25) # 重新赋值整个元组变量是可以的 mixed_tuple[3][0] = 400 # 可以!修改元组中列表的元素 -> (1, "two", 3.0, [400, 5])
元组操作: 类似列表,支持 +
(连接), *
(重复), in
(成员检查), len()
(长度)。
元组方法: 由于不可变,方法很少。主要有 .count(item)
和 .index(item)
,用法同列表。
为什么用元组?
不可变性: 保证数据不会被意外修改,提供安全性。常用于表示固定集合(如坐标、日期、配置项)。
性能: 通常比列表创建和访问稍快(尤其在元素数量少时)。
可哈希 (Hashable): 元组(如果其包含的所有元素也都是可哈希的)可以作为字典的键(后面讲字典),而列表不行。
用作函数的多个返回值: 函数可以方便地返回一个元组包含多个结果 (result1, result2)
。
一点玩笑: 元组就像是列表的只读版本。当你需要一组值并且想告诉别人“别动我的数据!”时,就用元组。试图修改元组会收获一个 TypeError
,这是 Python 在保护你的数据完整性(或者说,在嘲笑你忘了用列表)。
字典是 Python 中另一个极其重要的数据结构。它存储 键值对 (Key-Value Pairs)。键 (Key) 是唯一的、不可变的(通常是字符串、数字、元组),用于查找对应的值 (Value)。值可以是任何类型的数据。字典是无序的(在 Python 3.7+ 中,插入顺序被保留,但逻辑上仍视为无序),可变的。
创建字典: 用花括号 {}
,键值对用 key: value
表示,多个键值对用逗号 ,
分隔。
empty_dict = {} # 空字典 # 创建有初始键值对的字典 person = { "name": "Alice", "age": 30, "city": "New York", "hobbies": ["reading", "hiking"] } # 也可以用 dict() 构造函数 person2 = dict(name="Bob", age=25, city="London") # 关键字参数形式
访问值: 使用方括号 []
和键名,或者 .get(key[, default])
方法。
print(person["name"]) # "Alice" # print(person["job"]) # KeyError! 键不存在 print(person.get("age")) # 30 print(person.get("job")) # None (键不存在时默认返回None) print(person.get("job", "Unknown")) # "Unknown" (提供默认值)
添加/修改元素: 通过键赋值。如果键存在则修改其值;如果键不存在则添加新的键值对。
person["email"] = "[email protected]" # 添加新键值对 person["age"] = 31 # 修改已有键的值
删除元素:
del dict[key]
: 删除指定键的键值对。键不存在会引发 KeyError
。
.pop(key[, default])
: 删除指定键的键值对并返回对应的值。如果键不存在,返回 default
(若未提供 default
则引发 KeyError
)。
.popitem()
: 删除并返回字典中最后插入的键值对(在 Python 3.7+ 中)。返回形式为 (key, value)
。字典为空时引发 KeyError
。
.clear()
: 清空字典。
del person["city"] # 删除键 "city" email = person.pop("email") # 删除并返回 email 的值 # last_item = person.popitem() # 删除并返回最后插入的项(如 ('hobbies', ['reading', 'hiking']))
字典方法 (常用):
.keys()
: 返回一个包含字典所有键的视图对象 (view)。常用 list(dict.keys())
转成列表。
.values()
: 返回一个包含字典所有值的视图对象。
.items()
: 返回一个包含字典所有 (键, 值) 元组的视图对象。遍历字典时最常用。
.update([other])
: 用另一个字典 other
或键值对序列(如列表 [('key1', val1), ('key2', val2)]
)更新当前字典。存在的键更新值,不存在的键添加键值对。
.copy()
: 创建字典的浅拷贝。
for key in person.keys(): print(key) # 遍历所有键 for value in person.values(): print(value) # 遍历所有值 for key, value in person.items(): # 最常用遍历方式 print(f"{key}: {value}") # 更新 updates = {"age": 32, "occupation": "Engineer"} person.update(updates) # 更新age,添加occupation
检查键是否存在: 用 in
关键字。
if "name" in person: print("Name exists!") if "job" not in person: print("Job not found.")
字典推导式 (Dict Comprehensions): 类似列表推导式,用于创建字典。
# 创建一个数字到其平方的字典 squares = {x: x**2 for x in range(5)} # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} # 反转一个字典的键和值 (前提是值是可哈希且唯一的) original = {'a': 1, 'b': 2, 'c': 3} inverted = {value: key for key, value in original.items()} # {1: 'a', 2: 'b', 3: 'c'}
一点玩笑: 字典是你的万能查找表。当你需要根据某个“标签”快速找到对应的“东西”时,字典是首选。试图用不存在的键访问字典会收获一个 KeyError
,这是 Python 在说“查无此键,请先挂号(用 .get()
或检查 in
)”。
集合是一个无序的、不重复元素集。主要用途是进行成员关系测试(检查元素是否存在)、消除重复元素以及进行集合运算(并集、交集、差集等)。集合是可变的(也有不可变的 frozenset
)。
创建集合: 用花括号 {}
(注意:空集合必须用 set()
,因为 {}
创建的是空字典) 或用 set(iterable)
构造函数。集合会自动去重。
empty_set = set() # 空集合 fruits_set = {"apple", "banana", "cherry", "apple"} # 创建时自动去重 -> {'apple', 'banana', 'cherry'} numbers_set = set([1, 2, 2, 3, 3, 3]) # 从列表创建 -> {1, 2, 3}
基本操作:
in
: 检查元素是否在集合中。
len(set)
: 获取集合元素个数。
.add(item)
: 向集合添加一个元素。
.update(iterable)
: 向集合添加多个元素(来自可迭代对象)。
.remove(item)
: 从集合中移除元素 item
。如果 item
不存在,引发 KeyError
。
.discard(item)
: 从集合中移除元素 item
。如果 item
不存在,不报错。
.pop()
: 随机移除并返回集合中的一个元素(因为集合无序)。集合为空时引发 KeyError
。
.clear()
: 清空集合。
if "banana" in fruits_set: print("有香蕉") fruits_set.add("orange") fruits_set.update(["grape", "kiwi"]) fruits_set.remove("apple") fruits_set.discard("mango") # 即使没有'mango'也不报错 popped_element = numbers_set.pop() # 随机弹出一个
集合运算:
并集 (Union): set1 | set2
或 set1.union(set2)
-> 包含所有在 set1 或 set2 中的元素。
交集 (Intersection): set1 & set2
或 set1.intersection(set2)
-> 包含同时存在于 set1 和 set2 中的元素。
差集 (Difference): set1 - set2
或 set1.difference(set2)
-> 包含在 set1 中但不在 set2 中的元素。
对称差集 (Symmetric Difference): set1 ^ set2
或 set1.symmetric_difference(set2)
-> 包含在 set1 或 set2 中,但不同时存在的元素。
子集/超集:
set1 <= set2
或 set1.issubset(set2)
: 检查 set1 是否是 set2 的子集。
set1 < set2
: 检查 set1 是否是 set2 的真子集 (set1 是 set2 的子集且 set1 != set2)。
set1 >= set2
或 set1.issuperset(set2)
: 检查 set1 是否是 set2 的超集。
set1 > set2
: 检查 set1 是否是 set2 的真超集。
A = {1, 2, 3, 4} B = {3, 4, 5, 6} print(A | B) # {1, 2, 3, 4, 5, 6} print(A & B) # {3, 4} print(A - B) # {1, 2} print(B - A) # {5, 6} print(A ^ B) # {1, 2, 5, 6} print({1, 2} <= A) # True print(A < {1, 2, 3, 4, 5}) # True
集合推导式 (Set Comprehensions): 类似列表推导式,用于创建集合。
word = "mississippi" unique_letters = {letter for letter in word} # {'m', 'i', 's', 'p'} (无序)
一点玩笑: 集合最擅长两件事:去重和问“这个东西在不在里面?”。当你的列表里有一堆重复项,而你只想要唯一值时,list(set(your_list))
是经典操作(注意顺序会丢失)。集合运算让你感觉像回到了中学数学课,不过这次可能更有用(也可能没有)。
程序不能总是顺序执行。需要根据条件做决定(分支),或者重复执行某些操作(循环)。这就是流程控制。
if
, elif
, else
)根据条件的真假 (True
/False
) 执行不同的代码块。Python 使用缩进 (通常是4个空格) 来定义代码块! 这是语法的一部分,不是风格建议。
基本结构:
if condition1: # 如果 condition1 为 True,执行这里的代码块 ... elif condition2: # 如果 condition1 为 False 且 condition2 为 True,执行这里的代码块 # 可以有零个或多个 elif ... else: # 如果所有 if 和 elif 条件都为 False,执行这里的代码块 # 最多一个 else ...
condition
: 是一个表达式,其计算结果为布尔值 (True
或 False
)。可以是比较运算、逻辑运算、函数调用返回布尔值等。
执行逻辑: 从上到下检查条件。第一个为 True
的条件对应的代码块被执行,执行后跳过剩下的 elif
和 else
。如果所有条件都为 False
,则执行 else
块(如果有)。
示例:
score = 85 if score >= 90: grade = "A" print("Excellent!") elif score >= 80: # 90 > score >= 80 grade = "B" print("Good job!") elif score >= 70: grade = "C" print("Passed.") elif score >= 60: grade = "D" print("Barely passed.") else: grade = "F" print("Failed. See you next semester.") print(f"Your grade is {grade}") # 检查多个条件 (逻辑运算符) age = 25 has_license = True if age >= 18 and has_license: print("You can drive a car.") else: print("You cannot drive a car.") # 检查值在列表中 fruits = ["apple", "banana", "orange"] if "kiwi" in fruits: print("We have kiwi!") else: print("No kiwi today.")
一点玩笑: Python 的缩进规则让很多新手抓狂,尤其是从用花括号 {}
的语言转过来的人。忘记缩进或缩进不一致会直接导致 IndentationError
。这是 Python 强制你写出整洁代码的方式(或者说,是它对你代码排版有强迫症)。用好的编辑器(如 VS Code, PyCharm)能自动帮你管理缩进。
for
, while
)for
循环主要用于遍历序列(如列表、元组、字符串、字典、集合)或任何可迭代对象 (Iterable)。
基本语法:
for item in iterable: # 对 item 执行操作 ... # 循环体 (缩进块)
iterable
: 可迭代对象,即可以逐个返回元素的对象。
item
: 在每次迭代中,iterable
的下一个元素会被赋值给 item
。
遍历示例:
# 遍历列表 fruits = ["apple", "banana", "cherry"] for fruit in fruits: print(fruit) # 依次输出 apple, banana, cherry # 遍历字符串 message = "Hello" for char in message: print(char) # 依次输出 H, e, l, l, o # 遍历字典 (默认遍历键) person = {"name": "Alice", "age": 30, "city": "NY"} for key in person: # 等同于 for key in person.keys(): print(key) # 输出 name, age, city (顺序不确定, 但在3.7+插入顺序保留) for value in person.values(): print(value) # 输出 Alice, 30, NY for key, value in person.items(): # 最常用 print(f"{key}: {value}") # 输出 name: Alice, age: 30, city: NY # 使用 range() 函数生成数字序列 # range(stop): 0 到 stop-1 for i in range(5): # i 依次为 0, 1, 2, 3, 4 print(i) # range(start, stop): start 到 stop-1 for i in range(2, 5): # i 依次为 2, 3, 4 print(i) # range(start, stop, step) for i in range(0, 10, 2): # i 依次为 0, 2, 4, 6, 8 print(i) # 反向 for i in range(5, 0, -1): # i 依次为 5, 4, 3, 2, 1 print(i)
while
循环只要给定的条件为 True
,就重复执行代码块。
基本语法:
while condition: # 只要 condition 为 True,就执行这里的代码块 ... # 循环体 (缩进块)
关键: 循环体内必须有改变循环条件的语句(或使用 break
),否则会陷入无限循环!
示例:
# 计数器 count = 0 while count < 5: print(count) count += 1 # 改变条件,避免无限循环!等同于 count = count + 1 # 输出 0, 1, 2, 3, 4 # 用户输入验证 password = "" while password != "secret": password = input("Enter the password: ") # input() 获取用户输入 print("Access granted!") # 更复杂的条件 total = 0 number = int(input("Enter a number (0 to quit): ")) # 转整数 while number != 0: total += number number = int(input("Enter a number (0 to quit): ")) print(f"Total sum is: {total}")
break
, continue
, else
)break
: 立即终止当前所在的最内层循环(for
或 while
),跳出循环体,执行循环后面的语句。
continue
: 立即跳过当前循环的剩余语句,直接进入下一次迭代。
else
(用在循环后): 这个 else
比较特殊。只有当循环正常结束(不是因为 break
语句退出)时,才会执行 else
块中的代码。如果循环被 break
终止,else
块不执行。
示例:
# break 示例: 在列表中查找数字,找到即退出 numbers = [1, 3, 5, 7, 9, 2, 4, 6, 8] search_for = 4 found = False for num in numbers: if num == search_for: print(f"Found {search_for}!") found = True break # 找到就跳出循环,不再检查后面的元素 # 如果循环因 break 退出,这里的 else 不会执行 if not found: # 等价于 if found == False: print(f"{search_for} not found in the list.") # 上面的逻辑可以用循环的 else 更简洁地写: for num in numbers: if num == search_for: print(f"Found {search_for}!") break else: # 这个 else 属于 for 循环!只在循环正常完成 (没遇到 break) 时执行 print(f"{search_for} not found in the list.") # continue 示例: 打印1-10的奇数 for i in range(1, 11): if i % 2 == 0: # 如果是偶数 continue # 跳过本次循环剩余代码,进入下一次迭代 (i+1) print(i) # 只有奇数会执行到这里 -> 输出 1, 3, 5, 7, 9 # while + break: 更常见的用户输入模式 while True: # 看起来是无限循环 user_input = input("Enter 'quit' to exit: ") if user_input.lower() == 'quit': # .lower() 转小写,使输入大小写不敏感 break # 遇到 break 跳出循环 print(f"You entered: {user_input}") print("Goodbye!")
一点玩笑: while True:
加 break
是处理用户输入或事件循环的经典模式。break
是你的逃生舱口。忘记在 while
循环里写改变条件的语句或者 break
条件,程序就会变成热情的永动机,直到你把终端窗口关掉(或者系统管理员来找你)。continue
则像是循环里的“跳过此回合”卡。
函数是将一块实现特定功能的代码封装起来,并给它起一个名字。通过调用函数名并传递必要的参数,就可以执行这块代码,并可能返回结果。函数的主要目的是:
代码重用 (Reusability): 避免重复写相同的代码。
模块化 (Modularity): 将大程序分解成小的、易于管理的功能块。
提高可读性和可维护性。
定义函数: 使用 def
关键字。
def function_name(parameter1, parameter2, ...): """文档字符串 (可选,但强烈推荐)""" # 函数体:缩进的代码块 ... [return value] # 可选,用于返回结果。没有 return 或 return 后面没值,函数返回 None。
function_name
: 函数名。命名规则同变量,使用小写字母和下划线 (calculate_average
, print_report
)。
(parameter1, parameter2, ...)
: 形式参数 (形参)。定义函数时括号里写的变量名,用于接收调用时传入的值。可以有零个或多个,用逗号分隔。
"""文档字符串"""
: 三引号字符串,描述函数功能、参数、返回值等。可以通过 help(function_name)
或 function_name.__doc__
查看。写文档是好习惯!
函数体
: 实现函数功能的代码。
return
: 结束函数执行,并将 value
返回给调用者。一个函数可以有多个 return
语句(通常在条件分支中),但只会执行其中一个。函数执行到末尾没有 return
或执行了 return
后面没有值,都返回 None
。
调用函数: 使用函数名后跟圆括号 ()
,括号内提供实际参数 (实参)。
result = function_name(arg1, arg2, ...)
示例:
# 定义一个没有参数、没有返回值的函数 def say_hello(): """打印 Hello, World!""" print("Hello, World!") say_hello() # 调用函数,输出 Hello, World! # 定义有参数、有返回值的函数 def add_numbers(a, b): """计算两个数的和。 参数: a (int/float): 第一个数 b (int/float): 第二个数 返回: int/float: a 和 b 的和 """ total = a + b return total sum1 = add_numbers(5, 3) # 调用,实参 5 传给形参 a, 3 传给 b print(sum1) # 8 sum2 = add_numbers(2.5, 4.1) print(sum2) # 6.6
Python 中函数的参数传递本质上是 对象引用传递。理解这点很重要:
对于不可变对象 (int, float, str, tuple),在函数内部修改形参的值,不会影响实参的值(相当于传递了值的副本)。
对于可变对象 (list, dict, set),在函数内部修改形参指向的对象的内容 (如修改列表元素、添加字典键值对),会影响实参指向的同一个对象(因为传递的是对象的引用)。
# 不可变对象示例 def try_change_immutable(x): x = x * 2 # 试图修改形参x (整数不可变,这里实际上是创建了新对象赋值给x) print("Inside function (immutable):", x) # 10 num = 5 try_change_immutable(num) print("Outside function (immutable):", num) # 5 (没变) # 可变对象示例 def modify_mutable(lst): lst.append(4) # 修改了形参lst指向的列表对象(添加元素) print("Inside function (mutable):", lst) # [1, 2, 3, 4] my_list = [1, 2, 3] modify_mutable(my_list) print("Outside function (mutable):", my_list) # [1, 2, 3, 4] (被改变了!)
位置参数 (Positional Arguments): 最常见的参数传递方式。实参的顺序必须与形参定义的顺序一致。
def describe_pet(animal_type, pet_name): print(f"I have a {animal_type} named {pet_name}.") describe_pet("dog", "Fido") # 正确: animal_type="dog", pet_name="Fido" describe_pet("Fido", "dog") # 逻辑错误: animal_type="Fido", pet_name="dog" -> "I have a Fido named dog."
关键字参数 (Keyword Arguments): 在调用函数时,使用 形参名=值
的形式传递参数。这时实参的顺序可以与形参定义顺序不一致。
describe_pet(pet_name="Whiskers", animal_type="cat") # 正确且清晰
默认参数 (Default Arguments): 在定义函数时,给形参指定一个默认值。调用时如果不提供该实参,则使用默认值。带默认值的形参必须放在参数列表的最后面。
def describe_pet(pet_name, animal_type="dog"): # animal_type 有默认值 print(f"I have a {animal_type} named {pet_name}.") describe_pet("Fido") # 输出: I have a dog named Fido. (使用默认animal_type) describe_pet("Whiskers", "cat") # 输出: I have a cat named Whiskers.
可变数量参数:
*args
(Arbitrary Positional Arguments): 收集所有额外的位置参数到一个元组中。形参名习惯用 args
,但可以用其他名字。*
是必须的。
**kwargs
(Arbitrary Keyword Arguments): 收集所有额外的关键字参数到一个字典中。形参名习惯用 kwargs
,但可以用其他名字。**
是必须的。
它们通常放在参数列表的最后。顺序:先 *args
,后 **kwargs
。
def make_pizza(size, *toppings, **details): """制作一个披萨""" print(f"Making a {size}-inch pizza.") print("Toppings:") for topping in toppings: # toppings 是一个元组 print(f"- {topping}") print("Details:") for key, value in details.items(): # details 是一个字典 print(f" {key}: {value}") make_pizza(12, "mushrooms", "green peppers", "extra cheese", crust="thin", delivery=True) # 输出: # Making a 12-inch pizza. # Toppings: # - mushrooms # - green peppers # - extra cheese # Details: # crust: thin # delivery: True
return
)函数使用 return
语句返回结果。
可以返回任何类型的数据:单个值、多个值(实际上是返回一个元组)、列表、字典、函数甚至类对象。
return
语句执行后,函数立即终止,后续代码不会执行。
函数可以没有 return
语句或有 return
但不返回值,此时函数返回 None
def get_formatted_name(first_name, last_name, middle_name=''): """返回格式化的全名""" if middle_name: full_name = f"{first_name} {middle_name} {last_name}" else: full_name = f"{first_name} {last_name}" return full_name.title() # 返回字符串 musician = get_formatted_name('jimi', 'hendrix') print(musician) # Jimi Hendrix musician = get_formatted_name('john', 'hooker', 'lee') print(musician) # John Lee Hooker # 返回多个值 (实际上是返回一个元组) def calculate_stats(numbers): """计算列表的最小值、最大值和平均值""" if not numbers: # 如果列表为空 return None, None, None # 返回三个None min_val = min(numbers) max_val = max(numbers) avg_val = sum(numbers) / len(numbers) return min_val, max_val, avg_val # 返回一个元组 (min_val, max_val, avg_val) data = [10, 20, 5, 40, 15] min_num, max_num, average = calculate_stats(data) # 元组解包 print(f"Min: {min_num}, Max: {max_num}, Avg: {average:.2f}") # Min: 5, Max: 40, Avg: 18.00 # 返回 None 的示例 def print_greeting(name): print(f"Hello, {name}!") # 没有 return 语句,函数返回 None result = print_greeting("Alice") # 输出 Hello, Alice! print(result) # None
变量的作用域决定了它在程序的哪个部分可以被访问。
局部变量 (Local Variable): 在函数内部定义的变量。它们的作用域仅限于定义它们的函数内部。函数执行结束后,局部变量会被销毁。
全局变量 (Global Variable): 在函数外部定义的变量。它们的作用域是整个程序文件(模块),从定义点开始直到文件结束。函数内部可以读取全局变量的值。
在函数内部修改全局变量: 如果要在函数内部修改全局变量的值,需要使用 global
关键字声明。
global_var = "I'm global" # 全局变量 def test_scope(): local_var = "I'm local" # 局部变量 print(global_var) # 可以读取全局变量 -> "I'm global" print(local_var) # 可以访问局部变量 -> "I'm local" test_scope() # print(local_var) # 错误!NameError: name 'local_var' is not defined (局部变量在函数外不可访问) def modify_global(): global global_var # 声明要修改的是全局变量 global_var global_var = "Modified global!" # 修改全局变量 new_global = "New global?" # 这仍然是局部变量!没有用 global 声明 modify_global() print(global_var) # "Modified global!" (全局变量被修改) # print(new_global) # 错误!NameError (new_global 是 modify_global 的局部变量)
最佳实践: 尽量避免在函数内部修改全局变量。这会使代码难以理解和维护(称为副作用)。应该尽量通过参数传递数据,通过返回值返回结果。如果确实需要共享状态,可以考虑使用类(后面讲)。
一点玩笑: 作用域错误是常见的bug来源。在函数里吭哧吭哧改了半天变量,结果发现改的是个局部副本,全局变量纹丝不动。global
关键字是你的信号弹,告诉 Python“我要动真格的了”。滥用 global
会让你的代码变成面条代码(Spaghetti Code),状态到处飞。
Lambda 表达式用于创建小巧的、匿名的(没有名字的)函数对象。它们通常用于需要一个简单函数作为参数的地方(例如 sort()
的 key
参数,或 map()
、filter()
函数),而不必用 def
正式定义一个函数。
语法:
lambda arguments: expression
lambda
: 关键字。
arguments
: 参数列表(可以有多个,用逗号分隔)。
expression
: 一个单一的表达式。这个表达式的计算结果就是 Lambda 函数的返回值。注意: Lambda 函数只能包含一个表达式,不能包含复杂的语句块(如循环、if-elif-else
的多分支等,但可以用简单的条件表达式)。
特点:
匿名:没有函数名(虽然可以赋值给一个变量,但通常不这样做)。
简洁:一行写完。
即时:在需要的地方直接定义使用。
示例:
# 定义一个 lambda,计算平方 square = lambda x: x ** 2 # 虽然能赋值,但通常不推荐这样(用 def 更清晰) print(square(5)) # 25 # 更常见的用法:作为参数传递 # 1. 配合 sort/sorted 的 key 参数 points = [(1, 2), (3, 1), (5, 0), (2, 4)] # 按每个点的第二个元素(y坐标)排序 points_sorted_by_y = sorted(points, key=lambda point: point[1]) print(points_sorted_by_y) # [(5, 0), (3, 1), (1, 2), (2, 4)] # 2. 配合 map() 函数: 将一个函数应用于可迭代对象的每个元素 numbers = [1, 2, 3, 4] squared = list(map(lambda x: x * x, numbers)) # map 返回迭代器,用 list() 转列表 print(squared) # [1, 4, 9, 16] # 3. 配合 filter() 函数: 过滤可迭代对象 even_numbers = list(filter(lambda x: x % 2 == 0, numbers)) print(even_numbers) # [2, 4] # Lambda 中使用条件表达式 (三元运算符) is_positive = lambda num: "Positive" if num > 0 else ("Zero" if num == 0 else "Negative") print(is_positive(5)) # "Positive" print(is_positive(-2)) # "Negative" print(is_positive(0)) # "Zero"
何时使用 Lambda: 当函数逻辑非常简单(只有一个表达式),并且只在一个地方使用,不值得用 def
单独命名定义时。
一点玩笑: Lambda 是函数界的快餐。当你只需要一个简单的计算,又懒得正经写个 def
函数时,Lambda 就是你的救星。但别指望用它写复杂的逻辑,它只卖“简餐”。嵌套 Lambda 或者写太长的表达式会让你的代码可读性瞬间降到冰点。
当程序越来越大,把所有代码写在一个文件里会变得难以管理和维护。Python 使用模块 (Modules) 和 包 (Packages) 来组织代码。
什么是模块? 一个 .py
文件就是一个模块。模块名就是文件名(去掉 .py
后缀)。
作用:
代码组织: 将相关的函数、类、变量组织在一起。
命名空间管理: 避免不同模块中同名标识符的冲突。
代码复用: 可以被其他程序或模块导入使用。
创建模块: 直接创建一个 .py
文件,在里面写 Python 代码(函数、类、变量定义等)即可。例如,创建一个名为 mymath.py
的文件:
# mymath.py """一个简单的数学工具模块""" PI = 3.14159 # 模块级变量 (常量) def add(a, b): """计算两个数的和""" return a + b def subtract(a, b): """计算两个数的差""" return a - b def circle_area(radius): """计算圆的面积""" return PI * radius ** 2 # 模块可以包含可执行代码(通常用于测试或初始化) if __name__ == "__main__": # 如果直接运行这个模块 (python mymath.py),执行这里的代码 print("Running mymath module tests...") print(add(2, 3)) # 5 print(circle_area(1)) # 3.14159
导入模块 (import): 使用 import
关键字。
导入整个模块: import module_name
使用模块内的内容:module_name.function_name()
或 module_name.variable_name
。这种方式最清晰,能直接看出函数/变量来自哪个模块,避免命名冲突。
导入特定内容: from module_name import item1, item2
使用:直接使用 item1
, item2
。这种方式方便,但如果导入多个模块有同名 item 会冲突(后导入的覆盖先导入的)。
导入所有内容 (不推荐): from module_name import *
使用:直接使用模块内的所有函数、变量名。强烈不推荐! 容易导致命名冲突,且代码可读性差(不知道名字来自哪里)。
给模块或导入项起别名: import module_name as alias
或 from module_name import item as alias
使用:用 alias
代替原名。常用于模块名较长或有冲突时。
示例 (使用上面创建的 mymath.py):
# main.py (和 mymath.py 在同一目录下) import mymath # 导入整个模块 result = mymath.add(10, 5) # 使用模块名.函数名 print(result) # 15 print(mymath.PI) # 3.14159 from mymath import circle_area # 导入特定函数 area = circle_area(2) # 直接使用函数名 print(area) # 12.56636 import numpy as np # 导入 numpy 并起别名 np (这是科学计算的惯例) array = np.array([1, 2, 3]) # 使用 np.xxx from mymath import PI as pi_constant # 导入 PI 并起别名 print(pi_constant)
if __name__ == "__main__":
的作用:
每个 Python 模块都有一个内置属性 __name__
。
当模块被直接运行时 (例如 python mymath.py
),__name__
的值被设置为 "__main__"
。
当模块被导入到其他模块时 (例如 import mymath
),__name__
的值被设置为模块的名字 ("mymath"
)。
因此,把模块的测试代码或启动代码放在 if __name__ == "__main__":
块中,可以保证:
直接运行该模块时,测试/启动代码会执行。
该模块被导入到其他模块时,测试/启动代码不会执行。这非常有用!
什么是包? 包是一种用“文件夹”组织模块的方式。一个包是一个包含特殊文件 __init__.py
的目录(文件夹)。__init__.py
文件(可以是空文件)告诉 Python 这个目录是一个 Python 包。包内可以包含模块(.py
文件)和子包(子目录,里面也有自己的 __init__.py
)。
作用: 提供更高层次的模块化,适用于大型项目。
目录结构示例:
my_project/ │ ├── main.py │ └── mypackage/ # 包目录 ├── __init__.py # 包标识文件 (必需) ├── module1.py # 模块1 ├── module2.py # 模块2 └── subpackage/ # 子包 ├── __init__.py └── module3.py # 子包中的模块
导入包和模块:
# main.py 中导入 # 导入包中的模块 import mypackage.module1 from mypackage import module2 from mypackage.subpackage import module3 # 使用 mypackage.module1.some_function() module2.some_variable module3.another_function() # 导入模块中的特定内容 from mypackage.module1 import specific_function # __init__.py 的作用: # 可以在这个文件中导入包内的模块或定义包级别的变量/函数,简化导入。 # 例如在 mypackage/__init__.py 中添加: # from . import module1, module2 # . 表示当前包 # 那么在 main.py 中就可以: # import mypackage # mypackage.module1.func() # 不需要再单独 import mypackage.module1
Python 自带了一个非常丰富的标准库 (Standard Library),包含了大量实用的模块,覆盖文件操作、系统交互、网络通信、数据处理、日期时间、数学计算、数据压缩、加密等等领域。无需额外安装即可使用。
常用标准库模块示例:
math
: 数学函数 (sqrt
, sin
, cos
, pi
, e
...)
random
: 生成随机数 (random
, randint
, choice
, shuffle
...)
datetime
: 处理日期和时间 (datetime
, date
, time
, timedelta
...)
os
: 操作系统交互 (getcwd
, listdir
, mkdir
, rename
, remove
, system
...)
sys
: 访问解释器相关的变量和函数 (argv
, exit
, path
, version
...)
json
: JSON 编码和解码 (loads
, dumps
, load
, dump
)
re
: 正则表达式 (强大的字符串匹配和操作)
urllib.request
: 用于打开 URL (网页)
csv
: 读写 CSV (逗号分隔值) 文件
argparse
: 解析命令行参数 (创建用户友好的命令行界面)
使用示例:
import math print(math.sqrt(16)) # 4.0 print(math.pi) # 3.141592653589793 import random print(random.randint(1, 6)) # 模拟掷骰子,输出1-6之间的随机整数 fruits = ["apple", "banana", "cherry"] print(random.choice(fruits)) # 随机选择一个元素 from datetime import datetime now = datetime.now() # 获取当前日期和时间 print(now) # 如 2023-10-27 14:30:15.123456 print(now.strftime("%Y-%m-%d %H:%M:%S")) # 格式化输出 "2023-10-27 14:30:15" import os current_dir = os.getcwd() # 获取当前工作目录 print(current_dir) files = os.listdir('.') # 列出当前目录下的文件和子目录 print(files) import sys print(sys.argv) # 命令行参数列表。运行 `python script.py arg1 arg2` 时,sys.argv 是 ['script.py', 'arg1', 'arg2'] sys.exit(1) # 退出程序,返回状态码1 (非0通常表示错误)
一点玩笑: 不要重复发明轮子!Python 标准库就像一个巨大的工具箱。在动手写一个复杂功能前,先查查标准库有没有现成的模块。自己写的代码可能充满了 bug
,而标准库的代码已经被无数人踩过坑了(虽然也可能有 bug,但概率小得多)。import antigravity
是 Python 里的一个彩蛋(试试看,但别在生产环境用)。
程序经常需要读取磁盘上的文件(如配置文件、数据文件)或将结果保存到文件中。Python 通过内置的 open()
函数和文件对象来进行文件操作。
open()
)语法: file_object = open(filename, mode)
filename
: 要打开的文件名(字符串),可以包含路径(相对路径或绝对路径)。
mode
: 打开文件的模式(字符串)。常用模式:
'r'
: 读 (默认模式)。文件必须存在。
'w'
: 写。如果文件存在,则覆盖原有内容;如果文件不存在,则创建新文件。
'a'
: 追加。在文件末尾添加内容。如果文件不存在,则创建新文件。
'x'
: 创建。创建新文件并打开写入。如果文件已存在,则操作失败 (引发 FileExistsError
)。
'b'
: 二进制模式 (例如 'rb'
, 'wb'
)。用于处理图片、音频、视频等非文本文件。
't'
: 文本模式 (默认模式)。
'+'
: 读写模式 (例如 'r+'
, 'w+'
)。可读可写。
file_object
: 打开文件成功后返回的文件对象,后续操作都通过这个对象进行。
关闭文件: 使用 .close()
方法。非常重要! 打开文件后一定要关闭,以释放系统资源。即使程序结束,养成良好习惯。
file = open("example.txt", "w") # ... 操作文件 ... file.close() # 务必关闭
使用 with
语句 (推荐): with
语句会在代码块执行完毕后自动关闭文件,即使中间发生异常。这是最安全、最常用的方式。
with open("example.txt", "r") as file: # 在这个缩进块内操作 file 对象 data = file.read() # 离开 with 块后,文件自动关闭,无需调用 file.close()
文件对象提供了多种读写方法。
读取文件:
.read([size])
: 读取文件的所有内容(文本模式返回字符串,二进制模式返回字节对象)。如果指定 size
(整数),则读取最多 size
个字符/字节。
.readline()
: 读取文件的一行(包括行尾的换行符 \n
)。到达文件末尾时返回空字符串 ''
。
.readlines()
: 读取文件的所有行,返回一个列表,每个元素是一行字符串(包括行尾的 \n
)。
# 读取整个文件 (适合小文件) with open("poem.txt", "r") as f: content = f.read() print(content) # 逐行读取 (内存友好,适合大文件) with open("data.log", "r") as f: for line in f: # 文件对象本身是可迭代的,每次迭代返回一行 print(line.strip()) # .strip() 去除行首尾的空白字符(包括换行符) # 使用 readline() with open("config.ini", "r") as f: line1 = f.readline().strip() line2 = f.readline().strip() # ... # 使用 readlines() (将整个文件读入内存列表,慎用于大文件) with open("names.txt", "r") as f: lines = f.readlines() for line in lines: print(line.strip())
写入文件:
.write(string)
: 将字符串 string
写入文件。注意: 在文本模式下,写入的必须是字符串;在二进制模式下,写入的必须是字节对象。不会自动添加换行符。
.writelines(list_of_strings)
: 将一个包含字符串的列表写入文件。不会自动添加换行符! 如果列表中的字符串没有包含换行符,写入的内容会连在一起。通常需要自己给每个字符串加上 \n
。
# 写入字符串 with open("output.txt", "w") as f: # 'w' 模式会覆盖旧文件 f.write("Hello, File!\n") # 写入一行,注意加 \n f.write("This is line 2.\n") # 写入多行 (使用 writelines) lines = ["Line 1\n", "Line 2\n", "Line 3\n"] with open("output_writelines.txt", "w") as f: f.writelines(lines) # 追加模式 ('a') with open("log.txt", "a") as f: f.write(f"New log entry at {datetime.now()}\n")
文件指针:
文件对象内部维护一个指针,指示当前读写的位置。
.tell()
: 返回当前指针的位置(整数,表示从文件开头开始的字节偏移量)。
.seek(offset[, whence])
: 移动文件指针到指定位置。
offset
: 移动的偏移量(字节数)。
whence
(可选): 基准位置。
0
(默认): 文件开头。
1
: 当前位置。
2
: 文件末尾。
读写操作会改变指针位置。
with open("example.bin", "rb+") as f: # 二进制读写模式 data = f.read(4) # 读取前4个字节 print(f.tell()) # 输出 4 (指针现在在位置4) f.seek(10) # 移动到文件开头后第10个字节 f.write(b"ABCD") # 写入4个字节 f.seek(-5, 2) # 移动到文件末尾前5个字节 last_few = f.read(5)
一点玩笑: 忘记 close()
文件是常见错误,可能导致数据没完全写入磁盘,或者程序打开太多文件耗尽系统资源(“Too many open files”错误)。with
语句是你的健忘症解药。处理文件路径时,小心 Windows 的反斜杠 \
和 Linux/macOS 的正斜杠 /
,os.path.join()
函数可以帮你安全地拼接路径。
程序在运行过程中难免会遇到错误(异常),例如文件不存在、除以零、索引越界、类型错误、网络连接失败等。Python 使用异常 (Exceptions) 机制来处理这些错误。良好的异常处理可以防止程序意外崩溃,并提供更友好的错误信息或进行恢复操作。
Python 有很多内置的异常类型,都继承自 BaseException
。常见的异常类:
Exception
: 几乎所有内置的、非系统退出异常的基类。
SyntaxError
: 语法错误(在运行前就会被解释器发现)。
IndentationError
: 缩进错误(SyntaxError
的子类)。
NameError
: 尝试访问一个未定义的变量。
TypeError
: 操作或函数应用于不适当类型的对象(如 1 + 'a'
)。
ValueError
: 操作或函数接收到类型正确但值不合适的参数(如 int('abc')
)。
IndexError
: 序列(列表、元组、字符串)索引超出范围。
KeyError
: 字典中查找一个不存在的键。
FileNotFoundError
: 尝试打开不存在的文件。
ZeroDivisionError
: 除以零。
ImportError
: 导入模块失败。
KeyboardInterrupt
: 用户按下了中断键(通常是 Ctrl+C
)。
OSError
: 操作系统相关的错误(如文件读写权限问题)。
try...except
)使用 try...except
块来捕获和处理可能发生的异常。
基本语法:
try: # 尝试执行的代码块 # 这里可能会引发异常 ... except ExceptionType1: # 如果发生 ExceptionType1 类型的异常,执行这里的处理代码 ... except ExceptionType2 as e: # 如果发生 ExceptionType2 类型的异常,执行这里的处理代码 # 使用 `as e` 可以将异常对象赋值给变量 e,以便获取更多信息 print(f"Caught an exception: {e}") ... except (ExceptionType3, ExceptionType4): # 捕获多种类型的异常 ... except: # 捕获所有未被前面 except 捕获的异常 (不推荐,容易掩盖真正的问题) ... else: # 可选项。如果 try 块中没有发生任何异常,执行这里的代码 ... finally: # 可选项。无论是否发生异常,都会执行的代码块 # 通常用于清理资源(如关闭文件、网络连接) ...
示例:
# 处理除以零 try: num1 = int(input("Enter dividend: ")) num2 = int(input("Enter divisor: ")) result = num1 / num2 print(f"Result: {result}") except ZeroDivisionError: print("Error: Cannot divide by zero!") except ValueError: print("Error: Please enter valid integers!") else: print("Division performed successfully.") # 只在没发生异常时执行 finally: print("This always runs, cleanup can go here.") # 总是执行 # 处理文件打开错误 filename = "non_existent_file.txt" try: with open(filename, 'r') as f: content = f.read() except FileNotFoundError as e: print(f"Error: The file '{filename}' was not found. Details: {e}") except OSError as e: # 捕获更一般的操作系统错误 print(f"An OS error occurred: {e}")
要点:
应该尽量捕获特定的异常类型,而不是一个宽泛的 except:
。捕获所有异常可能会隐藏你未曾预料到的程序逻辑错误。
使用 as
将异常赋值给变量,可以获取异常的详细信息(如错误消息)。
else
块在无异常时执行,finally
块无论有无异常都执行(常用于确保资源释放)。
raise
)你可以在代码中主动抛出异常,以指示发生了错误或特殊情况。
语法: raise [ExceptionType([message])]
ExceptionType
: 要抛出的异常类型(通常是内置异常或自定义异常)。
message
(可选): 传递给异常对象的错误信息字符串。
示例:
def calculate_average(numbers): if not numbers: # 如果列表为空 raise ValueError("The list of numbers cannot be empty") return sum(numbers) / len(numbers) try: avg = calculate_average([]) except ValueError as e: print(e) # 输出: The list of numbers cannot be empty
自定义异常: 你可以创建自己的异常类(通常继承自 Exception
),来表示应用程序特定的错误。
class MyCustomError(Exception): """自定义异常类型""" pass # 暂时不需要额外属性或方法 def process_data(data): if data is None: raise MyCustomError("Invalid data: None received") # ... 处理数据 ... try: process_data(None) except MyCustomError as e: print(f"Custom error occurred: {e}")
一点玩笑: 异常处理是程序的保险丝。没有它,一个预料之外的错误(比如用户输入了字母而你期待数字)就能让整个程序炸掉(崩溃)。try...except
让你有机会优雅地处理错误,告诉用户“你输错了”,而不是直接闪退。但别用 except:
捕获所有异常来掩盖真正的逻辑错误,那就像用胶带粘住故障指示灯。
面向对象编程 (Object-Oriented Programming, OOP) 是一种编程范式,它将数据和操作数据的方法封装在称为“对象”的结构中。Python 是一门支持 OOP 的语言。核心概念包括:
类 (Class): 对象的蓝图或模板。它定义了对象将拥有什么样的属性 (数据) 和方法 (函数)。
对象 (Object) / 实例 (Instance): 根据类创建出来的具体实体。一个类可以创建多个对象。
属性 (Attribute): 对象内部存储的数据(变量)。
方法 (Method): 定义在类内部的函数,用于操作对象的数据或执行相关动作。
定义类: 使用 class
关键字。
class Dog: # 类名通常用大写字母开头 (驼峰命名法) """一个表示狗的简单类""" # 类属性 (所有实例共享) species = "Canis familiaris" # 初始化方法 (构造函数): __init__ def __init__(self, name, age): """初始化 Dog 实例的属性。 参数: name (str): 狗的名字 age (int): 狗的年龄 """ # 实例属性 (每个实例独有) self.name = name # self.name 是实例属性,name 是传入的参数 self.age = age # 实例方法: 第一个参数总是 self (表示实例本身) def description(self): """返回描述狗的字符串""" return f"{self.name} is {self.age} years old" def speak(self, sound): """让狗发出声音。 参数: sound (str): 狗发出的声音 返回: str: 包含狗名字和叫声的字符串 """ return f"{self.name} says {sound}!" # 创建 Dog 类的实例 (对象) my_dog = Dog("Buddy", 3) # 调用 __init__ 方法,self 是 my_dog, name="Buddy", age=3 your_dog = Dog("Lucy", 5) # 访问属性 print(my_dog.name) # "Buddy" print(my_dog.age) # 3 print(my_dog.species) # "Canis familiaris" (类属性) # 调用方法 print(my_dog.description()) # "Buddy is 3 years old" print(my_dog.speak("Woof Woof")) # "Buddy says Woof Woof!" print(your_dog.name) # "Lucy" print(your_dog.speak("Grrr")) # "Lucy says Grrr!"
self
参数:
类中的每个实例方法的第一个参数都必须是 self
(约定俗成的名字,也可以用其他名字,但强烈建议用 self
)。
self
代表类的当前实例对象本身。通过 self
,方法可以访问和操作该实例的属性以及其他方法。
在调用方法时,不需要显式传递 self
参数。Python 解释器会自动将调用该方法的对象作为 self
传入。例如 my_dog.speak("Woof")
等价于 Dog.speak(my_dog, "Woof")
。
__init__
方法:
这是一个特殊的构造方法。在创建类的新实例时,Python 会自动调用 __init__
方法。
它的主要作用是初始化新创建对象的实例属性。
可以接受参数(除了第一个 self
),用于在创建对象时设置属性的初始值。
类属性 vs 实例属性:
类属性: 定义在类内部、任何方法之外。它们属于类本身,被该类的所有实例共享。修改类属性会影响所有实例。
实例属性: 通常在 __init__
方法中通过 self.attribute_name = value
定义。它们属于每个具体的实例对象。修改一个实例的属性不会影响其他实例或类属性。实例属性也可以在其他方法中定义或修改。
Dog.species = "Canis lupus" # 修改类属性,所有实例的 species 都会变 print(my_dog.species) # "Canis lupus" print(your_dog.species) # "Canis lupus" my_dog.age = 4 # 修改实例属性 my_dog.age,只影响 my_dog print(my_dog.age) # 4 print(your_dog.age) # 5 (不变)
继承允许你创建一个新类(子类、派生类)来继承另一个类(父类、基类、超类)的属性和方法。子类可以:
复用父类的代码。
扩展父类,添加新的属性和方法。
重写 (Override) 父类的方法,提供子类特定的实现。
语法:
class ChildClass(ParentClass): # 子类特有的属性和方法 ...
super()
函数: 在子类中,super()
用于调用父类(超类)的方法。常用于在重写方法时,先执行父类的实现,再添加子类的额外功能。
示例:
class Animal: # 父类 (基类) def __init__(self, name): self.name = name def speak(self): """动物发出声音 (基类方法,被子类重写)""" print(f"{self.name} makes a sound.") class Dog(Animal): # Dog 继承自 Animal def __init__(self, name, breed): # 调用父类的 __init__ 初始化 name super().__init__(name) # Python 3 中 super() 不需要参数 # 添加 Dog 特有的属性 self.breed = breed # 重写父类的 speak 方法 def speak(self): print(f"{self.name} (a {self.breed}) barks: Woof!") class Cat(Animal): # Cat 继承自 Animal def speak(self): print(f"{self.name} meows: Meow!") # 创建子类实例 dog = Dog("Buddy", "Golden Retriever") cat = Cat("Whiskers") # 调用方法 (子类重写的方法) dog.speak() # 输出: Buddy (a Golden Retriever) barks: Woof! cat.speak() # 输出: Whiskers meows: Meow! # 父类方法仍然存在,但被重写了 animal = Animal("Generic") animal.speak() # 输出: Generic makes a sound. # isinstance() 检查对象是否是类或其子类的实例 print(isinstance(dog, Dog)) # True print(isinstance(dog, Animal)) # True (Dog 是 Animal 的子类) print(isinstance(dog, Cat)) # False # issubclass() 检查一个类是否是另一个类的子类 print(issubclass(Dog, Animal)) # True print(issubclass(Cat, Animal)) # True print(issubclass(Animal, Dog)) # False
封装是指将数据(属性)和操作数据的方法(方法)捆绑在一起,并对外部隐藏对象的内部实现细节。在 Python 中,主要通过命名约定来实现一定程度的封装,而不是严格的访问控制:
公有 (Public): 普通的属性和方法名(如 name
, age
, speak()
)。可以在类的外部直接访问和修改。
受保护 (Protected): 在属性或方法名前加一个下划线 _
(如 _internal_data
)。这只是一个约定,告诉程序员“这个属性或方法仅供内部使用或在子类中使用,不建议在类外部直接访问”。但 Python 并不会阻止外部访问。
私有 (Private): 在属性或方法名前加两个下划线 __
(如 __secret_code
)。Python 会对这样的名称进行名称改写 (Name Mangling),使其在类外部难以直接访问(实际名称变为 _ClassName__secret_code
)。这提供了一定程度的访问限制,但并非绝对私有(知道改写规则还是可以访问)。
示例:
class BankAccount: def __init__(self, account_holder, initial_balance=0): self.account_holder = account_holder # 公有属性 self._account_number = "ACC123456" # 受保护属性 (约定) self.__balance = initial_balance # 私有属性 (名称改写) def deposit(self, amount): """存款""" if amount > 0: self.__balance += amount print(f"Deposited ${amount}. New balance: ${self.__balance}") else: print("Invalid deposit amount.") def withdraw(self, amount): """取款""" if 0 < amount <= self.__balance: self.__balance -= amount print(f"Withdrew ${amount}. New balance: ${self.__balance}") return amount else: print("Invalid withdrawal amount or insufficient funds.") return 0 def get_balance(self): # 公有方法访问私有属性 (Getter) """获取余额""" return self.__balance # 创建对象 account = BankAccount("Alice", 1000) # 访问公有属性 print(account.account_holder) # "Alice" # 访问受保护属性 (不推荐,但可以) print(account._account_number) # "ACC123456" (约定上不建议) # 直接访问私有属性 (会出错) # print(account.__balance) # AttributeError: 'BankAccount' object has no attribute '__balance' # 实际名称被改写 print(account._BankAccount__balance) # 1000 (可以访问,但不应该这样做!破坏了封装) # 通过公有方法访问和修改 (安全) account.deposit(500) # Deposited $500. New balance: $1500 account.withdraw(200) # Withdrew $200. New balance: $1300 print(account.get_balance()) # 1300
封装的意义:
控制访问: 防止外部代码随意修改对象内部状态,可能导致数据不一致。
隐藏实现细节: 外部代码只需要知道对象的公有方法(接口)如何使用,不需要关心内部如何实现。这降低了耦合度,提高了代码的可维护性。
数据验证: 可以在设置属性值的方法(Setter)中加入检查逻辑。
Pythonic 风格: Python 更强调“约定优于强制”。使用单个下划线 _
表示受保护成员,双下划线 __
表示私有成员,并尽量通过公有方法来访问和修改内部状态。直接访问受保护或私有成员被认为是不好的做法。
多态是指不同类型的对象可以对相同的消息(方法调用)做出不同的响应。它允许你使用统一的接口来处理不同类型的对象。
在 Python 中,多态是“鸭子类型 (Duck Typing)”的体现:“如果它走起路来像鸭子,叫起来像鸭子,那么它就是鸭子”。Python 不关心对象的实际类型是什么,只关心对象是否拥有你调用的方法或属性。
示例 (结合继承和鸭子类型):
# 定义几个有 speak 方法的类 (不要求有共同父类) class Dog: def speak(self): return "Woof!" class Cat: def speak(self): return "Meow!" class Duck: def speak(self): return "Quack!" class Car: def drive(self): return "Vroom!" # 一个函数,期望传入的对象有 speak 方法 def animal_sound(animal): """让动物发出声音 (要求animal对象有speak方法)""" try: sound = animal.speak() print(sound) except AttributeError: print("This object doesn't speak!") # 创建对象 dog = Dog() cat = Cat() duck = Duck() car = Car() # 调用函数 (多态) animal_sound(dog) # "Woof!" animal_sound(cat) # "Meow!" animal_sound(duck) # "Quack!" animal_sound(car) # "This object doesn't speak!" (Car 没有 speak 方法) # 即使没有继承关系,只要有 speak 方法,就能工作 class Robot: def speak(self): return "Beep boop!" robot = Robot() animal_sound(robot) # "Beep boop!" (鸭子类型)
多态的好处: 提高代码的灵活性和可扩展性。你可以编写通用的代码来处理遵循相同接口(拥有相同方法名)的不同类型的对象,而无需修改这些通用代码。
一点玩笑: OOP 的四大支柱(封装、抽象、继承、多态)听起来很高大上,其实核心思想就是“把相关的数据和操作打包在一起”(类)、“代码复用和扩展”(继承)、“隐藏内部细节”(封装)和“一个接口,多种实现”(多态)。理解这些概念需要实践。尝试设计几个类(比如 Circle
, Rectangle
继承 Shape
)来体会。Python 的 OOP 比较灵活,不像 Java 那样严格,但也容易写出混乱的结构。设计模式的书可以以后再看。
恭喜你!你已经掌握了 Python 的核心基础。但这只是开始。要继续深入,可以学习:
深入标准库: 系统学习 os
, sys
, datetime
, collections
, itertools
, json
, csv
, re
(正则表达式) 等常用模块。
文件与数据持久化: 学习 pickle
序列化、数据库操作 (sqlite3
模块)、操作 Excel (openpyxl
, pandas
)、操作 XML/HTML (xml.etree.ElementTree
, BeautifulSoup
)。
Web 开发:
后端框架: Flask (轻量), Django (全能)。
Web 请求: requests
库 (发送 HTTP 请求)。
模板引擎: Jinja2。
异步框架: FastAPI, Sanic。
数据分析与科学计算:
NumPy
: 高性能多维数组计算。
pandas
: 强大的数据结构和分析工具 (DataFrame)。
Matplotlib
, Seaborn
: 数据可视化。
SciPy
: 科学计算库 (建立在 NumPy 之上)。
机器学习/人工智能:
scikit-learn
: 经典机器学习算法库。
TensorFlow
, PyTorch
: 深度学习框架。
Keras
: 高级神经网络 API (常运行在 TensorFlow 之上)。
OpenCV
: 计算机视觉库。
网络编程:
套接字编程: socket
模块。
异步 I/O: asyncio
库。
并发编程:
threading
: 线程。
multiprocessing
: 进程。
concurrent.futures
: 高级并发接口。
测试: unittest
, pytest
(编写单元测试)。
打包与发布: setuptools
, pip
, virtualenv
/venv
(虚拟环境), pipenv
, Poetry
。
类型注解: Python 3.5+ 支持的类型提示 (Type Hints),使用 typing
模块,提高代码可读性和可维护性,配合 mypy
等工具进行静态类型检查。
设计模式: 学习常用的软件设计模式在 Python 中的应用。
算法与数据结构: 夯实计算机科学基础。
学习建议:
做项目!做项目!做项目! 这是巩固知识、发现问题、学习新技术的最有效方法。从小的工具脚本开始,逐步挑战更大的项目。
阅读优秀的开源代码。 在 GitHub 上找 Python 项目学习别人的代码风格、架构和设计。
善用文档和搜索引擎。 Python 官方文档、库的文档、Stack Overflow 是你的良师益友。
参与社区。 提问、回答问题、参与讨论。
最后一点玩笑: 学 Python 就像学游泳,看一万字教程不如自己跳进水里扑腾几下(写代码)。遇到的错误 (bug
) 就是你要喝的水,喝多了自然就学会了。编程最大的乐趣不是一次写对,而是花了几个小时调试后,程序终于按预期跑起来的那一刻(然后发现新的 bug)。祝你调试愉快!