betterway20None보다는 예외

김승환·2021년 7월 11일

코딩의 기술

목록 보기
11/36

None을 반환하기보다는 예외를 발생시켜라

  • 유틸리티 함수를 작성할 때 반환 값을 None으로 하면서 이 값이 특별한 의미를 부여하려는 경향을 나타낸다.
# 한 수를 다른 수로 나누는 도우미 함수를 작성한다고 하자.
# 0으로 나누는 경우 결과가 정해져 있지 않으므로 None을 반환하는 것이 자연스러워 보인다.
# ZeroDivisionError가 나올 때 None으로 반환
def careful_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None
x, y = 1, 0
result = careful_divide(x, y)
if result is None:
    print('잘못된 입력')

print(result)

잘못된 입력
None

  • 자수가 0일 경우에는 0값을 반환한다.
  • 그 경우 if 함수에서 0을 flase로 받아드려 실행이 된다.
# 답은 0인데 아래 설정한 if 조건문은 0을 flase로 실행하게 된다.
x, y = 0, 5
result = careful_divide(x, y)
if not result:
    print('잘못된 입력') # 이 코드가 실행되는데, 사실 이 코드가 실행되면 안된다!

잘못된 입력

careful_divide와 같은 함수에서 None을 반환하면 오류를 야기하기 쉽다.

  • 실수를 줄이는 방법 :
    - 튜플로 분리한다. (연산이 성공인지 실패인지 표시한다.)
    - 계산에 성공한 경우 실제 결과값을 저장한다.
# 호출 부분에 언패킹을 이용하여 항상 튜플에서 상태부분을 살펴보게 한다.
# 계산이 가능하면 true로 써 return값을 반환
def careful_divide(a, b):
    try:
        return True, a / b
    except ZeroDivisionError:
        return False, None

x = careful_divide(0,4)
print(x)

(True, 0.0)

# 튜플로 상태를 검사하고 언패킹으로 값을 저장
success, result = careful_divide(0, 3)
if not success:
    print('잘못된 입력')
    
print(success)
print(result)

True
0.0

# 
success, result = careful_divide(3, 0)
if not success:
    print('잘못된 입력')

print(success)
print(result)

잘못된 입력
False
None

def careful_divide(a, b):
    try:
        return True, a / b
    except ZeroDivisionError:
        return False, None
    
x, y = 0, 5

#
_, result = careful_divide(0, 3)
if not success:
    print('잘못된 입력')
  • 위 방법의 문제는 호출하는 쪽에서 튜플의 첫 번째 부분(변수를 사용하지 않음을 밑줄로 표시하는 파이썬 표준 관례???)을 쉽게 무시
  • 한눈에 잘못됐는지 알아보기 어렵지만, None을 반환한 경우와 마찬가지로 실수 할 수 있다.
#
x, y = 0, 5

def careful_divide(a, b):
    try:
        return True, a / b
    except ZeroDivisionError:
        return False, None


#
_, result = careful_divide(x, y)
if not success:
    print('잘못된 입력')

내 마음대로 해석

  • 많은 데이터를 처리할 때 None이라는 값이 들어가면 데이터 처리할 때 문제가 발생할 수 있기 때문에 None이라는 값이 없는게 좋다.
  • 위 예시 코드에서 success의 값과는 관련없이 result에 따라 리스트에 저장이 될 것이다.
  • 데이터를 처리 할 때 success의 결과값을 따로 저장하지 않으면 flase가 있었는지 result에 None이 들어갔는지 파악하기 어렵다.

더 나은 두번쨰 방법

  • None을 반환하는 대신 Exception을 호출한 쪽으로 발생시켜 호출자가 이를 처리하게 한다.
#ZeroDivisionError가 발생한 경우 ValueError로 바꿔 던져 호출자에게 입력이 잘못 됐음을 알려준다.
def careful_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError as e:
        raise ValueError('잘못된 입력')
x, y = 5, 0
try:
    result = careful_divide(x, y) # 나누기가 불가능할 경우 실행이 안되기 때문에 result에 값이 들어가지 않는다.
except ValueError:
    print('잘못된 입력')
else:
    print('결과는 %.1f 입니다' % result)

잘못된 입력

  • 이런 접근 방법은 타입 애너테이션을 사용하는 코드에도 적용할 수 있다.( betterway90할때 다시,,,)
  • 함수의 반환 값이 항상 float이라고 지정할 수 있고, 그에 따라 None이 결코 반환되지 않음을 알릴 수 있다.
  • 파이썬의 점진적 타입 지정(gradual typing)에서는 함수의 인터페이스에 예외가 포함되는지 표현하는 방법(검증 오류)이 의도적으로 제외
  • 호출자가 어떤 Exception을 잡아내야 할지 결정할 때 문서를 참조할 것으로 예상하고, 발생시키는 예외를 문서에 명시해야한다.

위 내용을 반영하고, 독스트링과 타입 애너테이션까지 포함 시킨 예시

