纯Markdown输出直接从正文开始。1. 变量与赋值Python最容易被忽略的底层逻辑很多入门教程讲Python基本语法时第一课都是变量和赋值但大多数教程只告诉你这是变量那是常量根本没讲清楚Python赋值背后的机制。我在带新手和看别人代码时发现很多奇奇怪怪的bug其实都出在这一块——不是你不会写x 1而是你没搞懂这行代码在解释器里到底做了什么。1.1 动态类型与对象绑定关系Python是一种动态类型语言变量本身没有类型类型归属于对象。当你写下a 10时实际发生的事情是创建了一个整数对象10然后把名字a绑定到了这个对象上。听起来有点绕但理解这一点非常重要。举个例子你看这段代码a 10 b a a 20 print(b) # 输出多少新手往往会答错。答案是10——因为b a时b绑定的是a当时指向的对象10之后a重新绑定到20和b没有任何关系。字符串、整数、浮点数这类不可变对象赋值时天然就是值传递的效果这也是为什么很多从C/C转过来的朋友觉得Python很省心。但麻烦出现在可变对象上。列表、字典、集合都是可变对象赋值时绑定的是同一个对象于是会出现这种经典场景list_a [1, 2, 3] list_b list_a list_b.append(4) print(list_a) # [1, 2, 3, 4]list_a也被改了很多新手在这里懵掉觉得Python不讲武德。其实解释器并没有做任何特殊处理list_b list_a的含义就是把list_b绑定到list_a所绑定的那个列表对象两个名字指向的是同一块内存。想要真正复制一份用list_a.copy()或者list(list_a)但要注意这是浅拷贝——如果列表里套着列表还得用copy模块的deepcopy。这块内容看似基础实则是后续很多诡异bug的根源建议初学者在纸上画一画名字-对象的绑定关系画清楚之后你对变量的理解会直接上一个台阶。1.2 数字运算与类型转换的隐藏门道搜索热词里有两个高频词python类型转换和python abs函数。说明大家入门时都卡在这些细节上。Python的数值类型很直白整数int、浮点数float、复数complex日常编程用前两个就够了但坑在于自动类型转换。print(7 / 2) # 3.5除号永远返回浮点数 print(7 // 2) # 3地板除返回整数 print(7 % 2) # 1取余/和//的区别是新人第一次接触Python时最容易困惑的地方。更复杂一点的是混合运算1 2.0的结果是3.0类型自动提升为浮点数。这是Python的隐式转换规则——低精度向高精度靠拢。显式转换则靠int()、float()、str()三个函数。这里有两个高频翻车点# 翻车点1字符串转整数 int(3.5) # ValueError必须用float int(float(3.5)) # 先转浮点再取整结果是3 # 翻车点2浮点转整数 int(3.99) # 结果是3直接截断不四舍五入abs函数看起来简单就是求绝对值但它的细节也有讲究。abs(-5)返回5abs(34j)返回5.0——复数也能求模。如果你需要对一个列表里的所有元素求绝对值配合列表推导式是标准做法numbers [-1, 2, -3, 4] abs_numbers [abs(n) for n in numbers]1.3 赋值语法糖解包、链式、增量Python的赋值语句其实比多数语言灵活得多。我见过太多新手用临时变量交换值看到Python的写法之后直呼优雅a, b 1, 2 a, b b, a # 一行交换这叫元组解包。更实用的是在遍历字典时同时拿key和valuescores {math: 90, chinese: 85} for subject, score in scores.items(): print(subject, score)链式赋值a b 100让两个名字绑定同一个对象增量赋值x 1等价于x x 1。但要小心背后的陷阱——如果x是列表x [4]和x x [4]有微妙差别前者原地修改后者重新创建。浓重推荐阅读官方文档里关于赋值语句的部分短短几页纸但每一句都值得细品。2. 常用数据结构列表、字典、元组与集合选择指南爬虫教程、数据分析教程满天飞但你看任何实战项目核心操作无非就是对列表做遍历、对字典做映射、对集合做去重。数据结构是Python基本语法中最值得花时间的部分选对了容器代码写起来顺手选错了后面全是补丁。2.1 列表与元组可变与不可变的权衡列表[1, 2, 3]和元组(1, 2, 3)长得像亲兄弟但性格完全不同。列表可变可以增删改元组不可变创建之后不能修改。选元组不是因为省内存这种微小的性能差异而是语义上的承诺——当你不想让数据被意外修改时用元组表达意图。从代码健壮性角度讲我建议函数返回多个值时用元组固定配置项用元组需要动态增删的数据才用列表。比如函数的返回值def get_user(): name 张三 age 25 return name, age # 实际上返回的是元组 (name, age) name, age get_user()列表常用的操作要形成肌肉记忆append()尾部添加元素insert(index, element)指定位置插入extend()批量添加remove()按值移除第一个匹配项元素不存在会报ValueErrorpop()弹出并返回最后一个元素sort()原地排序sorted()返回新列表切片是个百用不厌的利器list[1:4]取出索引1到3的元素list[::-1]反转整个列表list[::2]取偶数位置的元素。很多算法题用切片一行能搞定的操作用循环要写五六行。2.2 字典Python中最常用的映射结构字典{key: value}几乎是Python出镜率最高的数据结构。爬虫解析返回的JSON数据接口请求参数数据库查询结果全部离不开字典。它的底层实现是哈希表所以查找速度是O(1)这就是为什么它能成为Python生态的基石。字典的基本操作d {name: 张三, age: 25} # 取值直接用key访问有风险 # d[gender] # KeyError d.get(gender, 未知) # 推荐方式取不到时返回默认值 # 修改或新增 d[city] 上海 # key不存在就新增存在就更新 # 遍历 for key in d: print(key) # 默认遍历key for key, value in d.items(): print(key, value) # 同时拿键值字典还有个非常好用的手段叫defaultdict来自collections模块。处理词频统计、分类汇总这类场景时defaultdict(int)能省掉一坨检查key是否存在的代码from collections import defaultdict words [apple, banana, apple, orange, apple] counter defaultdict(int) for word in words: counter[word] 1 print(dict(counter)) # {apple: 3, banana: 1, orange: 1}2.3 集合的去重能力与运算集合{1, 2, 3}最大的价值是去重list(set(my_list))一行搞定。第二个价值是集合运算——交集、并集、差集这些在处理爬虫数据、对比配置文件时非常实用。a {1, 2, 3, 4} b {3, 4, 5, 6} a b # 交集 {3, 4} a | b # 并集 {1, 2, 3, 4, 5, 6} a - b # 差集 {1, 2}热词里有爬虫python入门和python爬虫可视化界面爬虫实战中最常见的操作就是维护一个已访问的URL集合避免重复请求。这就是集合的典型应用场景。集合元素必须是可哈希的所以列表不能放进集合但元组可以。2.4 推导式写Python的正确姿势列表推导式是实现Python代码短、快、读的第一招。老实说任何Python教程都会讲但我想强调它的读法先写表达式再写循环最后写条件。# 基本形式 squares [x**2 for x in range(10)] # 带条件 even_squares [x**2 for x in range(10) if x % 2 0] # 嵌套循环 pairs [(x, y) for x in range(3) for y in range(3)] # 字典推导式和集合推导式同理 square_dict {x: x**2 for x in range(5)}推导式不只是写法简洁性能也比等价的for循环快因为底层的循环优化由解释器完成。看别人源码时如果你不懂推导式很多代码就像天书一旦掌握了你会发现自己写代码的效率和代码的可读性都明显提升。但注意别把推导式写得太过分——嵌套三层循环的推导式可读性已经崩了这时候该切回普通循环。3. 控制流与函数定义从线性执行到逻辑复用Python程序默认是从上到下逐行执行想让程序有判断能力和循环能力控制流语法是根本。而函数则是把重复逻辑封装起来的第一手段。这两块内容是Python基本语法的主干也是所有实战项目的地基。3.1 条件判断与布尔逻辑if、elif、else是Python分支判断的三件套。语法本身没什么好讲的我要强调的是几个高频误区和规范。第一个误区把elif写成else if。Python没有else if这种写法这是C系语言转过来的新手最常见的语法错误缩紧方式也不一样写出来还会直接报SyntaxError。第二个误区混淆和is。比较两个对象的值是否相等is比较两个对象是否是同一个对象。整数比较用就够了但比较None时Python社区约定俗成用is Nonex None if x is None: print(x is None)第三个误区条件表达式滥用三元运算符。Python支持a if condition else b这种三元表达但很多人写得晦涩难懂。我自己写代码的原则是三元表达式只用于简单赋值复杂逻辑一律用完整if-else可读性永远优先。布尔逻辑配套的还有and、or、not。Python的and和or有短路求值特性而且返回值不一定是布尔值而是决定结果的最后一个操作数name user_input or 默认值 # 如果user_input为空用默认值这是我从实际工作中学会的最实用的小技巧之一。3.2 for循环与while循环的正确用法Python的for循环和C系语言差异极大它是迭代器模型——直接遍历可迭代对象而不是管理一个索引变量。# 遍历字符串 for char in python: print(char) # 遍历列表并同时取索引 for idx, value in enumerate([a, b, c]): print(idx, value) # 固定次数循环 for _ in range(5): print(hello)range函数是高频工具它有三种参数形式range(stop)、range(start, stop)、range(start, stop, step)。注意range包左不包右range(5)生成0到4。基于这个特性range(len(my_list))这样的写法在Python社区是不推荐的直接遍历列表本身或使用enumerate才是Pythonic的方式。while循环通常用在不确定循环次数的场景。典型例子是用户输入校验直到合法为止while True: age input(请输入年龄) if age.isdigit(): break print(输入不合法请重新输入)不管哪种循环两个控制关键字务必掌握break立即终止整个循环continue跳过本次迭代继续下一次else循环正常结束后执行如果因为break跳出则不会执行第三个是Python独有的特性很多非常有经验的开发者都不一定知道。见过有人用标志变量来模拟else的效果绕了一大圈实际上直接用for...else几行就能解决。3.3 函数定义、参数传递与作用域函数定义用def关键字Python函数有四种参数类型这是新手容易晕的地方。def func(a, b, c10, *args, **kwargs): passa, b位置参数调用时按顺序传值c10默认参数调用时不传就用默认值*args可变位置参数接收任意多个位置参数打包成元组**kwargs可变关键字参数接收任意多个键值对打包成字典*args和**kwargs是Python函数定义中最灵活的部分。举个实际例子封装一个日志函数def log(level, message, *args, **kwargs): print(f[{level}] {message}) if args: print(附加参数:, args) if kwargs: print(附加键值:, kwargs)返回值用return如果函数没有return语句默认返回None。返回多个值直接写return a, b调用方用解包接收。作用域是函数绕不开的话题。Python的变量查找遵循LEGB规则Local局部的→ Enclosing外层函数里的→ Global模块全局的→ Built-in内置函数模块。很多新手在函数里想修改全局变量发现不生效count 0 def increment(): global count # 不加global会报UnboundLocalError count 13.4 闭包与lambda表达式闭包这个概念在Python基本语法里不算基础但掌握了之后很多高阶特性就容易理解了。简单说闭包就是函数加它引用的外部变量的组合def make_multiplier(n): def multiplier(x): return x * n return multiplier double make_multiplier(2) triple make_multiplier(3) print(double(5)) # 10 print(triple(5)) # 15n在make_multiplier执行完后本该释放但因为内部函数multiplier还引用它所以它被保留在闭包里。这就是闭包的意义——让函数携带状态。lambda表达式则是Python的一行匿名函数常用于sort()、max()这类需要传入函数参数的地方students [(张三, 90), (李四, 85), (王五, 95)] students.sort(keylambda x: x[1], reverseTrue)4. 字符串处理与常见内置函数实际项目里用得最多的话区域字符串可能是Python中使用频率最高的数据类型没有之一。爬虫要清洗文本数据分析要处理标签日志要格式化输出全离不开字符串操作。4.1 字符串基础操作与格式化字符串有三种定义方式单引号...、双引号...、三引号...或...。三引号常用于多行字符串和文档字符串docstring。单双引号本身没区别但可以免去转义message He said, hello # 省去转义字符串的操作方法非常多我重点说几个高频的text Hello, Python World! text.strip() # 去掉首尾空白 text.lower() # 全部转小写 text.upper() # 全部转大写 text.replace(Python, Java) # 替换 text.split(,) # 按逗号分隔成列表[ Hello, Python World! ] ,.join([a, b, c]) # join是split的反操作 text.startswith(Hello) # 判断前缀 text.find(Python) # 返回子串索引找不到返回-1不要小看这些方法它们是文本处理的地基。爬虫清洗数据时strip()、replace()、split()三件套能解决80%的脏数据问题。字符串格式化是另一个重点Python三种格式化方式name 张三 age 25 # 方式一百分号格式化老式 print(%s今年%d岁 % (name, age)) # 方式二format方法过渡式 print({}今年{}岁.format(name, age)) # 方式三f-stringPython 3.6推荐 print(f{name}今年{age}岁)f-string是现在的主流支持在花括号里写表达式和调用方法price 9.5 print(f价格是{price:.2f}元) # 保留两位小数 print(f大写{name.upper()}) # 调用方法4.2 常用内置函数一览除了前面提到的abs、int、float、str还有几个内置函数属于每天都要用的级别。len()返回容器长度type()查询类型isinstance(obj, Class)判断对象类型——后者更推荐因为它支持子类判断。range()配合enumerate()已经在循环部分讲过了zip()也是高频函数把多个可迭代对象打包成一个个元组names [张三, 李四, 王五] scores [90, 85, 95] for name, score in zip(names, scores): print(f{name}: {score})map()和filter()在配合lambda使用时非常方便nums [1, 2, 3, 4, 5] squares list(map(lambda x: x**2, nums)) even_nums list(filter(lambda x: x % 2 0, nums))不过说实话现在列表推导式更被社区推崇map和filter配lambda在可读性上略逊一筹。但在函数式编程风格的工具链里map和filter仍是高频出现。enumerate()在前文已经介绍了sorted()函数能够对任何可迭代对象排序并返回新列表比列表的sort()方法更通用sorted([3, 1, 2]) # [1, 2, 3]默认升序 sorted([3, 1, 2], reverseTrue) # [3, 2, 1]4.3 可迭代对象与迭代器协议Python基本语法里有一个很重要的概念叫可迭代对象——能用for循环遍历的对象都算。列表、元组、字符串、字典、集合、文件对象、range都是可迭代对象。它们能遍历的根本原因是实现了迭代器协议有__iter__()方法或者__getitem__()方法。很多新手问迭代器和可迭代对象的区别。简单说可迭代对象是可以被拿去迭代的容器迭代器是记录迭代状态的游标。比如my_list [1, 2, 3] it iter(my_list) # 获取迭代器 print(next(it)) # 1 print(next(it)) # 2生成器是迭代器的一种用yield关键字定义。它不是一次性创建所有数据而是用的时候才算省内存。处理大文件、大数据集时生成器是真正的最佳实践def read_big_file(file_path): with open(file_path, r, encodingutf-8) as f: for line in f: yield line.strip()这个函数每次只读一行内存占用不会因为文件过大而暴涨。在内存动不动就几个G的数据处理任务中这是很重要的优化手段。5. 新手最易踩的语法坑缩进、可变默认值与异常处理我在日常答疑和带新人过程中遇到太多看起来代码没问题但就是报错的情况。Python的语法坑往往是其他语言没有的这里专门整理几个高频雷区帮大家提前避坑。5.1 缩进与SyntaxError系列错误Python用缩进表示代码块这既是它的特色也是新手的噩梦。最常见的报错是IndentationError: unexpected indent和IndentationError: expected an indented block。前者是缩进多了后者是忘了缩进——比如if条件后面忘了写内容。建议所有Python项目统一使用4个空格缩进不要用Tab更不要混用。混用Tab和空格在Pycharm、VSCode里肉眼看起来一模一样但解释器会直接报错。代码编辑器里可以开启显示空白字符功能一眼就能识别出混用问题。SyntaxError还包括遗漏冒号、括号不匹配、引号未闭合。Python的报错定位通常很准确看到SyntaxError先看报错行和行尾的^指向位置多数情况下问题就出在那里。还有一类容易被忽略的逻辑错误而非语法错误比如误用赋值运算符if x 10: # 这是错误写法 pass if x 10: # 正确写法 pass条件判断和混用在Python中是直接报SyntaxError的这一点比C语言安全得多但仍然是高频错误。5.2 可变对象作为默认参数经典陷阱这是Python初学者最容易踩的坑没有之一def add_item(item, my_list[]): my_list.append(item) return my_list print(add_item(1)) # [1] print(add_item(2)) # [1, 2] 不是应该[2]吗原因是默认参数的值在函数定义时就被计算并保存了之后每次调用都复用同一个列表对象。这个例子在网上被称为可变默认参数陷阱。正确做法是默认值用None函数内部再创建新列表def add_item(item, my_listNone): if my_list is None: my_list [] my_list.append(item) return my_list这个坑深到什么程度我在面试Python新人时经常出这道题能答对的人不到一半。但一旦理解了对象绑定机制这个坑的本质就非常清晰——默认参数存的是对象引用而列表又是可变对象。5.3 异常处理try-except的正确打开方式程序报错不可怕可怕的是程序崩溃。异常处理是Python基本语法里和实战关系最紧密的部分。try: result 10 / int(user_input) except ValueError: print(请输入合法的数字) except ZeroDivisionError: print(除数不能为零) except Exception as e: print(f未知错误{e}) else: print(没有异常时执行) finally: print(无论是否异常都会执行)几个要点try里只放可能出错的代码不要把所有逻辑全塞进去except不要裸用只写except:至少写except Exception最好精确到具体异常类型捕获异常后要么处理、要么记录日志、要么抛出但别做吞掉异常这种不负责任的做法else子句在无异常时执行可以用来区分正常流程和异常流程finally用于释放资源爬虫程序常见的做法是try: response requests.get(url, timeout5) response.raise_for_status() # 状态码不是200时主动抛异常 except requests.exceptions.Timeout: print(f请求超时{url}) except requests.exceptions.ConnectionError: print(f连接失败{url}) except requests.exceptions.HTTPError as e: print(fHTTP错误{e.response.status_code} - {url}) else: data response.json() # 在此处理正常返回的数据 finally: session.close()5.4 作用域中的UnboundLocalError还有一个高频报错UnboundLocalError: local variable x referenced before assignment。这个报错的经典场景是count 0 def increment(): count 1 # UnboundLocalError increment()出错原因是在函数内部对count进行了赋值操作Python就把它视为局部变量在局部作用域内找不到初始值就报错。解决办法有两个要么用global关键字声明count是全局变量要么把count放到函数参数里传进来传出去def increment(count): return count 1 count increment(count)模块化的思路更符合Python的理念。能用返回值解决的问题尽量不要依赖global因为全局变量会让函数的耦合度变高、可测试性变差。6. 环境配置与学习工具Pycharm和VSCode怎么选搜索热词里大量出现python安装教程、vscode配置python、pycharm配置python环境、python下载说明很多人的第一步就卡在环境搭建上。这里结合个人经验给一套可复现的流程。6.1 Python解释器安装与版本选择官网下载Python安装包Windows系统安装时务必勾选Add Python to PATH——这是新手环境配置失败的首要原因。勾选之后终端直接输入python就能进入交互模式。版本选择上除非有特殊依赖我建议直接选Python 3.10以上的稳定版。别选最新的正式版可能存在第三方库兼容问题也别纠结Python 2和3哪个好——Python 2在2020年已经停止维护没有讨论价值。安装完成后在终端验证python --version pip --version如果提示pip不是内部命令大概率是PATH没配好。Windows系统手动添加Python安装目录和Scripts子目录到环境变量即可。6.2 Pycharm与VSCode的取舍Pycharm和VSCode是两个主流的Python开发环境不需要争论谁好谁坏按场景选。Pycharm适合纯Python开发。社区版免费专业版收费但学生可申请免费授权。优点开箱即用智能提示强大调试器直观虚拟环境管理方便。缺点是启动较慢占用内存较大如果电脑配置一般会有点吃力。VSCode适合需要写多种语言、资源占用敏感的情况。配合Python扩展和Pylance插件后代码提示和调试体验已经相当能打。热词里提到vscode配置python配置要点是安装Python扩展按CtrlShiftP打开命令面板执行Python: Select Interpreter选择解释器配置.vscode/launch.json支持断点调试我的个人建议是新手阶段用Pycharm它能减少环境问题对学习进度的干扰帮你专注于语法本身有了一定基础、需要写前后端多语言项目时再切换或补充VSCode。6.3 包管理与第三方库安装pip是Python官方的包管理工具。安装第三方库pip install requests pip install numpy pandas matplotlib热词里有python国内源这确实是国内用户绕不开的话题。默认的PyPI源在访问时经常超时或限速配置国内镜像源是常规操作。用清华源或阿里源都可以pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple如果不想每次手动指定可以配置全局pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple还有python安装numpy库的方法、python下载cv2这些热搜本质都是pip install numpy、pip install opencv-python的问题。需要注意的是某些库比如opencv的安装包名和导入名不一致——安装是opencv-python导入是cv2这个细节能卡住很多人。6.4 虚拟环境与依赖管理我见过太多人把所有第三方库装进全局环境最后项目多了互相冲突。虚拟环境是现代Python开发的基本功每个项目单独一套环境依赖互不干扰。# 创建虚拟环境 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate # 退出 deactivate激活后在虚拟环境里用pip安装的库只属于当前项目。导出依赖清单pip freeze requirements.txt换台机器或在新的环境里一键还原pip install -r requirements.txt这个习惯一旦养成项目的可复现性会大大提升。特别是以后接触到团队协作、部署上线时没有requirements.txt几乎寸步难行。7. 从语法走向实战几个值得动手练的方向语法学完不练等于白学。根据热搜词里出现的python爬虫、python数据分析与可视化、python小游戏、python爱心代码、python四叶草这几个方向特点不同适合不同需求的入门者。7.1 爬虫方向语法综合训练爬虫是把Python语法快速串起来的实用项目。一个最简单的爬虫需要用到requests库发送HTTP请求BeautifulSoup或正则表达式解析页面列表、字典存储数据csv或json模块保存结果import requests from bs4 import BeautifulSoup url https://example.com response requests.get(url) soup BeautifulSoup(response.text, html.parser) for title in soup.select(h2): print(title.text.strip())爬虫里会高频用到字符串处理那一章的所有方法strip()去除空白、split()分割文本、replace()清洗数据。写爬虫的过程就是在复习基础语法而且正反馈来得快——页面上的数据真真切切被抓下来了。爬虫入门时注意遵守robots协议、控制请求频率、不要爬取敏感数据做技术练习时尽量选择公开可爬的站点。7.2 数据分析方向语法与库的结合python数据分析与可视化核心套路永远是numpy处理数组pandas处理表格数据matplotlib和seaborn画图。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(sales_data.csv) print(df.describe()) df[monthly_sales].plot(kindline) plt.show()数据分析里最常用的Python语法是函数调用、列表推导式清洗数据、字典与DataFrame互转、日期字符串处理。掌握基础语法后学pandas会感觉这些库的设计其实就是在Python语法之上的自然延伸。7.3 简单应用爱心代码与四叶草热搜词中的python爱心代码和python四叶草属于趣味项目适合纯零基础的人做第一个练手程序。用turtle模块画爱心输出结果直观代码量不大而且能发给朋友看成就感强。这种小项目最大的价值不是实用而是让你在一个小时内完整经历写代码→运行→出错→调试→成功的循环这个循环对建立编程手感极其重要。7.4 培养调试思维才是终极目标不管是哪种方向我从实际经验中看到真正拉开差距的不是记住多少语法而是遇到报错时能不能快速定位问题。建议学习过程中刻意练习认真读报错信息Python的Traceback是很好的导航学会用print插入临时输出定位变量值用Python的pdb模块或者IDE的断点调试器逐步执行把一个错误现象在搜索引擎中搜索别人的踩坑记录会极大加速你的排错过程每次排错后总结根因一段时间后你会发现有些错误不再出现这就是形成了调试直觉我还想特别强调一点写Python代码不用怕犯错解释器的报错信息本身就带有明确的定位和提示。遇到TypeError、ValueError、KeyError每一类报错都在告诉你某个具体环节出了问题。把报错当成导航而不是灾难编程学习的心态会顺畅很多。从变量绑定、数据结构、控制流到函数定义再从异常处理、环境配置到实战方向Python基本语法这套体系的每个点之间都有内在逻辑。抓住对象、绑定、可迭代这几个核心概念基础打牢之后往后学爬虫学数据分析会发现自己不是在背新知识而是在已有的地基上盖房子。好工具加上扎实的基础后面怎么写都顺手。
