[python] 클래스 인스턴스의 속성을 검증하는 올바른 접근 방식

다음과 같은 간단한 Python 클래스가 있습니다.

class Spam(object):
    __init__(self, description, value):
        self.description = description
        self.value = value

다음 제약 조건을 확인하고 싶습니다.

  • “설명은 비워 둘 수 없습니다.”
  • “값은 0보다 커야합니다.”

1. 스팸 개체를 만들기 전에 데이터의 유효성을 검사 해야합니까 ?
2. __init__방법 에 대한 데이터를 확인 ?
3. is_validSpam 클래스에 메서드를 만들고 spam.isValid ()로 호출합니까?
4. is_validSpam 클래스에 정적 메서드를 만들고 Spam.isValid (description, value)?
5. setters 선언에 대한 데이터를 확인합니까?
6. 등

잘 디자인 된 / 파이 토닉 / 장황하지 않은 (많은 속성을 가진 수업에서) / 우아한 접근 방식을 추천 해 주시겠습니까?



답변

Python 속성 을 사용 하여 각 필드에 개별적으로 규칙을 명확하게 적용하고 클라이언트 코드가 필드를 변경하려고 할 때에도 적용 할 수 있습니다.

class Spam(object):
    def __init__(self, description, value):
        self.description = description
        self.value = value

    @property
    def description(self):
        return self._description

    @description.setter
    def description(self, d):
        if not d: raise Exception("description cannot be empty")
        self._description = d

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

    @value.setter
    def value(self, v):
        if not (v > 0): raise Exception("value must be greater than zero")
        self._value = v

__init__함수 내에서도 규칙을 위반하려는 모든 시도에서 예외가 발생하며이 경우 객체 생성이 실패합니다.

업데이트 : 2010 년과 지금 사이에 operator.attrgetter다음 사항에 대해 배웠습니다 .

import operator

class Spam(object):
    def __init__(self, description, value):
        self.description = description
        self.value = value

    description = property(operator.attrgetter('_description'))

    @description.setter
    def description(self, d):
        if not d: raise Exception("description cannot be empty")
        self._description = d

    value = property(operator.attrgetter('_value'))

    @value.setter
    def value(self, v):
        if not (v > 0): raise Exception("value must be greater than zero")
        self._value = v


답변

개체가 생성 될 때만 값의 유효성을 검사하고 잘못된 값을 전달하는 것이 프로그래밍 오류로 간주되면 어설 션을 사용합니다.

class Spam(object):
    def __init__(self, description, value):
        assert description != ""
        assert value > 0
        self.description = description
        self.value = value

이것은 당신이 얻는 것만 큼 간결하며 이것이 객체를 만들기위한 전제 조건임을 명확하게 문서화합니다.


답변

직접 롤링하지 않는 한 formencode 를 사용할 수 있습니다 . 많은 속성과 스키마 (하위 클래스 스키마 만)로 정말 빛나고 유용한 유효성 검사기가 많이 내장되어 있습니다. 보시다시피 이것은 “스팸 ​​개체를 만들기 전에 데이터 유효성 검사”접근 방식입니다.

from formencode import Schema, validators

class SpamSchema(Schema):
    description = validators.String(not_empty=True)
    value = validators.Int(min=0)

class Spam(object):
    def __init__(self, description, value):
        self.description = description
        self.value = value

## how you actually validate depends on your application
def validate_input( cls, schema, **input):
    data = schema.to_python(input) # validate `input` dict with the schema
    return cls(**data) # it validated here, else there was an exception

# returns a Spam object
validate_input( Spam, SpamSchema, description='this works', value=5)

# raises an exception with all the invalid fields
validate_input( Spam, SpamSchema, description='', value=-1)

당신은 또한 검사를 할 수 있지만 __init__(그리고 descriptors | decorators | metaclass로 완전히 투명하게 만들 수 있습니다),하지만 저는 그 팬이 아닙니다. 저는 사용자 입력과 내부 개체 사이의 깨끗한 장벽을 좋아합니다.


답변

생성자에 전달 된 값만 검증하려면 다음을 수행 할 수 있습니다.

class Spam(object):
    def __init__(self, description, value):
        if not description or value <=0:
            raise ValueError
        self.description = description
        self.value = value

물론 이것은 누군가가 다음과 같은 일을하는 것을 막을 수 없습니다.

>>> s = Spam('s', 5)
>>> s.value = 0
>>> s.value
0

따라서 올바른 접근 방식은 수행하려는 작업에 따라 다릅니다.


답변

시도해 볼 수 있습니다 pyfields.

from pyfields import field

class Spam(object):
    description = field(validators={"description can not be empty": lambda s: len(s) > 0})
    value = field(validators={"value must be greater than zero": lambda x: x > 0})

s = Spam()
s.description = "hello"
s.description = ""  # <-- raises error, see below

그것은 산출

ValidationError[ValueError]: Error validating [<...>.Spam.description=''].
  InvalidValue: description can not be empty.
  Function [<lambda>] returned [False] for value ''.

python 2 및 3.5 (와 반대 pydantic) 와 호환되며 값이 변경 될 때마다 유효성 검사가 발생합니다 (이 아닌 첫 번째뿐만 아니라 attrs). 생성자를 생성 할 수 있지만 기본적으로 위와 같이 생성하지는 않습니다.

mini-lambda오류 메시지를 훨씬 더 간단하게 표시하려면 일반 이전 람다 함수 대신 선택적으로 사용할 수 있습니다 (실패한 표현식이 표시됨).

자세한 내용은 pyfields문서 를 참조하십시오 (저는 저자입니다;))


답변