Concaténation de deux listes - différence entre '+ =' et extend ()

243

J'ai vu qu'il y a en fait deux (peut-être plus) façons de concaténer des listes en Python: Une façon est d'utiliser la méthode extend ():

a = [1, 2]
b = [2, 3]
b.extend(a)

l'autre pour utiliser l'opérateur plus (+):

b += a

Maintenant, je me demande: Laquelle de ces deux options est la façon `` pythonique '' de faire la concaténation de liste et y a-t-il une différence entre les deux (j'ai consulté le didacticiel Python officiel mais je n'ai rien trouvé sur ce sujet).

méthode d'aide
la source
1
Peut-être que la différence a plus d'implications en ce qui concerne le typage de canard et si votre peut-être pas vraiment une liste mais comme une liste prend en charge .__iadd__()/ .__add__()/ .__radd__()versus.extend()
Nick T

Réponses:

214

La seule différence au niveau du bytecode est que le .extendchemin implique un appel de fonction, qui est légèrement plus cher en Python que le INPLACE_ADD.

Ce n'est vraiment rien dont vous devriez vous inquiéter, sauf si vous effectuez cette opération des milliards de fois. Il est cependant probable que le goulot d'étranglement se situerait ailleurs.

SilentGhost
la source
16
Peut-être que la différence a plus d'implications en ce qui concerne le typage de canard et si votre peut-être pas vraiment une liste mais comme une liste prend en charge .__iadd__()/ .__add__()/ .__radd__()contre.extend()
Nick T
8
Cette réponse ne mentionne pas les différences de portée importantes.
wim
3
Et bien en fait, extend est plus rapide que INPLACE_ADD () c'est-à-dire la concaténation de liste. gist.github.com/mekarpeles/3408081
Archit Kapoor
178

