Python技法1:变长和定长序列拆分

导读:本篇文章讲解 Python技法1:变长和定长序列拆分,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

Python中的任何序列(可迭代的对象)都可以通过赋值操作进行拆分,包括但不限于元组、列表、字符串、文件、迭代器、生成器等。

元组拆分

元组拆分是最为常见的一种拆分,示例如下:

p = (4, 5)
x, y = p 
print(x, y) # 4 5

如果写成

x, y, z = p

那么就会抛出ValueError异常:“not enough values to unpack (expected 3, got 2)”
如果写成

p = (4, 5, 6)
x, y = p

那么就会抛出ValueError异常:“too many values to unpack (expected 2)”

元组拆包无处不在,比如我们知道Python的zip()函数相当于返回一个元组列表,我们可以对其进行迭代拆分:

list_a = [1, 3, 5]
list_b = [2, 4, 6]
print(list(zip(list_a, list_b)))
#[(1, 2), (3, 4), (5, 6)]
for a, b in zip(list_a, list_b):
    print("%d - %d" % (a, b))
# 1 - 2
# 3 - 4
# 5 - 6

上面的迭代语句其实隐式等价于for (a, b) in zip(list_a, list_b)
接下来容易出错的地方来了,很多时候我们会将附加索引下标的上述迭代错误地这样写:

for idx, a, b in enumerate(zip(list_a, list_b)):
    print("idx%d: %d - %d" % (idx, a, b))

此时就会抛出ValueError异常:"not enough values to unpack (expected 3, got 2)"。我们经过上面的讨论知道,这是不正确地元组拆包所致。

原来,迭代enumerate(zip(list_a, list_b))实际等价于迭代[(0, (1, 2)), (1, (3, 4)), (2, (5, 6))]:

print(list(enumerate(zip(list_a, list_b))))
# [(0, (1, 2)), (1, (3, 4)), (2, (5, 6))]

对其迭代需要进行两次复合的元组拆包,即:

for idx, (a, b) in enumerate(zip(list_a, list_b)):
    print("idx%d: %d - %d" % (idx, a, b))
# idx0: 1 - 2
# idx1: 3 - 4
# idx2: 5 - 6

还是同样地,两次拆包有一次隐式省略,上述迭代语句隐式等价于for (idx, (a, b)) in enumerate(zip(list_a, list_b)):

字符串拆分

字符串的拆分示意如下:

s = 'Hello'
a, b, c, d, e = s
print(a) # H

拆分时丢弃值

如果在拆分时想丢弃某些特定的值,可以用一个用不到的变量名来作为丢弃值的名称(常选’_’做为变量名),如下所示:

s = 'Hello'
a, b, _, d, _ = s
print(a) # H

嵌套序列拆分

Python也提供简洁的对嵌套序列进行拆分的语法。如下所示我们对一个比较复杂的异质列表进行拆分:

data = ['zhy', 50, 123.0, (2000, 12, 21)]
name, shares, price, (year, month, day) = data
print(year) # 2000

如果你想完整地得到(2000, 12, 21)这个表示时间戳的元组,那么你就得这样写:

data = ['zhy', 50, 123.0, (2000, 12, 21)]
name, shares, price, date = data
print(date) # (2000, 12, 21)

从任意长度的可迭代对象中拆分

之前我们说过,如果我们想从可迭代对象中分解出\(N\)个元素,但如果这个可迭代对象长度超过\(N\),则会抛出异常”too many values to unpack”。针对这个问题的解决方案是采用”*”表达式。
比如我们给定学生的分数,想去掉一个最高分和一个最低分,然后对剩下的学生求平均分,我们可以这样写:

def avg(data: list):
    return sum(data)/len(data)
# 去掉最高分,最低分然后做均分统计
def drop_first_last(grades):
    first, *middle, last = grades
    return avg(middle)
print(drop_first_last([1,2,3,4])) # 2.5

还有一种情况是有一些用户记录,记录由姓名+电子邮件+任意数量的电话号码组成,则我们可以这样分解用户记录:

record = ['zhy', 'zhy1056692290@qq.com', '773-556234', '774-223333']
name, email, *phone_numbers = record
print(phone_numbers) # ['773-556234', '774-223333']

事实上,如果电话号码为空也是合法的,此时phone_numbers为空列表。

record = ['zhy', 'zhy1056692290@qq.com']
name, email, *phone_numbers = record
print(phone_numbers) # []

还有一种使用情况则更为巧妙。如果我们需要遍历变长元组组成的列表,这些元组长度不一。那么此时*表达式可大大简化我们的代码。

records = [('foo', 1, 2), ('bar', 'hello'), ('foo', 3, 4)]
for tag, *args in records:
    if tag == 'bar':
        print(args)
# ['hello']

在对一些复杂的字符串进行拆分时,*表达式也显得特别有用。

line = "nobody:*:-2:-2:-2:Unprivileged User:/var/empty:/usr/bin/false"
uname, *fields, home_dir, sh = line.split(':')
print(home_dir) # /var/empty

*表达式也可以和我们前面说的嵌套拆分和变量丢弃一起结合使用。

record = ['ACME', 50, 123.45, (128, 18, 2012)]
name, *_, (*_, year) = record
print(year) # 2012

最后再介绍*表达式用于递归函数的一种黑魔法,比如与递归求和结合可以这样写:

items = [1, 10, 7, 4, 5, 9]
def sum(items):
    head, *tail = items
    return head + sum(tail) if tail else head
print(sum(items)) # 36

不过,Python由于自身递归栈的限制,并不擅长递归。我们最后一个递归的例子可以做为一种学术上的尝试,但不建议在实践中使用它。

引用

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/11784.html

(0)
小半的头像小半

相关推荐

半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!