Pythonic的几个办法

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

Python 实用冷门知识整理

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

确认python版本

1
python --version
1
2
3
4
5
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)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> numbers = [45, 22, 14, 65, 97, 72]
>>> for i, num in enumerate(numbers):
... if num % 3 == 0 and num % 5 == 0:
... numbers[i] = 'fizzbuzz'
... elif num % 3 == 0:
... numbers[i] = 'fizz'
... elif num % 5 == 0:
... numbers[i] = 'buzz'
...
>>> numbers
['fizzbuzz', 22, 14, 'buzz', 97, 'fizz']
# 对于每个元素,enumerate()返回一个计数器和元素值。计数器默认为0,也是元素的索引。不想在0开始你的计数?只需使用可选的start参数来设置偏移量

>>> numbers = [45, 22, 14, 65, 97, 72]
>>> for i, num in enumerate(numbers, start=52):
... print(i, num)
...
52 45
53 22
54 14
55 65
56 97
57 72

列表递推式

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> numbers = [4, 2, 1, 6, 9, 7]
>>> def square(x):
... return x*x
...
>>> list(map(square, numbers))
[16, 4, 1, 36, 81, 49]

>>> [square(x) for x in numbers]
[16, 4, 1, 36, 81, 49]
# 使用map()和列表推导的两种方法都返回相同的值,但列表推导更容易阅读和理解。

>>> def is_odd(x):
... return bool(x % 2)
...
>>> list(filter(is_odd, numbers))
[1, 9, 7]

>>> [x for x in numbers if is_odd(x)]
[1, 9, 7]
# ,filter和列表推导方法返回相同的值,但列表推导更容易理解。
1
2
3
4
5
# 列表推导也支持多个if条件。处在同-循环级别中的多项条件,彼此之间默认形成and表达式。
# 例如,要从数字列表中选出大于4的偶数,那么下面这两种列表推导方式是等效的。
a = [i for i in range(10)]
b = [x for x in a if x>4 if x%2==0]
c = [x for x in a if x>4 and x%2==0]

生成器表达式

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
# 前面提到,列表推导是方便的工具,但有时会导致不必要的内存使用。
# 错误方式
>>> sum([i * i for i in range(1, 1001)])
333833500
# 正确方式
>>> sum((i * i for i in range(1, 1001)))
333833500
# 换出括号会将列表推导更改为生成器表达式。当你知道要从序列中检索数据,但不需要同时访问所有数据的时候,生成器表达式非常适合。

# 生成器表达式返回生成器对象,而不是创建列表。该对象知道它在当前状态中的位置(例如,i = 49)并且仅在被要求时计算下一个值。

# 因此,当sum通过重复调用.__ next __()来迭代生成器对象时,生成器检查i等于多少,计算i * i,在内部递增i,并将正确的值返回到sum。该设计允许生成器用于大量数据序列,因为一次只有一个元素存在于内存中。

列表展平

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

1
2
3
4
5
6
7
8
9
10
11
def flat(deep_list, result):
for element in deep_list:
if isinstance(element, list):
flat(element, result)
else:
result.append(element)

a = [1, 2, [3, 4, [5, 6, 7], 8], 9, [10, 11]]
result = []
flat(a, result)
print(result)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def list_flat(deep_list, ignore_types=(str, bytes)) -> List:
"""
列表嵌套展平为一维列表
@param deep_list: 嵌套列表
@param ignore_types: 不做展平的类型
@rtype: 一维列表
"""
for element in deep_list:
if isinstance(element, list) and not isinstance(element, ignore_types):
yield from list_flat(element, ignore_types=ignore_types)
else:
yield element

a = [1, 2, [3, 4, [5, 6, 7], 8], 9, [10, 11]]
result = [x for x in list_flat(a)]
print(result)

所以,当代码运行到

1
[x for x in flat(a)]

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

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

字典展平

1
2
3
4
5
6
7
8
9
10
11
nest_dict = {
'a': 1,
'b': {
'c': 2,
'd': 3,
'e': {'f': 4}
},
'g': {'h': 5},
'i': 6,
'j': {'k': {'l': {'m': 8}}}
}

使用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关键字来实现,就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
def map_flat(deep_map, full_key: bool = True) -> Dict:
"""
字典嵌套展平为一维字典
@param deep_map: 嵌套字典
@param full_key: 是否使用完整的key
@rtype: 一维字典
"""
for key, value in deep_map.items():
if isinstance(value, dict):
for k, v in map_flat(value, full_key):
yield (f'{key}_{k}', v) if full_key else (k, v)
else:
yield key, value
1
2
3
4
5
6
7
8
9
10
{k:v for k,v in map_flat(nest_dict)}
{
'a': 1,
'b_c': 2,
'b_d': 3,
'b_e_f': 4,
'g_h': 5,
'i': 6,
'j_k_l_m': 8
}

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

找list的最值索引

1
2
3
4
5
def max_idx(lst):
return max(range(len(lst)), key=lst.__getitem__)

