2013-07-11 90 views
1

我擺弄着繼承,發現了一個似乎對我來說很陌生的行爲 - 即有時我可以重寫父裝飾函數(用於驗證),但有時候我不行,我不明白爲什麼或者有什麼不同。覆蓋裝飾的子類方法

單詞中的快速入門---我有一個人物對象我希望子類更特殊的人物對象。更具體的人會有一個額外的領域,「舞蹈」,並會在以前的領域,「名稱」有不同的驗證規則。

這裏是我的基本情況,其工作原理:

# Define the validation wrapper 
def ensure(name, validate, doc=None): 
    def decorator(Class): 
     privateName = "__" + name 
     def getter(self): 
      return getattr(self, privateName) 
     def setter(self, value): 
      validate(name, value) 
      setattr(self, privateName, value) 
     setattr(Class, name, property(getter, setter, doc=doc)) 
     return Class 
    return decorator 

# Define the not string validation 
def is_not_str(name, value): 
    if isinstance(value, str): 
     raise ValueError("{} cannot be a string.".format(name)) 

# Chosen to be exact opposite of above---demonstrating it's possible to reverse.  
def is_str(name, value): 
    if not isinstance(value, str): 
     raise ValueError("{} must be a string.".format(name)) 

@ensure("name", is_str) 
@ensure("url", is_str) 
class Person(object): 
    def __init__(self,s): 
     self.name = s.get('name',{}) 
     self.url = s.get('url','') 

    def __str__(self): 
     return "Person({{'name':'{}','url':'{}'}})".format(self.name, self.url) 

    def __repr__(self): 
     return str(self) 

@ensure("name", is_not_str) # require a number rather than a Name() object. 
class Crazyperson(Person): 
    def __init__(self,s): 
     super(Crazyperson,self).__init__(s) # idiom to inherit init 
     self.dance = s.get('dance')   # add new param. 

bill = Person({"name":"bill", 
       "url":"http://www.example.com"}) 

fred = Crazyperson({"name":1, 
        "url":"http://www.example.com", 
        "dance":"Flamenco"}) 

這工作得很好。因此,創建第一個對象bill,使得驗證is_str成功。如果你嘗試在那裏輸入一個數字,它就會失敗。第二個對象同樣接受非字符串,所以fred被成功創建。

現在,這裏的情況下它打破了,我想了解哪些...

def is_Name(name, value): 
    if not isinstance(value, dict) and not isinstance(value,Name): 
     raise ValueError("{} must be a valid Name object".format(name)) 

# new object that will be a non-string type of name. 
@ensure("firstname", is_str) 
@ensure("lastname", is_str) 
class Name(object): 
    def __init__(self,s): 
     self.firstname = s.get('firstname','') 
     self.lastname = s.get('lastname') 

    def __str__(self): 
     return "Name({{'firstname':'{}','lastname':'{}' }})".format(self.firstname, self.lastname) 

    def __repr__(self): 
     return str(self) 

@ensure("name", is_Name) # require it as the default for the base class 
@ensure("url", is_str) 
class Person(object): 
    def __init__(self,s): 
     self.name = Name(s.get('name',{})) 
     self.url = s.get('url','') 

    def __str__(self): 
     return "Person({{'name':'{}','url':'{}'}})".format(self.name, self.url) 

    def __repr__(self): 
     return str(self) 


@ensure("name", is_str) # require a number rather than a Name() object. 
class Crazyperson(Person): 
    def __init__(self,s): 
     super(Crazyperson,self).__init__(s) 
     self.name = s.get('name','') # THIS IS THE KEY 
     self.dance = s.get('dance') 

bill = Person({"name":{"firstname":"Bill", "lastname":"billbertson"}, 
       "url":"http://www.example.com"}) 

fred = Crazyperson({"name":"Fred", 
        "url":"http://www.example.com", 
        "dance":"Flamenco"}) 

在這種情況下,Crazyperson失敗。該錯誤提示,仍然被應用在__init__is_Name驗證功能:

Traceback (most recent call last): 
    File "<stdin>", line 3, in <module> 
    File "<stdin>", line 4, in __init__ 
    File "<stdin>", line 5, in __init__ 
    File "<stdin>", line 5, in __init__ 
AttributeError: 'str' object has no attribute 'get' 

它看起來像它稱爲Name初始化:Name(s.get('name',{}))的字符串名稱「弗雷德」。

但它似乎不能,因爲在前面的例子中,我能夠刪除一個完全矛盾的驗證(is_stris_not_str)。爲什麼這個減去相反但失敗更多?在第一種情況下,它不是同時應用is_stris_not_str,爲什麼它/現在/將is_Nameis_str應用於看似相同的語法?

我的問題是:這樣做的第一種方式有什麼不同,導致它從第二種方式獲得成功?我試圖在這裏隔離變量,但不明白爲什麼我可以撤消在方案I中從父類繼承的包裝驗證器,但不能做方案II中看起來類似的東西。看起來唯一有意義的區別是它是一個對象而不是字符串。我知道更好的體系結構方式可以有更多的抽象父類,沒有需要改變的驗證規則---而且這兩種類型的人都可以繼承,但我也明白我應該能夠改變在子類中的方法,所以我想至少明白爲什麼一個成功和失敗等這裏的區別。)

回答

1

在你的第二個設置中,is_Name功能不應用。您正在創建Name對象,無論在__init__方法:

class Person(object): 
    def __init__(self,s): 
     self.name = Name(s.get('name',{})) 
     self.url = s.get('url','') 

注意self.name = Name(...)線那裏。

Crazyperson.__init__()調用父類的方法:

def __init__(self,s): 
    super(Crazyperson,self).__init__(s) 
    self.dance = s.get('dance') 

傳遞sPerson.__init__()它創建了一個Name()對象。

所以,當你與你逝去的name設置爲字符串'Fred'Name.__init__(),其預期,而不是一本字典fred = Crazyperson({"name":"Fred", ...})創建fred

class Name(object): 
    def __init__(self,s): 
     self.firstname = s.get('firstname','') 
     self.lastname = s.get('lastname') 

,這是你的代碼失敗:

>>> 'Fred'.get('firstname', '') 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: 'str' object has no attribute 'get' 

僅在Person上設置name如果尚未設置self.name

class Person(object): 
    def __init__(self,s): 
     if not hasattr(self, 'name') 
      self.name = Name(s.get('name', {})) 
     self.url = s.get('url','') 

,並設置name第一Crazyperson

def __init__(self,s): 
    self.name = s.get('name', 0) 
    self.dance = s.get('dance') 
    super(Crazyperson,self).__init__(s) 
+0

我看,這是有道理的---對不起,這是我做了一個不同的錯誤,但起控制作用的都是一樣的。我已經糾正它,以便我有一個新的名稱__init__方法。然而,它顯然仍然首先調用原始方法,並給我同樣的錯誤。我將如何去做方案II中的初始化/這個變量名/第二種情況下的不同? (而不必重寫兩者共用的'url'部分?) – Mittenchops

+0

對不起,在場景II中調用錯誤的方法對我來說並不明顯。追溯是什麼?您可能需要使用該信息更新您的問題。 –

+0

您可以創建名稱*可選*。如果's'中沒有'name'鍵,則根本不要設置'self.name'。然後在'Crazyperson'中設置名字,從's'中刪除鍵並繼續。 –