学习:Python 学习资源推荐 & 学习笔记
本文最后更新于:2024-08-07
终于开始学习 Python 了,记录一下推荐的学习资源,顺带把笔记也放在这里
学习资源推荐
卡瓦邦噶! - 如何学Python?
一份非常详尽的 Python 学习资源推荐,零基础、进阶、深入理解、面试,各方面资料应有尽有博主还有另一个页面 卡瓦邦噶! - 珍藏资料,也有很多好东西
Piglei - Python 工匠
Python 非常自由,但相应的代价就是上下限差距会很大,这本书讨论如何把能跑的代码
改成优秀的代码
、如何利用 Python 语言特性写出更加Pythonic(Python风格)
的代码
还介绍了一些不止 Python,任何语言都通用的知识,例如变量命名、大型项目里如何统一代码风格
内容通俗易懂,GitHub 版本可以免费阅读,如果感觉讲的不错可以购买书籍版,比 web 版更加翔实Python Documents
有时候看不知道了几手的资料怎么都看不明白,说不定翻一下官方文档就懂了,有能力可以多看文档卡瓦邦噶!
和Python 工匠
里都提到推荐看一下 Python Documents - itertools 官方文档,了解标准库里的轮子,省的自己造CS61A
大名鼎鼎的CS61A
,久闻其名,还没看捕蛇者说
一档播客节目,名字中的蛇
当然就是Python
啦,偶然听听,能发现许多新资源上面提到的
卡瓦邦噶!
博主laixintao
就是主播之一
笔记
下面是学习 Python 过程中做的笔记,摘抄或复述看过的内容,加上一些感觉有价值的知识点,加深印象,还可以方便以后查找
== None
or is None
Reading Python 工匠: 与 None 值的比较
On 2024.07.14
判断变量是否为 None 时,应该用 is None
在 Python 中,有两种比较变量的方法:==
和 is
作者的解释:
==
:表示二者所指向的的值是否一致is
:表示二者是否指向内存中的同一份内容,也就是id(x)
是否等于id(y)
None
在 Python 语言中是一个单例对象,如果你要判断某个变量是否为 None 时,记得使用is
而不是==
,因为只有is
才能在严格意义上表示某个变量是否是 None。
==
实际上是调用 __eq__
魔法方法,可以通过自定义 __eq__
方法操作真伪值
判断变量是否为 None 时,应该用 is
而非 ==
需要重写 __eq__
魔法方法的案例
在 图书版 Python 工匠 12.1.2 比较运算符重载
章节,作者介绍了一个需要重写 __eq__
魔法方法的案例
例如,一个表示正方形的类 Square,接受一个边长作为参数,计算正方形面积
1 |
|
显然,边长相等的两个正方形,可以看作一样的正方形
但默认情况下,Python 认为这两个正方形是不同对象,返回 False
1 |
|
这时就需要重写 __eq__
方法,以及其他 5 个比较运算符 __ne__
、__lt__
、__le__
、__gt__
、__ge__
使用 functools
内置模块下的 @total_ordering
装饰器,可以减少工作量
只要重写 __eq__
,外加 __lt__
、__le__
、__gt__
、__ge__
中的一个方法
1 |
|
这个正方形类就可以做到正常比较大小了
可迭代对象(iterable)、迭代器(iterator)
Reading Python 工匠: 区分迭代器与可迭代对象
On 2024.07.14
什么是可迭代对象(iterable)
可迭代对象(iterable) 在 官方文档中的定义 是
An object capable of returning its members one at a time. Examples of iterables include all sequence types (such as list, str, and tuple) and some non-sequence types like dict, file objects, and objects of any classes you define with an
__iter__()
method or with a__getitem__()
method that implements sequence semantics.
所以可迭代对象指能够 每次返回一个成员
的对象类型
每迭代一次,可迭代对象就拿出来一个成员给你,直到可迭代对象从头到尾遍历完一遍位置
显然 list
、str
、tuple
都能满足上述两个条件
至于文档里说 dict
、file objects
也可以看作可迭代对象,似乎没什么问题(具体细节我不清楚
再有就是可以通过自定义 __iter__()
方法,把任意 class
变成可迭代对象
本来以为 set(集合) 对象是无序的,应该不能迭代才对
但偶然看这篇 Python Documents: Howto - Functional,发现 set 原来也可以迭代
查了查资料,set 不保证每次遍历时保持相同顺序,因为 set 元素顺序是由 hash 值决定的,当 set 发生变化时就有可能会改变原来的排序
区别于迭代器(iterator)
知道了什么是可迭代对象,那就试一试吧,用 next()
方法迭代看看
1 |
|
怎么迭代不起来呢?
回到文档,可迭代对象(iterable) 在官方文档中的定义 还有第二段话
Iterables can be used in a for loop and in many other places where a sequence is needed (zip(), map(), …). When an iterable object is passed as an argument to the built-in function iter(), it returns an iterator for the object. This iterator is good for one pass over the set of values. When using iterables, it is usually not necessary to call iter() or deal with iterator objects yourself. The for statement does that automatically for you, creating a temporary unnamed variable to hold the iterator for the duration of the loop.
可迭代对象可以用在循环或其他各种需要序列的地方
把可迭代对象当参数传给iter()
方法,会返回一个可迭代对象的迭代器(iterator)
迭代器适合用在一次性用途
使用可迭代对象(iterable)时,通常无须调用iter()
或自己手搓迭代器(iterator)
例如,在使用 for 循环时,for 循环会帮你自动控制迭代器(iterator)
,方便省心
因此,常见的 for i in range(4):
循环并不是在循环 range(4)
,而是在循环 iter(range(4))
这个返回的迭代器
只不过 Python 的 for 循环可以自动控制这一过程而已
用终端验证一下
1 |
|
对迭代器一直 next() 下去,就可以顺序拿到 list 里的值
1 |
|
预想应该是 4, 3, 2, 1
才对,但每次 next(iter(l))
都拿到 list 的第一个值
出现这种问题,原因是 next(iter(l))
里的 iter(l)
每使用一次 iter(l)
,都会创建一个新 迭代器(iterator)
所以 next(iter(l))
每次都是对一个崭新的迭代器取值,当然每次都只能拿到第一个值
可以在终端里看一下
1 |
|
调用三次 iter(l)
,返回了三个 list_iterator
对象(at 后面的地址不同)
既然这样,要想 next 拿到迭代器的所有值,就找个中介吧
1 |
|
这样就能拿到迭代器里的所有值了
生成器(generator) 和 生成器表达式(generator expression)
迭代器似乎很好用,但一次性的消耗品,如果每次使用前都要一堆准备工作,就失去便捷的意义了
因此 Python 里有两种简单使用迭代器的方法
生成器(generator)
在 function 里用yield
代替return
,这样函数就能准备好一串序列
,等next()
来拿生成器表达式(generator expression)
常见的for i in range(4):
就是一例Python Documents: generator expression 里给了另一个例子
1
2>>> sum(i*i for i in range(10)) # sum of squares 0, 1, 4, ... 81
285
怎么计算迭代器(iterator)里有多少元素
普通的列表可以用 len(list)
得到列表的元素个数
但迭代器是懒惰对象,不迭代一遍就没办法知道有多少个元素
一旦从头到尾迭代完一遍,这个迭代器也就失效了
数它有几个似乎意义不大(
迭代器(iterator)
是一次性消耗品,用完即弃
想要多次循环时,还是需要使用 list 之类的普通对象
Ref:
Python Documents: iterable
Python Documents: iterator
Python Documents: generator
Python Documents: generator expression
函数式编程
Reading Python Documents: Functional Programming HOWTO
On 2024.07.15
省流,最后一段建议不要使用 lambda 表达式
Fredrik Lundh once suggested the following set of rules for refactoring uses of lambda:
- Write a lambda function.
- Write a comment explaining what the heck that lambda does.
- Study the comment for a while, and think of a name that captures the essence of the comment.
- Convert the lambda to a def statement, using that name.
- Remove the comment.
I really like these rules, but you’re free to disagree about whether this lambda-free style is better.
使用 lambda 表达式虽然可以少写两行代码,但理解成本直线上升
从可读性、理解成本角度考虑,不推荐使用函数式编程,朴实地用 def 和 for 循环就好
这篇文章前面的介绍部分感觉不错,说编程语言解决问题的思路分四个流派:
Procedual:面向过程,例如 C、Pascal、Unix Shell
Declarative:SQL
Object-oriented:面向对象
Functional:函数式编程
Python 非常灵活,面向过程、面向对象、或者函数式编程,都能做到,可以根据场景选择不同的方式
这篇文章里讲到许多 可迭代对象(iterable)、迭代器(iterator)、生成器(generator) 的例子,顺便复习了上一节的知识点
看到几个可能比较有用的函数
filter(predicate, iter)
和(x for x in range(10) if is_even(x))
生成器表达式效果一样enumerate(iter, start=0)
经常用来同时取 index 和 valueany(iter) and all(iter)
判断迭代器里布尔值zip(iterA, iterB, …, strict=False)
类似 Excel 里面行列转换,默认取最短的那个迭代器长度作为最后的长度
如果希望严格限制所有迭代器长度一样,可以设置strict=True
itertools.count(start, step)
能得到一个无限长度的迭代器itertools.repeat(elem, [n])
重复 elem 元素 n 次itertools.islice(iter, [start], stop, [step])
切片,一个参数是 stop,两个参数是 start 和 stop,三个参数是 start、stop、stepitertools.tee(iter, [n])
把一个迭代器复制成 n 份,查了查,似乎是用在数据分析、日志分析之类的地方
对同一组数据进行多次分析,可以用 tee 分出多个迭代器,各自分析itertools.filterfalse(predicate, iter)
和 filter 相反itertools.dropwhile(predicate, iter)
字面意思itertools.combinations(iterable, r)
排列组合itertools.combinations([1, 2, 3, 4, 5], 2) =>
(1, 2), (1, 3), (1, 4), (1, 5),
(2, 3), (2, 4), (2, 5),
(3, 4), (3, 5),
(4, 5)
这篇读完了,写的还挺有意思的
补充一个 itertools.product(*iterables, repeat=1),相当于循环嵌套
开发大型项目常用工具
Reading 图书版 Python 工匠
13.1 开发大型项目常用工具介绍
章节
On 2024.07.16
Linter:
- flake8,检查代码风格,可自行定制
- black,更加严格的代码风格检查工具,几乎不可定制
- isort,分类 import 代码
- pre-commit,强制在 commit 前进行代码检查
- mypy,配合类型注解实现静态类型检查
四类常见内建容器类型
Reading Python 工匠:4. 容器的门道
On 2024.07.11
Python 中,有四类最常见的内建容器类型:列表(list)、元组(tuple)、字典(dict)、集合(set)
- 列表(List):有序、可变、允许重复。适用于需要按顺序存储和访问数据的场景。
- 元组(Tuple):有序、不可变、允许重复。适用于需要存储不可变数据的场景。
- 字典(Dictionary):有序(Python 3.7+)、可变、键唯一。适用于需要通过键快速查找值的场景。
- 集合(Set):无序、可变、不允许重复。适用于需要存储唯一元素并进行集合操作(如交集、并集)的场景。
Python 工匠:写更快的代码
避免频繁扩充列表/创建新列表
- 多用迭代器,
yield
、生成器表达式
- 尽量使用模块提供的懒惰对象:
- 使用
re.finditer
替代re.findall
- 直接使用可迭代的文件对象:
for line in fp
,而不是for line in fp.readlines()
- 使用
- 多用迭代器,
在列表头部操作多的场景使用 deque 模块
列表是基于数组结构(Array)实现的,当你在列表的头部插入新成员(
list.insert(0, item)
)时,它后面的所有其他成员都需要被移动,操作的时间复杂度是O(n)
。这导致在列表的头部插入成员远比在尾部追加(list.append(item)
时间复杂度为O(1)
)要慢。如果你的代码需要执行很多次这类操作,请考虑使用 collections.deque 类型来替代列表。因为 deque 是基于双端队列实现的,无论是在头部还是尾部追加元素,时间复杂度都是
O(1)
。使用集合/字典来判断成员是否存在
当你需要判断成员是否存在于某个容器时,用集合比列表更合适。因为
item in [...]
操作的时间复杂度是O(n)
,而item in {...}
的时间复杂度是O(1)
。这是因为字典与集合都是基于哈希表(Hash Table)数据结构实现的。Hint: 强烈建议阅读 TimeComplexity - Python Wiki,了解更多关于常见容器类型的时间复杂度相关内容。
如果你对字典的实现细节感兴趣,也强烈建议观看 Raymond Hettinger 的演讲 Modern Dictionaries(YouTube)
边界情况、海象操作符
Reading Python 工匠:15. 在边界处思考
Reading Python 工匠:16. 语句、表达式和海象操作符
On 2024.07.16
图书版 Python 工匠 没有收录这两章
在边界处思考
作者认为最好可以利用语言特性更加优雅的处理边界情况,具体到 Python 中
- 使用
try 捕获异常
而非if else
来处理边界情况
Python 社区比起LBYL (Look Before You Leap)
,更倾向于EAFP (sEasier to Ask for Forgiveness than Permission)
,因为 Python 中抛出异常是很轻量的操作
作者推荐阅读 Write Cleaner Python: Use Exceptions - 容器内容不存在时的处理
collections.defaultdict
可以处理字典类型里键不存在
的错误
使用setdefault
取值并修改字典值
使用dict.pop
删除不存在的键
列表切片不会抛出 Index 越界错误,无须做边界处理 - 使用
or
短路求值特性设定默认值时,除了None
,空容器也会被判定为False
,用if
来进行边界处理更准确 - 不要手动做用户输入数据校验,使用 pydantic 或框架自带的校验模块
- 善用
%
、abs()
、math.floor()
语句、表达式和海象操作符
海象操作符 (walrus operator)
结合了赋值和表达式两种功能
可以用于条件分支、循环、列表推导式中,有利于减少代码重复
但是海象操作符也提高了代码的理解成本,不宜盲目追求精炼
感觉海象操作符有点类似函数式编程,精炼但增加理解成本
本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0协议 。转载请注明出处~