Flask Error Handler 활용 배경

  • Python 마이크로서비스 배포로 Flask 웹프레임워크 주로 사용
  • Flask에서 제공하는 Error Handler 활용
  • 클라이언트에 보내는 에러 메세지를 커스터마이징하여 출력 세팅
  • 에러 발생 가능한 모든 4xx, 50x 에러 처리 필요
  • flask에서 제공하는 _aborter 라이브러리 활용

 

코드 구현

  • error_handler.py
from flask import Flask, jsonify
from werkzeug.exceptions import HTTPException, _aborter, default_exceptions

def error_app(app):
    """
    Error Handler 정의
    """
 
    def _error_handling(error):
        
        # HTTP Exception인 경우
        if isinstance(error, HTTPException):
            result = {
                "error_code" : error.code,
                "description" : error.description,
                "message" : str(error)
            }
        
        # 그 외 나머지 Exception인 경우
        else:
            # 500 에러로 mapping 돼 있는 error message를 추출
            description_500 = _aborter.mapping[500].description
            result = {
                "error_code" : 500,
                "description" : description_500,
                "message" : str(error)
            }
        
        # result dict 데이터를 json으로 변경
        res = jsonify(result)
        
        # response의 status_code를 result의 error_code 값으로 업데이트
        res.status_code = result['error_code']
        
        return res
    #####################################################################
    
    # error handler 등록
    for code in default_exceptions.keys():
        app.register_error_handler(code, _error_handling)
        
    return app

 

Flask app 소스코드

  • app.py
# 위에서 작성한 코드 패키지 형식으로 활용
from error_handler import _error_handling

# Flask app을 _error_handling으로 감싸주기
app = _error_handling(Flask(__name__))

 

💡데크 기본 개념

  • 스택(stack)과 큐(Queue)의 연산을 모두 가지고 있는 복합 자료형 자료구조
  • 데크(Deque) : Double Ended Queue의 줄임말로서 양쪽 끝 데이터를 모두 추출할 수 있는 큐의 형태
  • 데크 자료구조는 양쪽 끝에서 삽입 삭제 모두 가능하다
  • 파이썬에서는 데크 자료구조를 collections 라이브러리를 활용하여 사용 가능하다
  • 데크 자료구조는 이중 연결 리스트로 구현하는 것이 유용하다. 포인터를 2개 활용해서 앞뒤에서 삽입 삭제가 가능하다.

 

💡데크 자료구조 직접 구현

  • 다음 연산을 지원하는 원형 데크를 디자인하라

    • MyCircularDeque(k: int) - k값으로 최대 원형 데크의 사이즈 초기화하기
    • insertFront(value: int) - 입력값(value)을 데크의 앞쪽에 삽입하고, 성공했다면 True, 실패했다면 False 반환하기
    • insertLast(vlaue: int) - 입력값(value)을 데크의 뒤쪽에 삽입하고, 성공했다면 True, 실패했다면 False 
    • deleteFront() - 데크 자료구조의 맨 앞의 값을 삭제하고, 성공했다면 True, 실패했다면 False 
    • deleteLast() - 데크 자료구조의 맨 뒤의 값을 삭제하고, 성공했다면 True, 실패했다면 False 
    • getFront() - 데크 자료구조의 맨 앞의 값을 반환하되 만약 값이 없다면 -1을 반환
    • getLast() - 데크 자료구조의 맨 뒤의 값을 반환하되 만약 값이 없다면 -1을 반환
    • isEmpty() - 데크 자료구조의 값이 비어있다면 True, 아니면 False
    • isFull() - 데크 자료구조의 값이 가득 차 있다면 True, 아니면 False

  • 이중 연결 리스트를 활용하여 원형 데크 자료구조 구현

 

💻 원형 데크 초기화

class MyCircularDeque(object):

    def __init__(self, k):
        """
        :type k: int
        """
        # 왼쪽(head), 오른쪽(tail) index 역할을 할 연결리스트 정의
        self.head, self.tail = ListNode(None), ListNode(None)
        self.head.right, self.tail.left = self.tail, self.head
        
        # 최대 길이와 현재 길이 정보를 담을 변수 정의
        self.maxlen, self.length = k, 0

 

💻 원형 데크 데이터 추가 내장함수

