프로그래밍/파이썬
파이썬(Python), 클로저와 람다를 활용한 함수형 데이터 검증 시스템 구현하기
dang_dang
2025. 3. 15. 10:51
728x90
- 확장된 기본 검사 규칙:
- float, string, phone, url, not_empty 등 다양한 데이터 타입 검사
- 범위 검사 (range:min-max)
- 길이 검사 (min_length, max_length)
- 정규 표현식 패턴 검사 (pattern:regex)
- 열거형 값 검사 (in:val1,val2,val3)
- 고급 복합 규칙:
- 타입과 값 범위를 함께 검사 (예: 0-1000000 사이의 float)
- 필드별 다양한 검증 규칙 조합
- 중첩된 객체 구조 검증 가능
- 논리 연산자:
- AND 조건 (all(validators)) - 모든 규칙 만족
- OR 조건 (any(validators)) - 하나 이상의 규칙 만족
- 함수형 프로그래밍 강화:
- 함수를 직접 규칙으로 전달 가능
- 람다 함수를 활용한 동적 검증 로직
- 클로저를 통한 상태 캡처
[장점]
- 선언적 검증: 복잡한 코드 없이 간결하게 검증 규칙 정의
- 재사용성: 공통 검증 로직을 여러 곳에서 재사용
- 조합 가능: 작은 검증 단위를 조합하여 복잡한 검증 구현
- 확장성: 새로운 검증 규칙을 쉽게 추가 가능
실제 웹 애플리케이션, 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