Apakah Python memiliki daftar yang tidak dapat diubah?

93

Apakah python memiliki daftar yang tidak dapat diubah?

Misalkan saya ingin memiliki fungsionalitas dari kumpulan elemen yang teratur, tetapi yang ingin saya jamin tidak akan berubah, bagaimana ini dapat diterapkan? Daftar diurutkan tetapi dapat dimutasi.

cammil
sumber
4
@Marcin: Ini adalah pertanyaan bergaya FAQ, ditanyakan dan dijawab oleh orang yang sama.
RichieHindle
@Marcin: Anda jelas tidak melihat OP menjawab pertanyaannya sendiri .
Sven Marnach
2
Motivasi utama untuk tipe yang tidak dapat diubah dalam Python adalah bahwa mereka dapat digunakan sebagai kunci kamus dan dalam set.
Sven Marnach
16
Maaf jika saya telah menyinggung siapapun di sini. Saya hanya mencari daftar yang tidak dapat diubah di google dan tidak menemukan apa pun. Ketika saya mengetahui bahwa yang saya cari adalah tupel, saya bersusah payah menerbitkannya di sini. Untuk berjaga-jaga jika ada orang yang "bodoh" seperti saya.
cammil
5
Saya setuju. Kalau dipikir-pikir, ini tampak bodoh, tetapi untuk alasan apa pun, otak bodoh saya telah membawa saya ke jalan yang salah. Setelah hampir secara eksklusif menggunakan daftar, dan akhirnya menyadari bahwa saya membutuhkan daftar yang tidak dapat diubah, saya mengajukan pertanyaan yang wajar. Meskipun saya sangat sadar bahwa tupel ada, saya belum menghubungkan keduanya. Jika ini membantu orang lain di luar sana maka saya merasa ini bukan posting yang tidak berguna. Namun jika ini bukan jawaban yang tepat untuk pertanyaan sederhana ini, maka itu adalah masalah lain sama sekali.
cammil

Jawaban:

108

Iya. Ini disebut a tuple.

Jadi, selain [1,2]yang merupakan a listdan mana yang dapat dimutasi, (1,2)adalah a tupledan tidak bisa.


Informasi lebih lanjut:

Satu elemen tupletidak dapat dibuat dengan menulis (1), sebagai gantinya, Anda perlu menulis (1,). Ini karena interpreter memiliki berbagai kegunaan lain untuk tanda kurung.

Anda juga dapat menghilangkan tanda kurung sama sekali: 1,2sama dengan(1,2)

Perhatikan bahwa tupel tidak persis daftar kekal. Klik di sini untuk membaca lebih lanjut tentang perbedaan antara list dan tuple

cammil
sumber
6
Juga, jika Anda menempatkan penunjuk objek yang secara inheren dapat berubah di tupel (misalnya ([1,2],3)), tupel tidak lagi benar-benar tidak dapat diubah, karena objek daftar hanyalah penunjuk ke objek yang dapat berubah, dan sementara penunjuk tidak dapat diubah, objek yang direferensikan tidak.
Nisan.H
2
Juga, ketika Anda menjawab pertanyaan mendasar seperti itu, setidaknya berikan penjelasan lebih lanjut, seperti perbedaan kinerja (tuple sedikit lebih cepat) dan bahwa tupel dapat digunakan sebagai kunci dikt, sedangkan daftar tidak bisa. Saya yakin ada banyak perbedaan lainnya juga.
BrtH
3
Sebenarnya tupel kosong juga bisa ditulis (). Itulah salah satu kasus di mana tanda kurung diperlukan.
RemcoGerlich
1
@, Pernyataan Anda pasti benar dalam bahasa fungsional yang diketik; khususnya, (3,4,5)memiliki tipe yang sangat berbeda— (int x int x int)—daripada [3,4,5], yang memiliki tipe (listof int). Namun, tupel python benar-benar tampak lebih dekat ke daftar yang tidak dapat diubah: khususnya, mereka dapat diulangi, dan tampaknya mereka juga dapat disaring dan dipetakan.
John Clements
1
Tuple bukanlah List, mereka tidak memiliki perilaku yang kompatibel, dan Anda juga tidak dapat menggunakannya secara polimorfis.
jeremyjjbrown
7

Berikut adalah implementasi ImmutableList. Daftar yang mendasari tidak terekspos di anggota data langsung mana pun. Tetap saja, itu bisa diakses menggunakan properti closure dari fungsi anggota. Jika kita mengikuti konvensi untuk tidak mengubah konten closure menggunakan properti di atas, implementasi ini akan memenuhi tujuan. Instance dari kelas ImmutableList ini dapat digunakan di mana saja yang diharapkan daftar python normal.

