Descomprimiendo y el operador *

Los documentos de Python dan este código como la operación inversa de zip:

>>> x2, y2 = zip(*zipped) 

En particular, “zip () junto con el operador * se puede usar para descomprimir una lista”. ¿Puede alguien explicarme cómo funciona el operador * en este caso? Por lo que entiendo, * es un operador binario y puede utilizarse para la multiplicación o copia superficial … ninguno de los cuales parece ser el caso aquí.

Cuando se usa de esta manera, el * (asterisco, también conocido en algunos círculos como el operador “splat”) es una señal para desempaquetar los argumentos de una lista. Consulte http://docs.python.org/tutorial/controlflow.html#unpacking-argument-lists para obtener una definición más completa con ejemplos.

Aunque la respuesta de hammar explica cómo funciona la inversión en el caso de la función zip() , puede ser útil analizar el desempaquetado de argumentos en un sentido más general. Digamos que tenemos una función simple que toma algunos argumentos:

 >>> def do_something(arg1, arg2, arg3): ... print 'arg1: %s' % arg1 ... print 'arg2: %s' % arg2 ... print 'arg3: %s' % arg3 ... >>> do_something(1, 2, 3) arg1: 1 arg2: 2 arg3: 3 

En lugar de especificar directamente los argumentos, podemos crear una lista (o tupla para el caso) para mantenerlos, y luego decirle a Python que descomprima esa lista y use su contenido como los argumentos de la función:

 >>> arguments = [42, 'insert value here', 3.14] >>> do_something(*arguments) arg1: 42 arg2: insert value here arg3: 3.14 

Esto se comporta de manera normal si no tiene suficientes argumentos (o demasiados):

 >>> arguments = [42, 'insert value here'] >>> do_something(*arguments) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) /home/blair/ in () TypeError: do_something() takes exactly 3 arguments (2 given) 

Puede utilizar la misma construcción al definir una función para aceptar cualquier número de argumentos posicionales. Se dan a tu función como una tupla:

 >>> def show_args(*args): ... for index, value in enumerate(args): ... print 'Argument %d: %s' % (index, value) ... >>> show_args(1, 2, 3) Argument 0: 1 Argument 1: 2 Argument 2: 3 

Y por supuesto puedes combinar las dos técnicas:

 >>> show_args(*arguments) Argument 0: 42 Argument 1: insert value here 

Puede hacer algo similar con argumentos de palabras clave, utilizando un doble asterisco ( ** ) y un diccionario:

 >>> def show_kwargs(**kwargs): ... for arg, value in kwargs.items(): ... print '%s = %s' % (arg, value) ... >>> show_kwargs(age=24, name='Blair') age = 24 name = Blair 

Y, por supuesto, puede pasar argumentos de palabras clave a través de un diccionario:

 >>> values = {'name': 'John', 'age': 17} >>> show_kwargs(**values) age = 17 name = John 

Es perfectamente aceptable mezclar los dos, y siempre puede tener argumentos necesarios y argumentos adicionales opcionales para una función:

 >>> def mixed(required_arg, *args, **kwargs): ... print 'Required: %s' % required_arg ... if args: ... print 'Extra positional arguments: %s' % str(args) ... if kwargs: ... print 'Extra keyword arguments: %s' % kwargs ... >>> mixed(1) Required: 1 >>> mixed(1, 2, 3) Required: 1 Extra positional arguments: (2, 3) >>> mixed(1, 2, 3, test=True) Required: 1 Extra positional arguments: (2, 3) Extra keyword arguments: {'test': True} >>> args = (2, 3, 4) >>> kwargs = {'test': True, 'func': min} >>> mixed(*args, **kwargs) Required: 2 Extra positional arguments: (3, 4) Extra keyword arguments: {'test': True, 'func': } 

Si está tomando argumentos de palabras clave opcionales y desea tener valores predeterminados, recuerde que está tratando con un diccionario y, por lo tanto, puede usar su método get() con un valor predeterminado para usar si la clave no existe:

 >>> def take_keywords(**kwargs): ... print 'Test mode: %s' % kwargs.get('test', False) ... print 'Combining function: %s' % kwargs.get('func', all) ... >>> take_keywords() Test mode: False Combining function:  >>> take_keywords(func=any) Test mode: False Combining function:  

zip(*zipped) significa “alimentar cada elemento de zipped como un argumento para zip “. zip es similar a la transposición de una matriz, ya que hacerlo de nuevo lo dejará donde comenzó.

 >>> a = [(1, 2, 3), (4, 5, 6)] >>> b = zip(*a) >>> b [(1, 4), (2, 5), (3, 6)] >>> zip(*b) [(1, 2, 3), (4, 5, 6)] 

