2016-09-26 2 views
2

私は__setattr__は、クラス属性でどのように動作するかを正確に理解しようとしています。この質問は、私は単純なクラスに書き込まれてから属性を防ぐために__setattr__上書きしようとしたときについて来ました。__setattr__はクラス属性とどのように機能しますか?

class SampleClass(object): 

    def __init__(self): 
     self.PublicAttribute1 = "attribute" 

    def __setattr__(self, key, value): 
     raise Exception("Attribute is Read-Only") 

私はもともと属性を設定するだけで、外部の試みはエラーをスローだろうと思っていたが、__init__内部にも文があることを例外を発生させ、次のよう

私の最初の試みは、インスタンスレベルの属性を使用しましたスローされる。

後、私は別の問題を解決しようとしたときに、これを行うための別の方法を見つけることになりました。私がいたになってしまった何か:

class SampleClass(object): 
    PublicAttribute1 = "attribute" 

    def __setattr__(self, key, value): 
     raise Exception("Attribute is Read-Only") 

私は同じ結果を得るために期待されるが、私のsurpirseに私は、最初の宣言の後に行われてから変更を防止しながら、クラス属性を設定することができました。

これはしかし働く理由を私は理解していません。私はそれがクラスとインスタンスの変数と関係があることを知っています。私の理論では、クラス変数に__setattr__を使うと、同じ名前のインスタンス変数が作成されます。これは前にこの振る舞いを見たと思いますが、わかりません。

ここで何が起こっているのか正確に説明できますか?

答えて

3

__setattr__()は、クラスのインスタンスに適用されます。 2番目の例では、PublicAttribute1を定義したときに、そのクラスで定義しています。インスタンスがないので、__setattr__()は呼び出されません。

N.B. Pythonでは、.表記を使ってアクセスするものは、変数ではなく属性と呼ばれます。

インスタンスに同じ名前の属性を設定すると、class属性がシャドーされるのは間違いありません。たとえば、次のように

class C(object): 
    attr = 42 

c = C() 
print(c.attr)  # 42 
c.attr = 13 
print(c.attr)  # 13 
print(C.attr)  # 42 

Pythonは最初のインスタンス上で見ることにより、アクセス属性解決し、インスタンス上のその名前の属性がありません場合、それはインスタンスのクラスに見えるが、そのクラスの親(複数可)、及びそうそれがobjectに到達するまで、Pythonクラス階層のルートオブジェクトを返します。

上記の例では、クラスにattrを定義しています。したがって、c.attr(インスタンス属性)にアクセスすると、インスタンスにそのような属性がないため、クラスの属性の値42が取得されます。インスタンスの属性を設定してからc.attrを再度印刷すると、インスタンスにその名前の属性が存在するため、設定した値が取得されます。しかし、値42は、クラスの属性として、C.attrの3番目のprintのようにまだ存在します。

__init__()メソッドでインスタンス属性を設定するステートメントは、オブジェクトの属性を設定するコードと同様にPythonによって処理されます。 Pythonは、コードがそのクラスの "内側"か "外側"かどうかは気にしません。だから、オブジェクトを初期化するときに、どのように__setattr__()の "保護"をバイパスできますか?シンプル:保護がないクラス(通常は親クラスのメソッド)のメソッド__setattr__()を呼び出し、インスタンスに渡します。だからではなく、書き込みの

あなたが書く必要が
self.PublicAttribute1 = "attribute" 

object.__setattr__(self, "PublicAttribute1", "attribute") 

属性が__dict__名前付きインスタンスの属性辞書に格納されているので、あなたも書き込みして__setattr__周りを取得することができますそれに直接:

self.__dict__["PublicAttribute1"] = "attribute" 

Ei構文は醜く冗長ですが、あなたが追加しようとしている保護を元に戻すことができる比較的簡単なこと(もしあなたがそうすることができれば他の人もそうすることができます)は、Pythonが保護された属性を非常によくサポートしています。実際にはそうではなく、これは設計によるものです。 "我々はすべてここで大人に同意している。あなたは、Pythonで公開属性または非公開属性について考えるべきではありません。すべての属性は公開されています。単一の先行するアンダースコアを持つ "private"属性の名前付けの規則があります。これは、あなたのオブジェクトを使用している人に、何らかの実装の詳細がついていることを警告しますが、リスクを受け入れる必要があり、リスクを受け入れることができれば、それでもやり遂げることができます。

+1

答えをありがとう。この説明はまさに私が探していたものでした。 –

0

私はここで何かが欠けていない限り、答えは非常に明白です:最初のケースはラインで、__setattr__方法をトリガーする属性の割り当てがあります。

self.PublicAttribute1 = "attribute" 

しかし、後者の場合には、そのような属性を持っていません割り当て。

0

__setattr__()は、クラスのプロパティのいずれかに値が割り当てられるたびに呼び出されます。プロパティは__init__()で初期化されている場合でも、それは__setattr__()への呼び出しを行います。以下にあることを示すために、例を示します。