from functools import reduce

__author__ = 'hareesh'


class ImmutableList:
    """
    An unmodifiable List class which uses a closure to wrap the original list.
    Since nothing is truly private in python, even closures can be accessed and
    modified using the __closure__ member of a function. As, long as this is
    not done by the client, this can be considered as an unmodifiable list.

    This is a wrapper around the python list class
    which is passed in the constructor while creating an instance of this class.
    The second optional argument to the constructor 'copy_input_list' specifies
    whether to make a copy of the input list and use it to create the immutable
    list. To make the list truly immutable, this has to be set to True. The
    default value is False, which makes this a mere wrapper around the input
    list. In scenarios where the input list handle is not available to other
    pieces of code, for modification, this approach is fine. (E.g., scenarios
    where the input list is created as a local variable within a function OR
    it is a part of a library for which there is no public API to get a handle
    to the list).

    The instance of this class can be used in almost all scenarios where a
    normal python list can be used. For eg:
    01. It can be used in a for loop
    02. It can be used to access elements by index i.e. immList[i]
    03. It can be clubbed with other python lists and immutable lists. If
        lst is a python list and imm is an immutable list, the following can be
        performed to get a clubbed list:
        ret_list = lst + imm
        ret_list = imm + lst
        ret_list = imm + imm
    04. It can be multiplied by an integer to increase the size
        (imm * 4 or 4 * imm)
    05. It can be used in the slicing operator to extract sub lists (imm[3:4] or
        imm[:3] or imm[4:])
    06. The len method can be used to get the length of the immutable list.
    07. It can be compared with other immutable and python lists using the
        >, <, ==, <=, >= and != operators.
    08. Existence of an element can be checked with 'in' clause as in the case
        of normal python lists. (e.g. '2' in imm)
    09. The copy, count and index methods behave in the same manner as python
        lists.
    10. The str() method can be used to print a string representation of the
        list similar to the python list.
    """

    @staticmethod
    def _list_append(lst, val):
        """
        Private utility method used to append a value to an existing list and
        return the list itself (so that it can be used in funcutils.reduce
        method for chained invocations.

        @param lst: List to which value is to be appended
        @param val: The value to append to the list
        @return: The input list with an extra element added at the end.

        """
        lst.append(val)
        return lst

    @staticmethod
    def _methods_impl(lst, func_id, *args):
        """
        This static private method is where all the delegate methods are
        implemented. This function should be invoked with reference to the
        input list, the function id and other arguments required to
        invoke the function

        @param list: The list that the Immutable list wraps.

        @param func_id: should be the key of one of the functions listed in the
            'functions' dictionary, within the method.
        @param args: Arguments required to execute the function. Can be empty

        @return: The execution result of the function specified by the func_id
        """

        # returns iterator of the wrapped list, so that for loop and other
        # functions relying on the iterable interface can work.
        _il_iter = lambda: lst.__iter__()
        _il_get_item = lambda: lst[args[0]]  # index access method.
        _il_len = lambda: len(lst)  # length of the list
        _il_str = lambda: lst.__str__()  # string function
        # Following represent the >, < , >=, <=, ==, != operators.
        _il_gt = lambda: lst.__gt__(args[0])
        _il_lt = lambda: lst.__lt__(args[0])
        _il_ge = lambda: lst.__ge__(args[0])
        _il_le = lambda: lst.__le__(args[0])
        _il_eq = lambda: lst.__eq__(args[0])
        _il_ne = lambda: lst.__ne__(args[0])
        # The following is to check for existence of an element with the
        # in clause.
        _il_contains = lambda: lst.__contains__(args[0])
        # * operator with an integer to multiply the list size.
        _il_mul = lambda: lst.__mul__(args[0])
        # + operator to merge with another list and return a new merged
        # python list.
        _il_add = lambda: reduce(
            lambda x, y: ImmutableList._list_append(x, y), args[0], list(lst))
        # Reverse + operator, to have python list as the first operand of the
        # + operator.
        _il_radd = lambda: reduce(
            lambda x, y: ImmutableList._list_append(x, y), lst, list(args[0]))
        # Reverse * operator. (same as the * operator)
        _il_rmul = lambda: lst.__mul__(args[0])
        # Copy, count and index methods.
        _il_copy = lambda: lst.copy()
        _il_count = lambda: lst.count(args[0])
        _il_index = lambda: lst.index(
            args[0], args[1], args[2] if args[2] else len(lst))

        functions = {0: _il_iter, 1: _il_get_item, 2: _il_len, 3: _il_str,
                     4: _il_gt, 5: _il_lt, 6: _il_ge, 7: _il_le, 8: _il_eq,
                     9: _il_ne, 10: _il_contains, 11: _il_add, 12: _il_mul,
                     13: _il_radd, 14: _il_rmul, 15: _il_copy, 16: _il_count,
                     17: _il_index}

        return functions[func_id]()

    def __init__(self, input_lst, copy_input_list=False):
        """
        Constructor of the Immutable list. Creates a dynamic function/closure
        that wraps the input list, which can be later passed to the
        _methods_impl static method defined above. This is
        required to avoid maintaining the input list as a data member, to
        prevent the caller from accessing and modifying it.

        @param input_lst: The input list to be wrapped by the Immutable list.
        @param copy_input_list: specifies whether to clone the input list and
            use the clone in the instance. See class documentation for more
            details.
        @return:
        """

        assert(isinstance(input_lst, list))
        lst = list(input_lst) if copy_input_list else input_lst
        self._delegate_fn = lambda func_id, *args: \
            ImmutableList._methods_impl(lst, func_id, *args)

    # All overridden methods.
    def __iter__(self): return self._delegate_fn(0)

    def __getitem__(self, index): return self._delegate_fn(1, index)

    def __len__(self): return self._delegate_fn(2)

    def __str__(self): return self._delegate_fn(3)

    def __gt__(self, other): return self._delegate_fn(4, other)

    def __lt__(self, other): return self._delegate_fn(5, other)

    def __ge__(self, other): return self._delegate_fn(6, other)

    def __le__(self, other): return self._delegate_fn(7, other)

    def __eq__(self, other): return self._delegate_fn(8, other)

    def __ne__(self, other): return self._delegate_fn(9, other)

    def __contains__(self, item): return self._delegate_fn(10, item)

    def __add__(self, other): return self._delegate_fn(11, other)

    def __mul__(self, other): return self._delegate_fn(12, other)

    def __radd__(self, other): return self._delegate_fn(13, other)

    def __rmul__(self, other): return self._delegate_fn(14, other)

    def copy(self): return self._delegate_fn(15)

    def count(self, value): return self._delegate_fn(16, value)

    def index(self, value, start=0, stop=0):
        return self._delegate_fn(17, value, start, stop)


