티스토리 뷰

Python

[Python] assert문에 관한 모든 것

whatisyourname 2022. 5. 10. 14:52
반응형

파이썬뿐만 아니라 여러 언어(C / C++, Java...)에서는, assertion이란 개념을 차용하고 있다. 그리고 이러한 assertion 개념은 사용자의 코드를 시험하거나, 디버깅할 때 매우 유용하게 사용할 수 있는 테크닉 중 하나이다.

 

이번 포스팅에서는 파이썬에서의 assert문에 관하여 알아보자.

thumbnail
assert문에 대하여 알아보자.


1. assert문이란?

assert문 혹은 assertion은 코드 개발 간에 하나의 '검토'를 담당한다. assert문은 일정 조건이 사용자의 의도에 부합하는지 검사하는 역할을 하는 구문이라 생각하면 된다.

 

예를 들어, int형이 매개변수인 함수에 인자로 str형이 들어오는지 검사할 수 있다. 혹은 return 값이 원하는 형태인지 검사할 수 있다.

 

따라서, 사용자가 의도한 형태인지 손쉽게 검사할 수 있으며, 만약 의도하지 않은 입력 혹은 출력이 발생할 경우를 대비할 수 있다.


2. assert문 사용방법

assert문은 하나의 statement와 같이 사용한다. 만약 assert문을 사용하고 싶으면 다음과 같이 작성하면 된다.

assert expression[, assertion_message]

assert을 사용할 때, assert문에서의 expression은 항상 참이어야 한다고 가정한다. 만약 assert문이 참이라면, 아무것도 발생하지 않고 파이썬은 계속해서 프로그램을 이어나간다. 그러나, 만약 assert문이 거짓이라면 assert문은 AssertionError를 발생시키며, 프로그램을 종료한다.

 

assert문에 assertion_message는 필수는 아니지만 무엇에 대한 AssertionError인지 추가 정보를 써 주는 구문이니 만큼, 채워주는 것이 좋다.

 

직접 assert문을 써 보자.

 

1
2
3
4
5
6
7
8
9
10
11
number = 1
assert number > 0
# 아무것도 발생하지 않는다.
 
number2 = 1
assert number2 < 0
 
# Traceback (most recent call last):
#   File "assert.py", line 2, in <module>
#     assert number2 < 0
# AssertionError
cs

 

첫 번째 assert문(number > 0)은 참이기에 아무것도 발생하지 않았지만, 두 번째 assert문(number2 < 0)은 거짓이기에 AssertionError가 발생한 모습을 확인할 수 있다. 이 경우 프로그램은 종료된다.

 

만약 assert문에 추가적인 assertion_message를 넣어준다면 다음과 같이 넣어줄 수 있다.

 

1
2
3
4
5
6
7
number2 = 42
assert number2 < 0, f"number should be below zero, got {number2}"
 
# Traceback (most recent call last):
#   File "assert.py", line 2, in <module>
#     assert number2 < 0, f"number should be below zero, got {number2}"
# AssertionError: number should be below zero, got 42
cs

 

assertion_message를 추가적으로 넣어주니, AssertionError 옆에 추가적인 정보를 제공하는 것을 확인할 수 있다. 따라서 assertion_message에는 현재 기대하고 있는 상태에 대한 정보를 넣어준다면 디버깅하기 훨씬 편할 것이다. 다만 assertion_message는 필수는 아니기에, 필요할 경우 넣어주면 된다.

 

많은 경우 assert문이 함수라 착각하여 다음과 같은 실수를 범하곤 한다.

 

1
2
3
4
5
number2 = 42
assert (number2 < 0, f"number should be below zero, got {number2}")
 
# assert.py:2: SyntaxWarning: assertion is always true, perhaps remove parentheses?
#   assert (number2 < 0, f"number should be below zero, got {number2}")
cs

 

