파이썬에 불변 목록이 있습니까?


94

파이썬에는 불변 목록이 있습니까?

정렬 된 요소 컬렉션의 기능을 원하지만 변경되지 않을 것이라고 보장하고 싶은 경우 어떻게 구현할 수 있습니까? 목록은 순서가 있지만 변경할 수 있습니다.


4
@Marcin :이 질문은 같은 사람이 질문하고 답변 한 FAQ 스타일의 질문입니다.
RichieHindle

@Marcin : 당신은 분명히 OP 가 그녀 자신의 질문에 답한 것을 알아 차리지 못했습니다 .
Sven Marnach

2
파이썬에서 불변 유형에 대한 주된 동기는 사전 키와 세트로 사용할 수 있다는 것입니다.
스벤 Marnach

16
여기 누군가를 불쾌하게했다면 사과드립니다. Google에서 변경 불가능한 목록을 검색했지만 아무것도 찾지 못했습니다. 내가 찾고 있던 것이 튜플이라는 것을 알았을 때 나는 그것을 여기에 게시하는 수고를 받았습니다. 누군가 나처럼 "멍청한"사람 일 경우를 대비해.
cammil

5
나는 동의한다. 돌이켜 보면 그것은 어리석은 것처럼 보이지만 어떤 이유로 든 멍청한 두뇌가 나를 잘못된 길로 인도했습니다. 거의 독점적으로 목록을 사용하고 마침내 변경 불가능한 목록이 필요하다는 것을 깨달은 나는 자연스러운 질문을 던졌습니다. 튜플이 존재한다는 것을 잘 알고 있었지만 두 개를 연결하지 않았습니다. 이것이 다른 사람에게 도움이된다면 쓸모없는 게시물이 아니라고 생각합니다. 그러나 이것이이 간단한 질문에 대한 정답이 아니라면 그것은 완전히 다른 문제입니다.
cammil

답변:


108

예. 그것은 tuple.

그래서 그 대신에 [1,2]a list이고 변형 될 수있는 (1,2)것은 a tuple이고 할 수 없습니다.


추가 정보 :

하나의 요소 tuple는 write로 인스턴스화 할 수 없으며 (1)대신 작성 해야합니다 (1,). 인터프리터가 괄호에 대해 다양한 용도로 사용하기 때문입니다.

당신은 또한 모두 괄호로 멀리 수행 할 수 1,2와 동일합니다(1,2)

튜플은 정확히 불변 목록 이 아닙니다 . 목록과 튜플차이점 에 대해 자세히 알아 보려면 여기를 클릭하십시오.


6
또한 튜플에 본질적으로 변경 가능한 객체 포인터를 배치하면 (예 ([1,2],3):), 목록 객체는 변경 가능한 객체에 대한 포인터 일 뿐이며 포인터는 변경 불가능하지만 참조 된 객체는 그렇지 않기 때문에 튜플은 더 이상 변경이 불가능합니다.
Nisan.H

2
또한 이러한 기본 질문에 답할 때 성능 차이 (튜플이 약간 더 빠름) 및 튜플을 사전 키로 사용할 수 있지만 목록은 사용할 수없는 것과 같은 더 많은 설명을 제공하십시오. 나는 다른 많은 차이점도 있다고 확신합니다.
BrtH

3
실제로 빈 튜플도 작성할 수 있습니다 (). 괄호가 필요한 경우입니다.
RemcoGerlich

1
@Kane, 귀하의 진술은 유형화 된 기능 언어에서 확실히 사실입니다. 특히, (3,4,5)유형이 매우 다른 — (int x int x int)than [3,4,5], 유형이 (listof int)있습니다. 그러나, 파이썬의 튜플은 정말 가까이 불변의리스트에 보이지 않습니다 특히, 그들은 그들이 또한 여과하고 매핑 할 수 있습니다 게재되는 반복 될.
John Clements

1
튜플은 List가 아니며 호환되는 동작이 없으며 다형성으로 사용할 수도 없습니다.
jeremyjjbrown 15:57에

8

다음은 ImmutableList 구현입니다. 기본 목록은 직접 데이터 멤버에 노출되지 않습니다. 그래도 멤버 함수 의 클로저 속성을 사용하여 액세스 할 수 있습니다 . 위의 속성을 사용하여 클로저의 내용을 수정하지 않는 규칙을 따르면이 구현이 목적에 부합합니다. 이 ImmutableList 클래스의 인스턴스는 일반 파이썬 목록이 예상되는 모든 곳에서 사용할 수 있습니다.

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()

6