def main():
    lst1 = ['a', 'b', 'c']
    lst2 = ['p', 'q', 'r', 's']

    imm1 = ImmutableList(lst1)
    imm2 = ImmutableList(lst2)

    print('Imm1 = ' + str(imm1))
    print('Imm2 = ' + str(imm2))

    add_lst1 = lst1 + imm1
    print('Liist + Immutable List: ' + str(add_lst1))
    add_lst2 = imm1 + lst2
    print('Immutable List + List: ' + str(add_lst2))
    add_lst3 = imm1 + imm2
    print('Immutable Liist + Immutable List: ' + str(add_lst3))

    is_in_list = 'a' in lst1
    print("Is 'a' in lst1 ? " + str(is_in_list))

    slice1 = imm1[2:]
    slice2 = imm2[2:4]
    slice3 = imm2[:3]
    print('Slice 1: ' + str(slice1))
    print('Slice 2: ' + str(slice2))
    print('Slice 3: ' + str(slice3))

    imm1_times_3 = imm1 * 3
    print('Imm1 Times 3 = ' + str(imm1_times_3))
    three_times_imm2 = 3 * imm2
    print('3 Times Imm2 = ' + str(three_times_imm2))

    # For loop
    print('Imm1 in For Loop: ', end=' ')
    for x in imm1:
        print(x, end=' ')
    print()

    print("3rd Element in Imm1: '" + imm1[2] + "'")

    # Compare lst1 and imm1
    lst1_eq_imm1 = lst1 == imm1
    print("Are lst1 and imm1 equal? " + str(lst1_eq_imm1))

    imm2_eq_lst1 = imm2 == lst1
    print("Are imm2 and lst1 equal? " + str(imm2_eq_lst1))

    imm2_not_eq_lst1 = imm2 != lst1
    print("Are imm2 and lst1 different? " + str(imm2_not_eq_lst1))

    # Finally print the immutable lists again.
    print("Imm1 = " + str(imm1))
    print("Imm2 = " + str(imm2))

    # The following statemetns will give errors.
    # imm1[3] = 'h'
    # print(imm1)
    # imm1.append('d')
    # print(imm1)

if __name__ == '__main__':
    main()
hareesh
sumber
6

Anda dapat mensimulasikan daftar tertaut tunggal gaya Lisp yang tidak dapat diubah menggunakan tupel dua elemen (catatan: ini berbeda dari jawaban tupel elemen apa pun , yang membuat tupel yang jauh kurang fleksibel):

