2013-07-11 89 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'中删除键并继续。 –