예외처리와 관련하여 코드의 진행을 멈추고 싶지 않을 때, try/exception 구문을 사용한다.
그 중에서도 사용자 정의 에러를 만들어 보겠다.
방법은 파이썬에 내장된 기본 Base Exception을 상속받아 원하는 에러명과 메시지로 출력하는 것이다.
class UserDefinedError(Exception):
def __init__(self):
super().__init__("내가 만든 에러메시지다.")
def errorcheck(value):
if type(value) == "str"
print(value)
else:
raise UserDefinedError
try:
errorcheck(123)
except UserDefineError as e:
print(e)
> 내가 만든 에러메시지다.
먼저, 정의하고자 하는 에러명으로 클래스를 만든다. 이 때, 파이썬 내장 에러를 인자로 받아야하므로 Exception을 받는다. 그 다음 __init__
method 안에는 Exception으로부터 상속 받은 __init__
method를 호출하여 새로 정의하고자하는 에러메시지를 넣는다. super()
는 상속 받을 부모 클래스를 호출하는 역할을 한다.
클래스와 상속에 대한 개념이 있다면 생각보다 쉽게 사용자 정의 메시지를 만들 수 있다.
또 다른 방법으로는 에러를 발생시키는 시점에 메시지를 정의할 수 있다.
class UserDefinedError(Exception):
pass
# 조건문에서 errror 를 발생시킬 때 에러 메시지 정의 가능
raise UserDefinedError("내가 만든 에러메시지다 임마")
Iterator 란 값을 차례로 꺼낼 수 있는 객체이다. range를 사용하여 반복문을 실행하면, iterator가 생성되고 하나씩 숫자가 꺼내지는 형식이다.
숫자를 미리 만드는 것이 아니라, iterator를 생성해놓고 필요할 때마다 호출하는 방식이다. 따라서 미리 다 만들어놓는 것보다는 메모리 부담이 적다고 할 수 있다. 이러한 방식을 lazy evaluation 이라고 한다.
Iterator가 생성되기 위한 반복 가능한 객체들은 리스트, 딕셔너리, 문자열, 세트가 있다. 해당 객체들은 __iter__
method와 __next__
를 통해서 요소들을 꺼낼 수 있다.
list_iter = [1,2].__iter__()
print(list_iter)
> <list_iterator object at 0x7f63ec40fdc0> # iter 객체 생성
print(list_iter.__next__())
print(list_iter.__next__())
print(list_iter.__next__())
> 1
> 2
> StopIteration
더 이상 꺼낼 요소가 없는 경우, StopIteration 에러를 발생시키고 반복을 종료한다.
즉, 반복 가능한(iterable) 객체(리스트, 튜플 등) 에서 __iter__
method를 통해서 iterator 객체를 생성하고, __next__
method를 통해서 하나씩 요소를 꺼낸다.
이번에는 간단한 iterator를 직접 만들어보겠다.
for i in range(4):
print(4, end=" ")
> 0 1 2 3
위와 같은 결과가 나오게끔 iterator를 만들면 아래와 같다.
class Iterator:
def __init__(self, stop):
self.current = 0
self.stop = stop
# __iter__ method로 인스턴스 자신을 반환 -> iterator 생성
def __iter__(self):
return self
def __next__(self):
if self.current < self.stop:
n = self.current
self.current +=1
return n
else:
raise StopIteration
for i in Iterator(3):
print(i, end=" ")
> 0 1 2 3
한 번 뜯어보자.
일단, __init__
method를 통해서 인스턴스의 속성을 정의하였다. 시작점은 0이고, stop 이라는 속성을 통해 멈출 시점을 정한다.
그 다음은 __iter__
method를 통해서 iterator 객체를 생성한다. 이 때, 리스트나 튜플 같은 반복 가능한 객체를 인자로 받는 것이 아니라 숫자를 하나씩 인자로 사용하기 때문에 반복문이 돌 때마다 매 숫자 자체가 iterator 객체가 된다. 나도 100% 이해가 안되서 말이 어렵게 나오는 것 같다. 그냥 쉽게 생각하면 요소가 하나 들어있는 리스트(?) 를 만든다고 해야하나. 그게 맞는건가 잘모르겠다.
그리고나서 __next__
method를 통해서 stop 보다 낮은 경우에는 인스턴스의 current 속성에 1을 더하고, 그 값을 return 한다. 반복문으로 값이 더해지다가 stop과 같아지는 시점에 에러를 발생시키는 조건문을 만든다.
이버에는 인덱스를 통해 접근 가능하도록 __getitem__
method를 통해서 iterator 를 만들어 보겠다.
class Iterator:
def __init__(self, stop):
self.stop = stop
def __getitem__(self, index):
if index < self.stop:
return index
else:
raise IndexError
print(Iterator(3)[0], Iterator(3)[1], Interator(3)[2])
for i in Iterator:
print(i, end=" ")
> 0 1 2
> 0 1 2
__getitem__
method를 통해서 반복 가능한 iterator를 생성할 뿐만 아니라, 인덱스로도 접근 가능하게 되었다.
__init__
,__getitem__
처럼 앞, 뒤로 더블 언더스코어로 감싸고 있는 method를 special method 혹은 magic method 라고 한다. 파이썬에서 자동으로 호출해주는 method 이며, 종류가 매우 많다. 필요에 따라 검색해서 사용하면 될 것 같다.
Generator 는 iterator 를 생성해주는 함수이다.
Iterator 에서는 __iter__
,__next__
,__getitem__
method 를 구현해야 하지만 generator 안에서는 yield 키워드만 사용하면 된다.
이미지 출처
def generator():
yield 0
yield 1
yield 2
for i in generator():
print(i)
> 0
> 1
> 2
매우 간단하다. StopIteration 에러도 자동으로 발생한다. 개 꿀
근데, yield는 해석하면 양보하다 라는 뜻이다. 뭘 양보하는거지?
def generator():
yield 1
yield 2
return "return 지정 값"
try:
g = generator()
print(next(g))
print(next(g))
print(next(g))
except StopIteration as e:
print(e)
> 1
> 2
> return 지정 값
yield 키워드는 return과는 달리, 함수를 종료하지 않고, 해당 값을 함수 밖으로 전달한 뒤 바깥 코드가 실행되도록 양보한다.
위의 예에서 g라는 generator 객체를 만들었다. 그리고 next함수를 통해 값을 차례로 꺼낸다. 이 때, 코드가 진행되는 순서를 차례로 나열해보겠다.
복잡해보이지만, 결론은 yield는 함수를 종료하지 않고 잠시 중단하며 그 값을 함수 밖으로 전달한다는 것이다. 또한 return 은 함수를 종료시키는 동시에 StopIteration 에러를 발생시킨다는 점을 알고 넘어가면 될 것 같다.
이번에는 range와 같은 기능을 할 수 있는 generator 를 만들어 보겠다.
def generator(stop):
n = 0
while n < stop:
yield n
n += 1
for i in generator(3):
print(i, end=" ")
> 0 1 2
while문을 통해서 간단하게 작성 가능하다.
앞서 봐왔던 예제들은 값을 여러 번 밖으로 전달하기 위해 for문이나 while문을 사용하였다. 하지만 yield from
을 사용하면 동일한 기능을 구현할 수 있다.
def generator():
x = [1,2,3]
for i in x:
yield i
for i in generator():
print(i, end=" ")
> 1 2 3
위와 같은 결과를 yield from 을 사용해서 만들어 보겠다.
def generator():
x = [1,2,3]
yield from x
for i in generator():
print(i, end=" ")
> 1 2 3
이렇듯이 yield from 을 통해서 for문이나 while문 역할을 대신할 수 있다.
Generator 도 리스트, 딕셔너리 표현식과 같이 간단한 표현식으로 나타낼 수 있다.
( 표현식 for 변수 in iterable 객체 if 조건문)
g = (i for i in range(10) if x%2==0)
print(g)
> <generator object <genexpr> at 0x7f1518745f20>
for i in g:
print(i, end=" ")
> 0 2 4 6 8
지금까지 Exception, Iterator, Generator 에 대해서 알아봤다.
사실 iterator와 generator는 아직 완벽히 숙지하지는 못했다. 개념적으로는 어느정도 들어왔지만 직접 더 만들어보고 어떤 상황에 적합한 지를 고민해 볼 필요가 있을 것 같다.
리스트를 생성하고 append 했던 기존의 방식보다 메모리에 부담이 훨씬 덜 가는 방식이기 때문에 반드시 익혀두어야겠다.