def careful_divide(a: float, b: float) -> float: #타입이 지정된 모습.
    #독스트링 (어떤 작용이 있을지 설명해줌)
    """a를 b로 나눈다.
    Raises:
        ValueError: b가 0이어서 나눗셈을 할 수 없을 때
    """
    try:
        return a / b
    except ZeroDivisionError as e:
        raise ValueError('잘못된 입력')

x= careful_divide(0,4)
print(x)
y= careful_divide(2,0)
print(y)

0.0

ZeroDivisionError Traceback (most recent call last)
in careful_divide(a, b)
7 try:
----> 8 return a / b
9 except ZeroDivisionError as e:

ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

ValueError Traceback (most recent call last)
in
12 x= careful_divide(0,4)
13 print(x)
---> 14 y= careful_divide(2,0)
15 print(y)

in careful_divide(a, b)
8 return a / b
9 except ZeroDivisionError as e:
---> 10 raise ValueError('잘못된 입력')
11
12 x= careful_divide(0,4)

ValueError: 잘못된 입력

점진적 타입 검사 예시

  • 컴파일 시간에 타입 검사를 수행하면서 필요에 따라 타입 선언의 생략을 허용하는 검사 방식
# x,y에 숫자가 들어가야한다는 타입선언도 없이 에러가 발생하지 않음.

def adc(x, y):
  return x + y;

x = adc(10, 20)

print(x)

y= adc(10,"가")
print(y)

30

TypeError Traceback (most recent call last)
in
8 print(x)
9
---> 10 y= adc(10,"가")
11 print(y)

in adc(x, y)
2
3 def adc(x, y):
----> 4 return x + y;
5
6 x = adc(10, 20)

TypeError: unsupported operand type(s) for +: 'int' and 'str'

내 맘대로 해석 예시

import random
a=[]
b=[]
for i in range(1,150):
    x=random.randint(1,101)
    y=random.randint(1,101)
    a.append(x)
    b.append(y)
    
print(a)
print(b)

[4, 27, 24, 70, 100, 83, 75, 87, 60, 51, 80, 5, 94, 42, 87, 72, 88, 4, 59, 91, 77, 10, 101, 9, 32, 75, 78, 87, 10, 64, 5, 21, 1, 90, 53, 69, 58, 72, 86, 86, 92, 75, 33, 83, 85, 80, 81, 13, 27, 68, 91, 70, 10, 96, 87, 54, 64, 100, 2, 3, 70, 85, 44, 42, 39, 54, 55, 2, 92, 11, 27, 34, 22, 60, 62, 87, 88, 18, 5, 6, 59, 13, 73, 7, 71, 63, 48, 40, 89, 83, 13, 86, 66, 29, 36, 51, 100, 70, 98, 61, 21, 101, 64, 93, 82, 89, 57, 33, 40, 46, 91, 41, 56, 74, 33, 42, 98, 8, 11, 35, 31, 43, 53, 41, 97, 20, 3, 91, 80, 54, 3, 19, 99, 91, 49, 51, 29, 25, 52, 40, 43, 54, 39, 81, 16, 66, 61, 49, 100][56, 43, 74, 3, 99, 52, 70, 99, 28, 31, 58, 80, 91, 52, 86, 100, 8, 18, 4, 7, 69, 34, 14, 15, 65, 36, 91, 55, 90, 72, 14, 67, 53, 35, 2, 79, 31, 20, 16, 56, 83, 18, 35, 10, 10, 73, 53, 14, 77, 33, 3, 38, 65, 92, 85, 94, 39, 35, 55, 74, 42, 75, 36, 51, 99, 25, 32, 79, 12, 52, 68, 38, 51, 11, 68, 39, 6, 10, 8, 30, 54, 60, 91, 58, 94, 88, 74, 93, 99, 9, 58, 84, 55, 90, 9, 11, 99, 76, 3, 99, 2, 44, 14, 39, 13, 45, 71, 31, 55, 5, 26, 5, 69, 25, 10, 46, 51, 88, 13, 73, 91, 79, 90, 90, 57, 39, 99, 83, 49, 95, 92, 22, 64, 54, 39, 29, 26, 86, 101, 67, 45, 41, 19, 52, 25, 88, 25, 54, 45]

a[50] = 0
b[75] = 0
print(a)
print(b)