def _add(self, node: ListNode, new: ListNode):
	
    # 삽입할 연결 리스트의 기존 오른쪽 값을 정의한다
    # 아마 None 값일 것으로 예상한다.
    # front : n --> self.head.right.right --> self.tail.right
    # last  n --> self.tail.left.right  --> self.head.right
    n = node.right 
    
    # 연결 리스트의 오른쪽 값을 새로운 값으로 업데이트한다.
    # front : self.head.right.right == ListNode(value)
    # last  : self.tail.left.right  == ListNode(value)
    node.right = new
    
    # 새로운 노드의 left 값은 기존 node의 값으로 정의하고 (즉, head 역할)
    # right 값은 위에서 따로 정의한 None 값으로 정의해준다
    # head - new - None
    new.left, new.right = node, n
    
    # 그리고 n의 left 값을 새로운 값으로 정의한다.
    # front : n = self.head.right.right
    # last  : n = self.tail.left.right
    n.left = new

 

💻 원형 데크 데이터 삭제 내장함수

def _del(self, node: ListNode):
        
    # ? None 값을 미리 정의하는건가?
    n = node.right.right

    # 업데이트
    node.right = n

    # ? n의 왼쪽 값을 입력한 포인터의 값으로 정의? 왜?
    n.left = node

 

💻 원형 데크 앞쪽, 뒤쪽 데이터 추가

def insertFront(self, value):
    """
    :type value: int
    :rtype: bool
    """
    
    # 현재 길이 정보를 확인한다
    # 현재 길이가 최대 길이와 같다면 더이상 삽입할 수 없기에 False를 return
    if self.length == self.maxlen:
    	return False
    
    # 현재 길이가 아직 최대 길이에 도달하지 않았다면 데이터를 삽입할 것이기에
    # 현재 길이를 업데이트 해준다
    self.length += 1
    
    # value값을 원형 데크 자료구조에 추가해준다.
    # 내장 함수 _add 메서드를 사용할 예정
    # value 값을 연결리스트 자료구조 형태에 담아서 추가한다.
    self._add(self.head, ListNode(value))
    
    
def insertLast(self, value):
    """
    :type value: int
    :rtype: bool
    """

    # 현재 길이 확인하여 최대 길이에 도달했다면 False return
    if self.length == self.maxlen:
        return False

    # 현재 길이 업데이트
    self.length += 1

    # 뒤쪽에 값을 삽입할 때는 self.tail 포인터를 활용
    # self.tail.left == 초기에 self.head로 지정했음
    # 업데이트 될 것으로 예상하면서 작업
    self._add(self.tail.left, ListNode(value))

 

💻 원형 데크 앞쪽, 뒤쪽 데이터 삭제

def deleteFront(self):
    # 삭제할 값이 없다면 False return
    if self.length == 0:
        return False

    # 현재 길이 업데이터
    self.length -= 1

    # 삭제 로직
    self._del(self.head)

    return True


def deleteLast(self):
    # 삭제할 값이 없다면 False return
    if self.length == 0:
        return False

    # 현재 길이 업데이터
    self.length -= 1

    # 삭제 로직
    # 뒷 부분을 삭제할 때는 tail 포인터를 활용하는데
    # 왜 left.left의 값을 넣어주는지 궁금함
    self._del(self.tail.left.left)

 

💻 원형 데크 데이터 상태 확인 및 판별

def getFront(self):
    """
    :rtype: int
    맨 앞의 값 반환
    """
    return self.head.right.val if self.length else -1


def getRear(self):
    """
    :rtype: int
    맨 뒤 값 반환
    """
    return self.tail.left.val if self.length else -1


def isEmpty(self):
    """
    :rtype: bool
    데크 자료구조의 값이 비어 있는지 판별
    """
    return self.length == 0


def isFull(self):
    """
    :rtype: bool
    데크 자료구조의 값이 가득 차 있는지 판별
    """
    return self.length == self.maxlen

문제

  • 원형 큐를 디자인하기
  • 다음과 같은 함수가 실행되도록 구현
