Python中的部分列表解包

47
在Python中,赋值运算符可以将列表或元组解包为变量,就像这样:
l = (1, 2)
a, b = l # Here goes auto unpack

但是我需要指定左边确切相同数量的名称作为右边列表中的项数。但有时我不知道右边列表的大小,例如,如果我使用split()。

示例:

a, b = "length=25".split("=") # This will result in a="length" and b=25

但以下代码会导致错误:

a, b = "DEFAULT_LENGTH".split("=") # Error, list has only one item

有没有办法在上面的例子中拆开列表,这样我就可以得到a =“DEFAULT_LENGTH”,b等于None或未设置?一种简单直接的方法看起来有点冗长:

a = b = None
if "=" in string :
  a, b = string.split("=")
else :
  a = string

Python 2中的扩展元组拆包:https://dev59.com/RG435IYBdhLWcg3wlBGD - n611x007
11个回答

70

如果您没有使用Python 3,这可能对您没有用处。然而,为了完整起见,值得注意的是在扩展元组解包引入后,您可以做如下操作:

>>> a, *b = "length=25".split("=")
>>> a,b
("length", ['25'])
>>> a, *b = "DEFAULT_LENGTH".split("=")
>>> a,b
("DEFAULT_LENGTH", [])

也就是说,元组解包现在的工作方式类似于参数解包,因此您可以用*表示“其余的项目”,并将它们作为(可能为空的)列表获取。

对于你正在做的事情,Partition 可能是最好的解决方案。


48
# this will result in a="length" and b="25"
a, b = "length=25".partition("=")[::2]

# this will result in a="DEFAULT_LENGTH" and b=""
a, b = "DEFAULT_LENGTH".partition("=")[::2]

2
今天你应该使用拆包。a,*b =“length=25”。split("=")这等于:a ='length';b=['25']如果只有1个项目,则b=[]。 如果有多个项目,b被设置为整个剩余列表。 你甚至可以像这样做 first, *mid, last = "Hello world, welcome to the jungle!".split(" ")它们变成了('Hello', ['world,', 'welcome', 'to', 'the'], 'jungle!') - ninMonkey

7

这个解决方案比你的稍微好一些,但仍然不太优雅;如果有更好的方法做到这一点,我也不会感到惊讶。

a, b = (string.split("=") + [None])[:2]

1
不错。基本上是一个自制的分区版本。 - Mad Physicist

6

最好的方法是使用分隔字符串方法

将字符串在第一次出现sep的位置分割,并返回一个包含分隔符之前部分、分隔符本身和分隔符之后部分的3元组。如果未找到分隔符,则返回一个包含字符串本身,后跟两个空字符串的3元组。

版本2.5中新增。

>>> inputstr = "length=25"
>>> inputstr.partition("=")
('length', '=', '25')
>>> name, _, value = inputstr.partition("=")
>>> print name, value
length 25

它也适用于不包含=的字符串:

>>> inputstr = "DEFAULT_VALUE"
>>> inputstr.partition("=")
('DEFAULT_VALUE', '', '')

如果由于某些原因您使用的是Python 2.5版本之前的版本,您可以使用列表切片来实现类似的功能,虽然不太整洁。
>>> x = "DEFAULT_LENGTH"

>>> a = x.split("=")[0]
>>> b = "=".join(x.split("=")[1:])

>>> print (a, b)
('DEFAULT_LENGTH', '')

..当 x = "length=25" 时:

('length', '25')

可以轻松转换为函数或lambda:

>>> part = lambda x: (x.split("=")[0], "=".join(x.split("=")[1:]))
>>> part("length=25")
('length', '25')
>>> part('DEFAULT_LENGTH')
('DEFAULT_LENGTH', '')

我喜欢这个答案胜过被选中的那一个,因为它实际上解释了 partition 的作用。这种表示法并不是100%直观,乍一看不容易理解。 - Mad Physicist

4
你可以编写一个辅助函数来实现它。
>>> def pack(values, size):
...     if len(values) >= size:
...         return values[:size]
...     return values + [None] * (size - len(values))
...
>>> a, b = pack('a:b:c'.split(':'), 2)
>>> a, b
('a', 'b')
>>> a, b = pack('a'.split(':'), 2)
>>> a, b
('a', None)

1
有时候我不确定右边列表的大小,例如如果我使用split()函数。
是的,当我的情况中limit>1(所以我不能使用partition函数)时,我通常选择以下解决方案:
def paddedsplit(s, find, limit):
    parts= s.split(find, limit)
    return parts+[parts[0][:0]]*(limit+1-len(parts))

