数据结构和算法-2.解压可迭代对象赋值给多个变量

问题描述

如果我们遇到一个不确定个数的可迭代对象,如何解压它们呢?

如果直接用变量解压,变量个数超过元素个数时,会抛出一个 ValueError

如:

1
2
3
4
5
# 变量个数超过元素
>>> a,b,c,d = [1,2]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 4, got 2)

那么怎样才能从这个可迭代对象中解压出想要的元素来呢?

解决方案

Python 的星号表达式可以用来解决这个问题。

示例1:星号表达式用在中间部分

你在学习一门课程,在学期末的时候, 你想统计下家庭作业的平均成绩,但是需要排除掉第一个和最后一个分数,如何实现呢?

如果只有四个分数,你可能就直接去简单的手动赋值, 但如果有 24 个呢?

这时候星号表达式就派上用场了。

代码演示:

1
2
3
4
5
6
7
8
def test_use_asterisk_expr():
grades = (30, 40, 60, 80, 90, 98, 100)

# 使用*号表达式
first, *middle, last = grades
assert middle == [40, 60, 80, 90, 98]

assert 73.6 == sum(middle) / len(middle)

示例2:星号表达式用在后面的部分

假设你现在有一些用户的记录列表,每条记录包含一个名字、邮件,接着就是不确定数量的电话号码。

如:

1
2
> record = ('Dave', 'dave@example.com', '773-555-1212', '847-555-1212')
>

如何分解这些记录呢?

代码演示:

1
2
3
4
5
6
7
8
9
10
def test_use_asterisk_expr_behind():
record = ('张三', 'dave@example.com', '773-555-1212', '847-555-1212')
# 使用星号表达式
name, email, *phone_numbers = record

assert name == "张三"
assert email == 'dave@example.com'

# 解压出的 phone_numbers 变量永远都是列表类型,不管解压的电话号码数量是多少(包括 0 个)
assert phone_numbers == ['773-555-1212', '847-555-1212']

注意:

上面解压出的 phone_numbers 变量永远都是列表类型,不管解压的电话号码数量是多少(包括 0 个)。

所以,任何使用到 phone_numbers 变量的代码就不需要做多余的类型检查去确认它是否是列表类型了。

示例3:星号表达式用在前面的部分

假设你有一个公司前 8 个月销售数据的序列, 但是你想看下最近一个月数据和前面 7 个月的平均值的对比。

如:

1
2
> datas = [10, 8, 7, 1, 9, 5, 10, 3]
>

如何获得最近1个和前面7个数据呢?

代码演示:

1
2
3
4
5
6
7
def test_use_asterisk_expr_front():
datas = [10, 8, 7, 1, 9, 5, 10, 3]

*trailing, last = datas

assert trailing == [10, 8, 7, 1, 9, 5, 10]
assert last == 3

更多讨论

扩展的迭代解压语法是专门为解压不确定个数或任意个数元素的可迭代对象而设计的。

在什么时候使用星号表达式呢?

当可迭代对象的元素结构有确定的规则的(比如第 1 个元素后面都是电话号码),星号表达式让我们可以很容易的利用这些规则来解压出元素来, 而不是通过一些比较复杂的手段去获取这些关联的元素值。

遍历可变的长元组序列

值得注意的是,星号表达式在迭代元素为可变的长元组序列时是很有用的。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def test_var_long_tuple():
# 一个可变长元组的序列
records = [
('foo', 1, 2),
('bar', 'hello'),
('foo', 3, 4),
]

def do_foo(x, y):
print('foo:', x, y)

def do_bar(s):
print('bar:', s)

# 使用星号表达式遍历一个可变长元组的序列
for tag, *args in records:
if tag == 'foo':
do_foo(*args)
elif tag == 'bar':
do_bar(*args)

字符串的分割

星号解压语法在字符串操作的时候也会很有用,比如字符串的分割。

示例代码:

1
2
3
4
5
6
def test_separate_str():
line = 'nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false'
uname, *fields, homedir, sh = line.split(':')
assert uname == "nobody"
assert homedir == "/var/empty"
assert sh == "/usr/bin/false"

解压一些元素后丢弃

有时候,我们想解压一些元素后丢弃它们,虽然不能简单就使用 * , 但是可以使用一个普通的废弃名称,比如 _ 或者 ign (ignore)。

示例代码:

1
2
3
4
5
def test_ignore():
record = ('ACME', 50, 123.45, (12, 18, 2012))
name, *_, (*_, year) = record
assert name == 'ACME'
assert year == 2012

分隔列表

在很多函数式语言中,星号解压语法跟列表处理有许多相似之处。

比如,如果你有一个列表, 你可以很容易的将它分割成几部分。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def test_use_asterisk_expr():
grades = (30, 40, 60, 80, 90, 98, 100)

# 在中间使用星号表达式
first, *middle, last = grades
assert first == 30
assert middle == [40, 60, 80, 90, 98]
assert last == 100

# 在后边使用星号表达式
first, *behind= grades
assert first == 30
assert behind == [40, 60, 80, 90, 98, 100]

# 在前边使用星号表达式
*front, last = grades
assert front == [30, 40, 60, 80, 90, 98]
assert last == 100
毕小烦 wechat
「请扫一扫上面的二维码,关注老毕的微信公众号」
「您的赞赏是老毕持续创作的动力」