Kết hợp hai danh sách - sự khác biệt giữa '+ =' và ext ()


243

Tôi đã thấy thực sự có hai cách (có thể nhiều hơn) để nối các danh sách trong Python: Một cách là sử dụng phương thức extend ():

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

khác để sử dụng toán tử dấu cộng (+):

b += a

Bây giờ tôi tự hỏi: lựa chọn nào trong hai tùy chọn đó là cách kết hợp danh sách 'pythonic' và có sự khác biệt giữa hai tùy chọn này (tôi đã tra cứu hướng dẫn Python chính thức nhưng không thể tìm thấy bất cứ điều gì về chủ đề này).


1
Có lẽ sự khác biệt có nhiều ý nghĩa hơn khi nói đến vịt và nếu bạn có thể không thực sự là một danh sách-nhưng-giống-như-một-danh sách hỗ trợ .__iadd__()/ .__add__()/ .__radd__()so với.extend()
Nick T

Câu trả lời:


214

Sự khác biệt duy nhất ở mức mã byte là .extendcách liên quan đến lệnh gọi hàm, đắt hơn một chút trong Python so với INPLACE_ADD.

Thực sự không có gì bạn phải lo lắng, trừ khi bạn thực hiện thao tác này hàng tỷ lần. Tuy nhiên, có khả năng nút cổ chai sẽ nằm ở một nơi khác.


16
Có lẽ sự khác biệt có nhiều ý nghĩa hơn khi nói đến vịt và nếu bạn có thể không thực sự là một danh sách-nhưng-giống-như-một-danh sách hỗ trợ .__iadd__()/ .__add__()/ .__radd__()so với.extend()
Nick T

8
Câu trả lời này không đề cập đến sự khác biệt phạm vi quan trọng.
wim

3
Thực tế, việc mở rộng nhanh hơn INPLACE_ADD () tức là nối danh sách. gist.github.com/mekarpele/3408081
Archit Kapoor

178

Bạn không thể sử dụng + = cho biến không cục bộ (biến không cục bộ cho hàm và cũng không phải là toàn cục)

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

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

    def boo():
        l += [5]

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

main()

Đó là vì trình biên dịch trường hợp mở rộng sẽ tải biến lbằng cách sử dụng LOAD_DEREFlệnh, nhưng với + = nó sẽ sử dụng LOAD_FAST- và bạn nhận được*UnboundLocalError: local variable 'l' referenced before assignment*


4
Tôi gặp khó khăn với lời giải thích của bạn "biến không cục bộ cho hàm và cũng không phải là toàn cục " bạn có thể đưa ra ví dụ về biến đó không?
Stephane Rolland

8
Biến 'l' trong ví dụ của tôi chính xác là loại đó. Đó không phải là địa phương cho 'boo' chức năng (ngoài phạm vi của họ) 'foo' và, nhưng nó không phải là toàn cầu (bên trong định nghĩa 'chính' func, không phải trên cấp module)
monitorius

3
Tôi có thể xác nhận rằng lỗi này vẫn xảy ra với python 3.4.2 (bạn sẽ cần thêm dấu ngoặc đơn để in nhưng mọi thứ khác có thể giữ nguyên).
trichoplax

7
Đúng rồi. Nhưng ít nhất bạn có thể sử dụng câu lệnh l phi tiêu điểm trong boo trong Python3.
monitorius

trình biên dịch -> thông dịch viên?
tham gia

42

Bạn có thể xâu chuỗi các cuộc gọi chức năng, nhưng bạn không thể + = một cuộc gọi chức năng trực tiếp:

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

Tôi sẽ nói rằng có một số khác biệt khi đi kèm với numpy (tôi chỉ thấy rằng câu hỏi hỏi về việc ghép hai danh sách, không phải là mảng numpy, nhưng vì nó có thể là một vấn đề cho người mới bắt đầu, như tôi, tôi hy vọng điều này có thể giúp được ai đó người tìm kiếm giải pháp cho bài này), cho ex.

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

nó sẽ trở lại với lỗi

ValueError: toán hạng không thể được phát cùng với hình dạng (0,) (4,4,4)

b.extend(a) hoạt động hoàn hảo


5

Từ mã nguồn CPython 3.5.2 : Không có sự khác biệt lớn.

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

