pythonic的几个办法


Pythonic的几个办法

Python难点解析---高级篇2.Pythonic

Python 实用冷门知识整理

这些年来,Python 开发者用Pythonic这个形容词来描述那种符合特定风格的代码。这种Pythonice风格,既不是非常严密的规范,也不是由编译器强加给开发者的规则,而是大家在使用Python语言协同工作的过程中逐渐形成的习惯。

确认python版本

python --version
import sys
print(sys.version)
3.7.6 (default, Jan  8 2020, 20:23:39) [MSC v.1916 64 bit (AMD64)]
print(sys.version_info)
sys.version_info(major=3, minor=7, micro=6, releaselevel='final', serial=0)

有很多种流行的Python运行时环境,例如,CPython、 Jython、 IronPython 以及PyPy等。

enumerate迭代

enumerate可以把各种迭代器包装为生成器,以便稍后产生输出值。

可以给enumerate提供第二个参数,以指定开始计数时所用的值(默认为0)。

列表递推式

列表推导是构建列表(list)的快捷方式,而生成器表达式则可以用来创建其他任何类型的序列。

通常的原则是,只用列表推导来创建新的列表,并且尽量保持简短。如果列表推导的代码超过了两行,你可能就要考虑是不是得用 for 循环重写了。

在 Python 3 中都有了自己的局部作用域,就像函数似的。表达式内部的变量和赋值只在局部起作用,表达式的上下文里的同名变量还可以被正常引用,局部变量并不会影响到它们。

列表推导可以帮助我们把一个序列或是其他可迭代类型中的元素过滤或是加工,然后再新建一个列表。

列表推导的作用只有一个:生成列表。如果想生成其他类型的序列,生成器表达式就派上了用场。

生成器表达式

生成器表达式背后遵守了迭代器协议,可以逐个地产出元素,而不是先建立一个完整的列表,然后再把这个列表传递到某个构造函数里。

生成器表达式的语法跟列表推导差不多,只不过把方括号换成圆括号而已。

如果生成器表达式是一个函数调用过程中的唯一参数,那么不需要额外再用括号把它围起来。

生成器表达式就可以帮忙省掉运行 for 循环的开销

列表展平

一日一技:如何把多层嵌套的列表展平

所以,当代码运行到

的时候,每一次循环都会进入到 flat生成器里面。在 flat里面,对传入的参数使用for循环进行迭代,如果拿到的元素不是列表,那么就直接抛出,送到上一层。如果当前已经是最上层了,那么就再一次抛出给外面的列表推导式。如果当前元素是列表,那么继续生成一个生成器,并对这个新的生成器进行迭代,并把每一个结果继续往上层抛出。

最终,每一个数字都会被一层一层往上抛出给列表推导式,从而获得需要的结果。

字典展平

使用yield关键字来实现这个需求,在不炫技的情况下,只需要8行代码。在炫技的情况下,只需要3行代码。

要快速地把这个嵌套字典压扁,我们需要从下向上来处理字段。例如对于b->e->f->4这条路径,我们首先把最里面的{'f': 4}转换为一个元组('f', 4)。然后,把这个元组向上抛出,于是得到了元组('e', ('f', 4))。我们把 e拼接到f的前面,变为:('e_f', 4),继续往上抛出,得到('b', ('e_f', 4))。再把b拼接到e_f上面,得到('b_e_f', 4)。完成一条线路的组装。

这个逻辑如果使用yield关键字来实现,就是:

通过使用 yield关键字,字典的key会像是在流水线上一样,一层一层从内向外进行组装,从而形成完整的路径。

找list的最值索引

list去重并保留顺序

代码里面调用 pip

说到安装 Python 的第三方库,会 Python 的同学都知道,在终端使用pip install xxx即可。

那么如果我想在代码里面安装第三方库怎么办呢?可能有人想到使用 os 模块:

这种方法确实可行,并且即使你在虚拟环境中使用这种方式安装,也确实不会安装到系统的 Python 环境中。

但是这种方式总感觉有点奇怪。而且如果这个package_name字符串经过精心构造,可以执行任意系统命令,例如:

为了防止这种情况发生,我们可以直接调用pip这个 Python 包:

命令行下面的参数都可以通过转换为列表的形式执行,例如:

使用f-Strings格式化字符串

集合论

集合的本质是许多唯一对象的聚集。因此,集合可以用于去重

集合中的元素必须是可散列的,set 类型本身是不可散列的,但是 frozenset 可以。因此可以创建一个包含不同 frozensetset

