1. 为什么我建议你重新审视“Python基础”如果你正在搜索“Python基础学习笔记”大概率是刚入门或者感觉之前学得不够扎实想找个靠谱的笔记来复习。市面上有太多“三天速成”、“七天精通”的教程但真正能让你把基础打牢的往往不是那些炫技的“高级”内容而是对最基础概念的深刻理解和反复练习。我见过太多新手包括几年前的我自己一头扎进爬虫、数据分析或者Web框架的学习中结果写出来的代码bug频出效率低下根本原因就是基础不牢。变量作用域搞不清列表和字典的引用特性没吃透面对一个简单的if-else逻辑都写得磕磕绊绊。后来我花了大量时间结合了像黑马程序员这样体系化的课程结构以及菜鸟教程这样随时可查的文档库重新梳理了一遍基础才真正感觉“通了”。这篇笔记就是我那次“回炉重造”的产物。它不是简单的知识点罗列而是融合了课程体系的结构性、官方文档的准确性以及我个人在实战中踩过的无数个坑总结出的经验。我会告诉你哪些基础概念是未来进阶的“基石”必须死磕哪些“坑”是初学者几乎百分百会遇到的以及如何避开还会分享一些看似简单但能极大提升代码质量和学习效率的小技巧。无论你是完全的零基础小白还是已经学过但感觉云里雾里的“半吊子”这篇超过5000字的超细致笔记都能帮你构建一个清晰、坚固的Python知识地基。2. 环境搭建从“能用”到“好用”的跨越很多人觉得安装Python、配置个编辑器不是学习重点随便搞搞就行。但恰恰是这一步的随意为后续的学习埋下了无数隐患。一个稳定、高效、顺手的环境能让你在学习时心无旁骛把精力全部集中在代码逻辑本身。2.1 Python解释器的选择与安装别再用系统自带的Python了首先忘掉你电脑里可能已经存在的那个Python。在macOS或某些Linux发行版上系统会预装一个老版本的Python比如Python 2.7或3.6那是给系统工具用的。我们学习开发一定要自己安装一个独立的、干净的版本。为什么版本控制系统Python版本固定且老旧无法使用新版本的特性和性能优化。权限问题向系统Python的site-packages目录安装第三方库通常需要sudo权限容易把系统搞乱。依赖隔离不同项目可能需要不同版本的库混用会导致冲突。怎么做直接访问 Python官网 下载最新稳定版的安装包。安装时务必勾选“Add Python to PATH”这个选项Windows系统。这是无数新手遇到的第一个大坑——不勾选你就无法在命令行中直接使用python和pip命令。安装完成后打开终端Windows是CMD或PowerShellmacOS/Linux是Terminal输入python --version。如果能看到正确的版本号如Python 3.11.4恭喜你第一步成功了。注意在Windows上如果安装后命令仍不识别可能需要手动将Python的安装目录如C:\Users\你的用户名\AppData\Local\Programs\Python\Python311和其下的Scripts目录添加到系统的环境变量PATH中。这是一个经典的排查步骤。2.2 代码编辑器的进化之路从IDLE到VSCodePython自带了一个简单的集成开发环境叫IDLE。对于运行第一行print(Hello, World!)来说它足够了。但如果你想进行真正的项目开发IDLE的功能就太弱了。初级阶段Notepad / Sublime Text这类轻量级文本编辑器语法高亮做得好启动快适合写一些简单的脚本。但它们缺乏代码提示、调试等高级功能。推荐阶段Visual Studio Code (VSCode)这是目前绝大多数Python开发者的首选。它免费、开源、插件生态极其丰富。配置Python环境非常简单安装VSCode。在扩展商店搜索并安装官方插件“Python”由Microsoft发布。打开一个.py文件VSCode通常会自动识别Python解释器。如果没有可以点击左下角的状态栏选择解释器。VSCode提供了智能提示IntelliSense输入变量名、函数名时自动补全。代码格式化一键让杂乱的代码变得整齐依赖autopep8或black插件。集成终端直接在编辑器内运行命令行无需切换窗口。图形化调试设置断点逐行执行查看变量值是理解程序运行流程的神器。专业阶段PyCharmJetBrains公司的产品分社区版免费和专业版收费。它的功能更加强大尤其是对Web开发Django, Flask、数据科学Jupyter集成的支持开箱即用。但相对更吃系统资源。对于纯粹学习基础语法VSCode完全够用且更轻量。我的建议是直接从VSCode开始。它平衡了功能性和易用性其配置过程本身也是对开发环境理解的一次很好实践。2.3 包管理工具pip与虚拟环境venv项目的“隔离病房”这是基础学习中至关重要但最容易被忽略的一环。pipPython的包安装工具。安装Python时通常已经自带。在终端输入pip list可以查看已安装的第三方库。用pip install 包名来安装新库。问题来了如果你所有项目都直接pip install所有库都装到电脑的同一个地方。当项目A需要numpy 1.20而项目B需要numpy 1.24时冲突就发生了。你无法同时安装两个版本。解决方案虚拟环境venv。 虚拟环境就像一个独立的“房间”在这个房间里安装的Python包只对这个房间里的项目有效不会影响到房间外的其他项目。创建和使用虚拟环境命令行操作# 1. 进入你的项目目录 cd my_project # 2. 创建虚拟环境环境文件夹通常命名为venv或.venv python -m venv venv # 3. 激活虚拟环境 # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate # 激活后命令行提示符前通常会显示(venv)表示你已进入该环境。 # 4. 在虚拟环境中安装包例如安装requests pip install requests # 5. 当你完成工作可以退出虚拟环境 deactivate养成每个独立项目都创建并使用独立虚拟环境的习惯是走向专业开发的标志。VSCode可以很方便地选择特定虚拟环境作为项目的解释器。3. 核心语法精讲变量、数据类型与流程控制基础语法是建筑的砖块。这一部分黑马程序员的课程安排通常非常合理而菜鸟教程则提供了最快速的查询入口。我将结合两者并加入我的理解。3.1 变量与数据类型不仅仅是“盒子”很多教程把变量比作“盒子”里面放数据。这个比喻容易让人误解特别是在Python中。更准确的比喻变量是“标签”或“便利贴”。在Python中变量不是容器而是指向内存中某个对象的“名字”或“引用”。例如a [1, 2, 3] # 变量a像一张便利贴贴在了列表[1, 2, 3]上 b a # 变量b是另一张便利贴也贴在了同一个列表上 b.append(4) print(a) # 输出[1, 2, 3, 4]因为a和b指向的是同一个列表对象。理解“引用”这个概念对于后续学习列表、字典的修改函数参数传递等至关重要。基本数据类型数字int, float, complex注意整数除法在Python 3中总是返回浮点数10 / 3 3.333...而//才是整除。字符串str不可变。重点掌握切片s[start:end:step]和格式化。旧式%格式化Hello, %s % World新式str.format()Hello, {}.format(World)推荐f-string(Python 3.6)name World; fHello, {name}直观高效。布尔boolTrue和False。注意在条件判断中0、空字符串、空列表[]、None等都会被视作False。空值None表示“什么都没有”是一个独立的类型。常用于函数默认返回值或变量初始占位。3.2 列表、元组、字典、集合四大金刚的选用之道这是Python中最常用、最强大的数据结构。列表list可变有序的序列。[1, 2, 3]核心操作增(append,insert,extend)、删(pop,remove,del)、改(索引赋值)、查(索引、切片、in、index)。列表推导式优雅且高效的创建新列表的方式。[x*2 for x in range(10) if x%20]。务必掌握这是Pythonic的体现。坑点浅拷贝与深拷贝。list2 list1只是贴了个新标签引用。想要真正的副本用list2 list1.copy()浅拷贝或import copy; list2 copy.deepcopy(list1)深拷贝用于元素也是可变对象时。元组tuple不可变有序的序列。(1, 2, 3)用途用于保证数据不被修改例如作为字典的键因为键必须是不可变的或函数返回多个值实际上返回的是一个元组。单元素元组必须加逗号(1,)否则(1)只是一个整数。字典dict可变键值对的无序集合Python 3.7后插入有序。{name: Alice, age: 25}核心操作取键keys()、取值values()、取键值对items()常用于for k, v in dict.items():循环。安全取值直接dict[key]在key不存在时会报KeyError。使用dict.get(key, default_value)可以安全地返回默认值。字典推导式{k: v*2 for k, v in old_dict.items() if v 10}。集合set可变无序的唯一元素集合。{1, 2, 3}核心用途去重list(set(some_list))和集合运算交集、并集|、差集-、对称差集^。冻结集合frozenset不可变的集合可作为字典的键。选用原则需要有序、可修改的集合 -列表需要有序、不可修改的集合保护数据 -元组需要通过一个唯一的“键”来快速查找对应的“值” -字典需要确保元素唯一或进行交集、并集等数学运算 -集合3.3 程序流程控制让代码“活”起来条件判断if-elif-else注意冒号:和缩进。Python用缩进通常4个空格来定义代码块这是语法的一部分不是风格建议。避免过深的嵌套超过3层就要考虑是否能用函数或提前返回来简化逻辑。循环for whilefor循环主要用于遍历任何可迭代对象列表、元组、字符串、字典、文件等。for item in iterable:。while循环当条件满足时一直执行。务必确保循环内有改变条件的语句否则会死循环。循环控制break立即跳出整个循环。continue跳过本次循环的剩余语句进入下一次迭代。else在循环正常结束即不是被break打断后执行。这个特性很多人不知道但在搜索场景中很有用例如在列表中找元素找到就break没找到则循环正常结束执行else块提示“未找到”。一个综合例子# 遍历一个数字列表跳过负数遇到大于100的数就停止如果全部处理完都没遇到大于100的则打印提示。 numbers [10, -5, 20, 150, 30, -1] for num in numbers: if num 0: continue # 跳过负数 if num 100: print(f发现大于100的数{num}循环终止。) break print(f处理正数{num}) else: # 这个else属于for循环只有当循环完整遍历完所有元素未被break中断时才会执行 print(所有数字都已处理未发现大于100的数。)4. 函数从“写脚本”到“编程序”的关键一跃当你开始把一段重复使用的代码块封装成函数时你就从“写脚本”迈向了“编程序”。函数是代码复用的基石也是组织复杂逻辑的核心单元。4.1 函数定义与参数传递的“玄机”定义函数使用def关键字。def greet(name, greetingHello): # greeting是默认参数 这是一个问候函数文档字符串 return f{greeting}, {name}!参数类型位置参数按顺序传递greet(Alice, Hi)。关键字参数指定参数名传递顺序可变greet(greetingHi, nameAlice)。默认参数定义时给参数默认值。关键坑点默认参数必须指向不可变对象# 错误示范 def add_to_list(item, my_list[]): # 默认参数是可变对象列表 my_list.append(item) return my_list print(add_to_list(1)) # [1] print(add_to_list(2)) # [1, 2] 两次调用共享了同一个默认列表 # 正确做法 def add_to_list(item, my_listNone): if my_list is None: my_list [] # 每次调用都创建一个新列表 my_list.append(item) return my_list可变参数*args接收任意数量的位置参数在函数内部作为一个元组处理。**kwargs接收任意数量的关键字参数在函数内部作为一个字典处理。def log_message(level, *args, **kwargs): print(f[{level}], *args) # args被打包成元组用*解包打印 for key, value in kwargs.items(): print(f {key}: {value}) log_message(INFO, User login, success, user_id123, ip192.168.1.1)参数传递的本质Python中所有参数的传递都是“对象引用传递”。对于不可变对象数字、字符串、元组函数内修改形参不会影响实参。对于可变对象列表、字典函数内通过形参修改对象内容会直接影响外部的实参对象。4.2 作用域与LEGB规则变量在哪里生效这是另一个容易混淆的点。Python查找变量的顺序遵循LEGB规则LLocal局部作用域函数内部。EEnclosing闭包函数外的函数作用域嵌套函数的情况。GGlobal全局作用域当前模块文件层面。BBuilt-in内建作用域Python内置的变量如print,len。x global # G def outer(): x enclosing # E def inner(): x local # L print(x) # 输出 local inner() print(x) # 输出 enclosing outer() print(x) # 输出 globalglobal和nonlocal关键字在函数内部想修改全局变量需要使用global声明。在嵌套函数的内层函数中想修改外层函数非全局的变量需要使用nonlocal声明。count 0 def outer(): num 10 def inner(): nonlocal num # 声明要修改外层函数的变量 global count # 声明要修改全局变量 num 1 count 1 inner() print(num) # 11 outer() print(count) # 14.3 匿名函数lambda简洁的单行工具lambda用于创建小巧的匿名函数。格式lambda 参数列表: 表达式# 等价于 def square(x): return x * x square lambda x: x * x print(square(5)) # 25它最常用的场景是作为sort()、sorted()、map()、filter()等函数的key参数。students [(Alice, 88), (Bob, 95), (Charlie, 78)] # 按分数排序 students.sort(keylambda student: student[1], reverseTrue) print(students) # [(Bob, 95), (Alice, 88), (Charlie, 78)]但记住lambda函数只能包含一个表达式不能包含复杂的语句或多条命令。复杂的逻辑还是应该用def定义常规函数。5. 文件操作与异常处理程序的“输入输出”与“安全气囊”程序不可能永远在内存里运行总要和外部世界文件、网络、用户输入打交道。这部分是让程序变得实用的关键。5.1 文件读写上下文管理器是你的好朋友基本操作# 1. 打开文件旧式不推荐 f open(test.txt, r, encodingutf-8) # ‘r’表示读模式 content f.read() f.close() # 必须手动关闭否则可能资源泄露 # 2. 使用with语句推荐 with open(test.txt, r, encodingutf-8) as f: content f.read() # 离开with块后文件会自动关闭即使发生异常也会。打开模式r只读默认。w写入会覆盖原有文件。a追加在文件末尾添加。x创建新文件并写入如果文件已存在则失败。b二进制模式例如rb,wb用于处理图片、视频等非文本文件。读写模式例如r可读可写。读写方法f.read(size)读取整个文件或指定大小的内容。f.readline()读取一行。f.readlines()读取所有行返回一个列表。f.write(string)写入字符串。f.writelines(list)写入一个字符串列表。高效处理大文件不要用read()或readlines()一次读入整个大文件会占用大量内存。应该迭代读取with open(large_file.log, r, encodingutf-8) as f: for line in f: # 文件对象本身是可迭代的每次读一行 process(line) # 处理每一行5.2 异常处理优雅地应对程序“崩溃”程序运行时总会遇到意外文件找不到、网络断开、用户输入了错误格式的数据……异常处理就是程序的“安全气囊”。基本结构try-except-else-finallytry: # 可能出错的代码 result 10 / int(input(请输入一个除数: )) print(f结果是{result}) except ValueError: # 处理特定异常例如输入的不是数字 print(错误请输入一个有效的整数) except ZeroDivisionError: # 处理另一个特定异常除零错误 print(错误除数不能为零) except Exception as e: # 捕获所有其他未预料到的异常 print(f发生了未知错误{e}) else: # 当try块中的代码没有发生任何异常时执行 print(计算成功完成) finally: # 无论是否发生异常最终都会执行的代码常用于清理资源如关闭文件、网络连接 print(程序执行结束。)主动抛出异常raise你也可以在检测到错误条件时主动中断程序并抛出异常。def set_age(age): if not 0 age 150: raise ValueError(年龄必须在0到150之间) print(f年龄设置为{age})自定义异常通过继承Exception类可以创建更符合业务逻辑的异常类型。class MyCustomError(Exception): 自定义异常 pass try: raise MyCustomError(发生了自定义错误) except MyCustomError as e: print(e)异常处理的原则是“精准捕获向上传递”。只捕获你知道如何处理的异常不知道的应该让它继续向上层抛出。不要用一个except Exception:捕获所有异常然后默默吞掉这会让你难以调试程序。6. 模块与包代码组织的艺术当代码越来越多把所有东西都写在一个文件里会变得难以维护。模块和包是Python组织代码的方式。6.1 模块Module一个.py文件就是一个模块模块让你能够把相关的函数、类、变量组织在一起。使用import语句来导入。# 假设有一个文件叫 my_math.py # my_math.py 内容 def add(a, b): return a b PI 3.14159 # 在另一个文件中使用 import my_math # 导入整个模块 print(my_math.add(1, 2)) print(my_math.PI) from my_math import add, PI # 从模块中导入特定内容 print(add(3, 4)) print(PI) from my_math import add as my_add # 导入并重命名避免命名冲突 print(my_add(5, 6))if __name__ __main__:的妙用在一个模块文件中你可能会写一些测试代码。但当你把这个模块导入到其他文件时你通常不希望这些测试代码被执行。这时就需要这个“魔法变量”。# my_module.py def useful_function(): print(这是一个有用的函数) # 以下代码只有在直接运行这个文件时才会执行 if __name__ __main__: print(我正在被直接运行) useful_function() # 可以在这里测试函数 # 当被其他文件 import my_module 时这部分代码不会执行。__name__是模块的内置属性。当一个模块被直接运行时__name__等于__main__当它被导入时__name__等于模块的名字如my_module。6.2 包Package包含多个模块的文件夹包是一个包含__init__.py文件的文件夹。这个文件可以是空的也可以包含包的初始化代码。包让你能够用目录层次来组织模块。my_package/ ├── __init__.py ├── module_a.py └── sub_package/ ├── __init__.py └── module_b.py导入包中的模块import my_package.module_a from my_package.sub_package import module_b__init__.py的作用标识一个文件夹是一个Python包。可以在其中编写代码当包被导入时这些代码会首先执行。常用于批量导入子模块方便用户使用。# my_package/__init__.py from .module_a import * from .sub_package.module_b import some_function # 这样用户就可以直接 from my_package import ... 了理解模块和包是阅读和参与大型开源项目的基础也是你将自己代码工程化的第一步。7. 面向对象编程OOP入门用对象思考世界面向对象是Python乃至现代编程的核心范式。它通过“类”和“对象”将数据和操作数据的方法捆绑在一起更符合人类对现实世界的抽象。7.1 类与对象蓝图与实物类Class是创建对象的蓝图或模板。它定义了对象将拥有的属性数据和方法函数。对象Object/Instance是根据类创建出来的具体实体。class Dog: # 定义一个Dog类 # 类属性所有实例共享 species Canis familiaris # 初始化方法构造器在创建对象时自动调用 def __init__(self, name, age): # 实例属性每个对象独有 self.name name self.age age # 实例方法 def bark(self): return f{self.name} says: Woof! def get_human_age(self): 一个自定义方法 return self.age * 7 # 创建对象实例化 my_dog Dog(Buddy, 3) print(my_dog.name) # Buddy print(my_dog.bark()) # Buddy says: Woof! print(my_dog.get_human_age()) # 21 print(my_dog.species) # Canis familiaris关键概念self在类的方法中第一个参数必须是self它代表对象实例本身。通过self可以访问实例的属性和其他方法。__init__特殊方法魔法方法/双下方法在对象创建时初始化属性。实例属性 vs 类属性name和age属于每个狗对象自己而species是所有狗共享的。修改类属性会影响所有实例。7.2 封装、继承与多态OOP的三大支柱1. 封装Encapsulation将数据属性和操作数据的方法方法包装在类内部并对外部隐藏实现的细节。在Python中主要通过命名约定来实现“弱”封装公开属性self.name保护属性约定非强制self._protected_var一个下划线开头提示“请勿在类外部直接访问”。私有属性名称改写self.__private_var两个下划线开头Python会将其改名为_类名__private_var从外部无法直接访问但并非绝对安全。class BankAccount: def __init__(self, owner, balance): self.owner owner self.__balance balance # 私有属性 def deposit(self, amount): if amount 0: self.__balance amount print(f存款成功当前余额{self.__balance}) def get_balance(self): # 提供公开的方法来访问私有属性 return self.__balance account BankAccount(Alice, 1000) # print(account.__balance) # 报错AttributeError print(account.get_balance()) # 通过方法访问安全2. 继承Inheritance一个类子类可以继承另一个类父类的属性和方法并可以扩展或重写它们。这实现了代码的复用。class Animal: # 父类 def __init__(self, name): self.name name def speak(self): raise NotImplementedError(子类必须实现此方法) # 抽象方法 class Cat(Animal): # 子类继承自Animal def speak(self): # 重写父类方法 return f{self.name} says: Meow! class Dog(Animal): def speak(self): return f{self.name} says: Woof! animals [Cat(Whiskers), Dog(Buddy)] for animal in animals: print(animal.speak()) # 多态的体现3. 多态Polymorphism不同类的对象对同一消息方法调用做出不同的响应。如上例Cat和Dog对象都调用speak()方法但产生了不同的行为。这增加了程序的灵活性和可扩展性。7.3 魔法方法Magic Methods让对象更“Pythonic”魔法方法是以双下划线开头和结尾的方法如__init__、__str__、__len__。它们允许你自定义类的行为使其与Python的内置函数和操作符无缝协作。__str__(self)定义当使用print(obj)或str(obj)时对象的字符串表示。应该返回一个对用户友好的描述。__repr__(self)定义当在交互式环境直接输入对象名或使用repr(obj)时返回的字符串。应该返回一个对开发者更精确、通常能用于重新创建该对象的描述。__len__(self)定义当使用len(obj)时的行为。__add__(self, other)定义操作符的行为。class Book: def __init__(self, title, author, pages): self.title title self.author author self.pages pages def __str__(self): return f《{self.title}》 by {self.author} def __repr__(self): return fBook({self.title}, {self.author}, {self.pages}) def __len__(self): return self.pages def __add__(self, other): 将两本书的页数相加这只是一个示例实际意义不大 if isinstance(other, Book): return self.pages other.pages return NotImplemented book1 Book(Python Crash Course, Eric Matthes, 560) print(book1) # 输出 《Python Crash Course》 by Eric Matthes print(repr(book1)) # 输出 Book(Python Crash Course, Eric Matthes, 560) print(len(book1)) # 输出 560掌握这些基础的OOP概念你就能开始构建更加结构化和可维护的Python程序了。面向对象不是银弹但对于管理复杂状态和逻辑的项目来说它是不可或缺的工具。