Tuple lát không trả lại một đối tượng mới trái ngược với danh sách cắt


12

Trong Python (2 và 3). Bất cứ khi nào chúng ta sử dụng danh sách cắt, nó sẽ trả về một đối tượng mới, vd:

l1 = [1,2,3,4]
print(id(l1))
l2 = l1[:]
print(id(l2))

Đầu ra

>>> 140344378384464
>>> 140344378387272

Nếu điều tương tự được lặp lại với tuple, cùng một đối tượng được trả về, ví dụ:

t1 = (1,2,3,4)
t2 = t1[:]
print(id(t1))
print(id(t2))

Đầu ra

>>> 140344379214896
>>> 140344379214896

Sẽ thật tuyệt nếu ai đó có thể làm sáng tỏ lý do tại sao điều này xảy ra, trong suốt trải nghiệm Python của tôi, tôi đã ở dưới ấn tượng trống lát trả về một đối tượng mới.

Tôi hiểu rằng nó sẽ trả lại cùng một đối tượng vì các bộ dữ liệu là bất biến và không có điểm nào tạo ra một bản sao mới của nó. Nhưng một lần nữa, nó không được đề cập trong các tài liệu ở bất cứ đâu.



l2 = tuple(iter(l1))bỏ qua việc tối ưu hóa
Chris_Rands

Thông báo c-api choPyTuple_GetSlice đã được ghi lại không chính xác sau khi thấy câu hỏi của bạn. Tài liệu hiện đã được sửa (đây là vấn đề bpo38557 ).
wim

Câu trả lời:


13

Việc triển khai có thể trả lại các thể hiện giống hệt nhau cho các loại không thay đổi (trong CPython, đôi khi bạn có thể thấy các tối ưu hóa tương tự cho các chuỗi và số nguyên). Vì đối tượng không thể thay đổi, nên không có gì trong mã người dùng cần quan tâm liệu nó có giữ một thể hiện duy nhất hay chỉ là một tham chiếu khác đến một thể hiện hiện có.

Bạn có thể tìm thấy ngắn mạch trong mã C ở đây .

static PyObject*
tuplesubscript(PyTupleObject* self, PyObject* item)
{
    ... /* note: irrelevant parts snipped out */
    if (start == 0 && step == 1 &&
                 slicelength == PyTuple_GET_SIZE(self) &&
                 PyTuple_CheckExact(self)) {
            Py_INCREF(self);          /* <--- increase reference count */
            return (PyObject *)self;  /* <--- return another pointer to same */
        }
    ...

Đây là một chi tiết thực hiện, lưu ý rằng pypy không làm như vậy.


Cảm ơn @wim. Điều này có ý nghĩa bây giờ. Chỉ có một điều ngoài chủ đề là tôi chưa có kinh nghiệm trong C. Chính xác thì a-> ob_item làm gì? Tôi đã cố gắng tìm kiếm nó. nhưng tất cả những gì tôi có thể hiểu là nó lấy địa chỉ của "a" và di chuyển nó "ob_item" về phía trước. Sự hiểu biết của tôi là ob_item giữ số lượng địa chỉ lưu trữ tạo ra mục "1". #offTheTopic
Vijay Jangir

2
Nó có thể giúp nhìn vào typedef cho tuple, ở đây . Vì vậy, a->ob_itemnó giống như (*a).ob_item, tức là nó nhận được thành viên được gọi ob_itemtừ cái PyTupleObjectmà a đang trỏ đến và + ilow sau đó tiến đến đầu của lát cắt.
wim

3

Đó là một chi tiết thực hiện. Bởi vì danh sách có thể thay đổi, l1[:] phải tạo một bản sao, vì bạn sẽ không muốn thay đổi l2ảnh hưởng l1.

Vì một tuple là bất biến , tuy nhiên, không có gì bạn có thể làm điều t2đó sẽ ảnh hưởng t1theo bất kỳ cách hiển thị nào, vì vậy trình biên dịch là miễn phí (nhưng không bắt buộc ) để sử dụng cùng một đối tượng cho t1t1[:].


1

Trong Python 3. * my_list[:]là đường cú pháp trong type(my_list).__getitem__(mylist, slice_object)đó: slice_objectlà một đối tượng lát được xây dựng từ my_listcác thuộc tính (độ dài) và biểu thức [:]. Các đối tượng hành xử theo cách này được gọi là có thể đăng ký trong mô hình dữ liệu Python xem tại đây . Đối với danh sách và bộ dữ liệu __getitem__là một phương pháp tích hợp.

Trong CPython, và cho các danh sách và bộ dữ liệu, __getitem__được diễn giải bởi hoạt động mã byte BINARY_SUBSCRđược triển khai cho các bộ dữ liệu ở đây và cho các danh sách ở đây .

Trong trường hợp bộ dữ liệu, đi qua mã bạn sẽ thấy rằng trong khối mã này , static PyObject* tuplesubscript(PyTupleObject* self, PyObject* item)sẽ trả về một tham chiếu đến cùng tham số PyTupleObjectmà nó nhận được làm đối số đầu vào, nếu mục có kiểu PySlicevà lát cắt ước tính cho toàn bộ bộ dữ liệu.

    static PyObject*
    tuplesubscript(PyTupleObject* self, PyObject* item)
    {
        /* checks if item is an index */ 
        if (PyIndex_Check(item)) { 
            ...
        }
        /* else it is a slice */ 
        else if (PySlice_Check(item)) { 
            ...
        /* unpacks the slice into start, stop and step */ 
        if (PySlice_Unpack(item, &start, &stop, &step) < 0) { 
            return NULL;
        }
       ...
        }
        /* if we start at 0, step by 1 and end by the end of the tuple then !! look down */
        else if (start == 0 && step == 1 &&
                 slicelength == PyTuple_GET_SIZE(self) && 
                 PyTuple_CheckExact(self)) {
            Py_INCREF(self); /* increase the reference count for the tuple */
            return (PyObject *)self; /* and return a reference to the same tuple. */
        ...
}

Bây giờ bạn kiểm tra mã cho static PyObject * list_subscript(PyListObject* self, PyObject* item)và tự mình thấy rằng bất kể lát cắt nào, một đối tượng danh sách mới luôn được trả về.


1
Lưu ý rằng điều này khác với 2.7 , trong đó một start:stoplát cắt trên loại tích hợp, bao gồm tup[:], không đi qua BINARY_SUBSCR. Các lát cắt mở rộng start:stop:stepđi qua đăng ký, mặc dù.
wim

Được rồi, cảm ơn sẽ cập nhật để chỉ định phiên bản python.
Fakher Mokadem

0

Không chắc chắn về điều này nhưng có vẻ như Python cung cấp cho bạn một con trỏ mới vào cùng một đối tượng để tránh sao chép vì các bộ dữ liệu giống hệt nhau (và vì đối tượng là một tuple, nên nó không thay đổi được).

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.