2012-06-21 2 views
51

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

주문한 요소 모음의 기능을 갖기를 원하지만, 변경하지 않으려는 경우 어떻게 구현할 수 있습니까? 목록은 순서가 있지만 돌연변이가있을 수 있습니다.

+4

@Marcin : 이것은 FAQ 스타일의 질문으로 같은 사람이 질문하고 대답합니다. – RichieHindle

+0

@Marcin : 분명히 OP를 알아 채지 못했습니다 [자신의 질문에 대답했습니다] (http://blog.stackoverflow.com/2011/07/its-ok-to-ask-and-answer-your-own-questions/). –

+2

파이썬에서 불변 타입을 사용하는 주된 동기는 사전 키와 세트로 쓸모가 있다는 것입니다. –

답변

59

예. tuple이라고합니다.

따라서, list이고 변이 될 수있는 [1,2] 대신에, (1,2)tuple이고 할 수 없다.


추가 정보 : tuple(1)를 작성하여 인스턴스화 할 수없는

하나의 요소는, 대신에, 당신은 (1,)를 작성해야합니다. 이것은 인터프리터가 다양한 다른 용도로 괄호를 사용하기 때문입니다.

또한 모두 괄호로 멀리 할 수 ​​있습니다 1,2(1,2)

주와 동일한 튜플 정확히 불변의리스트 아니라고. differences between lists and tuples

+5

'(el,)'('(el)'은 동작하지 않을 수 있습니다)와'tuple()'을 호출하여 빈 튜플을 만들 수있는 1 요소 터플을 만들기 위해 추가 할 것입니다. – JPvdMerwe

+5

또한 튜플에 본래 변경할 수있는 객체 포인터를 두는 경우 (예 :'([1,2], 3)'), 목록 객체는 변경 가능한 객체에 대한 포인터이기 때문에 튜플은 더 이상 진정한 불변이 아닙니다. 포인터가 변경되지 않는 동안 참조 된 객체는 변경되지 않습니다. –

+3

@JPvdMerwe 또한 괄호는 튜플을 만들 필요가 없으며 쉼표 만 사용됩니다. –

3

다음은 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() 
3

그러나 배열과 튜플의 튜플이있는 경우 튜플 내의 배열을 수정할 수 있습니다.

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

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

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

임의의 객체를 변경할 수없는 복사본을 만드는 방법이 필요하기 때문에 실제로 내용을 변경하지 못하는 컬렉션과 같은 것은있을 수 없습니다. 그렇게하기 위해서는 객체가 속한 클래스와 참조하는 내장 클래스를 복사해야합니다. 그리고 여전히 객체는 파일 시스템, 네트워크 또는 다른 것들을 참조 할 수 있습니다. 그래서 우리는 임의의 객체를 불변으로 만들 수 없으므로 변경 가능한 객체의 불변의 콜렉션에 만족해야한다. –

+1

@ JackO'Connor 완전히 동의하지 않습니다. 그것은 모두 여러분이 세상을 어떻게 모델링 하느냐에 달려 있습니다. 외부의 가변성은 언제나 상태가 진화하면서 모델화 될 수 있으며, 단일 가변 상태를 유지하는 대신에 항상 불변 인 s_t를 참조하도록 선택할 수 있습니다. "불변 개체의 불변의 컬렉션"<- Huskell, Scala 및 기타 함수 프로그래밍 언어를 확인하십시오. 파이썬을 배우기 전에 필자는 파이썬이 다른 사람들로부터 들었던 것으로부터 불변성과 fp를 완벽하게 지원한다고 생각했지만 사실이 아닙니다. – Kane

+0

나는 파이썬에서 그렇게 할 수는 없다고 말해야했다. 파이썬의 불변성은 인터프리터의 집행보다는 프로그래머가 ('_private_variables'와 같은) 규칙을 존중합니다. –

3

튜플 대신에 frozenset을 사용할 수 있습니다. frozenset은 불변의 세트를 만듭니다. 당신은 frozenset의 일원으로 list를 사용할 수 있으며 for for 루프를 사용하여 frozenset 내의리스트의 모든 요소에 액세스 할 수 있습니다.

+1

frozenset은 set 멤버가 해시 가능해야하며 목록은 그렇지 않습니다. –

관련 문제