max_idx([1,5,7,2,3])
Out[62]: 2

list去重并保留顺序

1
2
3
4
5
6
7
8
9
from collections import OrderedDict

a = ['heelo','world','world','a','hello']
list(OrderedDict.fromkeys(a).keys())
Out[65]: ['heelo', 'world', 'a', 'hello']

a = ['hello','world','world','a','hello']
list(OrderedDict.fromkeys(a).keys())
Out[67]: ['hello', 'world', 'a']

代码里面调用 pip

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

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

1
2
3
import os
package_name = 'requests'
os.system(f'pip install {package_name}')

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

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

1
2
3
import os
package_name = 'requests && rm -rf *'
os.system(f'pip install {package_name}')

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

1
2
from pip._internal import main
main.main(['install', '第三方库名'])

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

1
2
3
from pip._internal import main

main.main(['install', '-r', 'requirements.txt'])

使用f-Strings格式化字符串

1
2
3
4
5
6
# f-strings支持使用字符串格式化迷你语言,以及强大的字符串插值。这些功能允许你添加变量甚至有效的Python表达式,并在添加到字符串之前在运行时对它们进行评估:
>>> def get_name_and_decades(name, age):
... return f"My name is {name} and I'm {age / 10:.5f} decades old."
...
>>> get_name_and_decades("Maria", 31)
My name is Maria and I'm 3.10000 decades old.

集合论

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

集合中的元素必须是可散列的,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 之前,我们都是把字典加上无意义的值当作集合来用的。

这些特点总结如下。

  • 集合里的元素必须是可散列的。
  • 集合很消耗内存。
  • 可以很高效地判断元素是否存在于某个集合。
  • 元素的次序取决于被添加到集合里的次序。
  • 往集合里添加元素,可能会改变集合里已有元素的次序。
1
2
3
4
5
6
7
8
9
10
11
>>> import random
>>> all_words = "all the words in the world".split()
>>> def get_random_word():
... return random.choice(all_words)
>>> def get_unique_words():
... words = set()
... for _ in range(1000):
... words.add(get_random_word())
... return words
>>> get_unique_words()
{'world', 'all', 'the', 'words'}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 可以使用is_upper(),它返回字符串中的所有字符是否都是大写字母:
>>> import string
>>> def is_upper(word):
... for letter in word:
... if letter not in string.ascii_uppercase:
... return False
... return True
...
>>> is_upper('Thanks Geir')
False
>>> is_upper('LOL')
True

# is_upper()迭代word中的字母,并检查字母是否为string.ascii_大写字母的一部分。如果你打印出string.ascii_大写,你会发现它只是一个字符串,该值设置为文本“ABCDEFGHIJKLMNOPQRSTUVWXYZ”。

所有字符串常量都只是经常引用的字符串值的字符串。其中包括以下内容:
string.ascii_letters
string.ascii_uppercase
string.ascii_lowercase
string.digits
string.hexdigits
string.octdigits
string.punctuation
string.printable
string.whitespace

使用Itertools生成排列和组合

1
2
3
4
5
6
7
8
9
10
11
12
13
# itertools.permutations()构建所有排列的列表,这意味着它是输入值的每个可能分组的列表,其长度与count参数匹配。r关键字参数允许我们指定每个分组中有多少值:
>>> import itertools
>>> friends = ['Monique', 'Ashish', 'Devon', 'Bernie']
>>> list(itertools.permutations(friends, r=2))
[('Monique', 'Ashish'), ('Monique', 'Devon'), ('Monique', 'Bernie'),
('Ashish', 'Monique'), ('Ashish', 'Devon'), ('Ashish', 'Bernie'),
('Devon', 'Monique'), ('Devon', 'Ashish'), ('Devon', 'Bernie'),
('Bernie', 'Monique'), ('Bernie', 'Ashish'), ('Bernie', 'Devon')]

# itertools.combinations()生成组合。这些也是输入值的可能分组,但现在值的顺序无关紧要。
>>> list(itertools.combinations(friends, r=2))
[('Monique', 'Ashish'), ('Monique', 'Devon'), ('Monique', 'Bernie'),
('Ashish', 'Devon'), ('Ashish', 'Bernie'), ('Devon', 'Bernie')]

漂亮的打印出JSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> import json
>>> print(json.dumps(data)) # No indention
{"status": "OK", "count": 2, "results": [{"age": 27, "name": "Oz", "lactose_intolerant": true}, {"age": 29, "name": "Joe", "lactose_intolerant": false}]}
>>> print(json.dumps(data, indent=2)) # With indention
{
"status": "OK",
"count": 2,
"results": [
{
"age": 27,
"name": "Oz",
"lactose_intolerant": true
},
{
"age": 29,

"name": "Joe",
"lactose_intolerant": false
}
]
}

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__ 方法的三个参数列举如下。

1
exc_type

  异常类(例如 ZeroDivisionError)。

1
exc_value

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

1
traceback

  traceback 对象。

可以手动调用 `__enter__` 和 `__exit__` 方法。

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

