私は継承をいじっていて、奇妙に思える動作を見つけました---つまり、親デコレータ関数(検証に使用される)をオーバーライドできる場合もありますが、できない場合もあり、理由や内容を理解できないこともあります違いは。
言葉で簡単に説明します---より特定の人物オブジェクトにサブクラス化したい人物オブジェクトがあります。より具体的なものには、追加のフィールド「ダンス」があり、前のフィールド「名前」には異なる検証ルールがあります。
動作する私の基本ケースは次のとおりです。
# 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"})
これはうまくいきます。is_str
したがって、検証が成功するように、最初のオブジェクト bill が作成されます。そこに数字を入れようとすると失敗します。2 番目のオブジェクトも同様に非文字列を受け入れるため、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'
文字列名「Fred」でName
初期化子を呼び出したようです。Name(s.get('name',{}))
is_str
前の例では、完全に矛盾する検証 ( vs )を削除できたためis_not_str
です。なぜこれは反対ではなく、より失敗するのですか? 最初のケースでは、 と の両方を適用していませんでしたがis_str
、is_not_str
なぜ/今/ 両方is_Name
とis_str
を一見同一の構文で適用するのでしょうか?
私の質問は、これを行う最初の方法と 2 番目の方法の違いは何ですか? ここで変数を分離しようとしましたが、シナリオ I で親クラスから継承されたラップされたバリデーターを元に戻すことができるのに、シナリオ II で似ていると思われることを実行できない理由がわかりません。意味のある唯一の違いは、文字列ではなくオブジェクトであることです。
(これを行うためのより良いアーキテクチャ上の方法は、変更する必要のある検証ルールを持たない、より抽象的な3番目の親クラスを持つことであることを理解しています---そして、両方の種類の人がそれを継承します。しかし、私はサブクラスのメソッドを変更できるようにするため、ここで一方が成功する理由と他方が失敗する理由の違いを少なくとも理解したい.)