web-dev-qa-db-fra.com

Décompression et opérateur *

La documentation python donne ce code comme l'opération inverse de Zip:

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

En particulier, "Zip () conjointement avec l'opérateur * peut être utilisé pour décompresser une liste". Quelqu'un peut-il m'expliquer comment fonctionne * l'opérateur dans ce cas? Pour autant que je sache, * est un opérateur binaire et peut être utilisé pour la multiplication ou la copie superficielle ... ni l'un ni l'autre ne semble être le cas ici.

31
Leah Xue

Lorsqu'il est utilisé comme ceci, le * (astérisque, également connu dans certains cercles comme l'opérateur "splat") est un signal pour décompresser les arguments d'une liste. Voir http://docs.python.org/tutorial/controlflow.html#unpacking-argument-lists pour une définition plus complète avec des exemples.

22
Philip Southam

Bien que réponse de hammar explique comment fonctionne l'inversion dans le cas de la fonction Zip(), il peut être utile d'examiner le déballage des arguments dans un sens plus général. Disons que nous avons une fonction simple qui prend quelques arguments:

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

Au lieu de spécifier directement les arguments, nous pouvons créer une liste (ou un tuple d'ailleurs) pour les conserver, puis dire Python à décompresser cette liste et utilise son contenu comme arguments de la fonction:

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

Cela se comporte normalement si vous n'avez pas assez d'arguments (ou trop):

>>> arguments = [42, 'insert value here']
>>> do_something(*arguments)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/blair/<ipython console> in <module>()

TypeError: do_something() takes exactly 3 arguments (2 given)

Vous pouvez utiliser la même construction lors de la définition d'une fonction pour accepter n'importe quel nombre d'arguments positionnels. Ils sont donnés à votre fonction de Tuple:

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

Et bien sûr, vous pouvez combiner les deux techniques:

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

Vous pouvez faire une chose similaire avec des arguments de mots clés, en utilisant un double astérisque (**) Et un dictionnaire:

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

Et, bien sûr, vous pouvez passer des arguments de mots clés dans un dictionnaire:

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

Il est parfaitement acceptable de mélanger les deux, et vous pouvez toujours avoir des arguments requis et des arguments supplémentaires facultatifs pour une fonction:

>>> 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': <built-in function min>}

Si vous prenez des arguments de mots clés facultatifs et que vous souhaitez avoir des valeurs par défaut, n'oubliez pas que vous avez affaire à un dictionnaire et que vous pouvez donc utiliser sa méthode get() avec une valeur par défaut à utiliser si la clé n'existe pas:

>>> 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: <built-in function all>
>>> take_keywords(func=any)
Test mode: False
Combining function: <built-in function any>
61
Blair

Zip(*zipped) signifie "alimenter chaque élément de zipped en argument à Zip". Zip est similaire à la transposition d'une matrice dans la mesure où le faire à nouveau vous ramènera là où vous avez commencé.

>>> 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)]
21
hammar

C'est en fait assez simple une fois que vous comprenez vraiment ce que fait Zip().

La fonction Zip prend plusieurs arguments (tous de type itérable) et associe les éléments de ces itérables selon leurs positions respectives.

Par exemple, supposons que deux arguments ranked_athletes, rewards Soient passés à Zip, l'appel de fonction Zip(ranked_athletes, rewards):

  • jumeler l'athlète qui s'est classé premier (position i = 0) avec la première/meilleure récompense (position i = 0)
  • il déplacera l'élément suivant, i = 1
  • jumeler le 2e athlète avec sa récompense, le 2e de reward.
  • ...

Cela sera répété jusqu'à ce qu'il n'y ait plus d'athlète ou de récompense. Par exemple, si nous prenons le 100m aux Jeux olympiques de 2016 et Zip les récompenses que nous avons:

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

Renvoie un itérateur sur les tuples (paires) suivants:

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

Remarquez comment Yohan Blake n'a aucune récompense.

Maintenant l'opérateur *, C'est encore plus simple, si vous avez une liste [1, 2] Cela la décompresse en 1, 2. Il transforme essentiellement un objet en plusieurs (autant que la taille de la liste).

Donc, si nous combinons ces deux, Zip(*x) signifie en fait: prenez cette liste d'objets, décompressez-la dans de nombreux objets et associez des éléments de tous ces objets selon leurs index. Cela n'a de sens que si les objets sont itérables (comme les listes par exemple) sinon la notion de index n'a pas vraiment de sens.

Voici à quoi cela ressemble si vous le faites pas à pas:

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

Notez que dans ce cas, si nous appelons print(list(Zip(x))) nous appairons simplement les éléments de x (qui sont 2 listes) avec rien (car il n'y a pas d'autre itérable pour les associer):

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

Une autre bonne façon de comprendre le fonctionnement de Zip est d'implémenter votre propre version, voici quelque chose qui fera plus ou moins le même travail que Zip mais limité au cas de deux listes (au lieu de de nombreux itérables):

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

Remarquez comment je n'ai pas appelé print(list(Zip_two_lists(*x))) c'est parce que cette fonction contrairement au vrai Zip n'est pas un générateur (une fonction qui construit un itérateur), mais à la place nous créons une liste en mémoire. Par conséquent, cette fonction n'est pas aussi bonne, vous pouvez trouver un meilleur approximation du vrai Zip dans la documentation de Python . C'est souvent une bonne idée de lire ces équivalences de code que vous avez tout autour de cette documentation, c'est un bon moyen de comprendre ce qu'une fonction fait sans ambiguïté.

1
cglacet