Python 进阶之道

Python 和 JavaScript 在笔者看来是很相似的语言,本文归纳了 Python 的各种 tricks。

函数

匿名函数

函数的简化写法,配合 map、filter、reduce 等高阶函数实现函数式编程

# def foo(parameters):
#     return expression
foo = lambda parameters: expression

map - 映射

numbers = [1, 2, 3, 4, 5]
list(map(lambda e: e ** 2, numbers))
# [1, 4, 9, 16, 25]

filter - 过滤

values = [None, 0, '', True, 'alphardex', 666]
list(filter(lambda e:e, values))
# [True, "alphardex", 666]

reduce - 归并

星号和双星号

数据容器的合并

函数参数的打包与解包

数据容器

列表

迭代

同时迭代元素与其索引

用 enumerate 即可

同时迭代 2 个以上的可迭代对象

用 zip 即可

测试是否整体/部分满足条件

all 测试所有元素是否都满足于某条件,any 则是测试部分元素是否满足于某条件

解包

最典型的例子就是 2 数交换

用星号运算符解包可以获取剩余的元素

用下划线可以忽略某个变量

字典

迭代

排序

缺失键处理

get 返回键值,如果键不在字典中,将会返回一个默认值

setdefault 返回键值,如果键不在字典中,将会添加它并设置一个默认值

语言专属特性

推导式

推导式是一种快速构建可迭代对象的方法,因此凡是可迭代的对象都支持推导式

列表推导式

获取 0-10 内的所有偶数

字典推导式

将装满元组的列表转换为字典

集合推导式

求所有数字的平方并去除重复元素

生成器表达式

求 0-10 内的所有偶数的和

装饰器

装饰器是一个可调用的对象,顾名思义它能够装饰在某个可调用的对象上,给它增加额外的功能

常用于缓存、权限校验、日志记录、性能测试、事务处理等场景

以下实现了一个简单的日志装饰器,能打印出函数的执行时间、函数名、函数参数和执行结果

如果想让装饰器能接受参数,那就要再嵌套一层

在 django 中,可以通过装饰器对函数视图进行功能增强(比如@login_required 进行登录的权限校验,@cache_page 进行视图的缓存等)

上下文管理器

用于资源的获取与释放,以代替 try-except 语句

常用于文件 IO,锁的获取与释放,数据库的连接与断开等

其实在 pathlib 里已经给我们封装好了文件 IO 方法

至于上下文管理器的实现,可以用@contextmanager

多重继承

在 django 中经常要处理类的多重继承的问题,这时就要用到 super 函数

如果单单认为 super 仅仅是“调用父类的方法”,那就错了

在继承单个类的情况下,可以认为 super 是调用父类的方法(ES6 里面亦是如此)

但多重继承就不一样了,因为方法名可能会有冲突,所以 super 就不能单指父类了

在 Python 中,super 指的是 MRO 中的下一个类,用来解决多重继承时父类的查找问题

MRO 是啥?Method Resolution Order(方法解析顺序)

看完下面的例子,就会理解了

首先,因为 D 继承了 B 类,所以调用 B 类的__init__,打印了enter B

打印enter B后的 super 寻找 MRO 中的 B 的下一个类,也就是 C 类,并调用其__init__,打印enter C

打印enter C后的 super 寻找 MRO 中的 C 的下一个类,也就是 A 类,并调用其__init__,打印A

打印A后回到 C 的__init__,打印leave C

打印leave C后回到 B 的__init__,打印leave B

特殊方法

在 django 中,定义 model 的时候,希望 admin 能显示 model 的某个字段而不是 XXX Object,那么就要定义好__str__

每当你使用一些内置函数时,都是在调用一些特殊方法,例如 len()调用了__len__(), str()调用__str__()等

以下实现一个 2d 数学向量类,里面有多个特殊方法

如果把 Vector 改造为多维向量呢?关键就是要实现序列协议(__len__和__getitem__)

协议:本质上是鸭子类型语言使用的非正式接口

不仅如此,还要实现多分量的获取以及散列化

想了解所有的特殊方法可查阅官方文档arrow-up-right,以下列举些常用的:

类方法和静态方法

@classmethod 是类方法,它定义操作类的方法,也就是说会将类绑定给方法,而不是实例

@staticmethod 是静态方法,啥都不绑定,一般用来给类绑定各种工具方法(不涉及对实例和类的操作)

在 django 中,我们经常要在视图函数中对模型类进行各种查询

然而,很多查询都是重复的代码,根据 DRY 原则,它们都是可以被封装的

那么,如果我们要给模型类封装一些查询操作,就要用到@classmethod

以下是 Post 类,里面定义了 latest_posts 方法用来获取最新的几个 Post

这样在视图函数中,就能直接调用该方法进行查询,节省了不少代码

描述符

实现了__set__或__get__协议的类就是描述符

set 和 get 代表存和取,因此描述符是一种对多个类属性运用相同存取逻辑的一种方式

例如 django 的 ORM 中的字段类型是描述符,用来把数据库记录中的字段数据与 Python 对象的属性对应起来

以下实现一个简单的描述符类,用来在读写属性时验证属性的正确性

元类

进入元类这个概念之前,我们先回顾一下 type()这个函数,不,其实它是个类

通过 type(),我们可以获取一个对象所属的类,但通过 help 函数,发现 type()居然也可以用来创建类!

name 是新类的名称,bases 是继承的子类,dict 则是新类的属性名与其对应值的字典

那么什么是元类呢?

平时我们用类来创建对象,但一切类都继承了对象,说白了类也是对象,而元类就是用来创建类对象的类

说白了,元类就是制造类的工厂

通过以上的例子我们知道 type 就是用来创造一切类的元类,它是 Python 内置的元类

既然有内置的元类,也意味着你也可以自定义元类

以下实现一个元类,用来把类的所有非私有属性自动转换为大写(不已_开头的属性都是非私有的)

思路很简单:把属性和对应的值字典(attrs)里的非私有属性键改为大写(upper)就行了

元类的最经典的用途就是 ORM 的实现,以 django 的 ORM 为例

如果你访问一个模型实例的属性(例如这里的 age),你并不会得到什么 IntegerField(),而是得到了 24 这个数字,这就是元类的作用

元类平时很少用到,如果要动态修改类的属性,可以用猴子补丁(直接修改类方法)或者类装饰器

当然,这并不代表元类没什么用,想用到它的时候自然会用到的

Last updated