위의 경우 파이썬 SyntaxWarning을 띄우며, assert문이 항상 참이라는 경고를 띄운다. (파이썬 버젼이 최신이 아닐 경우 안 띄워줄 수도 있다. 위의 경우 파이썬 3.10 버전에서의 경고창이다.)

 

왜 항상 참일까? 구조적으로 보면, 현재 assert문의 괄호는 함수에 인자를 넘겨주는 것이 아닌, 두 개의 데이터가 있는 tuple 하나가 있는 것과 같다. assert문은 함수가 아니다.

 

tuple은 안이 비어있지 않으면 항상 참이니, assert문은 항삼 참인 결과가 되어, assert문이 의미가 없어진다.

 

만약, assert문이 길어져 여러 줄로 작성해야 할 때는, 백슬래쉬(\)를 활용하여 줄 병합을 할 수 있다.

 

1
2
3
4
5
6
7
8
number2 = 42
assert number2 < 0 and isinstance(number2, int), \
    f"number should be below zero, got {number2}"
 
# Traceback (most recent call last):
#   File "assert.py", line 2, in <module>
#     assert number2 < 0 and isinstance(number2, int), \
# AssertionError: number should be below zero, got 42
cs

 

위의 코드에서 불필요하게 줄이 길어지는 막기 위하여 백슬래쉬(\)를 활용하였다.

 


3. assert문 구체적으로 활용하기

assert문은 expression이 참/거짓을 가질 수 있다면 어떤 문장이 오든 상관없다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assert 3 == 2
assert 5 in [246810]
 
= 2
= x
null = None
 
assert x is not y
assert null is not None
 
= 3.0
assert isinstance(z, int)
assert all([TrueTrueFalse])
assert any([FalseFalseFalse])
 
# 모두 AssertionError를 띄울 수 있는 문장들이다.
cs

 

이외에도 assert문 에는 참/거짓을 반환하는 함수나, 표현식이나, 객체나, 여러 다양한 식들이 올 수 있다.

 

이외에도 프로그램이 정상 작동하도록 보장하거나 예외 상황을 미연에 방지할 수 있는 구문이다.

 

다음과 같이 Rectangle class가 있다고 하여보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
import math
 
class Rectangle:
    def __init__(self, length1, length2):
        if length1 <= 0 or length2 <= 0:
            raise ValueError("length should be positive")
 
        self.length1 = length1
        self.length2 = length2
 
    def area(self):
        assert self.length1 > 0 and self.length2 > 0"length should be positive"
        return length1 * length2
cs

 

위와 같이 작성하였다고 하자. 생성자(__init__)에서 length1과 length2가 모두 0보다 작거나 같은 경우 ValueError를 띄우도록 하였다.

 

또한, area 메서드에서 assert문을 통해 length1과 length2가 0보다 크다는 것을 보장하였고, 만약 하나라도 0보다 작거나 같은 경우 이에 대한 문구를 띄우도록 하였다.

 

사실 __init__에서 length1과 length2가 0보다 크다는 것을 보장하였는데 왜 assert문이 더 필요할까?

 

만약 누군가가 class에 'change_length1'이란 새로운 메서드를 추가하였다고 가정하여보자.

 

1
2
3
4
5
6
7
8
9
10
11
class Rectangle:
    def __init__(self, length1, length2): ...
 
    def area(self): ...
 
    def change_length1(self, new_length1):
        self.length1 = new_length1
 
square1 = Rectangle(34)
square1.change_length1(-3)
print(square1.area())
cs

 

위의 코드에서 만약 change_length1을 이용하여 length1이 음수로 바뀔 경우 하나의 버그인데, 이에 대하여 경고문을 띄울 방법이 없다.

 

그러나, area에 미리 assert문을 통해 length1과 length2문이 양수임을 보장할 수 있다. 따라서, assert문은 방어적 프로그래밍에 활용하기 매우 좋은 구문이라고 할 수 있다.

 

위의 코드를 실행하면 다음과 같은 결과를 얻을 수 있다.