En realidad, es bastante simple una vez que realmente entiendes lo que hace zip() .

La función zip toma varios argumentos (todos de tipo iterable) y empareja elementos de estos iterables de acuerdo con sus respectivas posiciones.

Por ejemplo, digamos que tenemos dos argumentos ranked_athletes, rewards pasadas a zip , la función llamada zip(ranked_athletes, rewards ) será:

  • par atleta que ocupó el primer lugar (posición i = 0) con la primera / mejor recompensa (posición i = 0)
  • moverá el siguiente elemento, i = 1
  • empareja el 2º atleta con su recompensa, el 2º de la reward .

Esto se repetirá hasta que no haya más atleta o recompensa. Por ejemplo, si tomamos los 100 m en los Juegos Olímpicos de 2016 y zip las recompensas que tenemos:

 ranked_athletes = ["Usain Bolt", "Justin Gatlin", "Andre De Grasse", "Yohan Blake"] rewards = ["Gold medal", "Silver medal", "Bronze medal"] zip(ranked_athletes, rewards) 

Devolverá un iterador sobre las siguientes tuplas (pares):

 ('Usain Bolt', 'Gold medal') ('Justin Gatlin', 'Silver medal') ('Andre De Grasse', 'Bronze medal') 

Fíjate en que Yohan Blake no tiene recompensa.

Ahora, el operador * , esto es aún más simple, si tiene una lista [1, 2] esto lo desempaqueta en 1, 2 . Básicamente, transforma un objeto en muchos (tantos como el tamaño de la lista).

Entonces, si combinamos estos dos, zip(*x) significa en realidad: tomar esta lista de objetos, descomprimirla en muchos objetos y emparejar elementos de todos estos objetos de acuerdo con sus índices . Solo tiene sentido si los objetos son iterables (como las listas, por ejemplo) de lo contrario, la noción de índice realmente no tiene sentido.

Aquí está lo que parece si lo haces paso a paso:

 >>> print(x) # x is a list of lists [[1, 2, 3], ['a', 'b', 'c', 'd']] >>> print(*x) # unpack x [1, 2, 3] ['a', 'b', 'c', 'd'] >>> print(list(zip(*x))) # And pair items from the resulting lists [(1, 'a'), (2, 'b'), (3, 'c')] 

Tenga en cuenta que en este caso, si llamamos print(list(zip(x))) solo emparejaremos los elementos de x (que son 2 listas) con nada (ya que no hay otro iterable para vincularlos):

 [ ([1, 2, 3], ), (['a', 'b', 'c', 'd'], )] ^ ^ [1, 2, 3] is paird with nothing | | same for the 2nd item from x: ['a', 'b', 'c', 'd'] 

Otra buena manera de entender cómo funciona zip es mediante la implementación de su propia versión, aquí hay algo que hará más o menos el mismo trabajo que zip pero se limita al caso de dos listas (en lugar de muchas iterables):

 def zip_two_lists(A, B): shortest_list_size = min(len(A), len(B)) # We create empty pairs pairs = [tuple() for _ in range(shortest_list_size)] # And fill them with items from each iterable # according to their the items index: for index in range(shortest_list_size): pairs[index] = (A[index], B[index]) return pairs print(zip_two_lists(*x)) # Outputs: [(1, 'a'), (2, 'b'), (3, 'c')] 

Observe que no llamé print(list(zip_two_lists(*x))) porque esta función, a diferencia del zip real, no es un generador (una función que construye un iterador), sino que creamos una lista en la memoria. Por lo tanto, esta función no es tan buena, puede encontrar una mejor aproximación al zip real en la documentación de Python . A menudo es una buena idea leer estas equivalencias de código que tiene en torno a esta documentación, es una buena manera de entender lo que hace una función sin ambigüedades.

Propongo este para descomprimir una lista comprimida de listas cuando se haga zip con izip_longest:

 >>> a =[2,3,4,5,6] >>> b = [5,4,3,2] >>> c=[1,0]] >>>[list([val for val in k if val != None]) for k in zip(*itertools.izip_longest(a,b,c))] 

como izip_longest está agregando None para listas más cortas que las más largas, elimino None de antemano. Y vuelvo a la original a, b, c

 [[2, 3, 4, 5, 6], [5, 4, 3, 2], [1, 0]]