프로그래밍/파이썬

파이썬(Python), 클로저와 람다를 활용한 함수형 데이터 검증 시스템 구현하기

dang_dang 2025. 3. 15. 10:51
728x90
  1. 확장된 기본 검사 규칙:
    • float, string, phone, url, not_empty 등 다양한 데이터 타입 검사
    • 범위 검사 (range:min-max)
    • 길이 검사 (min_length, max_length)
    • 정규 표현식 패턴 검사 (pattern:regex)
    • 열거형 값 검사 (in:val1,val2,val3)
  2. 고급 복합 규칙:
    • 타입과 값 범위를 함께 검사 (예: 0-1000000 사이의 float)
    • 필드별 다양한 검증 규칙 조합
    • 중첩된 객체 구조 검증 가능
  3. 논리 연산자:
    • AND 조건 (all(validators)) - 모든 규칙 만족
    • OR 조건 (any(validators)) - 하나 이상의 규칙 만족
  4. 함수형 프로그래밍 강화:
    • 함수를 직접 규칙으로 전달 가능
    • 람다 함수를 활용한 동적 검증 로직
    • 클로저를 통한 상태 캡처

[장점]

  • 선언적 검증: 복잡한 코드 없이 간결하게 검증 규칙 정의
  • 재사용성: 공통 검증 로직을 여러 곳에서 재사용
  • 조합 가능: 작은 검증 단위를 조합하여 복잡한 검증 구현
  • 확장성: 새로운 검증 규칙을 쉽게 추가 가능

실제 웹 애플리케이션, API 서버, 데이터 처리 시스템 등에서 이러한 검증 시스템을 활용하면 코드 중복을 줄이고 일관된 데이터 검증을 구현할 수 있음.

def create_validator(rule):
    """
    유효성 검사 규칙 (rule)에 따라 검사 함수 (클로저)를 생성하는 함수
    
    Args:
        rule: 유효성 검사 규칙 (문자열, 딕셔너리, 함수 등)
        
    Returns:
        function: 유효성 검사 함수 (클로저)
    """
    if isinstance(rule, str):
        # 문자열 규칙
        if rule == "integer":
            return lambda x: isinstance(x, int)
        elif rule == "float":
            return lambda x: isinstance(x, (int, float))
        elif rule == "string":
            return lambda x: isinstance(x, str)
        elif rule == "email":
            import re
            email_regex = r"[^@]+@[^@]+\.[^@]+"  # 간단한 이메일 정규 표현식
            return lambda x: re.match(email_regex, x) is not None
        elif rule == "phone":
            import re
            phone_regex = r"^\d{3}-\d{3,4}-\d{4}$"  # 한국 전화번호 형식
            return lambda x: re.match(phone_regex, x) is not None
        elif rule == "url":
            import re
            url_regex = r"^https?://[^\s/$.?#].[^\s]*$"  # 기본적인 URL 형식
            return lambda x: re.match(url_regex, x) is not None
        elif rule == "not_empty":
            return lambda x: bool(x)
        
        # 범위 검사
        elif rule.startswith("range:"):
            parts = rule.split(":")[1].split("-")
            min_val, max_val = int(parts[0]), int(parts[1])
            return lambda x: min_val <= x <= max_val
        
        # 문자열 길이 검사
        elif rule.startswith("length:"):
            length = int(rule.split(":")[1])
            return lambda x: len(x) == length
        elif rule.startswith("min_length:"):
            min_length = int(rule.split(":")[1])
            return lambda x: len(x) >= min_length
        elif rule.startswith("max_length:"):
            max_length = int(rule.split(":")[1])
            return lambda x: len(x) <= max_length
        
        # 문자열 패턴 검사
        elif rule.startswith("pattern:"):
            import re
            pattern = rule.split(":", 1)[1]
            return lambda x: re.match(pattern, x) is not None
        
        # 목록 포함 여부 검사
        elif rule.startswith("in:"):
            options = rule.split(":", 1)[1].split(",")
            return lambda x: x in options
    
    elif isinstance(rule, dict):
        # 딕셔너리 규칙 (여러 규칙 조합)
        if "type" in rule:
            # 타입 검사와 추가 검사 조합
            type_validator = create_validator(rule["type"])
            sub_validators = []
            
            for key, sub_rule in rule.items():
                if key == "type":
                    continue
                    
                if key == "min":
                    sub_validators.append(lambda x, min_val=rule["min"]: x >= min_val)
                elif key == "max":
                    sub_validators.append(lambda x, max_val=rule["max"]: x <= max_val)
                elif key == "enum":
                    sub_validators.append(lambda x, enum=rule["enum"]: x in enum)
                else:
                    # 다른 규칙들도 처리 가능
                    pass
            
            return lambda x: type_validator(x) and all(v(x) for v in sub_validators)
        else:
            # 객체 필드 검사
            field_validators = {}
            for key, sub_rule in rule.items():
                field_validators[key] = create_validator(sub_rule)
            
            return lambda x: all(v(x.get(k)) for k, v in field_validators.items())
    
    elif callable(rule):
        # 함수나 람다 규칙
        return rule
    
    elif isinstance(rule, list):
        # 여러 규칙 중 하나만 만족해도 됨 (OR 조건)
        validators = [create_validator(sub_rule) for sub_rule in rule]
        return lambda x: any(v(x) for v in validators)
    
    # 기본 검사 함수
    return lambda x: False