Traceback (most recent call last):
  File "assert.py", line 20, in <module>
    print(square1.area())
  File "assert.py", line 12, in area
    assert self.length1 > 0 and self.length2 > 0, "length should be positive"
AssertionError: length should be positive

 

이러한 예시에 assert는 예외적인 상황을 미연에 방지할 수 있는 장치라 할 수 있다.


4. assert문 사용시 주의사항

assert문 사용시 몇 가지 주의사항이 있다.

 

1) try...except문과 같이 사용하지 말 것

애초에 assert문을 사용하는 이유가 무엇인가? 몇 가지 조건 사항이 부합되지 않으면 프로그램이 강제 종료되도록 사용한다. 애초에 몇 가지 조건 사항이 맞지 않으면 이후 코드가 실행되지 않아야 하기 때문이다. 따라서, except로 AssertionError를 처리해서는 안 된다.


2) 예외 처리 목적으로 사용하지 말 것

 assert문의 목적은 예외 처리가 아닌, 의도되지 않은 프로그래밍 오류를 잡기 위해서이다. 따라서, 개발 과정에서 사용되어야 하는 문구이며, 실제 사용될 코드에서는 assert문이 있어서는 안 된다. 완성된 코드는 대부분 버그가 해결된 상태여야 하며, 모든 assertion이 작동되지 않아야 하기 때문이다.

 

또한, 예상 가능한 버그는 assert문을 사용해서는 안된다. 예를 들어 FileNotFoundError와 같이 사전에 충분히 예상가능한 버그는 assert 보다 try-except문을 통해 처리해 주는 것이 옳은 방식이다.


3) 의도하지 않은 결과, 혹은 부수적인 행동을 실행하는 코드일 때를 주의할 것

만약 assert문 자체가 예상치 못한 부수적인 행동을 한다면 assert문을 쓰지 말아야 한다.

 

다음 코드를 보자. assert문이 두 개 사용되었다.

 

1
2
3
4
5
6
7
8
9
10
11
def what_is_pop(input_list, index):
    item = input_list.pop(index)
    return item
 
list1 = [12345]
 