nil = ()
cons = lambda ele, l: (ele, l)

misalnya untuk daftarnya [1, 2, 3], Anda akan memiliki yang berikut ini:

l = cons(1, cons(2, cons(3, nil))) # (1, (2, (3, ())))

Standar cardan cdrfungsi Anda sangat mudah:

car = lambda l: l[0]
cdr = lambda l: l[1]

Karena daftar ini saling terkait, menambahkan ke depan adalah O (1). Karena daftar ini tidak dapat diubah, jika elemen yang mendasari dalam daftar juga tidak dapat diubah, Anda dapat dengan aman membagikan sublist apa pun untuk digunakan kembali di daftar lain.

kevinji
sumber
Bagaimana implementasi ini lebih fleksibel daripada tupel asli (a, b, c)?
Literal
@ Literal Anda dapat menambahkan ke daftar tertaut tunggal, tidak seperti tupel biasa, yang dibekukan. Inilah yang membuat mereka jauh lebih fleksibel dan pokok dalam bahasa pemrograman fungsional.
kevinji
Terimakasih atas tanggapan Anda. Saya masih mencoba memahami manfaat dari implementasi ini, karena saya juga dapat menambahkan elemen dengan membuat instance tupel baru: (z,) + (a, b, c). Apakah ini masalah kinerja?
Literal
4

Tetapi jika ada tupel array dan tupel, maka array di dalam tupel dapat dimodifikasi.

>>> a
([1, 2, 3], (4, 5, 6))

>>> a[0][0] = 'one'

>>> a
(['one', 2, 3], (4, 5, 6))
Gopal
sumber
9
Tidak mungkin ada koleksi yang membuat isinya tidak dapat diubah, karena Anda memerlukan cara untuk membuat salinan objek yang tidak dapat diubah. Untuk melakukan itu, Anda harus menyalin kelas yang dimiliki objek tersebut, dan bahkan kelas bawaan yang dirujuknya. Dan tetap saja, objek bisa merujuk ke sistem file, atau ke jaringan, atau sesuatu yang lain yang akan selalu bisa berubah. Jadi karena kita tidak bisa membuat objek yang berubah menjadi tidak berubah, kita harus puas dengan koleksi objek yang bisa berubah.
Jack O'Connor
1
@ JackO'Connor Tidak sepenuhnya setuju. Itu semua tergantung pada bagaimana Anda memodelkan dunia: mutabilitas eksternal selalu dapat dimodelkan sebagai keadaan yang berkembang dalam waktu, dan alih-alih mempertahankan satu keadaan yang dapat berubah, saya selalu dapat memilih untuk merujuk ke s_t yang tidak dapat diubah. "Koleksi objek yang tidak berubah" <- lihat Huskell, Scala, dan bahasa pemrograman fungsional lainnya. Sebelum saya mulai belajar Python, saya dulu percaya Python memiliki dukungan penuh terhadap kekekalan dan fp dari apa yang saya dengar dari orang lain, tetapi ternyata tidak benar.
Kane
Saya seharusnya mengatakan, tidak mungkin ada hal seperti itu di Python. Kekekalan Python bergantung pada pemrogram yang menghormati konvensi (seperti _private_variables), daripada penegakan apa pun dari penerjemah.
Jack O'Connor
1
Bahasa seperti Haskell membuat lebih banyak jaminan, meskipun jika programmer benar-benar ingin menjadi jahat, mereka masih dapat menulis /proc/#/mematau menautkan ke pustaka yang tidak aman atau apa pun untuk merusak model.
Jack O'Connor
1

List dan Tuple memiliki perbedaan dalam gaya kerjanya.

Di LIST kita dapat membuat perubahan setelah pembuatannya, tetapi jika Anda menginginkan urutan berurutan di mana tidak ada perubahan yang dapat diterapkan di masa mendatang, Anda dapat menggunakan TUPLE.

informasi lebih lanjut::

 1) the LIST is mutable that means you can make changes in it after its creation
 2) In Tuple, we can not make changes once it created
 3) the List syntax is
           abcd=[1,'avn',3,2.0]
 4) the syntax for Tuple is 
           abcd=(1,'avn',3,2.0) 
      or   abcd= 1,'avn',3,2.0 it is also correct
Avnish kumar
sumber
-1

Alih-alih tuple, Anda dapat menggunakan frozenset. frozenset membuat set yang tidak bisa diubah. Anda dapat menggunakan list sebagai anggota frozenset dan mengakses setiap elemen daftar di dalam frozenset menggunakan perulangan tunggal.

Vishal Mopari
sumber
3
frozenset membutuhkan anggota setnya untuk dapat di-hash, sedangkan daftar tidak.
matias elgart