Menggabungkan dua daftar - perbedaan antara '+ =' dan extended ()

243

Saya telah melihat sebenarnya ada dua (mungkin lebih) cara untuk menggabungkan daftar dengan Python: Salah satu caranya adalah dengan menggunakan metode extended ():

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

yang lain untuk menggunakan operator plus (+):

b += a

Sekarang saya bertanya-tanya: Manakah dari dua opsi itu yang merupakan cara 'pythonic' untuk melakukan daftar concatenation dan apakah ada perbedaan di antara keduanya (saya telah melihat tutorial Python resmi tetapi tidak dapat menemukan apa pun tentang topik ini).

metode helpermet
sumber
1
Mungkin perbedaannya memiliki lebih banyak implikasi ketika berhadapan dengan ducktyping dan jika Anda mungkin tidak benar-benar-daftar-tetapi-suka-daftar- mendukung .__iadd__()/ .__add__()/ .__radd__()versus.extend()
Nick T

Jawaban:

214

Satu-satunya perbedaan pada tingkat bytecode adalah bahwa .extendcara melibatkan pemanggilan fungsi, yang sedikit lebih mahal di Python daripada INPLACE_ADD.

Ini benar-benar tidak perlu Anda khawatirkan, kecuali jika Anda melakukan operasi ini miliaran kali. Namun demikian, kemungkinan kemacetan akan terletak di tempat lain.

SilentGhost
sumber
16
Mungkin perbedaannya memiliki lebih banyak implikasi ketika berhadapan dengan ducktyping dan jika Anda mungkin tidak benar-benar-daftar-tetapi-suka-daftar- mendukung .__iadd__()/ .__add__()/ .__radd__()versus.extend()
Nick T
8
Jawaban ini gagal menyebutkan perbedaan pelingkupan yang penting.
wim
3
Yah sebenarnya, extends lebih cepat daripada INPLACE_ADD () yaitu daftar concatenation. gist.github.com/mekarpeles/3408081
Archit Kapoor
178

Anda tidak dapat menggunakan + = untuk variabel non-lokal (variabel yang bukan lokal untuk fungsi dan juga bukan global)

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

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

    def boo():
        l += [5]

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

main()

Itu karena untuk memperpanjang case compiler akan memuat variabel lmenggunakan LOAD_DEREFinstruksi, tetapi untuk + = itu akan menggunakan LOAD_FAST- dan Anda dapatkan*UnboundLocalError: local variable 'l' referenced before assignment*

monitorius
sumber
4
Saya mengalami kesulitan dengan penjelasan Anda "variabel yang tidak lokal untuk fungsi dan juga tidak global " dapatkah Anda memberikan contoh variabel seperti itu?
Stephane Rolland
8
Variabel 'l' dalam contoh saya persis seperti itu. Ini bukan lokal untuk fungsi 'foo' dan 'boo' (di luar cakupannya), tetapi ini bukan global (didefinisikan di dalam fungsi 'utama', bukan pada tingkat modul)
monitorius
3
Saya dapat mengonfirmasi bahwa kesalahan ini masih terjadi dengan python 3.4.2 (Anda harus menambahkan tanda kurung untuk dicetak tetapi yang lainnya tetap sama).
trichoplax
7
Betul sekali. Tapi setidaknya Anda bisa menggunakan pernyataan l nonlocal di boo di Python3.
monitorius
compiler -> interpreter?
joelb
42

Anda dapat membuat panggilan fungsi, tetapi Anda tidak dapat + = melakukan panggilan fungsi secara langsung:

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

Saya akan mengatakan bahwa ada beberapa perbedaan ketika datang dengan numpy (saya hanya melihat bahwa pertanyaannya bertanya tentang menggabungkan dua daftar, bukan array numpy, tetapi karena mungkin menjadi masalah bagi pemula, seperti saya, saya harap ini dapat membantu seseorang yang mencari solusi untuk posting ini), mis.

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

itu akan kembali dengan kesalahan

ValueError: operan tidak dapat disiarkan bersama dengan bentuk (0,) (4,4,4)

b.extend(a) bekerja dengan sempurna