myCircularQueue = MyCircularQueue(3) 
myCircularQueue.enQueue(1) # return True 
myCircularQueue.enQueue(2) # return True 
myCircularQueue.enQueue(3) # return True 
myCircularQueue.enQueue(4) # return False 
myCircularQueue.Rear() # return 3 
myCircularQueue.isFull() # return True 
myCircularQueue.deQueue() # return True 
myCircularQueue.enQueue(4) # return True 
myCircularQueue.Rear() # return 4

 

원형 큐(Circular Queue) 개념

  • 원형 큐(Queue)는 FIFO 구조를 가지고 있는 점에서 기존 큐(Queue)와 동일하다
  • 마지막 위치가 시작 위치와 연결되는 점이 다른 점이다.
  • 기존의 큐는 공간이 꽉 차게 되면 더 이상 요소를 채울 수 없다.
  • 기존 큐는 앞쪽 값들이 빠져서 충분한 공간이 생기는 것처럼 보여도 해당 위치로 추가할 수 없다.
  • 원형 큐는 앞쪽 요소들이 빠지더라도 원형으로 이뤄져 있기 때문에 앞쪽에 추가 할 수 있다.
  • 즉, 재활용이 가능한 자료구조이다.

 

원형 큐 삽입 삭제 원리

출처 : 파이썬 알고리즘 인터뷰 p.260

  • 마지막 위치와 시작 위치를 연결하도록 원형 구조를 세팅하고, 값의 시작점과 끝점을 따라 투 포인터가 움직인다.
  • 위 그림을 참고하면, enQueue() 를 통해 rear 포인터를 이동시키고, deQueue 를 통해 front 포인터를 이동시킨다.
  • 이 로직을 통해 투 포인터가 돌면서 이동하게 된다.
  • 만약 rear 포인터와 front 포인터가 만나게 되면 해당 원형 큐 구조에 여유 공간이 없다는 의미이므로 공간 부족 에러를 발생해야 한다.

 

원형 큐 구현 풀이

  • 배열을 사용하여 구현한다.
  • rear 포인터와 front 포인터를 구분한다
  • 현재 포인터의 위치를 전체 큐 값의 개수에 따라 제한한다.
  • 속도 : 52ms
class MyCircularQueue(object):

    def __init__(self, k: int):
        self.q = [None] * k     # 원형 큐 정의 (배열 활용)
        self.maxlen = k         # 최대 길이 정의
        self.fp = 0             # front pointer
        self.rp = 0             # rear pointer


    def enQueue(self, value: int) -> bool:
        # 값을 추가할 때는 rear pointer 활용한다
        # rear 포인터 위치에 있는 큐 값이 없으면 입력된 value를 넣어준다
        
        if self.q[self.rp] is None:
            self.q[self.rp] = value
            # 입력해준 후 rear 포인터이 값을 업데이트 한다
            self.rp = (self.rp + 1) % self.maxlen
            return True
        else:
            return False


    def deQueue(self) -> bool:
        """
        원형 큐의 첫 번째 값이 제거되도록 기능 구현
        """
        # 값을 삭제할 때는 front pointer를 활용한다.
        # front pointer의 값이 아무것도 없으면 삭제할 값이 없다는 의미
        if self.q[self.fp] is None:
            return False
            
        # 값이 있으면, 그 값을 삭제하되 출력(반환)되지 않도록 세팅 필요
        else:
            self.q[self.fp] = None
            # front pointer를 업데이트한다.
            self.fp = (self.fp + 1) % self.maxlen
            return True
            
            
   def Front(self) -> int:
        """
        원형큐의 맨 앞에 있는 값을 반환
        값이 없을 경우 -1 반환
        """
         return -1 if self.q[self.fp] is None else self.q[self.fp]
        

    def Rear(self):
        """
        :rtype: int
        원형 큐의 맨 뒤에 있는 값을 반환
        값이 없을 경우 -1 반환
        """
        # rear pointer 에서 1을 빼준 위치에서 값을 가져와야 한다.
        return -1 if self.q[self.rp - 1] is None else self.q[self.rp - 1]
        

    def isEmpty(self):
        """
        :rtype: bool
        값이 비어 있을 경우 True, 아닐 경우 False
        포인터의 위치가 같은데 front pointer의 값이 없다면 해당 원형 큐의 값을 아무것도 없는 비어있는 상태라는 의미
        """
        return self.fp == self.rp and self.q[self.fp] is None
        

    def isFull(self):
        """
        :rtype: bool
        값이 모두 채워져 있는 경우를 아래와 같은 로직으로 판단
        우선 포인터의 위치가 같아야 하며, 그럴 때 front pointer의 값이 채워져 있어야 한다.
        """
        return self.fp == self.rp and self.q[self.fp] is not None