assert list1[-1== what_is_pop(list1, -1)
assert list1[1==  what_is_pop(list1, 1)
 
print(list1)
# [1, 3, 4]
cs

 

실제 assert문이 두 개 모두 참이어서 AsserionError가 발생하지 않았지만, 'what_is_pop' 함수 내의 pop 함수로 인하여 원래 list1의 값이 변경되었음을 확인할 수 있다.

 

따라서 예상하지 못하거나 의도치 않은 결과가 발생하였고, 이는 이후 코드에서 에러를 발생시킬 수 있다. assert문에서의 코드도 하나의 코드이기 때문에 이후의 코드에 영향을 줄 수 있다.


4) 성능 문제에 주의할 것

assert문은 방어적으로 코드를 짤 때 유용하게 사용할 수 있지만, 남발할 경우 코드의 성능에 영향을 미칠 수 있다. 만약 assert문이 너무 복잡할 경우, 시간 / 공간적인 낭비를 초래할 수 있다. 


그럼, assert문을 더 이상 사용하고 싶지 않다면 어떻게 해야할까?

 

만약 모든 assert문을 사용하고 싶지 않다면, 두 가지 방법을 활용하여 assert문을 없앨 수 있다.


  1. 파이썬 파일 실행 시 -O 혹은 -OO 옵션을 넣어준다.
  2. 환경 변수 PYTHONOPTIMIZE에 적당한 값을 넣어준다.

두 가지 방법을 이해하기 전 파이썬 debug모드와 optimize 동작 모드를 간단히 살펴보자.

 

파이썬 내부에는 '__debug__'라는 내장 상수가 존재한다. (공식 문서의 설명은 여기를 누르면 된다.)

 

이 내장 상수는 현재 파이썬이 Normal(debug) 모드로 실행되었는지, 혹은 Optimized 모드로 실행되었는지 알려주는 상수이다. Normal의 경우 __debug__는 참이며, Optimized의 경우 __debug__는 거짓이 된다. (다른 언어에서 Optimized 모드는 release 모드와 비슷하다고 생각하면 된다.)

 

파이썬 사용자는 실제 개발 과정 시 Normal 모드를 사용하여 코드를 개발 및 테스트를 진행할 수 있으며, 만약 개발이 완료되었다면 Optimize 모드를 사용하여 코드를 release 할 수 있다.

 

이것이 어떻게 assert문과 연관이 될까? assert문을 풀어보면 다음과 같이 쓸 수 있다. 위의 코드와 아래의 코드는 같은 역할을 한다.

 

1
2
3
4
5
assert expression, assertion_message
# ----------------------------------------
if __debug__:
    if not expression:
        raise AssertionError(assertion_message)
cs

 

위의 코드에서 assert문이 __debug__문이 참일 때만 실행되는 것을 확인할 수 있다.

 

따라서, assert문은 __debug__문이 참일 때, 즉 Normal(debug) 모드일 때만 실행된다고 생각할 수 있다.

 

따라서 Normal모드에서 Optimize 모드를 사용하여 파이썬을 실행하겠다고 하면, 파일을 실행할 때 -O 옵션 혹은 -OO 옵션을 넣어주거나, 환경변수 PYTHONOPTIMIZE에 알맞은 값을 넣어주어 실행할 수 있다.

 

이 경우 assert문이 실행이 되지 않으니, 사실상 assert문을 모두 지운 것과 같은 효과를 낸다.

 

다만, 예외 처리를 꼼꼼히 안 해주거나 assert문과 관련된 버그가 남아있으면 assert문이 사라짐에 따라 문제가 생길 수 있다.

 

구체적인 코드로 살펴보자. 다음과 같이 debug.py 파일을 작성하였다고 하자.

 

1
2
3
4
5
6
print(f"{__debug__}")
 
if __debug__:
    print("I'm Normal mode!")
else:
    print("I'm optimized mode!")
cs

 

이에 대하여 -O 옵션을 넣지 않았을 때와 넣었을 때의 출력 결과는 다음과 같다.

 

-O 옵션을 넣어주지 않았을 때 Normal 모드로 실행되었고 넣어주었을 때 optimized 모드로 실행된 것을 확인할 수 있다.

 

만약 일일이 -O 옵션을 넣어주기 힘들 때는, PYTHONOPTIMIZE 환경 변수를 사용하여 두 모드 간 변경을 해줄 수 있다.

 

만약 PYTHONOPTIMIZE 설정을 켜고 싶다면 다음과 같이 명령 프롬프트에 넣어주면 된다.

<windows>
C:\> set PYTHONOPTIMIZE = 1 # python -o debug.py와 같음
C:\> set PYTHONOPTIMIZE = 2 # python -oo debug.py와 같음

<Linux / macOS>
$ export PYTHONOPTIMIZE = 1
$ export PYTHONOPTIMIZE = 2

PYTHONOPTIMIZE 설정에 빈 문자열이 아닌 다른 것을 넣어줄 경우, 파이썬은 자동으로 optimized 모드로 실할 것이다. 만약 원래 설정으로 돌아가고 싶다면 1 혹은 2 대신 0을 넣어주면 된다. 3 이상의 정수를 넣어주면 아무 변화가 없다.


assert문을 잘 활용한다면, 코드의 버그를 더 잘 잡고 디버깅이 한결 편해지리라 생각한다.

 

도움이 되었다면 지나가는 길에 하트 하나 눌러주세요, 양질의 글을 쓰는데 하나의 동기부여가 됩니다😍

지적이나 오타 수정 댓글 환영합니다!!

반응형
댓글
Total
Today
Yesterday
공지사항
최근에 올라온 글
최근에 달린 댓글
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함