2017-02-03 5 views
6

記述子プロトコルはうまく動作しますが、解決したい問題が1つあります。これは、サブクラス化されPythonでディスクリプタプロトコルを扱うときに、どのように属性名を取得できますか?

class Field(object): 
    def __init__(self, type_, name, value=None, required=False): 
     self.type = type_ 
     self.name = "_" + name 
     self.required = required 
     self._value = value 

    def __get__(self, instance, owner): 
     return getattr(instance, self.name, self.value) 

    def __set__(self, instance, value): 
     if value: 
      self._check(value) 
      setattr(instance, self.name, value) 
     else: 
      setattr(instance, self.name, None) 

    def __delete__(self, instance): 
     raise AttributeError("Can't delete attribute") 

    @property 
    def value(self): 
     return self._value 

    @value.setter 
    def value(self, value): 
     self._value = value if value else self.type() 

    @property 
    def _types(self): 
     raise NotImplementedError 

    def _check(self, value): 
     if not isinstance(value, tuple(self._types)): 
      raise TypeError("This is bad") 

class Country(BaseModel): 
    name = CharField("name") 
    country_code_2 = CharField("country_code_2", min_length=2, max_length=2) 
    country_code_3 = CharField("country_code_3", min_length=3, max_length=3) 

    def __init__(self, name, country_code_2, country_code_3): 
     self.name = name 
     self.country_code_2 = country_code_2 
     self.country_code_3 = country_code_3 

これまでのところ、とても良い、この作品:使用

class CharField(Field): 
    def __init__(self, name, value=None, min_length=0, max_length=0, strip=False): 
     super(CharField, self).__init__(unicode, name, value=value) 
     self.min_length = min_length 
     self.max_length = max_length 
     self.strip = strip 

    @property 
    def _types(self): 
     return [unicode, str] 

    def __set__(self, instance, value): 
     if self.strip: 
      value = value.strip() 

     super(CharField, self).__set__(instance, value) 

そして、モデルクラスです

私は記述を持っていますちょうど期待どおり。

唯一の問題は、フィールドが宣言されるたびにフィールド名を指定する必要があることです。例えばcountry_code_2フィールドの場合は"country_code_2"です。

モデルクラスの属性名を取得し、それをフィールドクラスで使用する方法はありますか?

答えて

9

簡単な方法があり、難しい方法があります。

簡単な方法は、追加object.__set_name__() methodをPythonの3.6(以降)を使用し、あなたの記述を与えることです:

def __set_name__(self, owner, name): 
    self.name = '_' + name 

クラスが作成されると、Pythonは自動的にあなたが設定された任意の記述子でそのメソッドを呼び出します。クラスオブジェクトと属性名を渡します。

以前のPythonバージョンでは、次の最良の選択肢はmetaclassです。作成されたすべてのサブクラスで呼び出され、属性名を属性値(ユーザ記述子インスタンスを含む)にマッピングする便利な辞書が与えられます。あなたは、その後記述子にその名前を渡すために、この機会を利用することができます

class BaseModelMeta(type): 
    def __new__(mcls, name, bases, attrs): 
     cls = super(BaseModelMeta, mcls).__new__(mcls, name, bases, attrs) 
     for attr, obj in attrs.items(): 
      if isinstance(obj, Field): 
       obj.__set_name__(cls, attr) 
     return cls 

これは、Python 3.6がネイティブにサポートしていることを、フィールド上の同じ__set_name__()メソッドを呼び出します。次に、使用することをBaseModelのメタクラスとして:

class BaseModel(object, metaclass=BaseModelMeta): 
    # Python 3 

または

class BaseModel(object): 
    __metaclass__ = BaseModelMeta 
    # Python 2 

また、あなたがして、それを飾る任意のクラスのための__set_name__通話を行うには、クラスのデコレータを使用することができ、それは飾るする必要がありますすべてのクラス。メタクラスは自動的に継承階層を介して伝播されます。

+0

提案を実装した後で更新プログラムを追加しました。これを見ていただけますか? –

+0

'six.with_metaclass()'を正しく使用していないので、基本クラスとして使用する必要があります。 http://stackoverflow.com/questions/18513821/python-metaclass-understanding-the-with-metaclassを参照してください。代わりに、 'six'を使う前にPython 2や3で動作させたいかもしれません:-) –

+0

' six.with_metaclass() 'を' __metaclass__ = BaseModelMeta'に置き換えました。 (私はPython 2を使っています)これは同じエラーを出しています。 –

0

これは私の本の中で、Python Descriptorsですが、3.6で新機能を追加するための第2版は更新されていません。それ以外は、ディスクリプタに関するかなり包括的なガイドで、1つの機能だけで60ページを取ります。

とにかく、メタクラスせずに名前を取得する方法は、この非常に単純な機能である:

def name_of(descriptor, instance): 
    attributes = set() 
    for cls in type(instance).__mro__: 
     # add all attributes from the class into `attributes` 
     # you can remove the if statement in the comprehension if you don't want to filter out attributes whose names start with '__' 
     attributes |= {attr for attr in dir(cls) if not attr.startswith('__')} 
    for attr in attributes: 
     if type(instance).__dict__[attr] is descriptor: 
      return attr 

あなたが記述子の名前を使用するたびに、インスタンスが関与していると考えると、これはあまりにもすべきではありませんどのように使用するか把握することは困難です。また、最初に見た後に名前をキャッシュする方法を見つけることもできます。

+0

これでは不十分です。記述子はMROの後に続きます。 –

+0

あなたのコメントは解読するのが非常に困難でした。私はあなたが関数がスーパークラスのメンバーを取得していないと言っていたと思いますか?私はそれに取り組んでいます。私はそれを数分で終わらせます。 –

+0

編集が終了しました –

関連する問題