Vous ne pouvez pas utiliser + = pour une variable non locale (variable qui n'est pas locale pour la fonction et non globale)

def main():
    l = [1, 2, 3]

    def foo():
        l.extend([4])

    def boo():
        l += [5]

    foo()
    print l
    boo()  # this will fail

main()

C'est parce que pour le compilateur de cas d' extension, la variable sera chargée en lutilisant l' LOAD_DEREFinstruction, mais pour + = il utilisera LOAD_FAST- et vous obtenez*UnboundLocalError: local variable 'l' referenced before assignment*

monitorius
la source
4
J'ai des difficultés avec votre explication "variable qui n'est pas locale pour la fonction et non globale " pourriez-vous donner un exemple d'une telle variable?
Stephane Rolland
8
La variable «l» dans mon exemple est exactement de ce type. Ce n'est pas local pour les fonctions 'foo' et 'boo' (en dehors de leurs portées), mais ce n'est pas global (défini à l'intérieur de la fonction 'principale', pas au niveau du module)
monitorius
3
Je peux confirmer que cette erreur se produit toujours avec python 3.4.2 (vous devrez ajouter des parenthèses à imprimer mais tout le reste peut rester le même).
trichoplax
7
C'est vrai. Mais au moins, vous pouvez utiliser l' instruction l non locale dans boo en Python3.
monitorius
compilateur -> interprète?
joelb
42

Vous pouvez chaîner des appels de fonction, mais vous ne pouvez pas + = un appel de fonction directement:

class A:
    def __init__(self):
        self.listFoo = [1, 2]
        self.listBar = [3, 4]

    def get_list(self, which):
        if which == "Foo":
            return self.listFoo
        return self.listBar

a = A()
other_list = [5, 6]

a.get_list("Foo").extend(other_list)
a.get_list("Foo") += other_list  #SyntaxError: can't assign to function call
isarandi
la source
8

Je dirais qu'il y a une différence en ce qui concerne numpy (je viens de voir que la question demande de concaténer deux listes, pas un tableau numpy, mais comme cela pourrait être un problème pour les débutants, comme moi, j'espère que cela peut aider quelqu'un qui cherchent la solution à ce post), par ex.

import numpy as np
a = np.zeros((4,4,4))
b = []
b += a

il reviendra avec erreur

ValueError: les opérandes n'ont pas pu être diffusés avec les formes (0,) (4,4,4)

b.extend(a) fonctionne parfaitement

Lance Ruo Zhang
la source
5

Du code source de CPython 3.5.2 : Pas de grande différence.

static PyObject *
list_inplace_concat(PyListObject *self, PyObject *other)
{
    PyObject *result;

    result = listextend(self, other);
    if (result == NULL)
        return result;
    Py_DECREF(result);
    Py_INCREF(self);
    return (PyObject *)self;
}
VicX
la source
4

extend () fonctionne avec n'importe quel itérable *, + = fonctionne avec certains mais peut devenir funky.

import numpy as np

l = [2, 3, 4]
t = (5, 6, 7)
l += t
l
[2, 3, 4, 5, 6, 7]

l = [2, 3, 4]
t = np.array((5, 6, 7))
l += t
l
array([ 7,  9, 11])

l = [2, 3, 4]
t = np.array((5, 6, 7))
l.extend(t)
l
[2, 3, 4, 5, 6, 7]

Python 3.6
* assez sûr .extend () fonctionne avec n'importe quel itérable mais veuillez commenter si je me trompe

grofte
la source
Tuple est définitivement un itérable, mais il n'a pas de méthode extend (). La méthode extend () n'a rien à voir avec l'itération.
wombatonfire du
.extend est une méthode de la classe list. De la documentation Python: list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.Je suppose que j'ai répondu à mon propre astérisque.
grofte
Oh, vous vouliez dire que vous pouvez passer n'importe quel itérable pour étendre (). Je l'ai lu comme "extend () est disponible pour tout itérable" :) Mon mauvais, mais cela semble un peu ambigu.
wombatonfire
1
Dans l'ensemble, ce n'est pas un bon exemple, du moins pas dans le contexte de cette question. Lorsque vous utilisez un +=opérateur avec des objets de types différents (contrairement à deux listes, comme dans la question), vous ne pouvez pas vous attendre à obtenir une concaténation des objets. Et vous ne pouvez pas vous attendre à ce qu'un listtype soit renvoyé. Jetez un oeil à votre code, vous obtenez un numpy.ndarrayau lieu de list.
wombatonfire
2

En fait, il existe des différences entre les trois options: ADD, INPLACE_ADDetextend . Le premier est toujours plus lent, tandis que les deux autres sont à peu près les mêmes.

Avec ces informations, je préfère utiliser extend, qui est plus rapide que ADD, et me semble plus explicite que ce que vous faites INPLACE_ADD.

Essayez plusieurs fois le code suivant (pour Python 3):

import time

def test():
    x = list(range(10000000))
    y = list(range(10000000))
    z = list(range(10000000))

    # INPLACE_ADD
    t0 = time.process_time()
    z += x
    t_inplace_add = time.process_time() - t0

    # ADD
    t0 = time.process_time()
    w = x + y
    t_add = time.process_time() - t0

    # Extend
    t0 = time.process_time()
    x.extend(y)
    t_extend = time.process_time() - t0

    print('ADD {} s'.format(t_add))
    print('INPLACE_ADD {} s'.format(t_inplace_add))
    print('extend {} s'.format(t_extend))
    print()

for i in range(10):
    test()
ADD 0.3540440000000018 s
INPLACE_ADD 0.10896000000000328 s
extend 0.08370399999999734 s

ADD 0.2024550000000005 s
INPLACE_ADD 0.0972940000000051 s
extend 0.09610200000000191 s

ADD 0.1680199999999985 s
INPLACE_ADD 0.08162199999999586 s
extend 0.0815160000000077 s

ADD 0.16708400000000267 s
INPLACE_ADD 0.0797719999999913 s
extend 0.0801490000000058 s

ADD 0.1681250000000034 s
INPLACE_ADD 0.08324399999999343 s
extend 0.08062700000000689 s

ADD 0.1707760000000036 s
INPLACE_ADD 0.08071900000000198 s
extend 0.09226200000000517 s

ADD 0.1668420000000026 s
INPLACE_ADD 0.08047300000001201 s
extend 0.0848089999999928 s

ADD 0.16659500000000094 s
INPLACE_ADD 0.08019399999999166 s
extend 0.07981599999999389 s

ADD 0.1710910000000041 s
INPLACE_ADD 0.0783479999999912 s
extend 0.07987599999999873 s

ADD 0.16435900000000458 s
INPLACE_ADD 0.08131200000001115 s
extend 0.0818660000000051 s
dalonsoa
la source
2
Vous ne pouvez pas comparer ADDavec INPLACE_ADDet extend(). ADDproduit une nouvelle liste et y copie les éléments des deux listes originales. Pour sûr, il sera plus lent que le fonctionnement sur place de INPLACE_ADDet extend().
wombatonfire
Je le sais. Le but de cet exemple est de comparer différentes façons d'avoir une liste avec tous les éléments ensemble. Bien sûr, cela prend plus de temps car cela fait différentes choses, mais il est bon de le savoir au cas où vous souhaiteriez conserver les objets originaux inchangés.
dalonsoa
1

J'ai recherché le didacticiel Python officiel mais je n'ai rien trouvé sur ce sujet

Il se trouve que ces informations sont enfouies dans la FAQ de programmation :

... pour les listes, __iadd__[c.-à-d. +=] équivaut à appeler extendla liste et à renvoyer la liste. Voilà pourquoi nous disons que pour les listes, +=est un "raccourci" pourlist.extend

Vous pouvez également le constater par vous-même dans le code source CPython: https://github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011

Flux
la source
-1

Selon Python for Data Analysis.

«Notez que la concaténation de liste par addition est une opération relativement coûteuse car une nouvelle liste doit être créée et les objets copiés. Utiliser extend pour ajouter des éléments à une liste existante, en particulier si vous créez une grande liste, est généralement préférable. " Donc,

everything = []
for chunk in list_of_lists:
    everything.extend(chunk)

est plus rapide que l'alternative concaténative:

everything = []
for chunk in list_of_lists:
    everything = everything + chunk

entrez la description de l'image ici entrez la description de l'image ici

littlebear333
la source
4
everything = everything + tempn'est pas nécessairement implémenté de la même manière que everything += temp.
David Harrison
1
Vous avez raison. Merci pour ton rappel. Mais mon point concerne la différence d'efficacité. :)
littlebear333
6
@ littlebear333 everything += tempest implémenté d'une manière qui everythingn'a pas besoin d'être copiée. Cela rend à peu près votre réponse un point discutable.
nog642