Python 里技
你可能不知道的 Python 特性!
我们主要围绕 Python 的 OOP 内容展开, 主要原因是在教学过程中被省略掉了这一重要的部分. 虽然说后端开发并不需要你掌握什么特别 fancy 的东西, 但是面向对象的概念却在后端开发中处处可见.
剩下的内容我们会讨论一些 Python 的语言特性, 例如一些元编程的内容. 我们不会深入到 CPython 的具体实现, 只会蜻蜓点水般地简单介绍.
回顾: Python 基础(可选)
示例 1: Nahida to Python
Nahida 很喜欢编程(作为智慧之神那是必须的). 她从官网 Python 的新版本, 并已经添加到了系统的环境配置里. 为了检查这一点, 她在终端里键入了
$ python3 --version
Python 3.11.4
Nahida 之前从来没有接触过 Python! 为了体验与其它语言的不同, 她写了一个经典的 Hello world 程序 (test.py
):
hello, world = 'hello', 'world' # multiple assignment
print(f'{hello}', '%s!' % (world)) # f-string, % operator (.format() is omitted here)
$ python3 -i test.py
hello world!
>>> world = 'python'
hello python
在交互(interactive)环境下, Nahida 又将打印的内容更改为了 hello python!
(这是一个非常好用的调试方法)
Nahida 又听说, Python 的数学运算非常强大, 据说自带高精度, 还有一些好用的内置方法, 而且还有 numpy
, scipy
等库的加持(你甚至可以拿它们去做高代作业, 解决计算几何的一些问题 :)
>>> (2 ** 114513 - 1) % 998244353
766171354
>>> x = 1172.5
>>> x.as_integer_ratio()
(2345, 2)
>>> import numpy as np
>>> a = np.array([[1,0],[0,1]])
>>> b = np.array([[4,1],[2,2]])
>>> np.multiply(a, b)
字符串处理也是不甘示弱: 切片操作(slice)可以轻易获取一个字符串的子串; in
关键字可以轻松判断字符串的包含关系; 还有强大的正则表达式支持(re
库)......
>>> text = 'The Temple of Wisdom'
>>> text[4:]
Temple of Wisdom
>>> 'Wisdom' in text
True
>>> import re
>>> message = 'hello from Nahida@akademiya.edu.sumeru'
>>> emails = re.findall(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', message)
>>> emails
['Nahida@akademiya.edu.sumeru']
以及内置的许多好用的数据结构: 列表(list), 元组(tuple), 字典(dict) 以及集合(set), 还支持一个优雅的特性: 推导式(Comprehension), 能够最大程度上让你的代码保证一定可读性的同时高效 (真的不用 For Loop?)
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [i ** 3 for i in range(4)]
[0, 1, 8, 27]
>>> {str(i) : i for i in range(10) if i % 2 != 0}
{'1': 1, '3': 3, '5': 5, '7': 7, '9': 9}
>>> sum(i ** 3 for i in range(4))
36
Python 的 for loop 语句相当于其它语言的 foreach, 我们还有一些方便的迭代函数, 如 zip, enumerate. 我们推荐你花时间去了解迭代器(iterator)的概念.
文件 I/O 的格式也很简洁: 例如, 打开一个文件, 读取每一行, 并且记录行数, Python 可以这么写
with open('foo.txt', 'r') as f:
for lineno, line in enumerate(f, start=1): # line number tracking on a file
# do something
# and you don't have to manually close the file here
还有错误处理的相关语句: try, except 和 finally.
Python 里的函数定义很简单, 你都不需要人为去指定参数类型(当然你也可以做标记, Type Hints). Python 的另外一个好处是灵活的函数参数. 例如, 一个可变参数以一个元组(或字典)的形式传入函数
def touch_fish(*args, **kwargs):
pass
touch_fish(1, 2, 3, arg1=4, arg2=5)
以上这些内容, 听说就是财大 Python 课程的全部. Nahida 听后, 立马把复习的讲义看了一遍, 然后将它们转换为了罐装知识放在这里.

Python OOP Basics
OOP(Object-Oriented Programming), 面向对象编程.
在之前的 Python 开发中, 基本上不需要用到面向对象的概念, 但是在实际的开发项目中, 类(class)这一概念非常重要.
Python 本身就是一个面向对象的语言. 所有的基础数据类型都是"对象".
a = 'hello world' # string object
a.upper() # a method applied to the string
items = [1, 2, 3] # list object
items.append(4) # a method applied to the list
isinstance(5, int)
举个例子, 你要开发一款游戏, 现在设计怪物好了, 有史莱姆和哥布林, 现在需要你写代码, 你怎么写?
slime_hp, slime_atk, slime_xp = 10, 2, 10
goblin_hp, goblin_atk, goblin_xp = 15, 3, 20
写完你发现这样太麻烦了. 每种怪物都有血量, 攻击, 和经验值的属性(attributes), 如果能封装一个怪物的模型?
于是就有了类 (class), 它类似于 C 语言里的结构体(struct). 但类不光能够存储异质的数据, 还能在类中声明函数:
class Monster:
def __init__(self, name, hp, atk, xp):
self.name = name
self.hp = hp
self.atk = atk
self.xp = xp
def damage(self, pts):
self.hp -= pts
这样之后创建一个怪物就简单多了. 例如
slime = Monster('slime', 10, 2, 10) # data
getattr(slime, 'name') # attritube access function
slime.damage(5) # behavior
这里 slime 就是 Monster 类的一个实例(Instance). 你可以用 isinstance
来判断这种关系.
__init__
称为构造函数, 当实例创建以后就会自动调用这个函数. 在大部分情况下, 它用来存放这个实例的数据, 也就是用来初始化的.
如果我要创建多个史莱姆, 那么可以再将史莱姆作为一个类封装起来!
类就是一系列函数的集合(当然这不意味着内部无法定义变量), 这些函数能够对所谓的"实例"进行操作.
类的定义还有其它的几种形式:
利用
__slots__
去定义一个纯数据结构. 可以优化性能, 节省内存开销, 并且能够更快获取属性(内部是值引用实现的)
class Monster:
__slot__ = ('name', 'hp', 'atk', 'xp')
def __init__(self, name, hp, atk, xp):
# ...
此外还有 dataclass
(方便存放 data object) 和 typing.NamedTuple
(赋予不可变性) 的两种方式. 感兴趣的同学可以自行探索! 让我们继续回到主题.
OOP 另一个重要的思想是继承(Inheritance). 假设我想给史莱姆设计一个特殊的机制, 例如弹跳, 这时候我要额外设计一个 slime 类, 但它同时需要怪物的这些属性和机制, 当然我们不需要复制粘贴过来, 继承机制就可以帮助我们实现这一点
class Slime(Monster):
def __init__(self, name, hp, atk, xp):
super().__init__(name, hp, atk, xp)
def bounce():
atk += atk
这时候 Slime 类就称为是 Monster 类的一个导出类(derived class)或者称子类(subclass). 同理有基类, 父类和超类(superclass). 通过继承创建的对象是这个父类的一个特殊版本.
简单来说, 继承能干什么: 拓展已有的代码
继承还能够重载(override)函数, 支持多继承等.
代码复用的思想: 接口(Interface), 抽象基类(Abstract Base Class), 句柄类(handler class), 模板类......内容太多, 这里塞不下. 我们推荐你找一本OOP的书去深入学习.
装饰器(Decorators)
接下来我们会涉及一些 Python 元编程(Metaprogramming)的内容. 但是Don't panic! 这并没有你想象中那么吓人(至少不如C++), 而且在实际中, 我们并不会编写这一部分的代码.
啥是元编程? 简单来说, 就是对代码编程. 例如宏就是一个典型的元编程例子(当然Python里没有宏): 用一个指定的字符串替代一个代码
#define PI 3.14159 // this code was written in C/C++
装饰器是一个函数, 只不过这个函数会创建一个 wrapper 把另一个函数围起来. wrapper 就是一个新函数, 和原函数功能基本一样, 只不过稍微多了些功能.
wrapper 需要一个函数作为参数传入(是的, Python 的函数也是一个对象, 换作 C 语言那就是函数指针), 然后返回一个函数.
返回的这个函数返回我们传入的参数. 只不过在这个函数里, 它有些其他的行为.
下面就是一个装饰器的例子: 它能够在每次调用函数时打印信息
def add(a, b):
return a + b
# wrapper function
def logged(func):
def wrapper(*args, **kwargs):
print('Calling', func.__name__)
return func(*args, **kwargs)
return wrapper
然后你就可以这么去调用
>>> logged_add = logged(add)
>>> logged_add(3, 4)
所以装饰器其实就是一个语法糖
def add(a, b):
return a + b
add = logged(add)
@logged
def add(x, y):
return x + y
两段代码其实是一样的!
再贴一个常用的装饰器: 它可以显示程序运行的毫秒数
def metric(fn):
"""running time for each main function"""
@functools.wraps(fn)
def wrapper(*args, **kw):
print('start executing %s' % (fn.__name__))
start_time = time.time()
result = fn(*args, **kw)
end_time = time.time()
t = 1000 * (end_time - start_time)
print('%s executed in %s ms' % (fn.__name__, t))
return result
return wrapper
为什么要使用装饰器:
调试找BUG. (例如上面打印 log)
避免代码重复. (不用你反复写调试的打印语句)
某一功能的开关. (例如上面讲到的
classmethod
)
魔术方法(Magic methods)
There is no magic in computer science.
魔术方法的另一种说法是 Special methods, 好吧, 这样就变得朴实无华了.
魔法方法允许你在类中, 将 __<name>__
这类的自定义函数(也叫做 Dunder methods)绑定到类的特殊方法中.
你觉得你没用过, 其实你天天都在用! 例如, 初始化一个类的 __init__
方法, 就是一个魔术方法.
这么说来, 怎么感觉和内置变量(builtins)很类似? 例如, 你应该知道 Python 的主程序入口是
if __name__ == '__name__':
# code
这里 __name__ 就是一个内置的变量, 它表示当前模块的名字(『if __name__ == "__main__"』到底啥意思❓). 此外, 我们还有 __doc__
, __file__
, 等等.
那魔术方法其实也可以认为是 Python 内置的一些方法, 其实你只是在对它进行函数重载. 例如, 你自定义了一个二维向量类, 然后想实现一个向量加法, 那么你可以这么做
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __add__(self, other):
if isinstance(other, Vector2D):
return Vector2D(self.x + other.x, self.y + other.y)
else:
raise TypeError("Unsupported operand type for +")
这里 __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 用户.
最后更新于
这有帮助吗?