2017-02-03 17 views
6

설명자 프로토콜이 제대로 작동하지만 해결할 문제가 하나 있습니다. 이 서브 클래스입니다파이썬에서 설명자 프로토콜로 작업 할 때 어떻게 속성 이름을 얻을 수 있습니까?

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

간단한 방법이 있으며 어려운 방법이 있습니다. 당신이 설정 한 기술자에

클래스가 생성됩니다
def __set_name__(self, owner, name): 
    self.name = '_' + name 

, 파이썬이 자동으로 호출하는 방법

간단한 방법은 파이썬 3.6 (이상)을 사용하여 설명을 추가 object.__set_name__() method을 제공하는 것입니다 클래스 객체와 속성 이름을 전달합니다.

이전 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 

이 파이썬 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'를 사용하기 전에 파이썬 2 또는 3에서 작동하게 만들기 시작할 수도 있습니다 :-) –

+0

'six.with_metaclass()'를'__metaclass__ = BaseModelMeta'로 바꿉니다. (나는 Python 2를 사용하고있다) 이것은 같은 오류를 준다. –

0

나는이 책을 내 책인 Python Descriptors으로 보내지만 3.6 버전의 새로운 기능을 추가하기 위해 두 번째 버전을 업데이트하지는 않았습니다. 그 외에도 하나의 기능만으로 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

수정이 완료되었습니다. –