Объединение двух списков - разница между '+ =' и extend ()

243

Я видел, что на самом деле есть два (возможно, больше) способа объединения списков в Python: Один из способов - это использовать метод extend ():

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

другой использовать оператор плюс (+):

b += a

Теперь я задаюсь вопросом: какой из этих двух вариантов является «питоническим» способом объединения списков и есть ли разница между ними (я посмотрел официальный учебник по Python, но ничего не нашел по этой теме).

helpermethod
источник
1
Возможно, разница имеет большее значение, когда дело доходит до утиного типа, и если ваш « может быть, не совсем список, но как список поддерживает» .__iadd__()/ .__add__()/ .__radd__()против.extend()
Ник Т

Ответы:

214

Единственное отличие на уровне байт-кода заключается в том, что этот .extendспособ включает вызов функции, который в Python немного дороже, чем INPLACE_ADD.

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

SilentGhost
источник
16
Возможно, разница имеет большее значение, когда дело доходит до утиного типа, и если ваш « может быть, не совсем список, но похожий на список» поддерживает .__iadd__()/ .__add__()/ .__radd__()против.extend()
Ник Т
8
В этом ответе не упоминаются важные различия в области видимости.
Вим
3
Ну, на самом деле, extends выполняется быстрее, чем INPLACE_ADD (), то есть конкатенация списка. gist.github.com/mekarpeles/3408081
Капур
178

Вы не можете использовать + = для нелокальной переменной (переменная, которая не является локальной для функции, а также не глобальной)

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

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

    def boo():
        l += [5]

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

main()

Это потому, что для расширенного случая компилятор загрузит переменную, lиспользуя LOAD_DEREFинструкцию, но для + = он будет использовать LOAD_FAST- и вы получите*UnboundLocalError: local variable 'l' referenced before assignment*

monitorius
источник
4
У меня возникли трудности с вашим объяснением «переменная, которая не является локальной для функции, а также не глобальной », не могли бы вы привести пример такой переменной?
Стефан Роллан
8
Переменная 'l' в моем примере именно такого рода. Это не локально для функций 'foo' и 'boo' (вне их областей), но это не глобально (определено внутри функции 'main', а не на уровне модуля)
monitorius
3
Я могу подтвердить, что эта ошибка все еще происходит с python 3.4.2 (вам нужно добавить скобки для печати, но все остальное может остаться прежним).
Трихоплакс
7
Это правильно. Но по крайней мере вы можете использовать нелокальный оператор l в boo в Python3.
monitorius
компилятор -> интерпретатор?
Joelb
42

Вы можете связывать вызовы функций, но не можете + = вызов функции напрямую:

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
источник
8

Я бы сказал, что есть некоторая разница, когда речь идет о numpy (я только что увидел вопрос о объединении двух списков, а не массива numpy, но, поскольку это может быть проблемой для новичка, такого как я, я надеюсь, что это может кому-то помочь кто ищет решение этого поста), к примеру.

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

он вернется с ошибкой

ValueError: операнды не могут быть переданы вместе с формами (0,) (4,4,4)

b.extend(a) работает отлично

Ланс Руо Чжан
источник
5

Из исходного кода CPython 3.5.2 : большой разницы нет.

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
источник
4

extend () работает с любыми повторяемыми *, + = работает с некоторыми, но может стать прикольным.

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
* уверен, что .extend () работает с любой итерацией, но, пожалуйста, прокомментируйте, если я ошибаюсь

grofte
источник
Tuple, безусловно, итеративный, но у него нет метода extended (). Метод extend () не имеет ничего общего с итерацией.
wombatonfire
.extend - это метод класса списка. Из документации Python: list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.думаю, я ответил на свою звездочку.
Грофте
О, вы имели в виду, что вы можете передать любую итерацию в extension (). Я читаю это как "extend () доступен для любого повторяемого" :) Мое плохо, но это звучит немного двусмысленно.
wombatonfire
1
В общем, это не хороший пример, по крайней мере, не в контексте этого вопроса. Когда вы используете +=оператор с объектами разных типов (в отличие от двух списков, как в вопросе), вы не можете ожидать, что вы получите объединение объектов. И вы не можете ожидать, что будет listвозвращен тип. Посмотрите на ваш код, вы получите numpy.ndarrayвместо list.
wombatonfire
2

На самом деле, существует различие между тремя вариантами: ADD, INPLACE_ADDи extend. Первый всегда медленнее, а два других примерно одинаковы.

С этой информацией я бы предпочел использовать extend, что быстрее ADD, и, как мне кажется, более явно о том, что вы делаете, чем INPLACE_ADD.

Попробуйте следующий код несколько раз (для 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
источник
2
Вы не можете сравнить ADDс INPLACE_ADDи extend(). ADDсоздает новый список и копирует в него элементы двух оригинальных списков. Конечно, это будет медленнее, чем на месте операции INPLACE_ADDи extend().
wombatonfire
Я знаю это. Смысл этого примера - сравнение разных способов составления списка со всеми элементами вместе. Конечно, это занимает больше времени, потому что это делает разные вещи, но все же это хорошо знать, если вы заинтересованы в сохранении исходных объектов без изменений.
Далонсоа
1

Я посмотрел официальный учебник по Python, но ничего не смог найти по этой теме

Эта информация находится в FAQ по программированию :

... для списков __iadd__[то есть +=] эквивалентно вызову extendв списке и возвращению списка. Вот почему мы говорим, что для списков, +=это «сокращение» дляlist.extend

Вы также можете убедиться в этом в исходном коде CPython: https://github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011

Flux
источник
-1

Согласно Python для анализа данных.

«Обратите внимание, что объединение списков путем сложения является сравнительно дорогой операцией, поскольку необходимо создать новый список и скопировать объекты. Использование расширения для добавления элементов в существующий список, особенно если вы создаете большой список, обычно предпочтительнее. Таким образом,

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

быстрее конкатенативной альтернативы:

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

введите описание изображения здесь введите описание изображения здесь

littlebear333
источник
4
everything = everything + tempне обязательно реализуется так же, как everything += temp.
Дэвид Харрисон
1
Ты прав. Спасибо за ваше напоминание. Но моя точка зрения касается разницы в эффективности. :)
littlebear333
6
@ littlebear333 everything += tempреализован таким образом, что everythingего не нужно копировать. Это в значительной степени делает ваш ответ спорным.
nog642