给定两个集合 aba | b 返回的是它们的合集,a & b 得到的是交集,而 a - b 得到的是差集。

除空集之外,集合的字面量——{1}{1, 2},等等——看起来跟它的数学形式一模一样。如果是空集,那么必须写成 set() 的形式。

在 Python 3 里面,除了空集,集合的字符串表示形式总是以 {...} 的形式出现。

{1, 2, 3} 这种字面量句法相比于构造方法(set([1, 2, 3]))要更快且更易读。后者的速度要慢一些,因为 Python 必须先从 set 这个名字来查询构造方法,然后新建一个列表,最后再把这个列表传入到构造方法里。但是如果是像 {1, 2, 3} 这样的字面量,Python 会利用一个专门的叫作 BUILD_SET 的字节码来创建集合。

数学符号
python运算符
方法
描述

set的实现以及导致的结果

setfrozenset 的实现也依赖散列表,但在它们的散列表里存放的只有元素的引用(就像在字典里只存放键而没有相应的值)。在 set 加入到 Python 之前,我们都是把字典加上无意义的值当作集合来用的。

这些特点总结如下。

  • 集合里的元素必须是可散列的。

  • 集合很消耗内存。

  • 可以很高效地判断元素是否存在于某个集合。

  • 元素的次序取决于被添加到集合里的次序。

  • 往集合里添加元素,可能会改变集合里已有元素的次序。

使用字符串常量访问公共字符串组

使用Itertools生成排列和组合

漂亮的打印出JSON

with上下文管理

with模块

with 语句会设置一个临时的上下文,交给上下文管理器对象控制,并且负责清理上下文。这么做能避免错误并减少样板代码,因此 API 更安全,而且更易于使用。

上下文管理器对象存在的目的是管理 with 语句,就像迭代器的存在是为了管理 for 语句一样。

with 语句的目的是简化 try/finally 模式。finally 子句中的代码通常用于释放重要的资源,或者还原临时变更的状态。

上下文管理器协议包含 __enter____exit__ 两个方法。

  • with 语句开始运行时,会在上下文管理器对象上调用__enter__ 方法。

  • with 语句运行结束后,会在上下文管理器对象上调用__exit__方法,以此扮演 finally 子句的角色。

with的行为

执行 with 后面的表达式得到的结果是上下文管理器对象,不过,把值绑定到目标变量上(as 子句)是在上下文管理器对象上调用 __enter__ 方法的结果。

  • __enter__ 方法除了返回上下文管理器之外,还可能返回其他对象。

  • 不管控制流程以哪种方式退出 with 块,都会在上下文管理器对象上调用 __exit__ 方法,而不是在 __enter__ 方法返回的对象上调用。

  • with 语句的 as 子句是可选的。对 open 函数来说,必须加上 as 子句,以便获取文件的引用。

如果 __exit__ 方法返回 None,或者 True 之外的值,with 块中的任何异常都会向上冒泡。

解释器调用 __enter__ 方法时,除了隐式的 self 之外,不会传入任何参数。传给 __exit__ 方法的三个参数列举如下。

  异常类(例如 ZeroDivisionError)。

  异常实例。有时会有参数传给异常构造方法,例如错误消息,这些参数可以使用 exc_value.args 获取。

  traceback 对象。

可以手动调用 __enter____exit__ 方法。

with 不仅能管理资源,还能用于去掉常规的设置和清理代码,或者在另一个过程前后执行的操作

上下文装饰器

contextlib 模块中的实用工具

  1. closing: 如果对象提供了 close() 方法,但没有实现 __enter__/__exit__ 协议,那么可以使用这个函数构建上下文管理器。

  2. suppress: 构建临时忽略指定异常的上下文管理器。

  3. @contextmanager: 这个装饰器把简单的生成器函数变成上下文管理器,这样就不用创建类去实现管理器协议了。

  4. ContextDecorator: 这是个基类,用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数。

  5. ExitStack: 这个上下文管理器能进入多个上下文管理器。with 块结束时,ExitStack 按照后进先出的顺序调用栈中各个上下文管理器的 __exit__ 方法。如果事先不知道 with 块要进入多少个上下文管理器,可以使用这个类。例如,同时打开任意一个文件列表中的所有文件。

显然,在这些实用工具中,使用最广泛的是 @contextmanager 装饰器,因此要格外留心。

这个装饰器也有迷惑人的一面,因为它与迭代无关,却要使用 yield 语句。

重点介绍下**@contextmanager**

