En Python, el operador de asignación puede descomprimir una lista o una tupla en variables, como esta:

l = (1, 2)
a, b = l # Here goes auto unpack

Pero necesito especificar exactamente la misma cantidad de nombres a la izquierda como un recuento de elementos en la lista a la derecha. Pero a veces no sé el tamaño de la lista a la derecha, por ejemplo, si uso split ().

Ejemplo:

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

Pero el siguiente código provocará un error:

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

¿Es posible de alguna manera desempaquetar la lista en el ejemplo anterior para que pueda obtener a = "DEFAULT_LENGTH" yb es igual a None o no está configurado? Una forma directa parece algo larga:

a = b = None
if "=" in string :
  a, b = string.split("=")
else :
  a = string
42
grigoryvp 14 abr. 2009 a las 23:44

11 respuestas

La mejor respuesta
# 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]
48
Chris Upchurch 14 abr. 2009 a las 22:48

No recomiendo usar esto, pero solo por diversión, aquí hay un código que realmente hace lo que quieres. Cuando llama a unpack(<sequence>), la función unpack usa el módulo inspect para encontrar la línea de origen real donde se llamó la función, luego usa el módulo ast para analizar esa línea y contar el número de variables que se desempaquetan.

Advertencias:

  • Para la asignación múltiple (por ejemplo, (a,b) = c = unpack([1,2,3])), solo usa el primer término en la asignación
  • No funcionará si no puede encontrar el código fuente (por ejemplo, porque lo está llamando desde la respuesta)
  • No funcionará si la instrucción de asignación abarca varias líneas

Código:

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
dplepage 4 dic. 2013 a las 20:25

¿Has probado esto?

values = aString.split("=")
if len(values) == 1:
   a = values[0]
else:
   a, b = values
-2
S.Lott 14 abr. 2009 a las 19:54

La mejor manera es usar el método de cadena de partición:

Divida la cadena en la primera aparición de sep, y devuelva una tupla de 3 que contiene la parte anterior al separador, el separador en sí y la parte posterior al separador. Si no se encuentra el separador, devuelva una tupla de 3 que contiene la cadena en sí, seguida de dos cadenas vacías.

Nueva en la versión 2.5.

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

También funciona para cadenas que no contienen =:

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

Si por alguna razón está utilizando una versión de Python anterior a la 2.5, puede usar el corte de listas para hacer lo mismo, aunque un poco menos ordenado:

>>> x = "DEFAULT_LENGTH"

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

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

..y cuando x = "length=25":

('length', '25')

Se convierte fácilmente en una función o lambda:

>>> part = lambda x: (x.split("=")[0], "=".join(x.split("=")[1:]))
>>> part("length=25")
('length', '25')
>>> part('DEFAULT_LENGTH')
('DEFAULT_LENGTH', '')
6
dbr 20 dic. 2009 a las 17:33

Como alternativa, ¿quizás usar una expresión regular?

>>> 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')

Si se asegura de que re.match () siempre tiene éxito, .groups () siempre devolverá el número correcto de elementos para descomprimir en su tupla, de modo que pueda hacerlo de forma segura

a,b = unpack_re.match(x).groups()
0
NickZoic 15 abr. 2009 a las 04:56

Podrías escribir una función auxiliar para hacerlo.

>>> 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)
4
FogleBird 14 abr. 2009 a las 19:52

Pero a veces no conozco el tamaño de la lista a la derecha, por ejemplo, si uso split ().

Sí, cuando tengo casos con límite> 1 (por lo que no puedo usar la partición), generalmente me vuelvo loco por:

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] está ahí para obtener un ‘str 'o‘ unicode' vacío, que coincida con cualquiera de los que produjo la división. Puede usar Ninguno si lo prefiere).

1
Peter Mortensen 20 ago. 2016 a las 03:07

Esto puede no serle útil a menos que esté usando Python 3. Sin embargo, para completar, vale la pena señalar que el desempaquetado de tuplas extendido introducido allí le permite hacer cosas como:

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

Es decir. el desempaquetado de tuplas ahora funciona de manera similar a como lo hace en el desempaquetado de argumentos, por lo que puede denotar "el resto de los elementos" con * y obtenerlos como una lista (posiblemente vacía).

Sin embargo, la partición es probablemente la mejor solución para lo que está haciendo.

58
Peter Mortensen 20 ago. 2016 a las 03:06

No use este código, es una broma, pero hace lo que quiere:

a = b = None
try: a, b = [a for a in 'DEFAULT_LENGTH'.split('=')]
except: pass
0
RossFabricant 14 abr. 2009 a las 19:55

Se han propuesto muchas otras soluciones, pero debo decir que la más sencilla para mí sigue siendo

a, b = string.split("=") if "=" in string else (string, None)
0
dF. 15 abr. 2009 a las 00:29

Esto es ligeramente mejor que su solución, pero aún no es muy elegante; No me sorprendería si hay una mejor manera de hacerlo.

a, b = (string.split("=") + [None])[:2]
7
Adam Rosenfield 14 abr. 2009 a las 19:49