username, password, hash= paddedsplit(credentials, ':', 2)

(parts[0][:0] 是为了获取一个空的 'str' 或 'unicode',与分割产生的任何一个匹配。如果您喜欢,也可以使用 None。)


0

不要使用这段代码,它只是一个玩笑,但它确实能做你想要的事情:

a = b = None
try: a, b = [a for a in 'DEFAULT_LENGTH'.split('=')]
except: pass

1
等到有人试图将其扩展为适用于3个变量(或使用Python3)时,就会发现问题了!在你的代码中加入这样的内容可能会相当恶劣 :-) 更明智的方法可能是只在except块中放置a=theString。 - Brian

0

作为另一种选择,也许可以使用正则表达式?

>>> import re
>>> unpack_re = re.compile("(\w*)(?:=(\w*))?")

>>> x = "DEFAULT_LENGTH"
>>> unpack_re.match(x).groups()
('DEFAULT_LENGTH', None)

>>> y = "length=107"
>>> unpack_re.match(y).groups()
('length', '107')

如果您确保re.match()始终成功,则.groups()将始终返回正确数量的元素以解压缩到元组中,因此您可以安全地执行以下操作

a,b = unpack_re.match(x).groups()

0

我不建议使用这个,但是只是为了好玩,这里有一些代码可以实现你想要的功能。当你调用unpack(<sequence>)时,unpack函数使用inspect模块找到函数被调用的源代码行,然后使用ast模块解析该行并计算被拆包的变量数。

注意事项:

  • 对于多重赋值(例如(a, b) = c = unpack([1,2,3])),它仅使用赋值语句中的第一个术语。
  • 如果无法找到源代码(例如因为在repl中调用它),它将无法工作。
  • 如果赋值语句跨越多行,则无法工作。

代码:

import inspect, ast
from itertools import islice, chain, cycle

def iter_n(iterator, n, default=None):
    return islice(chain(iterator, cycle([default])), n)

def unpack(sequence, default=None):
    stack = inspect.stack()
    try:
        frame = stack[1][0]
        source = inspect.getsource(inspect.getmodule(frame)).splitlines()
        line = source[frame.f_lineno-1].strip()
        try:
            tree = ast.parse(line, 'whatever', 'exec')
        except SyntaxError:
            return tuple(sequence)
        exp = tree.body[0]
        if not isinstance(exp, ast.Assign):
            return tuple(sequence)
        exp = exp.targets[0]
        if not isinstance(exp, ast.Tuple):
            return tuple(sequence)
        n_items = len(exp.elts)
        return tuple(iter_n(sequence, n_items, default))
    finally:
        del stack

# Examples
if __name__ == '__main__':
    # Extra items are discarded
    x, y = unpack([1,2,3,4,5])
    assert (x,y) == (1,2)
    # Missing items become None
    x, y, z = unpack([9])
    assert (x, y, z) == (9, None, None)
    # Or the default you provide
    x, y, z = unpack([1], 'foo')
    assert (x, y, z) == (1, 'foo', 'foo')
    # unpack() is equivalent to tuple() if it's not part of an assignment
    assert unpack('abc') == ('a', 'b', 'c')
    # Or if it's part of an assignment that isn't sequence-unpacking
    x = unpack([1,2,3])
    assert x == (1,2,3)
    # Add a comma to force tuple assignment:
    x, = unpack([1,2,3])
    assert x == 1
    # unpack only uses the first assignment target
    # So in this case, unpack('foobar') returns tuple('foo')
    (x, y, z) = t = unpack('foobar')
    assert (x, y, z) == t == ('f', 'o', 'o')
    # But in this case, it returns tuple('foobar')
    try:
        t = (x, y, z) = unpack('foobar')
    except ValueError as e:
        assert str(e) == 'too many values to unpack'
    else:
        raise Exception("That should have failed.")
    # Also, it won't work if the call spans multiple lines, because it only
    # inspects the actual line where the call happens:
    try:
        (x, y, z) = unpack([
            1, 2, 3, 4])
    except ValueError as e:
        assert str(e) == 'too many values to unpack'
    else:
        raise Exception("That should have failed.")

0

许多其他解决方案已经被提出,但我必须说对我来说最直接的还是

a, b = string.split("=") if "=" in string else (string, None)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接