@contextmanager 装饰器能减少创建上下文管理器的样板代码量,因为不用编写一个完整的类,定义 __enter____exit__ 方法,而只需实现有一个 yield 语句的生成器,生成想让 __enter__ 方法返回的值。

在使用 @contextmanager 装饰的生成器中,yield 语句的作用是把函数的定义体分成两部分:yield 语句前面的所有代码在 with 块开始时(即解释器调用 __enter__ 方法时)执行, yield 语句后面的代码在 with 块结束时(即调用 __exit__ 方法时)执行。

这个类的 __enter__ 方法有如下作用。

(1) 调用生成器函数,保存生成器对象(这里把它称为 gen)。

(2) 调用 next(gen),执行到 yield 关键字所在的位置。

(3) 返回 next(gen) 产出的值,以便把产出的值绑定到 with/as 语句中的目标变量上。

with 块终止时,__exit__ 方法会做以下几件事:

  1. 检查有没有把异常传给 exc_type;如果有,调用 gen.throw(exception),在生成器函数定义体中包含 yield 关键字的那一行抛出异常。

  2. 否则,调用 next(gen),继续执行生成器函数定义体中 yield 语句之后的代码。

上文示例有一个严重的错误:如果在 with 块中抛出了异常,Python 解释器会将其捕获,然后在 looking_glass 函数的 yield 表达式里再次抛出。

但是,那里没有处理错误的代码,因此 looking_glass 函数会中止,永远无法恢复成原来的 sys.stdout.write 方法,导致系统处于无效状态。

以下代码是基于生成器的上下文管理器,而且实现了异常处理——从外部看,行为与前文一样

前面说过,为了告诉解释器异常已经处理了,__exit__ 方法会返回 True,此时解释器会压制异常。如果 __exit__ 方法没有显式返回一个值,那么解释器得到的是 None,然后向上冒泡异常。使用 @contextmanager 装饰器时,默认的行为是相反的:装饰器提供的__exit__ 方法假定发给生成器的所有异常都得到处理了,因此应该压制异常。6 如果不想让 @contextmanager 压制异常,必须在被装饰的函数中显式重新抛出异常。

@contextmanager 装饰器装饰的生成器中,yield 与迭代没有任何关系。

@contextmanager 装饰器能把包含一个 yield 语句的简单生成器变成上下文管理器——这比定义一个至少包含两个方法的类要更简洁。

for/else块

else 子句不仅能在 if 语句中使用,还能在 for、while 和 try 语句中使用。

for/elsewhile/elsetry/else 的语义关系紧密,不过与 if/else 差别很大。

在循环中,else 的语义恰好相反:“运行这个循环,然后做那件事。” else 子句的行为如下。

for: 仅当 for 循环运行完毕时(即 for 循环没有被 break 语句中止)才运行 else 块。

while: 仅当 while 循环因为条件为假值而退出时(即 while 循环没有被 break 语句中止)才运行 else 块。

try: 仅当 try 块中没有异常抛出时才运行 else 块。官方文档还指出:“else 子句抛出的异常不会由前面的 except 子句处理。”

在所有情况下,如果异常或者 returnbreakcontinue 语句导致控制权跳到了复合语句的主块之外,else 子句也会被跳过。

在这些语句中使用 else 子句通常能让代码更易于阅读,而且能省去一些麻烦,不用设置控制标志或者添加额外的 if 语句。

合理使用列表

  1. 列表对象(list)是一个查询效率高于更新操作的数据结构,删除和插入需要对剩下的元素做移动操作

  2. deque 是一个双向队列的数据结构,删除元素和插入元素会很快

序列解包

元组拆包可以应用到任何可迭代对象上,唯一的硬性要求是,被可迭代对象中的元素数量必须要跟接受这些元素的元组的空档数一致。除非我们用 * 来表示忽略多余的元素。

os.path.split() 函数就会返回以路径和最后一个文件名组成的元组 (path, last_part)

在平行赋值中,* 前缀只能用在一个变量名前面,但是这个变量可以出现在赋值表达式的任意位置

链式比较操作

assert用法

slots优化内存

使用__slots__使用了100M内存,比使用__dict__存储属性值节省了2倍。 其实使用collection模块的namedtuple也可以实现__slots__相同的功能。namedtuple其实就是继承自tuple,同时也因为__slots__的值被设置成了一个空tuple以避免创建__dict__ collection 和普通创建类方式相比,也节省了不少的内存。所在在确定类的属性值固定的情况下,可以使用__slots__方式对内存进行优化。但是这项技术不应该被滥用于静态类或者其他类似场合,那不是python程序的精神所在。

暂留

最后更新于

这有帮助吗?