>>> class X(object): 
...  def __init__(self, a, b): 
...   self.a = a 
...   self.b = b 
...  def __setattr__(self, k, v): 
...   print 'Set Attr: {} -> {}'.format(k, v) 
... 
>>> x = X(1, 3)  # <-- __setattr__() called by __init__() 
Set Attr: a -> 1 
Set Attr: b -> 3 
>>> x.a = 9   # <-- __setattr__() called during external assignment 
Set Attr: a -> 9 
2

以下

属性の割り当てや削除がインスタンスの辞書、決してクラスの辞書を更新するPythonドキュメントで見つけることができます。クラスに__setattr __()または__delattr __()メソッドがある場合は、インスタンスディクショナリを直接更新する代わりにこのメソッドが呼び出されます。

あなたはは、それが呼び出されたインスタンスの辞書を変更する()__setattrの__をはっきり見ることができたよう。したがって、self.instanceを割り当てると、例外が発生します例外(「属性は読み取り専用」です)。 例外が発生しないように、クラスの属性を設定する場合はそうではありません。

+0

ありがとうございます。なぜ2番目の例がうまくいくのか説明していますが、2番目のケースでは、エラーをスローするクラス変数を設定しようとすると、それでもなぜ表示されません。クラスのnインスタンスでfoo.PublicAttribute1 = "newattribute"を呼び出すと、クラス変数を設定しませんか? \ _ \ _ setattr \ _ \ _()をバイパスしないでしょうか? –

2

__setattr__は、インスタンスののインスタンスでのみ呼び出され、実際のクラス自体は呼び出されません。しかし、クラスは依然としてオブジェクトであるため、クラスの属性を設定するときにクラスのクラスの__setattr__が呼び出されます。

属性をクラスに設定する方法を変更するには、メタクラスを調べる必要があります。メタクラスはほとんどすべてのアプリケーションにとって不幸なので、非常に混乱する可能性があります。そして、ユーザー定義クラス/オブジェクトを読み取り専用にしようとするのは、通常は悪い考えです。つまり、ここには簡単な例があります。

>>> class SampleMetaclass(type): # type as parent, not object 
...  def __setattr__(self, name, value): 
...   raise AttributeError("Class is read-only") 
... 
>>> class SampleClass(metaclass=SampleMetaclass): 
...  def __setattr__(self, name, value): 
...   raise AttributeError("Instance is read-only") 
... 
>>> SampleClass.attr = 1 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 3, in __setattr__ 
AttributeError: Class is read-only 
>>> s = SampleClass() 
>>> s.attr = 1 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 3, in __setattr__ 
AttributeError: Instance is read-only 
+0

メタクラスの '__setattr__'はメタクラス(クラス)のインスタンスに属性をバインドするときにのみ呼び出され、OPの場合のようにクラス変数を設定するときには呼び出されません。 –

+0

@MosesKoledoyeそうです。しかし、Pythonはそれ自身でいくつかのクラス属性を設定する必要があるため、これは少し難解です。でも、やっぱり。 – Dunes

+0

メタクラスの '__new__'でクラス属性を捕捉することができます。 'not attr.startswith( '__')' –

3

クラスで定義され__setattr__方法は、唯一のistancesクラスのの属性の割り当てのために呼ばれています。メソッドでクラスのインスタンスに割り当てられていないため、クラス変数は呼び出されません。

もちろん、クラスもインスタンスです。それらはtype(またはカスタムメタクラス、通常typeのサブクラス)のインスタンスです。したがって、クラス変数の作成を防ぐには、__setattr__メソッドを持つメタクラスを作成する必要があります。

しかし、それはあなたが欲しいものをあなたのクラスにするために必要なものではありません。 (__init__メソッドで)1回だけ書き込むことができる読み取り専用属性を取得するには、より単純なロジックを使用します。読み取り専用属性や店舗のためproperty記述子を使用することであろう

class Foo: 
    def __init__(self, a, b): 
     self.a = a 
     self.b = b 
     self._initialized = True 

    def __setattr__(self, name, value): 
     if self.__dict__.get('_initialized'): 
      raise Exception("Attribute is Read-Only") 
     super().__setattr__(name, value) 

別のオプション:一つのアプローチは、それが設定された後に割り当てをロックダウンする__setattr__を伝え__init__方法、の終わりに別の属性を設定することです通常に割り当てることができる「プライベート」変数の実際の値。あなたは、このバージョンで__setattr__を使用していないと思います。

class Foo(): 
    def __init__(a, b): 
     self._a = a 
     self._b = b 

    @property 
    def a(self): 
     return self._a 

    @property 
    def b(self): 
     return self._b 
+0

私はあなたの最初の提案に非常によく似た何かを実際に試してみましたが、自己について知っていました.__ dict__get( 'initialized')私はブール値を設定しようとしたときにエラーを投げつけました。私がこれらのアプローチから離脱したもう一つの理由は、いつもクラスのインスタンスを持っていなかった場所で外部的に属性を使いたいということです。それは良いpythonプログラミングの練習ではないかもしれません。私はまだ比較的新しい言語ですので、わかりません。 –

関連する問題