# 기본 타입 검사기
is_integer = create_validator("integer")
is_float = create_validator("float")
is_string = create_validator("string")
is_email = create_validator("email")
is_phone = create_validator("phone")
is_url = create_validator("url")

# 범위 및 길이 검사기
is_adult = create_validator("range:18-120")  # 18세 이상, 120세 이하
is_password = create_validator("min_length:8")  # 8자 이상 비밀번호
has_name = create_validator("min_length:2")  # 2자 이상 이름

# 패턴 검사기
is_korean_name = create_validator("pattern:^[가-힣]{2,5}$")  # 한글 이름 (2-5자)
is_zipcode = create_validator("pattern:^\\d{5}$")  # 5자리 우편번호

# 복합 검사기
is_valid_user = create_validator({
    "name": "min_length:2",
    "email": "email",
    "age": "range:18-120"
})

# 고급 복합 검사기
product_validator = create_validator({
    "id": "string",
    "price": {
        "type": "float",
        "min": 0,
        "max": 1000000
    },
    "category": "in:전자제품,의류,식품,도서"
})

# OR 조건 검사기 (이메일 또는 전화번호)
is_contact = create_validator(["email", "phone"])

# 커스텀 함수 사용
is_even = create_validator(lambda x: isinstance(x, int) and x % 2 == 0)

# 테스트 함수
def test_validators():
    print("=== 기본 타입 검사 ===")
    print(f"is_integer(42): {is_integer(42)}")
    print(f"is_integer('42'): {is_integer('42')}")
    print(f"is_float(3.14): {is_float(3.14)}")
    print(f"is_email('user@example.com'): {is_email('user@example.com')}")
    print(f"is_phone('010-1234-5678'): {is_phone('010-1234-5678')}")
    print(f"is_url('https://www.example.com'): {is_url('https://www.example.com')}")
    
    print("\n=== 범위 및 길이 검사 ===")
    print(f"is_adult(25): {is_adult(25)}")
    print(f"is_adult(15): {is_adult(15)}")
    print(f"is_password('secret'): {is_password('secret')}")
    print(f"is_password('secret123'): {is_password('secret123')}")
    
    print("\n=== 패턴 검사 ===")
    print(f"is_korean_name('홍길동'): {is_korean_name('홍길동')}")
    print(f"is_korean_name('Kim'): {is_korean_name('Kim')}")
    print(f"is_zipcode('12345'): {is_zipcode('12345')}")
    
    print("\n=== 복합 검사 ===")
    user1 = {"name": "홍길동", "email": "hong@example.com", "age": 30}
    user2 = {"name": "김", "email": "invalid", "age": 15}
    print(f"is_valid_user(user1): {is_valid_user(user1)}")
    print(f"is_valid_user(user2): {is_valid_user(user2)}")
    
    print("\n=== 고급 복합 검사 ===")
    product1 = {"id": "P001", "price": 50000, "category": "전자제품"}
    product2 = {"id": "P002", "price": -1000, "category": "가구"}
    print(f"product_validator(product1): {product_validator(product1)}")
    print(f"product_validator(product2): {product_validator(product2)}")
    
    print("\n=== OR 조건 검사 ===")
    print(f"is_contact('user@example.com'): {is_contact('user@example.com')}")
    print(f"is_contact('010-1234-5678'): {is_contact('010-1234-5678')}")
    print(f"is_contact('contact'): {is_contact('contact')}")
    
    print("\n=== 커스텀 함수 검사 ===")
    print(f"is_even(4): {is_even(4)}")
    print(f"is_even(5): {is_even(5)}")

if __name__ == "__main__":
    test_validators()

 

728x90