[4, 27, 24, 70, 100, 83, 75, 87, 60, 51, 80, 5, 94, 42, 87, 72, 88, 4, 59, 91, 77, 10, 101, 9, 32, 75, 78, 87, 10, 64, 5, 21, 1, 90, 53, 69, 58, 72, 86, 86, 92, 75, 33, 83, 85, 80, 81, 13, 27, 68, 0, 70, 10, 96, 87, 54, 64, 100, 2, 3, 70, 85, 44, 42, 39, 54, 55, 2, 92, 11, 27, 34, 22, 60, 62, 87, 88, 18, 5, 6, 59, 13, 73, 7, 71, 63, 48, 40, 89, 83, 13, 86, 66, 29, 36, 51, 100, 70, 98, 61, 21, 101, 64, 93, 82, 89, 57, 33, 40, 46, 91, 41, 56, 74, 33, 42, 98, 8, 11, 35, 31, 43, 53, 41, 97, 20, 3, 91, 80, 54, 3, 19, 99, 91, 49, 51, 29, 25, 52, 40, 43, 54, 39, 81, 16, 66, 61, 49, 100][56, 43, 74, 3, 99, 52, 70, 99, 28, 31, 58, 80, 91, 52, 86, 100, 8, 18, 4, 7, 69, 34, 14, 15, 65, 36, 91, 55, 90, 72, 14, 67, 53, 35, 2, 79, 31, 20, 16, 56, 83, 18, 35, 10, 10, 73, 53, 14, 77, 33, 3, 38, 65, 92, 85, 94, 39, 35, 55, 74, 42, 75, 36, 51, 99, 25, 32, 79, 12, 52, 68, 38, 51, 11, 68, 0, 6, 10, 8, 30, 54, 60, 91, 58, 94, 88, 74, 93, 99, 9, 58, 84, 55, 90, 9, 11, 99, 76, 3, 99, 2, 44, 14, 39, 13, 45, 71, 31, 55, 5, 26, 5, 69, 25, 10, 46, 51, 88, 13, 73, 91, 79, 90, 90, 57, 39, 99, 83, 49, 95, 92, 22, 64, 54, 39, 29, 26, 86, 101, 67, 45, 41, 19, 52, 25, 88, 25, 54, 45]

0으로 봤을 때 0을 찾기 매우 어려움

#기존 전처리를 통해 확인해보지 않으면 실수 할 수 있는 예시
def careful_divide(a, b):
    try:
        return True, a / b
    except ZeroDivisionError:
        return False, None
    
success_list=[]
result_list=[]

for i in range(100):
    success, result = careful_divide(a[i], b[i])
    success_list.append(success)
    result_list.append(result)

print(success_list)
print(result_list)

[True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True][0.07142857142857142, 0.627906976744186, 0.32432432432432434, 23.333333333333332, 1.0101010101010102, 1.5961538461538463, 1.0714285714285714, 0.8787878787878788, 2.142857142857143, 1.6451612903225807, 1.3793103448275863, 0.0625, 1.032967032967033, 0.8076923076923077, 1.0116279069767442, 0.72, 11.0, 0.2222222222222222, 14.75, 13.0, 1.1159420289855073, 0.29411764705882354, 7.214285714285714, 0.6, 0.49230769230769234, 2.0833333333333335, 0.8571428571428571, 1.5818181818181818, 0.1111111111111111, 0.8888888888888888, 0.35714285714285715, 0.31343283582089554, 0.018867924528301886, 2.5714285714285716, 26.5, 0.8734177215189873, 1.8709677419354838, 3.6, 5.375, 1.5357142857142858, 1.108433734939759, 4.166666666666667, 0.9428571428571428, 8.3, 8.5, 1.095890410958904, 1.528301886792453, 0.9285714285714286, 0.35064935064935066, 2.0606060606060606, 0.0, 1.8421052631578947, 0.15384615384615385, 1.0434782608695652, 1.0235294117647058, 0.574468085106383, 1.641025641025641, 2.857142857142857, 0.03636363636363636, 0.04054054054054054, 1.6666666666666667, 1.1333333333333333, 1.2222222222222223, 0.8235294117647058, 0.3939393939393939, 2.16, 1.71875, 0.02531645569620253, 7.666666666666667, 0.21153846153846154, 0.39705882352941174, 0.8947368421052632, 0.43137254901960786, 5.454545454545454, 0.9117647058823529, None, 14.666666666666666, 1.8, 0.625, 0.2, 1.0925925925925926, 0.21666666666666667, 0.8021978021978022, 0.1206896551724138, 0.7553191489361702, 0.7159090909090909, 0.6486486486486487, 0.43010752688172044, 0.898989898989899, 9.222222222222221, 0.22413793103448276, 1.0238095238095237, 1.2, 0.32222222222222224, 4.0, 4.636363636363637, 1.0101010101010102, 0.9210526315789473, 32.666666666666664, 0.6161616161616161]

# 개선된 방법의 예시
def careful_divide(a: float, b: float) -> float: #타입이 지정된 모습.
    #독스트링 (어떤 작용이 있을지 설명해줌)
    """a를 b로 나눈다.
    Raises:
        ValueError: b가 0이어서 나눗셈을 할 수 없을 때
    """
    try:
        return a / b
    except ZeroDivisionError as e:
        raise ValueError('잘못된 입력')

result_list=[]

for i in range(100):
    print(i)
    result = careful_divide(a[i],b[i])    
    result_list.append(result)


print(result_list)

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

ZeroDivisionError Traceback (most recent call last)
in careful_divide(a, b)
8 try:
----> 9 return a / b
10 except ZeroDivisionError as e:

ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

ValueError Traceback (most recent call last)
in
15 for i in range(100):
16 print(i)
---> 17 result = careful_divide(a[i],b[i])
18 result_list.append(result)
19

in careful_divide(a, b)
9 return a / b
10 except ZeroDivisionError as e:
---> 11 raise ValueError('잘못된 입력')
12
13 result_list=[]

ValueError: 잘못된 입력

profile
인공지능 파이팅!

0개의 댓글