覆盖修饰的子类方法

时间:2013-07-11 15:41:01

标签: python validation subclass wrapper decorator

我正在摆弄继承并发现一种对我来说很奇怪的行为 - 也就是说,有时候我可以覆盖父装饰器功能(用于验证),但有时我不能,而且我不能明白为什么或有什么区别。

单词的快速演练---我有一个人物对象我喜欢子类化到更具体的人物对象。更具体的人将有一个额外的领域,"舞蹈,"并且在前一个字段上会有不同的验证规则," name。"

这是我的基本案例:

# 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失败了。该错误表明is_Name中的__init__验证功能仍在应用中:

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'

看起来它已在字符串名称&#34; Fred&#34;上调用了Name初始化程序:Name(s.get('name',{}))

但它似乎不可能,因为在前面的示例中,我能够删除完全矛盾的验证(is_stris_not_str)。为什么不那么相反但是失败更多?在第一种情况下,它不同时应用is_stris_not_str,为什么/现在/同时应用is_Nameis_str的语法看似相同?

我的问题是:第一种做法导致它从第二种方式取得成功的不同之处是什么?我试图在这里分离变量,但不明白为什么我可以撤消在场景I中从父类继承的包装验证器,但不能在场景II中做类似的事情。似乎唯一有意义的区别是它是一个对象而不是一个字符串。

(我知道更好的架构方法是拥有第三个更抽象的父类,没有需要改变的验证规则 - 两种人都会从中继承。但我也理解我我应该能够改变子类中的方法,所以我想至少理解为什么一个成功而另一个成功失败。)

1 个答案:

答案 0 :(得分: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') 

s传递给Person.__init__(),这会创建Name()个对象。

因此,当您使用fred创建fred = Crazyperson({"name":"Fred", ...})时,您将name设置为字符串'Fred'并传递给Name.__init__(),而这需要字典:

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'

如果尚未设置name,则仅在Person上设置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)