문제

  • 스택을 이용해 다음 연산을 지원하는 큐 자료구조를 구현하라
    • push(x) : 값 x를 큐 마지막에 삽입하라
    • pop() : 큐의 처음에 있는 값을 제거한다.
    • peek() : 큐의 처음에 있는 값을 조회한다.
    • empty() : 큐가 비어있는지 여부를 확인한다.
  • 원본 url : https://leetcode.com/problems/implement-queue-using-stacks/

 

문제 풀이

  • 스택의 연산만을 활용하기 위해서는 2개의 스택이 필요하다
  • 이유는 맨 마지막 값만을 활용할 수 있기 때문에 스택의 연산으로 값을 추출하면 값이 곧 최신에 들어간 값(마지막 값)이다.
  • 그런데 큐는 맨 처음에 들어간 값이 추출돼야 해서 스택의 연산으로 할 경우 이동이 필요하다.
  • output 값이 아무것도 없기 전까지는 재입력하는 알고리즘이 돌아가지 않는다
  • 시간복잡도는 O(1) 로 계산되게끔 구현한다
  • 실행속도 : 18 ms
class MyQueue(object):

    def __init__(self):
        self.input = []
        self.output = []

    def push(self, x):
        """
        :type x: int
        :rtype: None
        """
        self.input.append(x)


    def pop(self):
        """
        :rtype: int
        """
        # input 값을 재정렬하는 과정 거친 후
        self.peek()
        # 제일 처음에 들어온 값을 추출
        return self.output.pop()

    def peek(self):
        """
        :rtype: int
        """
        # 출력값이 아무것도 없다면 입력값 확인해서 값 재정렬
        if not self.output:

            # 새로 들어온 값이 있다면
            # 해당 값은 output으로 queue 구조로 재입력 돼야 함
            while self.input:

                # 우선 input값에서 마지막 값을 빼고
                # 해당 값을 output에 집어 넣는다
                self.output.append(self.input.pop())

        # 결과값이 맨 마지막 위치한 값이 제일 처음 들어온 값으로 위에서 세팅했음
        return self.output[-1]


    def empty(self):
        """
        :rtype: bool
        """
        # 입력값과 결과값 stack을 모두 확인해야 함
        # 입력값에 값이 들어온 후 결과값에 재정령 안 돼 있을 수도 있기 때문
        return self.input == [] and self.output == []

문제

  • 큐를 이용해 다음 연산을 지원하는 스택(Last-In-First-Out)을 구현
    • push(a) : 값 a를 스택에 삽입한다
    • pop() : 스택의 첫번째 요소를 삭제하고 반환한다. (제일 나중에 들어간 값이 첫번째 요소를 의미할 것이다)
    • top() : 스택의 첫번째 요소를 가져온다.
    • empty() : 스택이 비어 있는지 여부를 확인한다. (True / False)
  • 원본 url : https://leetcode.com/problems/implement-stack-using-queues/

 