Lance Ruo Zhang
sumber
5

Dari kode sumber CPython 3.5.2 : Tidak ada perbedaan besar.

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

memperpanjang () bekerja dengan iterable *, + = bekerja dengan beberapa tetapi bisa menjadi 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
* cukup yakin .extend () berfungsi dengan iterable tapi tolong beri komentar jika saya salah

atap
sumber
Tuple jelas merupakan iterable, tetapi tidak memiliki metode extended (). memperpanjang () metode tidak ada hubungannya dengan iterasi.
wombatonfire
.extend adalah metode kelas daftar. Dari dokumentasi Python: list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.Tebak saya menjawab tanda bintang saya sendiri.
Grofte
Oh, maksud Anda, Anda dapat melewati segala kemungkinan untuk memperpanjang (). Saya membacanya sebagai "memperpanjang () tersedia untuk setiap iterable" :) Buruk saya, tetapi kedengarannya agak ambigu.
wombatonfire
1
Secara keseluruhan, ini bukan contoh yang baik, setidaknya tidak dalam konteks pertanyaan ini. Ketika Anda menggunakan +=operator dengan objek dari tipe yang berbeda (bertentangan dengan dua daftar, seperti dalam pertanyaan), Anda tidak bisa berharap bahwa Anda akan mendapatkan gabungan dari objek. Dan Anda tidak dapat berharap bahwa akan ada listjenis yang dikembalikan. Lihatlah kode Anda, Anda mendapatkan numpy.ndarrayalih - alih list.
wombatonfire
2

Sebenarnya, ada perbedaan antara tiga pilihan: ADD, INPLACE_ADDdanextend . Yang pertama selalu lebih lambat, sementara dua lainnya kira-kira sama.

Dengan informasi ini, saya lebih suka menggunakan extend, yang lebih cepat daripada ADD, dan menurut saya lebih eksplisit dari apa yang Anda lakukan daripada INPLACE_ADD.

Coba kode berikut beberapa kali (untuk 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
sumber
2
Anda tidak dapat membandingkan ADDdengan INPLACE_ADDdan extend(). ADDmenghasilkan daftar baru dan menyalin elemen dari dua daftar asli ke dalamnya. Pasti itu akan lebih lambat daripada operasi inplace dari INPLACE_ADDdan extend().
wombatonfire
Saya tahu itu. Inti dari contoh ini adalah membandingkan berbagai cara memiliki daftar dengan semua elemen secara bersamaan. Tentu butuh waktu lebih lama karena melakukan hal-hal yang berbeda, tetapi tetap baik untuk mengetahui jika Anda tertarik untuk melestarikan benda-benda asli tanpa diubah.
dalonsoa
1

Saya telah mencari tutorial resmi Python tetapi tidak dapat menemukan apa pun tentang topik ini

Informasi ini kebetulan terkubur di FAQ Pemrograman :

... untuk daftar, __iadd__[yaitu +=] setara dengan memanggil extenddaftar dan mengembalikan daftar. Itu sebabnya kami mengatakan bahwa untuk daftar, +=adalah "singkatan" untuklist.extend

Anda juga dapat melihatnya sendiri dalam kode sumber CPython: https://github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011

Aliran
sumber
-1

Menurut Python untuk Analisis Data.

“Perhatikan bahwa daftar gabungan dengan penambahan adalah operasi yang relatif mahal karena daftar baru harus dibuat dan objek disalin. Menggunakan perluasan untuk menambahkan elemen ke daftar yang ada, terutama jika Anda membangun daftar besar, biasanya lebih disukai. " Jadi,

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

lebih cepat daripada alternatif ratenatif:

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

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

littlebear333
sumber
4
everything = everything + temptidak harus diimplementasikan dengan cara yang sama seperti everything += temp.
David Harrison
1
Kamu benar. Terima kasih atas pengingatnya. Tetapi poin saya adalah tentang perbedaan efisiensi. :)
littlebear333
6
@ littlebear333 everything += tempdiimplementasikan sedemikian rupa sehingga everythingtidak perlu disalin. Ini cukup banyak membuat jawaban Anda menjadi titik perdebatan.
nog642