{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 21. 변수 영역과 클로저의 상호작용 방식을 이해하라"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "숫자로 이뤄진 list를 정렬하되, 정렬한 리스트의 앞쪽에는 우선수위를 부여한 몇몇 숫자를 위치시켜야 한다고 가정"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sort_priority(values, group):\n",
    "    def helper(x):\n",
    "        if x in group:\n",
    "            return (0, x)\n",
    "        return (1, x)\n",
    "    values.sort(key=helper)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "numbers = [8, 3, 1, 2, 5, 4, 7, 6]\n",
    "group = [2, 3, 5, 7]\n",
    "sort_priority(numbers, group)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2, 3, 5, 7, 1, 4, 6, 8]\n"
     ]
    }
   ],
   "source": [
    "print(numbers)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 파이썬이 클로저(closure)를 지원 : 클로저란 자신이 정의된 영역 밖의 변수를 참조하는 함수.\n",
    "- 함수가 일급 객체 : 일급 객체라는 말은 이를 직접 가리킬 수 있고, 변수에 대입하거나 다른 함수에 인자로 전달할 수 있으며, 식이나 if 문에서 함수를 비교하거나 함수에서 반환하는 것 등이 가능하다는 것을 의미한다.\n",
    "- 시퀀스를 비교하는 구체적인 규칙이 있음: 0번 인덱스에 있는 값을 비교한다음 이 값이 같으면 다시 1번 인덱스를 값을 비교한다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "클로저를 사용해 우선순위가 높은 원소를 발견했음을 표시하는 플래그를 설정하면 어떨까?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sort_priority2(values, group):\n",
    "    found = False\n",
    "    def helper(x):\n",
    "        if x in group:\n",
    "            found = True\n",
    "            return (0, x)\n",
    "        return (1, x)\n",
    "    values.sort(key=helper)\n",
    "    return found"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "발견: False\n",
      "[2, 3, 5, 7, 1, 4, 6, 8]\n"
     ]
    }
   ],
   "source": [
    "found = sort_priority2(numbers, group)\n",
    "print('발견:', found)\n",
    "print(numbers)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "왜 False가 나올까?\n",
    "\n",
    "식 안에서 변수를 참조할 때 파이썬 인터프리터는 이 참조를 해결하기 위해 다음 순서로 영역을 뒤진다.\n",
    "\n",
    "1. 현재 함수의 영역\n",
    "2. 현재 함수를 둘러싼 영역(현재 함수를 둘러싸고 있는 함수 등)\n",
    "3. 현재 코드가 들어 있는 모듈의 영역(전역 영역(global scope) 이라고도 부름)\n",
    "4. 내장 영역"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "파이썬에는 클로저 밖으로 데이터를 끌어내는 특별한 구문인 nonlocal 문이 있다"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sort_priority2(values, group):\n",
    "    found = False\n",
    "    def helper(x):\n",
    "        nonlocal found\n",
    "        if x in group:\n",
    "            found = True\n",
    "            return (0, x)\n",
    "        return (1, x)\n",
    "    values.sort(key=helper)\n",
    "    return found"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "발견: True\n",
      "[2, 3, 5, 7, 1, 4, 6, 8]\n"
     ]
    }
   ],
   "source": [
    "found = sort_priority2(numbers, group)\n",
    "print('발견:', found)\n",
    "print(numbers)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "global 문을 보완해 줄 수 있음"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "nonlocal을 사용하는 방식이 복잡해지면 도우미 함수로 상태를 감싸는 편이 더 낫다"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Sorter:\n",
    "    def __init__(self, group):\n",
    "        self.group = group\n",
    "        self.found = False\n",
    "    \n",
    "    def __call__(self, x):\n",
    "        if x in self.group:\n",
    "            self.found = True\n",
    "            return (0, x)\n",
    "        return (1, x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "sorter = Sorter(group)\n",
    "numbers.sort(key=sorter)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sorter.found"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 기억해야 할 내용\n",
    "- 클로저 함수는 자신이 정의된 영역 외부에서 정의된 변수도 참조할 수 있다.\n",
    "- 기본적으로 클로저 내부에 사용한 대입문은 클로저를 감싸는 영역에 영향을 끼칠 수 없다.\n",
    "- 클로저가 자신을 감싸는 영역의 변수를 변경한다는 사실을 표시할 때는 nonlocal 문을 사용하라.\n",
    "- 간단한 함수가 아닌 경우에는 nonlocal 문을 사용하지 마라."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}