요소가 두 개인 튜플을 사용하여 Lisp 스타일의 불변 단일 연결 목록을 시뮬레이션 할 수 있습니다 (참고 : 이것은 훨씬 덜 유연한 튜플을 생성하는 모든 요소 튜플 답변 과 다릅니다 ).

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

예를 들어 목록의 [1, 2, 3]경우 다음과 같습니다.

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

표준 carcdr기능은 간단합니다.

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

이 목록은 단일 링크이므로 앞에 추가하는 것은 O (1)입니다. 이 목록은 변경할 수 없으므로 목록의 기본 요소도 변경할 수없는 경우 다른 목록에서 재사용 할 수 있도록 모든 하위 목록을 안전하게 공유 할 수 있습니다.


이 구현이 네이티브 튜플 (a, b, c)보다 어떻게 더 유연합니까?
Literal

@Literal 고정 된 일반 튜플과 달리 단일 연결 목록 앞에 추가 할 수 있습니다. 이것이 함수형 프로그래밍 언어에서 훨씬 더 다재다능하고 필수 요소입니다.
kevinji

응답 해 주셔서 감사합니다. 새로운 튜플 인스턴스 ((z,) + (a, b, c))를 만들어 요소를 추가 할 수도 있기 때문에이 구현의 이점을 이해하려고 노력하고 있습니다. 성능의 문제입니까?
리터럴

@Literal 그래. 귀하의 경우 요소를 연결하는 것은 O (n)이고 n은 기존 목록의 길이입니다. 크기 n + 1의 새 튜플을 할당 한 다음 요소를 복사해야하기 때문입니다. 여기서 concat은 O (1)입니다. 원래 "목록"에 대한 포인터와 함께 크기 2 튜플을 할당해야하기 때문입니다.
케빈 지

4

그러나 배열과 튜플의 튜플이 있으면 튜플 내부의 배열을 수정할 수 있습니다.

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

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

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

9
임의의 개체의 변경 불가능한 복사본을 만드는 방법이 필요하기 때문에 내용을 변경 불가능하게 만드는 컬렉션과 같은 것은 실제로있을 수 없습니다. 그렇게하려면 해당 객체가 속한 클래스와 참조하는 내장 클래스를 복사해야합니다. 그럼에도 불구하고 객체는 파일 시스템이나 네트워크 또는 항상 변경 가능한 다른 것을 참조 할 수 있습니다. 그래서 우리는 임의의 객체를 불변으로 만들 수 없기 때문에 불변의 불변 객체 컬렉션에 만족해야합니다.
잭 오코너

1
@ JackO'Connor 완전히 동의하지 않습니다. 그것은 모두 당신이 세상을 모델링하는 방법에 달려 있습니다 : 외부 가변성은 항상 시간이 지남에 따라 상태가 진화함에 따라 모델링 될 수 있으며, 단일 가변 상태를 유지하는 대신 항상 불변 인 s_t를 참조하도록 선택할 수 있습니다. "불변 개체의 불변 컬렉션"<-Huskell, Scala 및 기타 함수형 프로그래밍 언어를 확인하십시오. 파이썬을 배우기 전에 저는 파이썬이 다른 사람들로부터들은 내용에서 불변성과 fp를 완벽하게 지원한다고 믿었지만 사실이 아닙니다.
Kane

나는 말 했어야했다. 파이썬에는 정말로 그런 것이있을 수 없다. 파이썬의 불변성은 _private_variables인터프리터의 어떠한 시행이 아니라 (와 같은 ) 관례를 존중하는 프로그래머에 의존합니다 .
Jack O'Connor

1
Haskell과 같은 언어는 훨씬 더 많은 보증을 제공하지만 프로그래머가 정말로 악의를 원하면 여전히 /proc/#/mem안전하지 않은 라이브러리 에 쓰거나 링크하여 모델을 깨뜨릴 수 있습니다.
Jack O'Connor

1

List와 Tuple은 작업 스타일이 다릅니다.

LIST에서는 생성 후 변경할 수 있지만 나중에 변경 사항을 적용 할 수없는 순서가 지정된 시퀀스를 원하면 TUPLE을 사용할 수 있습니다.

추가 정보 ::

 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

-2

튜플 대신 frozenset을 사용할 수 있습니다. frozenset은 불변 집합을 만듭니다. 목록을 frozenset의 구성원으로 사용하고 단일 for 루프를 사용하여 frozenset 내의 모든 목록 요소에 액세스 할 수 있습니다.


4
frozenset은 목록이 아닌 세트 멤버가 해시 가능해야합니다.
Matias elgart
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.