Об’єднання двох списків - різниця між '+ =' та розширенням ()


243

Я бачив, що насправді є два (можливо більше) способи об'єднання списків у Python: Один із способів - це використовувати метод exte ()

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

інший - використовувати оператор плюс (+):

b += a

Тепер мені цікаво: який із цих двох варіантів є "пітонічним" способом конкатенації списку і чи є різниця між ними (я шукав офіційний підручник з Python, але не міг нічого знайти на цю тему).


1
Можливо, різниця має більше наслідків, коли мова йде про каченяти, і якщо ваш, можливо, не справді список, але такий, як список підтримує .__iadd__()/ .__add__()/ .__radd__()проти.extend()
Nick T

Відповіді:


214

Єдина відмінність рівня байт-коду полягає в тому, що .extendспосіб включає виклик функції, який у Python трохи дорожчий, ніжINPLACE_ADD .

Це насправді не про що ви повинні турбуватися, якщо ви не виконуєте цю операцію в мільярди разів. Однак, ймовірно, що вузьке місце лежатиме де-небудь ще.


16
Можливо, різниця має більше наслідків, коли мова йде про каченяти, і якщо ваш, можливо, не справді список, але такий, як список, підтримує .__iadd__()/ .__add__()/ .__radd__()проти.extend()
Нік Т

8
У цій відповіді не згадуються важливі відмінності в оцінці.
Вім

3
Насправді, розширення швидше, ніж INPLACE_ADD (), тобто з'єднання списку. gist.github.com/mekarpeles/3408081
Archit Kapoor

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*


4
У мене виникають труднощі з вашим поясненням "змінна, яка не є локальною для функції, а також не є глобальною ", чи можете ви навести приклад такої змінної?
Стефан Ролланд

8
Змінна 'l' у моєму прикладі саме такого типу. Це не локально для функцій 'foo' та 'boo' (поза їх областями), але це не глобально (визначається всередині 'main' func, не на рівні модуля)
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

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

4

exte () працює з будь-яким ітерабельним *, + = працює з деякими, але може стати фанк.

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 () працює з будь-яким ітерабельним, але будь ласка, прокоментуйте, якщо я невірний


Tuple, безумовно, є ітерабельним, але він не має методу exte (). метод exte () не має нічого спільного з ітерацією.
вогняний вогонь

.extend - метод класу списку. З документації Python: list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.Здогадайтесь, я відповів власною зірочкою.
grofte

О, ви мали на увазі, що ви можете передати будь-який ітерабельний файл для продовження (). Я читаю це як "extension () доступний для будь-якого ітерабельного" :) Моє погано, але це звучить трохи неоднозначно.
вогнепальний вогонь

1
Загалом, це не гарний приклад, принаймні не в контексті цього питання. Якщо ви використовуєте +=оператор з об'єктами різного типу (всупереч двом спискам, як у запитанні), ви не можете очікувати, що ви отримаєте конкатенацію об'єктів. І ви не можете розраховувати, що повернеться listтип. Подивіться на свій код, ви отримаєте numpy.ndarrayзамість цього list.
вогнепальний вогонь

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

2
Ви не можете порівняти ADDз INPLACE_ADDі extend(). ADDстворює новий список і копіює в нього елементи двох оригінальних списків. Напевно, це буде повільніше, ніж робота на місці INPLACE_ADDі extend().
вогняний вогонь

Я це знаю. Суть цього прикладу - порівняння різних способів складання списку з усіма елементами разом. Звичайно, це займає більше часу, оскільки це робить різні речі, але все-таки добре це знати, якщо ви зацікавлені в збереженні оригінальних об'єктів без змін.
далонсоа

1

Я переглянув офіційний підручник з Python, але не зміг нічого знайти на цю тему

Ця інформація може бути захована у FAQ щодо програмування :

... для списків __iadd__[тобто +=] еквівалентно виклику extendдо списку та поверненню списку. Тому ми говоримо, що для списків +=це "стенограма" дляlist.extend

Ви також можете це побачити у вихідному коді CPython: https://github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011


-1

За даними Python для аналізу даних.

«Зауважте, що конкатенація списку шляхом додавання є порівняно дорогою операцією, оскільки слід створити новий список і скопіювати об'єкти на них. Зазвичай використовувати перевагу для додавання елементів до наявного списку, особливо якщо ви створюєте великий список. Таким чином,

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

швидше, ніж конкатенативна альтернатива:

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

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


4
everything = everything + tempне обов'язково реалізується так само, як everything += temp.
Девід Гаррісон

1
Ти правий. Дякуємо за ваше нагадування. Але моя суть полягає в різниці ефективності. :)
littlebear333

6
@ littlebear333 everything += tempреалізовано таким чином, що everythingйого не потрібно копіювати. Це в значній мірі робить вашу відповідь суперечливою.
nog642
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.