Pythonには不変のリストがありますか?


93

Pythonには不変のリストがありますか?

順序付けられた要素のコレクションの機能が必要であるが、変更されないことを保証したい場合、これをどのように実装できますか?リストは順序付けられていますが、変更することができます。


4
@Marcin:これはFAQ形式の質問で、同じ人が質問して回答します。
RichieHindle 2012年

@Marcin:OPが彼女自身の質問に答えたことにあなたは明らかに気づいていませんでした。
Sven Marnach 2012年

2
Pythonでの不変型の主な動機は、それらが辞書キーとして、およびセットで使用できることです。
Sven Marnach 2012年

16
ここで誰かを怒らせてしまったらお詫びします。グーグルで不変のリストを検索しただけで何も見つかりませんでした。探していたのがタプルだとわかったとき、ここで公開するのに苦労しました。誰かが私と同じくらい「ばかげている」場合に備えて。
cammil 2012年

5
同意する。後から考えると愚かなように見えますが、どういうわけか、私の愚かな脳が私を間違った道に導いてくれました。ほぼ独占的にリストを使用していて、最終的に不変のリストが必要であることに気づいたので、私は自然な質問をしました。タプルが存在することはよく知っていましたが、2つを接続していませんでした。これが他の誰かを助けるなら、私はこれが役に立たない投稿ではないと感じます。しかし、これがこの単純な質問に対する正しい答えではない場合、それはまったく別の問題です。
cammil 2012年

回答:


107

はい。それはと呼ばれますtuple

したがって、[1,2]どちらがalistであり、変更できるのではなく(1,2)、aでtupleあり、変更できません。


さらに詳しい情報:

を書くことtupleによって1つの要素をインスタンス化することはできません。(1)代わりに、を書く必要があります(1,)。これは、インタプリタには他にもさまざまな括弧の用途があるためです。

かっこを完全に削除することもできます:1,2と同じです(1,2)

タプルは正確に不変のリストではないことに注意してください。リストとタプルの違いについて詳しくは、ここをクリックしてください


6
また、本質的に可変のオブジェクトポインタをタプル(たとえば([1,2],3))に配置すると、リストオブジェクトは可変オブジェクトへのポインタにすぎず、ポインタは不変ですが、参照されるオブジェクトは不変ではないため、タプルは真に不変ではなくなります。
nisan.H 2012年

2
また、このような基本的な質問に答えるときは、パフォーマンスの違い(タプルがわずかに速い)やタプルをdictキーとして使用できるのに対し、リストでは使用できないなど、少なくとももう少し説明してください。他にもたくさんの違いがあると思います。
BrtH 2012年

3
実際には、空のタプルを書き込むこともできます()。これは、括弧が必要な1つのケースです。
RemcoGerlich 2015

1
@Kane、あなたのステートメントは型付き関数型言語では確かに真実です。具体的に(3,4,5)は、タイプが非常に異なる(int x int x int)[3,4,5]—タイプが(listof int)。しかし、Pythonのタプルは本当に近い不変のリストに思えるん:具体的には、彼らができる繰り返し処理すること、そして彼らはまた、濾過し、マッピングすることができます表示されます。
ジョンクレメンツ2015

1
タプルはリストではなく、互換性のある動作もありません。また、多態的に使用することもできません。
jeremyjjbrown 2016年

7

これがImmutableListの実装です。基になるリストは、直接データメンバーでは公開されません。それでも、メンバー関数のクロージャープロパティを使用してアクセスできます。上記のプロパティを使用してクロージャの内容を変更しないという規則に従う場合、この実装は目的を果たします。このImmutableListクラスのインスタンスは、通常のPythonリストが期待される場所であればどこでも使用できます。

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

2要素のタプルを使用して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凍結されている通常のタプルとは異なり、単一リンクリストの先頭に追加できます。これが、関数型プログラミング言語の汎用性と定番となっている理由です。
ケビンジ

ご返信ありがとうございます。新しいタプルインスタンス(z、)+(a、b、c)を作成して要素を追加することもできるため、この実装の利点を理解しようとしています。それはパフォーマンスの問題ですか?
リテラル

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、およびその他の関数型プログラミング言語をチェックしてください。Pythonを学び始める前は、Pythonは他の人から聞いたことから、不変性とfpを完全にサポートしていると信じていましたが、それは真実ではありませんでした。
ケイン

言うべきだったのですが、Pythonにはそんなことはあり得ません。Pythonの不変性は_private_variables、インタープリターからの強制ではなく、プログラマーが(のような)規則を尊重することに依存しています。
ジャックオコナー

1
Haskellのような言語は、より多くの保証を提供しますが、プログラマーが本当に悪を望んでいたとしても、/proc/#/mem安全でないライブラリやモデルを壊すものに書き込んだり、リンクしたりすることができます。
ジャックオコナー

1

リストとタプルは、作業スタイルに違いがあります。

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

-1

タプルの代わりに、frozensetを使用できます。凍結セットは不変のセットを作成します。リストをfrozensetのメンバーとして使用し、単一のforループを使用してfrozenset内のリストのすべての要素にアクセスできます。


3
フリーズセットでは、セットメンバーがハッシュ可能である必要がありますが、リストはハッシュ可能ではありません。
matias elgart 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.