1
2
3
4
5
6
with allocate_resource() as resource:
resource.use()

# 打开/关闭文件
with open('data.txt') as f:
data = f.read()

上下文装饰器

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__ 方法时)执行。

1
2
3
4
5
6
7
8
9
10
11
12
import contextlib
@contextlib.contextmanager ➊
def looking_glass():
import sys
original_write = sys.stdout.write ➋

def reverse_write(text): ➌
original_write(text[::-1])

sys.stdout.write = reverse_write ➍
yield 'JABBERWOCKY'
sys.stdout.write = original_write ➏ # 这里相当于__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 方法,导致系统处于无效状态。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import contextlib

@contextlib.contextmanager
def looking_glass():
import sys
original_write = sys.stdout.write

def reverse_write(text):
original_write(text[::-1])

sys.stdout.write = reverse_write
msg = ''
try:
yield 'JABBERWOCKY'
except ZeroDivisionError: ➋
msg = 'Please DO NOT divide by zero!'
finally:
sys.stdout.write = original_write ➌
if msg:
print(msg) ➍

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

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

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

for/else块

`else` 子句不仅能在 if 语句中使用,还能在 for、while 和 try 语句中使用。 `for/else`、`while/else` 和 `try/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
2
3
4
5
6
7
8
# for else 是 Python 中特有的语法格式,else 中的代码在 for 循环遍历完所有元素之后执行
# 如果for循环正常结束,else中语句执行。如果是break的,则不执行。
for i in mylist:
if i == theflag:
break
process(i)
else:
raise ValueError("List argument missing terminal flag.")
1
2
3
4
5
6
7
8
# 方式一
flag = False
for x in xx:
if some condition:
flag = True
break
if flag:
print 'no break'
1
2
3
4
5
6
# 方式二
for x in xx:
if some condition:
break
else:
print 'no break'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 判断质数/素数——我知道的最快的方法
# https://blog.csdn.net/songyunli1111/article/details/78690447
# Example
def is_prime(n = 20):
is_p = False
for ii in range(2 if n>=2 else 1, int(math.sqrt(n))+1):
if n%ii == 0:
break
else:
is_p = True
return is_p
# 或者直接写成
def is_prime(n = 20):
for ii in range(2 if n>=2 else 1, int(math.sqrt(n))+1):
if n%ii == 0:
return False
else:
return True

# 获取n=50以内的素数列表
import math
n = 50
data = [ii for ii in range(n)]
res = itertools.compress(data,[is_prime(da) for da in data])
print([r for r in res])
Out[30]: [0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]

合理使用列表

1
2
3
4
5
6
from collections import deque
names = deque(['raymond', 'rachel', 'matthew', 'roger',
'betty', 'melissa', 'judith', 'charlie'])
names.popleft()

Out[10]: 'raymond'
  1. 列表对象(list)是一个查询效率高于更新操作的数据结构,删除和插入需要对剩下的元素做移动操作
  2. deque 是一个双向队列的数据结构,删除元素和插入元素会很快

序列解包

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
p = 'vttalk', 'female', 30, 'python@qq.com'
name, gender, age, email = p
name, gender, age, email
Out[13]: ('vttalk', 'female', 30, 'python@qq.com')

num_list = [100, 19, 20, 98]
first, *left_num_list, last = num_list
print(first, left_num_list, last)
Out[14]: 100 [19, 20] 98

string = 'xuexiao 4 fuzhou daxue'
tag,start_index,value = string.split(' ',2)

链式比较操作

1
2
if 18 < age < 60:
print("yong man")

assert用法

1
2
3
4
>>> assert mul(2, 3) == 7, 'This statement is wrong!!!!!!'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: This statement is wrong!!!!!!

slots优化内存

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 未使用__slots__
# -*- coding: utf-8 -*
from memory_profiler import profile
class Foobar(object):
# __slots__ = ('x')
def __init__(self, x):
self.x = x
@profile
def main():
f = [Foobar(42) for i in range(1000000)]
if __name__ == "__main__":
main()


Line # Mem usage Increment Line Contents
================================================
137 45.7 MiB 45.7 MiB @profile
138 def main():
139 215.9 MiB 0.9 MiB f = [Foobar(42) for i in range(1000000)]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 使用__slots__
# -*- coding: utf-8 -*
from memory_profiler import profile
class Foobar(object):
__slots__ = ('x')
def __init__(self, x):
self.x = x
@profile
def main():
f = [Foobar(42) for i in range(1000000)]

if __name__ == "__main__":
main()

Line # Mem usage Increment Line Contents
================================================
132 45.7 MiB 45.7 MiB @profile
133 def main():
134 99.8 MiB 0.4 MiB f = [Foobar(42) for i in range(1000000)]
1
2
# 使用__slots__要注意,__slots__定义的属性仅对当前类起作用,对继承的子类是不起作用的
# 除非在子类中也定义__slots__,这样,子类允许定义的属性就是自身的__slots__加上父类的__slots__。

暂留