2016-09-26 80 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__()只適用於該類的實例。在你的第二個例子中,當你定義PublicAttribute1時,你正在課堂上定義它;沒有實例,所以不調用__setattr__()

N.B.在Python中,使用.表示法訪問的內容稱爲屬性,而不是變量。 (在其他語言中,它們可能被稱爲「成員變量」或類似的名稱)。

如果您在實例上設置了相同名稱的屬性,那麼類屬性將會被遮蔽。例如:

class C(object): 
    attr = 42 

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

Python的解決,首先看在實例屬性的訪問,以及是否有該名稱的實例上沒有屬性,它看起來對實例的類,那麼類的父(S)等直到它到達object,這是Python類層次結構的根對象。

所以在上面的例子中,我們定義了attr這個類。因此,當我們訪問c.attr(實例屬性)時,我們得到42,該類的屬性值,因爲實例上沒有這樣的屬性。當我們設置實例的屬性時,再次打印c.attr,我們得到我們剛纔設置的值,因爲現在在實例上有一個名稱爲該屬性的屬性。但值42仍然作爲類的屬性C.attr存在,如我們在第三個print中看到的那樣。

在您的__init__()方法中設置實例屬性的語句由Python像處理對象上的任何代碼一樣處理。 Python不關心代碼是在類「內部」還是「外部」。所以,你可能會想,如何在初始化對象時繞過__setattr__()的「保護」?簡單:你調用一個沒有保護的類的__setattr__()方法,通常是父類的方法,並將它傳遞給你的實例。

因此,而不是寫:

self.PublicAttribute1 = "attribute" 

你必須寫:

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

由於屬性被存儲在實例的屬性字典,命名爲__dict__,你也可以在你的__setattr__通過寫作直接到:

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

Ei它的語法是醜陋而冗長的,但是你可以用相對容易的方式來顛覆你想要添加的保護(畢竟,如果你可以這樣做,其他人也可以這樣做)可能會導致你得出這樣的結論:Python不會對受保護的屬性有非常好的支持。事實上它不是,這是設計。 「我們都在這裏同意大人。」您不應該考慮Python的公有或私有屬性。所有屬性都是公共的。有一個約定命名「私人」屬性與一個單獨的前導下劃線;這會警告任何使用你的對象的人,他們正在搞亂某種實現細節,但如果他們需要並願意接受風險,他們仍然可以做到。

+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

謝謝。這就解釋了爲什麼第二個例子可行,但我仍然不明白爲什麼,在第二種情況下,如果我嘗試設置類變量,它會拋出錯誤。如果我在類的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__方法中),您可以通過一些更簡單的邏輯來獲得。一種方法是在__init__方法,它告訴__setattr__來鎖定任務的結束設定另一個屬性設置完成後:

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) 

另一種選擇是使用property描述爲只讀屬性和存儲可以正常分配的「私有」變量中的實際值。你會不會在這個版本使用__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編程習慣。我對這門語言還比較陌生,所以我不確定。 –