web-dev-qa-db-ja.com

Pythonで可変性を確認しますか?

これを考慮してください コード

a = {...} # a is an dict with arbitrary contents
b = a.copy()
  1. Dictのキーと値で可変性はどのような役割を果たしますか?
  2. 1つのdictのキーまたは値への変更が他のdictに反映されないようにするにはどうすればよいですか?
  3. これは、dictキーのハッシュ可能な 制約 とどのように関連していますか?
  4. Python 2.xとPython 3.xの間で動作に違いはありますか?

Pythonで型が変更可能かどうかを確認するにはどうすればよいですか?

30
Matt Joiner

1)キーは変更可能であってはなりませんただしハッシュ可能であると同時に変更可能であるユーザー定義クラスがある場合。それがあなたに強制されているすべてです。 ただし、ハッシュ可能で可変のオブジェクトをdictキーとして使用することは、悪い考えかもしれません。

2)2つのdict間で値を共有しないことによって。キーは不変でなければならないため、キーを共有しても問題ありません。 copyモジュールの意味で、辞書をコピーすることは間違いなく安全です。ここでdictコンストラクターを呼び出すことも機能します:b = dict(a)。不変の値を使用することもできます。

3)すべての組み込みの不変型はハッシュ可能です。すべての組み込みの可変タイプはハッシュ可能ではありません。オブジェクトをハッシュ可能にするには、オブジェクトが変更されている場合でも、オブジェクトの存続期間全体にわたって同じハッシュを持っている必要があります。

4)私が知っていることではありません。 2.xについて説明しています。

型が不変でない場合、型は可変です。組み込みの不変型である場合、型は不変です:strintlongboolfloatTuple、そしておそらく私が忘れている他のいくつか。ユーザー定義の型は常に変更可能です。

オブジェクトが不変でない場合、オブジェクトは可変です。オブジェクトが不変型のサブオブジェクトのみで再帰的に構成されている場合、そのオブジェクトは不変です。したがって、リストのタプルは変更可能です。タプルの要素を置き換えることはできませんが、リストインターフェイスを介してそれらを変更し、データ全体を変更することはできます。

19
Karl Knechtel

Pythonには、言語レベルでの可変性や不変性などは実際にはありません。一部のオブジェクト(文字列やタプルなど)はそれらを変更する方法を提供しないため、事実上不変ですが、純粋に概念的なものです。これを示す言語レベルのプロパティは、コードにもPython自体にも)ありません。

不変性は実際にはdictとは関係ありません。可変値をキーとして使用することはまったく問題ありません。重要なのは比較とハッシュです。オブジェクトは常にそれ自体と同じでなければなりません。例えば:

_class example(object):
    def __init__(self, a):
        self.value = a
    def __eq__(self, rhs):
        return self.value == rhs.value
    def __hash__(self):
        return hash(self.value)

a = example(1)
d = {a: "first"}
a.data = 2
print d[example(1)]
_

ここで、examplenot不変です。 _a.data = 2_で変更しています。それでも、問題なくハッシュのキーとして使用しています。どうして?変更するプロパティは同等性に影響を与えません。ハッシュは変更されず、example(1)は常にexample(1)と等しく、他のプロパティは無視されます。

これの最も一般的な使用法は、キャッシュとメモ化です。プロパティをキャッシュするかどうかは、オブジェクトを論理的に変更せず、通常、同等性に影響を与えません。

(ここで停止します。一度に5つの質問をしないでください。)

10
Glenn Maynard

モジュール collections にはMutableSequence、MutableSet、MutableMappingがあります。既製のタイプの可変性をチェックするために使用できます。

issubclass(TYPE, (MutableSequence, MutableSet, MutableMapping))

これをユーザー定義型で使用する場合は、型をそれらの1つから継承するか、仮想サブクラスとして登録する必要があります。

class x(MutableSequence):
    ...

または

class x:
    ...

abc.ABCMeta.register(MutableSequence,x)
4
Kabie

ハッシュ可能な型も不変であるという保証は実際にはありませんが、少なくとも__hash__を正しく実装するには、型が不変である必要があります、それ自体のハッシュに関して、そして平等に関して。これは特定の方法で強制されるものではありません。

しかし、私たちは皆大人です。本当に意味がない限り、__hash__を実装するのは賢明ではありません。大まかに言えば、これは、型が実際に辞書キーとして使用できる場合、そのように使用されることを意図しているということです。

