Python 里技
你可能不知道的 Python 特性!
我们主要围绕 Python 的 OOP 内容展开, 主要原因是在教学过程中被省略掉了这一重要的部分. 虽然说后端开发并不需要你掌握什么特别 fancy 的东西, 但是面向对象的概念却在后端开发中处处可见.
剩下的内容我们会讨论一些 Python 的语言特性, 例如一些元编程的内容. 我们不会深入到 CPython 的具体实现, 只会蜻蜓点水般地简单介绍.
深入任何一门语言, 都需要你大量地进行编码, 实验, 去熟练这些特性. 本文如果能起到抛砖引玉的作用, 那就是万幸. 若有时间, 我们很推荐你去自学完成给出的补充资料!
回顾: Python 基础(可选)
示例 1: Nahida to Python
Nahida 很喜欢编程(作为智慧之神那是必须的). 她从官网 Python 的新版本, 并已经添加到了系统的环境配置里. 为了检查这一点, 她在终端里键入了
Nahida 之前从来没有接触过 Python! 为了体验与其它语言的不同, 她写了一个经典的 Hello world 程序 (test.py
):
在交互(interactive)环境下, Nahida 又将打印的内容更改为了 hello python!
(这是一个非常好用的调试方法)
Nahida 又听说, Python 的数学运算非常强大, 据说自带高精度, 还有一些好用的内置方法, 而且还有 numpy
, scipy
等库的加持(你甚至可以拿它们去做高代作业, 解决计算几何的一些问题 :)
字符串处理也是不甘示弱: 切片操作(slice)可以轻易获取一个字符串的子串; in
关键字可以轻松判断字符串的包含关系; 还有强大的正则表达式支持(re
库)......
我们推荐你去了解下正则表达式, 因为某些场景的字符串匹配用它真的很方便, 同时很多编程语言和 Unix 工具都支持正则表达式, 例如 sed
. 这里有一个互动教程.
不要让 regex 成为你"书到用时方恨少"的下一个遗憾. (血的教训)
以及内置的许多好用的数据结构: 列表(list), 元组(tuple), 字典(dict) 以及集合(set), 还支持一个优雅的特性: 推导式(Comprehension), 能够最大程度上让你的代码保证一定可读性的同时高效 (真的不用 For Loop?)
字典尤其重要. 其实字典就可以构成一个简单的数据库(对于一个真实的数据库, 表的数据关系更为复杂而已), 在我们编写接口的时候, 也必然需要处理这种映射关系. 我们返回数据的 JSON 格式通常就是这三个数据结构的嵌套.
Python 的 for loop 语句相当于其它语言的 foreach, 我们还有一些方便的迭代函数, 如 zip, enumerate. 我们推荐你花时间去了解迭代器(iterator)的概念.
文件 I/O 的格式也很简洁: 例如, 打开一个文件, 读取每一行, 并且记录行数, Python 可以这么写
还有错误处理的相关语句: try, except 和 finally.
Python 里的函数定义很简单, 你都不需要人为去指定参数类型(当然你也可以做标记, Type Hints). Python 的另外一个好处是灵活的函数参数. 例如, 一个可变参数以一个元组(或字典)的形式传入函数
以上这些内容, 听说就是财大 Python 课程的全部. Nahida 听后, 立马把复习的讲义看了一遍, 然后将它们转换为了罐装知识放在这里.
Python OOP Basics
OOP(Object-Oriented Programming), 面向对象编程.
在之前的 Python 开发中, 基本上不需要用到面向对象的概念, 但是在实际的开发项目中, 类(class)这一概念非常重要.
Python 本身就是一个面向对象的语言. 所有的基础数据类型都是"对象".
举个例子, 你要开发一款游戏, 现在设计怪物好了, 有史莱姆和哥布林, 现在需要你写代码, 你怎么写?
写完你发现这样太麻烦了. 每种怪物都有血量, 攻击, 和经验值的属性(attributes), 如果能封装一个怪物的模型?
于是就有了类 (class), 它类似于 C 语言里的结构体(struct). 但类不光能够存储异质的数据, 还能在类中声明函数:
这样之后创建一个怪物就简单多了. 例如
这里 slime 就是 Monster 类的一个实例(Instance). 你可以用 isinstance
来判断这种关系.
__init__
称为构造函数, 当实例创建以后就会自动调用这个函数. 在大部分情况下, 它用来存放这个实例的数据, 也就是用来初始化的.
实例在类内部按照惯例称为 self
. 当然这个名称并不重要(改名为 abc
也没问题), 它总是作为第一个参数传入类内部的函数. 这个概念类似于 C++ 里的 this
指针.
另外, 类并不会定义一个作用域(scope), 如果你想要在类中的方法调用类的另一个方法, 这其实需要你对一个实例操作, 那么你必须显性引用它!
如果我要创建多个史莱姆, 那么可以再将史莱姆作为一个类封装起来!
类就是一系列函数的集合(当然这不意味着内部无法定义变量), 这些函数能够对所谓的"实例"进行操作.
单独的类什么也做不到(就和单独的函数一样), 它只是作为一个定义存在.
但是, 通过一些方式, 你可以操作类自身, 例如静态方法(static method).
另外一种方式是类方法(class method). 它通常用作类的初始化, 它的第一个函数参数就是类本身(这对解决继承的问题很方便)
Python 和其它 OOP 语言的差别
如果你熟悉其它 OOP 语言(例如 JAVA), 属性会有 private, public 关键字修饰, 对于 Python 来说, Python 仅有一套命名规则来限定.
Python 里也有 property, 作为 accessor method 的另一种方法:
类的定义还有其它的几种形式:
利用
__slots__
去定义一个纯数据结构. 可以优化性能, 节省内存开销, 并且能够更快获取属性(内部是值引用实现的)
此外还有 dataclass
(方便存放 data object) 和 typing.NamedTuple
(赋予不可变性) 的两种方式. 感兴趣的同学可以自行探索! 让我们继续回到主题.
OOP 另一个重要的思想是继承(Inheritance). 假设我想给史莱姆设计一个特殊的机制, 例如弹跳, 这时候我要额外设计一个 slime 类, 但它同时需要怪物的这些属性和机制, 当然我们不需要复制粘贴过来, 继承机制就可以帮助我们实现这一点
这时候 Slime 类就称为是 Monster 类的一个导出类(derived class)或者称子类(subclass). 同理有基类, 父类和超类(superclass). 通过继承创建的对象是这个父类的一个特殊版本.
有继承就有对父类的初始化. 需要调用 super()
方法. 同时, 父类的属性, 在子类中是不可见的, 但可以通过 name mangling 机制去获取.
简单来说, 继承能干什么: 拓展已有的代码
继承还能够重载(override)函数, 支持多继承等.
代码复用的思想: 接口(Interface), 抽象基类(Abstract Base Class), 句柄类(handler class), 模板类......内容太多, 这里塞不下. 我们推荐你找一本OOP的书去深入学习.
装饰器(Decorators)
接下来我们会涉及一些 Python 元编程(Metaprogramming)的内容. 但是Don't panic! 这并没有你想象中那么吓人(至少不如C++), 而且在实际中, 我们并不会编写这一部分的代码.
啥是元编程? 简单来说, 就是对代码编程. 例如宏就是一个典型的元编程例子(当然Python里没有宏): 用一个指定的字符串替代一个代码
装饰器是一个函数, 只不过这个函数会创建一个 wrapper 把另一个函数围起来. wrapper 就是一个新函数, 和原函数功能基本一样, 只不过稍微多了些功能.
wrapper 需要一个函数作为参数传入(是的, Python 的函数也是一个对象, 换作 C 语言那就是函数指针), 然后返回一个函数.
返回的这个函数返回我们传入的参数. 只不过在这个函数里, 它有些其他的行为.
下面就是一个装饰器的例子: 它能够在每次调用函数时打印信息
然后你就可以这么去调用
所以装饰器其实就是一个语法糖
两段代码其实是一样的!
再贴一个常用的装饰器: 它可以显示程序运行的毫秒数
为什么要使用装饰器:
调试找BUG. (例如上面打印 log)
避免代码重复. (不用你反复写调试的打印语句)
某一功能的开关. (例如上面讲到的
classmethod
)
魔术方法(Magic methods)
There is no magic in computer science.
魔术方法的另一种说法是 Special methods, 好吧, 这样就变得朴实无华了.
魔法方法允许你在类中, 将 __<name>__
这类的自定义函数(也叫做 Dunder methods)绑定到类的特殊方法中.
你觉得你没用过, 其实你天天都在用! 例如, 初始化一个类的 __init__
方法, 就是一个魔术方法.
这么说来, 怎么感觉和内置变量(builtins)很类似? 例如, 你应该知道 Python 的主程序入口是
这里 __name__ 就是一个内置的变量, 它表示当前模块的名字(『if __name__ == "__main__"』到底啥意思❓). 此外, 我们还有 __doc__
, __file__
, 等等.
那魔术方法其实也可以认为是 Python 内置的一些方法, 其实你只是在对它进行函数重载. 例如, 你自定义了一个二维向量类, 然后想实现一个向量加法, 那么你可以这么做
这里 __add__
实际上就是作为加法运算符的重载. __str__
是将对象转换为字符串时自动调用, 有点类似于 ToString
.
利用魔术方法, 你可以实现很多东西:
实现对类属性的封装! 利用
__getattr__
.不用
def
也能实现一个函数! 利用__callable__
.实现属于自己的一个容器, 例如实现一个双链表之类的! 你需要实现迭代器需要的方法. 例如
__iter__
.
如果你感兴趣, 可以自行搜索这些话题.
值得学习的模块
re
库: 正则表达式os
库: 操作系统接口我们推荐大家用
os
库作为 shell 脚本的一种替代.(更可读, 更好写)
collections
库: 能够帮助我们简化许多数据处理的操作.
一些操作例子:
defaultdict
对缺省的字典键初始化. 这个应该你在课堂上学过.Counter
计数特化的字典deque
双向队列ChainMap
多重查询
Pathlib
库: 面向对象的文件系统路径
跨平台开发很有用. 不需要你用
os
库去判断操作系统型号然后选用到底是 / 还是 \.
其它待补充
补充学习
python-mastery: 一个不错的 Python 进阶教程仓库. 亮点在于教程和练习写的很详细.
cs-61a: UCB 非常优质的公开课程, 富含精心准备的教案和代码练习. 这门课用 Python 讲 SICP 这本书, 不仅能让你上手 Python 编程, 还带你真正进入 CS 的世界.
清华大学2023科协暑培 Python 基础: b 站上刷到的, 讲得很细致, 同时也给出了在线文档, 感觉和我们讲的东西很贴近, 大家也可以去观摩学习一下.
Clean Code: A Handbook of Agile Software Craftsmanship: 有关 OOP 很好的一本参考书.
一本名字跟他很接近的书: Clean Python
我想找些和 Python 相关的书籍! 这个网站推荐了适合各个阶段学习的书籍.
装饰器的超详细教学: 同时推荐这个UP主, 资深 Python 用户.
最后更新于