extend () hoạt động với bất kỳ iterable *, + = nào hoạt động với một số nhưng có thể trở nên thú vị.

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
* khá chắc chắn .extend () hoạt động với bất kỳ lần lặp nào nhưng vui lòng nhận xét nếu tôi không chính xác


Tuple chắc chắn là một iterable, nhưng nó không có phương thức extend (). Phương thức extend () không liên quan gì đến phép lặp.
hỏa hoạn

.extend là một phương thức của lớp danh sách. Từ tài liệu Python: list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.Đoán tôi đã trả lời dấu hoa thị của riêng tôi.
lưới

Ồ, bạn có nghĩa là bạn có thể vượt qua bất kỳ lần lặp nào để mở rộng (). Tôi đọc nó là "extend () có sẵn cho bất kỳ lần lặp nào" :) Thật tệ, nhưng nghe có vẻ hơi mơ hồ.
hỏa hoạn

1
Nói chung, đây không phải là một ví dụ hay, ít nhất là trong bối cảnh của câu hỏi này. Khi bạn sử dụng một +=toán tử với các đối tượng thuộc các loại khác nhau (trái với hai danh sách, như trong câu hỏi), bạn không thể mong đợi rằng bạn sẽ có được sự kết hợp của các đối tượng. Và bạn không thể mong đợi rằng sẽ có một listloại được trả về. Có một cái nhìn vào mã của bạn, bạn nhận được một numpy.ndarraythay vì list.
wombatonfire

2

Trên thực tế, có sự khác biệt giữa ba lựa chọn: ADD, INPLACE_ADDextend . Cái trước luôn chậm hơn, trong khi hai cái kia gần giống nhau.

Với thông tin này, tôi thà sử dụng extend, nhanh hơn ADD, và dường như tôi rõ ràng hơn về những gì bạn đang làm hơn INPLACE_ADD.

Hãy thử đoạn mã sau một vài lần (đối với 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
Bạn không thể so sánh ADDvới INPLACE_ADDextend(). ADDtạo một danh sách mới và sao chép các thành phần của hai danh sách gốc vào nó. Để chắc chắn rằng nó sẽ chậm hơn so với hoạt động tại chỗ INPLACE_ADDextend().
wombatonfire

Tôi biết điều đó. Điểm của ví dụ này là so sánh các cách khác nhau để có một danh sách với tất cả các yếu tố với nhau. Chắc chắn sẽ mất nhiều thời gian hơn vì nó làm những việc khác nhau, nhưng vẫn tốt để biết trong trường hợp bạn quan tâm đến việc bảo tồn các đối tượng ban đầu không bị thay đổi.
dalonsoa

1

Tôi đã tra cứu hướng dẫn Python chính thức nhưng không thể tìm thấy bất cứ điều gì về chủ đề này

Thông tin này sẽ được chôn trong Câu hỏi thường gặp về lập trình :

... cho danh sách, __iadd__[tức là +=] tương đương với việc gọi extendtrong danh sách và trả về danh sách. Đó là lý do tại sao chúng tôi nói rằng đối với các danh sách, +=là một "tốc ký" cholist.extend

Bạn cũng có thể thấy điều này cho chính mình trong mã nguồn CPython: https://github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011


-1

Theo Python để phân tích dữ liệu.

Lưu ý rằng việc ghép danh sách bằng cách thêm vào là một hoạt động tương đối tốn kém vì một danh sách mới phải được tạo và các đối tượng được sao chép qua. Sử dụng phần mở rộng để nối các phần tử vào danh sách hiện có, đặc biệt nếu bạn đang xây dựng một danh sách lớn, thường là tốt hơn. Vì vậy,

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

là nhanh hơn so với thay thế nối:

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

nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây


4
everything = everything + tempkhông nhất thiết phải được thực hiện theo cách tương tự như everything += temp.
David Harrison

1
Bạn đúng rồi. Cảm ơn sự nhắc nhở của bạn. Nhưng quan điểm của tôi là về sự khác biệt của hiệu quả. :)
littlebear333

6
@ littlebear333 everything += tempđược thực hiện theo cách everythingkhông cần sao chép. Điều này khá nhiều làm cho câu trả lời của bạn một điểm moot.
nog642
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.