likedictであるが、不変でもあるものを探している場合は、 namedtuple 標準ライブラリにあるものから最善の策を尽くしてください。確かに、それはあまり良い概算ではありませんが、それは始まりです。

  1. dictkeysはハッシュ可能である必要があります。これは、それらが不変のhash値を持っていることを意味します。 dictvaluesは可変である場合とそうでない場合があります。ただし、それらが変更可能である場合、これは2番目の質問に影響を与えます。

  2. 「キーの変更」は、2つのディクトの間に反映されません。文字列などの不変の値への変更も反映されません。オブジェクトはID(つまり参照)によって格納されるため、ユーザー定義クラスなどの可変オブジェクトへの変更は反映されます。

    _class T(object):
      def __init__(self, v):
        self.v = v
    
    
    t1 = T(5)
    
    
    d1 = {'a': t1}
    d2 = d1.copy()
    
    
    d2['a'].v = 7
    d1['a'].v   # = 7
    
    
    d2['a'] = T(2)
    d2['a'].v   # = 2
    d1['a'].v   # = 7
    
    
    import copy
    d3 = copy.deepcopy(d2) # perform a "deep copy"
    d3['a'].v = 12
    d3['a'].v   # = 12
    d2['a'].v   # = 2
    _
  3. これは最初の2つの答えで説明できると思います。

  4. この点で私が知っていることではありません。

いくつかの追加の考え

キーの動作を理解するために知っておくべき2つの主なことがあります:キーは ハッシュ可能 でなければなりません(つまり、実装することを意味します object.__hash__(self) )そしてそれらは「比較可能」でなければなりません(つまり、 object.__cmp__(self) のようなものを実装します)。ドキュメントからの重要なポイントの1つ:デフォルトでは、ユーザー定義オブジェクトのハッシュ関数は id() を返します。

この例を考えてみましょう。

_class K(object):
  def __init__(self, x, y):
     self.x = x
     self.y = y
  def __hash__(self):
     return self.x + self.y

k1 = K(1, 2)
d1 = {k1: 3}
d1[k1] # outputs 3
k1.x = 5
d1[k1] # KeyError!  The key's hash has changed!
k2 = K(2, 1)
d1[k2] # KeyError!  The key's hash is right, but the keys aren't equal.
k1.x = 1
d1[k1] # outputs 3

class NewK(object):
  def __init__(self, x, y):
     self.x = x
     self.y = y
  def __hash__(self):
     return self.x + self.y
  def __cmp__(self, other):
     return self.x - other.x

nk1 = NewK(3, 4)
nd1 = {nk1: 5}
nd1[nk1] # outputs 5
nk2 = NewK(3, 7)
nk1 == nk2 # True!
nd1[nk2] # KeyError! The keys' hashes differ.
hash(nk1) == hash(nk2) # False
nk2.y = 4
nd1[nk2] # outputs 5

# Where this can cause issues:
nd1.keys()[0].x = 5
nd1[nk1] # KeyError! nk1 is no longer in the dict!
id(nd1.keys()[0]) == id(nk1)  # Yikes. True?!
nd1.keys()[0].x = 3
nd1[nk1]  # outputs 5
id(nd1.keys()[0]) == id(nk1)  # True!
_

ははるかに理解しやすく、dictはオブジェクトへの参照を格納します。ハッシュ可能に関するセクションを読んでください。文字列のようなものは不変です。文字列を「変更」すると、変更したdictが新しいオブジェクトを参照するようになります。変更可能なオブジェクトは「インプレースで変更」できるため、両方のdictの値が変更されます。

_d1 = {1: 'a'}
d2 = d1.copy()
id(d1[1]) == id(d2[1]) # True
d2[1] = 'z'
id(d1[1]) == id(d2[1]) # False

# the examples in section 2 above have more examples of this.
_

とにかく、これがすべての要点です:

  • キーの場合、可変性ではなく、ハッシュ可能性と比較可能性である可能性があります。 、あなたが気にすること。
  • 定義上、可変オブジェクトの値は、その参照を変更せずに変更できるため、値の可変性に注意が必要です。

これらの点のどちらかをテストする一般的な方法はないと思います。適合性のテストは、ユースケースによって異なります。たとえば、オブジェクトが___hash___および比較(___eq___または___cmp___)関数を実装しているかどうかを確認するだけで十分な場合があります。同様に、オブジェクトの___setattr___メソッドを何らかの方法で「チェック」して、オブジェクトが変更可能かどうかを判断できる場合があります。

2
Robert Kluin