Published 2021. 1. 6. 10:47

파이썬을 사용한 프로젝트를 진행하거나 코드를 읽다보면 종종 Assertion 구문을 만나곤 합니다. 정확하게 이 구문이 언제 사용되는지 혹은 어떻게 사용해야 하는지 알지 못하고 넘어가곤 했습니다.

읽은 책의 내용을 토대로 Assertion 구문을 언제 그리고 어떻게 사용하는지 알아보겠습니다.

 

Assertion

assert는 뒤의 조건이 True가 아니면 AssertError를 발생합니다.

 

왜 assert가 필요한 것일까?

어떤 함수는 성능을 높이기 위해 반드시 정수만을 입력받아 처리하도록 만들 수 있다. 이런 함수를 만들기 위해서는 반드시 함수에 정수만 들어오는지 확인할 필요가 있다. 이를 위해 if문을 사용할 수도 있고 '예외 처리'를 사용할 수도 있지만 '가정 설정문'을 사용하는 방법도 있다.

 

아래 코드는 함수 인자가 정수인지 확인하는 코드이다.

lists = [1, 3, 6, 3, 8, 7, 13, 23, 13, 2, 3.14, 2, 3, 7]

 

def test(t):
    assert type(t) is int, '정수 아닌 값이 있네'

for i in lists:
    test(i)

 

결과

AssertionError: 정수 아닌 값이 있네

 

lists에 실수가 하나 있으므로 AssertionError가 발생했다. assert 문은 다음 형식으로 작동한다.

 

assert 조건, '메시지'

'메시지'는 생략할 수 있다.

 

assert는 개발자가 프로그램을 만드는 과정에 관여한다. 원하는 조건의 변수 값을 보증받을 때까지 assert로 테스트 할 수 있다.

이는 단순히 에러를 찾는것이 아니라 값을 보증하기 위해 사용된다.

 

예를 들어 함수의 입력 값이 어떤 조건의 참임을 보증하기 위해 사용할 수 있고 함수의 반환 값이 어떤 조건에 만족하도록 만들 수 있다. 혹은 변수 값이 변하는 과정에서 특정 부분은 반드시 어떤 영역에 속하는 것을 보증하기 위해 가정 설정문을 통해 확인 할 수도 있다.

 

이처럼 실수를 가정해 값을 보증하는 방식으로 코딩 하기 때문에 이를 '방어적 프로그래밍'이라 부른다.


Assertion 구문은 어떤 조건을 테스트하는 디버깅 보조 도구 라는 것이 핵심입니다.

아래의 코드는 assert 가 사용되는 예시입니다.

온라인 쇼핑몰에서 할인 쿠폰 기능을 시스템에 추가하고, 다음과 같은 apply_discount 함수를 작성했습니다.

 

def apply_discount(product, discount):
  price = int(product['price'] * (1.0 - discount))
  assert 0 <= price <= product['price']
  return price

 

의도대로라면, 이 함수로 계산된 가격은 0원보다 낮을 수 없고, 할인되었기 때문에 원래의 가격보다 높으면 안됩니다.

일반적인 경우라면, 할인율(discount)이 0이상 1이하의 범위일 것입니다. 이런 경우에는 당연하게도 할인된 가격이 assert 구문의 조건을 참으로 만들게 됩니다.

 

하지만, 할인율(discount)이 0이상 1이하의 범위가 아니라면 어떨까요?

예를 들면 할인율이 2가 되면, price는 음수가 될 것입니다. 즉, 상품을 사는 고객에게 돈을 더 줘야 됩니다.

다행히도 assert 구문에서는 이런 경우에 assert 구문의 조건이 거짓이 되므로 AssertionError라는 예외가 발생하게 됩니다.

만약 자신이 이 코드를 작성한 프로그래머라고 가정해 보겠습니다. 이 함수내에 Assertion 구문이 없었다면, 쇼핑몰을 운영하는 도중 문제가 발생했을 때, 디버깅 하는것이 생각보다 쉽지 않을 수 있습니다.

 

반대로, Assertion 구문을 적절하게 위치시켜 버그 상황시에 AssertionError 예외가 발생한다면 위치에 대한 스택트레이스(stacktrace)를 확인하여 버그를 쉽게 디버깅 할 수 있을것입니다.

 

이것이 Assertion 구문이 가지는 힘입니다.

 

일반 예외처리와 무엇이 다른가?

Assertion 구문은 일반적인 if구문 try - except 구문을 사용한 예외처리와 다른 역할을 합니다. 예를 들면 File-Not-Found와 같은 예상되는 에러 조건을 검사하기 위해 사용되는것은 올바른 활용 방식이 아닙니다.

 

이 구문은 예상하지 않은 프로그램의 상태를 확인하기 위해 활용해야 합니다. 구문의 조건을 만족하지 않으면 프로그램이 정상적으로 실행되지 않고 종료되는데, 이는 프로그램의 버그가 있다는 것을 의미합니다.

 

이런 특징으로 비추어 볼 때, Assertion 구문이 런타임 환경이 아닌 디버깅 환경에 도움을 주는 역할을 한다는 것을 알 수 있습니다. 개발자는 이를 토대로 개발환경에서 편안하게 디버깅하게 됩니다.

 

문법

assert_stmt ::= "assert" expression1 [",", expression2]

 

expression1은 테스트 조건이고, 뒤의 expression2는 테스트 조건이 거짓일 때, 예외의 메시지로 전달할 메시지입니다.

주의 사항

위의 문법을 인터프리터가 해석하는 방식을 간단한 토막코드로 만들게되면 다음과 같습니다.

if __debug__:
  if not expression1:
    raise AssertionError(expression2)

이 코드를 보면, 앞서 설명했던 런타임 환경이 아닌 디버깅 환경에 도움을 주는 역할을 해야만 하는 이유를 이해할 수 있습니다.

 

Assertion 구문은 __debug__라는 전역변수를 검사를 합니다. 이 전역변수는 일반적인 상황에서는 항상 참이지만 최적화가 필요한 경우에는 거짓이 되게 됩니다.

 

따라서, Assertion 구문을 예외처리에 잘못 활용하게된다면, 코드가 의도한대로 동작하지 않을 수 있습니다.

예를들면 데이터 유효성 검증을 하는데 Assertion 구문을 사용하게 된다면 어떨까요?

 

def delete_product(prod_id, user):
  assert user.is_admin(), 'Must Be Admin'
  assert store.has_product(prod_id), 'Unknown product'
  store.get_product(prod_id).delete()

일반적인 경우라면 __debug__ 전역변수는 참이므로 사용자에 대한 권한 확인과 제품이 존재하는지 확인하는 과정이 올바르게 진행될 것입니다.

 

반면, PYTHONOPTIMIZE와 같은 환경변수 설정으로 인해 Assertion 구문이 비활성화가 된다면 위의 함수는 의도와 동작하지 않게 될 것이고 이는 큰 장애로 귀결될 수 있습니다.

 

결국, 이런 문제를 회피하기 위해서는 데이터 유효성 검증시에 Assertion 구문을 절대 사용하지 말아야 합니다. 대신 유효성 검사에서는 if 구문 등을 사용하여 처리하고 예외를 발생시켜야 합니다.

 

 

출처 : kirade.github.io/python/2019/03/23/python-assert-구문-사용하기/

 

 

복사했습니다!