Частичный список распаковывается в Python - PullRequest
41 голосов
/ 14 апреля 2009

В 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

Ответы [ 11 ]

56 голосов
/ 15 апреля 2009

Это может быть бесполезно, если вы не используете Python 3. Однако, для полноты, стоит отметить, что представленная там расширенная распаковка , представленная там, позволяет вам делать такие вещи как:

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

т.е. Распаковка кортежей теперь работает аналогично распаковке аргументов, поэтому вы можете обозначить «остальные элементы» с помощью * и получить их как (возможно, пустой) список.

Разделение, вероятно, лучшее решение для того, что вы делаете, однако.

48 голосов
/ 14 апреля 2009
# 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]
7 голосов
/ 14 апреля 2009

Это немного лучше, чем ваше решение, но все же не очень элегантно; меня не удивит, если есть лучший способ сделать это.

a, b = (string.split("=") + [None])[:2]
6 голосов
/ 15 апреля 2009

Самый лучший способ - использовать метод строки разбиения :

Разбить строку при первом появлении 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')

Легко превращается в функцию или лямбду:

>>> part = lambda x: (x.split("=")[0], "=".join(x.split("=")[1:]))
>>> part("length=25")
('length', '25')
>>> part('DEFAULT_LENGTH')
('DEFAULT_LENGTH', '')
4 голосов
/ 14 апреля 2009

Вы можете написать вспомогательную функцию, чтобы сделать это.

>>> 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 голос
/ 15 апреля 2009

Но иногда я не знаю размер списка справа, например, если я использую split ().

Да, когда у меня есть дела с лимитом> 1 (поэтому я не могу использовать раздел), я обычно набираю:

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 голосов
/ 05 декабря 2013

Я не рекомендую использовать это, но просто для забавы вот код, который на самом деле делает то, что вы хотите. Когда вы вызываете unpack(<sequence>), функция unpack использует модуль inspect, чтобы найти фактическую строку источника, где была вызвана функция, а затем использует модуль ast для анализа этой строки и подсчета количества распакованных переменных. .

Предостережения:

  • Для множественного присвоения (например, (a,b) = c = unpack([1,2,3])) используется только первый член в присвоении
  • Он не будет работать, если не может найти исходный код (например, потому что вы вызываете его из реплея)
  • Не будет работать, если оператор присваивания занимает несколько строк

Код:

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 голосов
/ 15 апреля 2009

В качестве альтернативы, возможно, использовать регулярное выражение?

>>> 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 голосов
/ 15 апреля 2009

Было предложено много других решений, но я должен сказать, что самым простым для меня все еще является

a, b = string.split("=") if "=" in string else (string, None)
0 голосов
/ 14 апреля 2009

Не используйте этот код, он предназначен для шутки, но он делает то, что вы хотите:

a = b = None
try: a, b = [a for a in 'DEFAULT_LENGTH'.split('=')]
except: pass
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...