문제 풀이

  • 큐는 파이썬 자료구조 중 deque를 활용한다
  • 큐의 연산(맨 앞에 있는 값을 제일 먼저 가져오는)만을 사용하기 위해 스택의 LIFO를 구현해줘야 한다
  • 즉, 맨 나중에 들어오는 값이 맨 앞(맨 왼쪽에 위치하게끔 바꿔야 한다.
  • 실행 속도 : 39ms
class MyStack(object):

    def __init__(self):
        self.q = collections.deque()

    def push(self, x):
        """
        :type x: int
        :rtype: None
        """
        # Step1. 값을 집어 넣는다
        self.q.append(x)

        # Step2. 데이터를 재정렬한다.
        # 현재 들어 있는 값중 맨 마지막 값은 가만히 있으면 되기 때문에
        # 전체 개수에서 1개 뺀만큼만 이동하면 된다.
        for _ in range(len(self.q)-1):
            # deque 자료구조의 연산중 popleft는 결국 que의 선입선출 연산구조를 의미한다.
            self.q.append(self.q.popleft())

    def pop(self):
        """
        :rtype: int
        """
        return self.q.popleft()

    def top(self):
        """
        :rtype: int
        """
        return self.q[0]


    def empty(self):
        """
        :rtype: bool
        """
        return len(self.q)==0

문제

  • 매일 화씨 온도 리스트 temperature를 입력받을 때, 더 따뜻한 날씨을 위해서는 몇일을 기다려야 하는지 출력
  • 더 따뜻한 날씨가 없다면 0으로 표기
  • 원본 url : https://leetcode.com/problems/daily-temperatures/

예시

Example 1:

Input: temperatures = [73,74,75,71,69,72,76,73]
Output: [1,1,4,2,1,1,0,0]

Example 2:

Input: temperatures = [30,40,50,60]
Output: [1,1,1,0]

Example 3:

Input: temperatures = [30,60,90]
Output: [1,1,0]

 

문제 풀이 1

  • 특별한 자료구조 형태를 적용하지 않고, 단순히 Brute Force 방식으로 풀이
  • O(n) 속도로 예상됨. 기본적인 for문과 if문으로 구성
class Solution(object):
    def dailyTemperatures(self, temperatures):
        """
        :type temperatures: List[int]
        :rtype: List[int]
        """
        result=[]
        for i, t in enumerate(temperatures):
            # 다음 따뜻한 날을 체크하기 위한 변수
            day=0

            # 전체 온도의 개수에서 2개를 뺀 이유는 리스트의 index를 사용하는데
            # index에서 +1 하기 때문에 마지막 index 값에서 1을 더하면 out of range error 발생 
            length=len(temperatures)-2

            # 맨 마지막 값인지 아닌지 체크
            if length >= i:
                # 다음 온도가 현재 온도 보다 높은지 체크하고 만약 낮거나 같다면 day와 i가 +1 증가하여
                # 그 다음 온도를 체크하면서 day값을 측정
                while t >= temperatures[i+1] and i < length:
                    day += 1
                    i += 1

                # 특별한 예외 상황이 있는데
                # 만약 끝까지 확인했는데 더 높은 온도가 없으면 0으로 입력해야 함
                # 이 부분이 없으면 맨 끝까지 확인하면서 증가한 day가 입력될 것으로 예상
                if i==length and t >= temperatures[i+1]:
                    result.append(0)
                else:
                    result.append(day+1)

            # 맨 마지막 값이라면 무조건 0으로 입력
            else:
                result.append(0)

        return result

 

문제 풀이 2

  • 현재 index를 stack 자료구로로 쌓아두다가, 이전보다 상승하는 지점에서 현재 온도와 stack에 쌓아둔 index 지점의 온도 차이 비교
  • 만약 더 높다면, stack의 값을 꺼내 현재 index와 비교하여 그 차이를 정답으로 입력한다.
  • 즉, 현재 온도가 이전 온도들과 비교할 때 stack에 쌓여 있는 index를 활용할 것이고, 이 index의 값이 현재 온도보다 작다면 해당 index는 추출되고 그 차이가 정답으로 옮겨져서 결국, stack에는 해당 index가 안 쌓여 있을 것이다.
  • 속도 : 31ms
class Solution(object):
    def dailyTemperatures(self, temperatures):
        """
        :type temperatures: List[int]
        :rtype: List[int]
        """
        # 정답 리스트 세팅 (0으로 세팅해두면, 알아서 아래 로직이 거치지 않는 곳은 0으로 세팅 됨)
        answer=[0] * len(temperatures)

        # 위 온도 값의 index를 담을 자료 구조
        stack=[]

        for i, cur in enumerate(temperatures):
            # step1. stack 값이 있는지 확인
            # step2. 현재 온도가 stack의 쌓여 있는 index의 값보다 크다면
            while stack and cur > temperatures[stack[-1]]:
                # stack에서 해당 index 추출
                last = stack.pop()
                # 해당 index 위치에 정답 입력
                # 현재 i 와 차이값으로 세팅
                answer[last]=i-last

            # 현재 온도의 index가 추가 돼야 추후 온도들과 비교 가능
            stack.append(i)

        return answer

+ Recent posts