{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "***\n", "***\n", "# 22. 약한참조, 반복자, 발생자\n", "***\n", "***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***\n", "## 1 약한 참조\n", "***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1-1 약한 참조의 정의\n", "- 약한 참조 (Weak Reference)\n", " - 레퍼런스 카운트로 고려되지 않는 참조" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1-2 약한 참조의 필요성" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![inheritance](../images/cyclingref.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 1) 레퍼런스 카운트가 증가되지 않으므로 순환 참조가 방지된다.\n", " - 순환 참조 (Cyclic Reference)\n", " - 서로 다른 객체들 사이에 참조 방식이 순환 형태로 연결되는 방식\n", " - 독립적으로 존재하지만 순환 참조되는 서로 다른 객체 그룹은 쓰레기 수집이 안된다.\n", " - 주기적으로 순환 참조를 조사하여 쓰레기 수집하는 기능이 있지만, CPU 자원 낭비가 심하다.\n", " - 이러한 쓰레기 수집 빈도가 낮으면 순환 참조되는 많은 객체들이 메모리를 쓸데없이 점유하게 됨\n", "- 2) 다양한 인스턴스들 사이에서 공유되는 객체에 대한 일종의 케시(Cache)를 만드는 데 활용된다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1-3 약한 참조 모듈\n", "#### 1) weakref.ref(o)\n", "- weakref 모듈의 ref(o) 함수\n", " - 객체 o에 대한 약한 참조를 생성한다.\n", " - 해당 객체가 메모리에 정상적으로 남아 있는지 조사한다.\n", " - 객체가 메모리에 남아 있지 않으면 None을 반환한다.\n", "\n", "- 약한 참조로 부터 실제 객체를 참조하는 방법\n", " - 약한 참조 객체에 함수형태 호출" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "refcount - 2\n", "\n", "refcount - 3\n", "\n", "refcount - 3\n", "\n" ] } ], "source": [ "import sys\n", "import weakref # weakref 모듈 임포트\n", "class C:\n", " pass\n", "c = C() # 클래스 C의 인스턴스 생성 \n", "c.a = 1 # 인스턴스 c에 테스트용 값 설정 \n", "print(\"refcount -\", sys.getrefcount(c)) # 객체 c의 레퍼런스 카운트 조회\n", "print()\n", "\n", "d = c # 일반적인 레퍼런스 카운트 증가 방법\n", "print(\"refcount -\", sys.getrefcount(c)) # 객체 c의 레퍼런스 카운트 조회\n", "print()\n", "\n", "r = weakref.ref(c) # 약한 참조 객체 r 생성\n", "print(\"refcount -\", sys.getrefcount(c)) # 객체 c의 레퍼런스 카운트 조회 --> 카운트 불변\n", "print()" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "<__main__.C object at 0x1083f0f28>\n", "<__main__.C object at 0x1083f0f28>\n", "1\n", "\n", "None\n" ] }, { "ename": "AttributeError", "evalue": "'NoneType' object has no attribute 'a'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;32mdel\u001b[0m \u001b[0md\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# None을 리턴한다\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 10\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# 속성도 참조할 수 없다\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m: 'NoneType' object has no attribute 'a'" ] } ], "source": [ "print(r) # 약한 참조(weakref) 객체\n", "print(r()) # 약한 참조로 부터 실제 객체를 참조하는 방법: 약한 참조 객체에 함수형태로 호출\n", "print(c)\n", "print(r().a) # 약한 참조를 이용한 실제 객체 멤버 참조\n", "print()\n", "\n", "del c # 객체 제거\n", "del d\n", "print(r()) # None을 리턴한다\n", "print(r().a) # 속성도 참조할 수 없다" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 내장 자료형 객체 (리스트, 튜플, 사전 등)에 대해서는 약한 참조를 만들 수 없다." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "cannot create weak reference to 'dict' object", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0md\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m'one'\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'two'\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mwd\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mweakref\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mref\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: cannot create weak reference to 'dict' object" ] } ], "source": [ "d = {'one': 1, 'two': 2}\n", "wd = weakref.ref(d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 2) weakref.proxy(o)\n", "- weakref의 proxy(o)는 객체 o에 대한 약한 참조 프록시를 생성한다.\n", " - 프록시를 이용하면 함수 형식을 사용하지 않아도 실제 객체를 바로 참조할 수 있다.\n", " - ref(o) 함수보다 더 선호되는 함수" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "refcount - 2\n", "refcount - 2\n", "\n", "<__main__.C object at 0x10847ce10>\n", "<__main__.C object at 0x10847ce10>\n", "2\n" ] } ], "source": [ "import sys\n", "import weakref\n", "class C:\n", " pass\n", "\n", "c = C()\n", "c.a = 2\n", "print(\"refcount -\", sys.getrefcount(c)) # 객체 c의 레퍼런스 카운트 조회\n", "p = weakref.proxy(c) # 프록시 객체를 만든다\n", "print(\"refcount -\", sys.getrefcount(c)) # 객체 c의 레퍼런스 카운트 조회 --> 카운트 불변\n", "print( )\n", "print(p)\n", "print(c)\n", "print(p.a)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n", "[, ]\n" ] } ], "source": [ "import weakref\n", "class C:\n", " pass\n", "\n", "c = C() # 참조할 객체 생성\n", "r = weakref.ref(c) # weakref 생성\n", "p = weakref.proxy(c) # weakref 프록시 생성\n", "print(weakref.getweakrefcount(c)) # weakref 개수 조회\n", "print(weakref.getweakrefs(c)) # weakref 목록 조회\n", "\n", "#python2.x에서의 결과\n", "# 2\n", "# [, ]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1-4 약한 사전\n", "- 약한 사전 (Weak Dictionary)\n", " - 사전의 키(key)나 값(value)으로 다른 객체들에 대한 약한 참조를 지니는 사전\n", " - 주로 다른 객체들에 대한 캐시(Cache)로 활용\n", " - 일반적인 사전과의 차이점\n", " - 키(key)나 값(value)으로 사용되는 객체는 약한 참조를 지닌다.\n", " - 실제 객체가 삭제되면 자동적으로 약한 사전에 있는 (키, 값)의 쌍도 삭제된다.\n", " - 즉, 실제 객체가 사라지면 캐시역할을 하는 약한 사전에서도 해당 아이템이 제거되므로 효율적인 객체 소멸 관리가 가능하다. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- weakref 모듈의 WeakValueDictionary 클래스\n", " - weakref 모듈의 WeakValueDictionary 클래스의 생성자는 약한 사전을 생성한다." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "[(1, <__main__.C object at 0x10861f048>)]\n", "4\n", "[]\n" ] } ], "source": [ "import weakref\n", "class C:\n", " pass\n", "\n", "c = C()\n", "c.a = 4\n", "d = weakref.WeakValueDictionary() # WeakValueDictionary 객체 생성\n", "print(d)\n", "\n", "d[1] = c # 실제 객체에 대한 약한 참조 아이템 생성\n", "print(list(d.items())) # 사전 내용 확인\n", "print(d[1].a) # 실제 객체의 속성 참조\n", "\n", "del c # 실제 객체 삭제\n", "print(list(d.items())) # 약한 사전에 해당 객체 아이템도 제거되어 있음\n", "\n", "#python2.x에서의 결과\n", "# \n", "# [(1, <__main__.C instance at 0x10dccad40>)]\n", "# 4\n", "# []" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 일반 사전을 통하여 동일한 예제를 수행하면 마지막에 해당 객체를 삭제해도 일반 사전 내에서는 여전히 존재함" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{}\n", "[(1, <__main__.C object at 0x10861f048>)]\n", "4\n", "[(1, <__main__.C object at 0x10861f048>)]\n" ] } ], "source": [ "class C:\n", " pass\n", "\n", "c = C()\n", "c.a = 4\n", "d = {} # 일반 사전 객체 생성\n", "print(d)\n", "\n", "d[1] = c # 실제 객체에 대한 일반 참조 아이템 생성\n", "print(list(d.items())) # 사전 내용 확인\n", "print(d[1].a) # 실제 객체의 속성 참조\n", "\n", "del c # 객체 삭제 (사전에 해당 객체의 레퍼런스가 있으므로 객체는 실제로 메모리 해제되지 않음)\n", "print(list(d.items())) # 일반 사전에 해당 객체 아이템이 여전히 남아 있음" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***\n", "## 2 반복자 (Iterator)\n", "***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2-1 반복자 (Iterlator) 객체\n", "- 반복자 객체\n", " - 내부적으로 \\_\\_next\\_\\_(self)를 지니고 있는 객체\n", " - 내부적으로 지닌 Sequence 자료를 차례로 반환\n", " - 더 이상 넘겨줄 자료가 없을 때 StopIteration 예외를 발생시킴\n", " - next() 내장 함수에 대응됨\n", " \n", "- 임의의 객체에 대해 반복자 객체를 얻어오는 방법\n", " - iter(o) 내장 함수\n", " - 객체 o의 반복자 객체를 반환한다.\n", "- 집합적 객체 A --> iter(A) --> 반복자 객체 B 반환 --> next(B) --> 집합적 자료형 안의 내부 원소를 하나씩 반환\n", "- 반복자 객체의 메모리 효율성\n", " - 반복자가 원 객체의 원소들을 복사하여 지니고 있지 않다." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "1\n", "2\n", "3\n" ] }, { "ename": "StopIteration", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mI\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mI\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 17\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mI\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m: " ] } ], "source": [ "L = [1,2,3]\n", "# print(next(L)) <-- 에러 발생\n", "\n", "I = iter(L)\n", "print(I)\n", "\n", "# Python2.7에서는 반복자 객체에 직접 next() 메소드 호출\n", "# print(I.next())\n", "# print(I.next())\n", "# print(I.next())\n", "# print(I.next())\n", "\n", "# Python3에서는 next() 내장 함수 사용\n", "print(next(I))\n", "print(next(I))\n", "print(next(I))\n", "print(next(I))" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "2\n", "3\n" ] } ], "source": [ "K = {1: \"aaa\", 2: \"bbb\", 3: \"ccc\"}\n", "J = iter(K)\n", "print(next(J))\n", "print(next(J))\n", "print(next(J))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 리스트 객체에 반복자를 활용한 예" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "2\n", "3\n" ] } ], "source": [ "t = iter([1, 2, 3])\n", "while 1:\n", " try:\n", " x = next(t)\n", " except StopIteration:\n", " break\n", " print(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 리스트 객체에 대해 일반적인 for ~ in 반복 문 사용예" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n", "3\n", "4\n" ] } ], "source": [ "def f(x):\n", " print(x + 1)\n", "\n", "for x in [1,2,3]:\n", " f(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- for ~ in 구문에 반복자를 활용할 수 있다.\n", " - for 문이 돌때 마다 반복자 객체의 next() 함수가 자동으로 호출되어 순차적으로 각 객체에 접근 가능하다.\n", " - StopIteration이 발생하면 for ~ in 구문이 자동으로 멈춘다." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n", "3\n", "4\n" ] } ], "source": [ "def f(x):\n", " print(x + 1)\n", "\n", "t = iter([1, 2, 3])\n", "for x in t:\n", " f(x)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n", "3\n", "4\n" ] } ], "source": [ "def f(x):\n", " print(x + 1)\n", "\n", "for x in iter([1, 2, 3]):\n", " f(x)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n", "3\n", "4\n" ] } ], "source": [ "def f(x):\n", " print(x + 1)\n", "\n", "for x in iter((1, 2, 3)):\n", " f(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2-2 클래스에 반복자 구현하기" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n" ] } ], "source": [ "class Seq:\n", " def __getitem__(self, n):\n", " if n == 10:\n", " raise IndexError()\n", " return n\n", " \n", "s = Seq() \n", "for line in s: \n", " print(line)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 내장 함수 iter()에 대응되는 \\_\\_iter\\_\\_(self) 및 next()에 대응되는 \\_\\_next\\_\\_(self)의 구현 \n", " - 객체 o에 iter()를 호출하면 자동으로 \\_\\_iter\\_\\_(self) 함수 호출\n", " - \\_\\_iter\\_\\_(self) 함수는 \\_\\_next\\_\\_(self) 함수를 지닌 반복자 객체를 반환" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- for ~ in 호출시에는 ____next ____() 함수가 ____getitem ____() 보다 우선하여 호출됨" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "abc\n", "\n", "def\n", "\n", "ghi\n", "\n", "<__main__.Seq object at 0x110e2a748>\n", "['abc\\n', 'def\\n', 'ghi']\n", "('abc\\n', 'def\\n', 'ghi')\n" ] } ], "source": [ "class Seq:\n", " def __init__(self, file):\n", " self.file = open(file)\n", " \n", " def __getitem__(self, n): # for ~ in 이 호출될 때에는 __next__() 함수에 의하여 가려짐. \n", " if n == 10:\n", " raise StopIteration()\n", " return n\n", "\n", " def __iter__(self):\n", " return self\n", " \n", " def __next__(self):\n", " line = self.file.readline() # 한 라인을 읽는다.\n", " if not line: \n", " raise StopIteration # 읽을 수 없으면 예외 발생\n", " return line # 읽은 라인을 리턴한다.\n", " \n", "s = Seq('readme.txt') # s 인스턴스가 next() 메소드를 지니고 있으므로 s 인스턴스 자체가 반복자임 \n", "\n", "for line in s: # 우선 __iter__() 메소드를 호출하여 반복자를 얻고, 반복자에 대해서 for ~ in 구문에 의하여 __next__() 메소드가 호출됨\n", " print(line)\n", "\n", "print()\n", "\n", "print(Seq('readme.txt'))\n", "\n", "# list() 내장 함수가 객체를 인수로 받으면 해당 객체의 반복자를 얻어와 next()를 매번 호출하여 각 원소를 얻어온다. \n", "print(list(Seq('readme.txt'))) \n", "\n", "# tuple() 내장 함수가 객체를 인수로 받으면 해당 객체의 반복자를 얻어와 next()를 매번 호출하여 각 원소를 얻어온다. \n", "print(tuple(Seq('readme.txt'))) " ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "abc\n", "\n", "def\n", "\n", "ghi\n", "\n", "<__main__.Seq object at 0x110e2a780>\n", "['abc\\n', 'def\\n', 'ghi']\n", "('abc\\n', 'def\\n', 'ghi')\n" ] } ], "source": [ "class Seq:\n", " def __init__(self, fname):\n", " self.file = open(fname)\n", " \n", " #def __getitem__(self, n):\n", " # if n == 10:\n", " # raise StopIteration()\n", " # return n\n", "\n", " def __iter__(self):\n", " return self\n", " \n", " def __next__(self):\n", " line = self.file.readline() # 한 라인을 읽는다.\n", " if not line: \n", " raise StopIteration() # 읽을 수 없으면 예외 발생\n", " return line # 읽은 라인을 리턴한다.\n", " \n", "s = Seq('readme.txt') # s 인스턴스가 next() 메소드를 지니고 있으므로 s 인스턴스 자체가 반복자임 \n", "for line in s: # 우선 __iter__() 메소드를 호출하여 반복자를 얻고, 반복자에 대해서 for ~ in 구문에 의하여 next() 메소드가 호출됨\n", " print(line),\n", "\n", "print()\n", "\n", "print(Seq('readme.txt'))\n", "\n", "print(list(Seq('readme.txt'))) # list() 내장 함수가 객체를 인수로 받으면 해당 객체의 반복자를 얻어와 next()를 매번 호출하여 각 원소를 얻어온다. \n", "print(tuple(Seq('readme.txt'))) # tuple() 내장 함수가 객체를 인수로 받으면 해당 객체의 반복자를 얻어와 next()를 매번 호출하여 각 원소를 얻어온다. " ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n", "\n", "<__main__.Seq object at 0x110e07d68>\n", "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", "(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n" ] } ], "source": [ "class Seq:\n", " def __init__(self, fname):\n", " self.file = open(fname)\n", " \n", " def __getitem__(self, n):\n", " if n == 10:\n", " raise StopIteration()\n", " return n\n", "\n", "# def __iter__(self):\n", "# return self\n", " \n", "# def __next__(self):\n", "# line = self.file.readline() # 한 라인을 읽는다.\n", "# if not line: \n", "# raise StopIteration() # 읽을 수 없으면 예외 발생\n", "# return line # 읽은 라인을 리턴한다.\n", " \n", "s = Seq('readme.txt') # s 인스턴스가 next() 메소드를 지니고 있으므로 s 인스턴스 자체가 반복자임 \n", "for line in s: # 우선 __iter__() 메소드를 호출하여 반복자를 얻고, 반복자에 대해서 for ~ in 구문에 의하여 next() 메소드가 호출됨\n", " print(line),\n", "\n", "print()\n", "\n", "print(Seq('readme.txt'))\n", "\n", "print(list(Seq('readme.txt'))) # list() 내장 함수가 객체를 인수로 받으면 해당 객체의 반복자를 얻어와 next()를 매번 호출하여 각 원소를 얻어온다. \n", "print(tuple(Seq('readme.txt'))) # tuple() 내장 함수가 객체를 인수로 받으면 해당 객체의 반복자를 얻어와 next()를 매번 호출하여 각 원소를 얻어온다. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2-3 사전의 반복자" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 사전에 대해 for ~ in 구문은 키에 대해 반복한다." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "one 1\n", "two 2\n", "three 3\n", "four 4\n", "five 5\n" ] } ], "source": [ "d = {'one':1, 'two':2, 'three':3, 'four':4, 'five':5}\n", "\n", "for key in d:\n", " print(key, d[key])" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "one 1\n", "two 2\n", "three 3\n", "four 4\n", "five 5\n" ] } ], "source": [ "d = {'one':1, 'two':2, 'three':3, 'four':4, 'five':5}\n", "\n", "for key in iter(d):\n", " print(key, d[key])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- python2.x\n", " - d.iterkeys() 함수\n", " - 사전 d가 지닌 키에 대한 반복자 객체를 반환한다.\n", "\n", "- python3.x\n", " - iter(d) 또는 iter(d.keys()) 사용" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n" ] } ], "source": [ "print(type(d.keys()))\n", "print(type(iter(d.keys())))" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "'dict_keys' object is not an iterator", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: 'dict_keys' object is not an iterator" ] } ], "source": [ "next(d.keys())" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'one'" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "next(iter(d.keys()))" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "one two three four five \n", "one two three four five " ] } ], "source": [ "#python3.x\n", "for key in d.keys(): # 키에 대한 반복자, iter(d.keys()) 가 반환한 반복자에 대해 __next__(self) 함수가 순차적으로 불리워짐\n", " print(key, end=\" \")\n", "\n", "print()\n", "\n", "for key in iter(d.keys()): # 키에 대한 반복자, iter(d.keys()) 가 반환한 반복자에 대해 __next__(self) 함수가 순차적으로 불리워짐\n", " print(key, end=\" \")" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "one\n", "two three four five " ] } ], "source": [ "keyset = iter(d)\n", "\n", "print(next(keyset)) # 반복자 객체는 항상 next() 내장 함수에 값을 반환할 수 있음 (내부적으로 __next__(self) 호출)\n", "\n", "for key in keyset: # keyset 반복자에 대해 next() 메소드가 순차적으로 호출됨\n", " print(key, end=\" \")" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n" ] } ], "source": [ "print(type(d.values()))\n", "print(type(iter(d.values())))" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 2 3 4 5 \n", "1 2 3 4 5 " ] } ], "source": [ "#python3.x\n", "for key in d.values(): # 키에 대한 반복자, iter(d.keys()) 가 반환한 반복자에 대해 __next__(self) 함수가 순차적으로 불리워짐\n", " print(key, end=\" \")\n", "\n", "print()\n", "\n", "for key in iter(d.values()): # 키에 대한 반복자, iter(d.keys()) 가 반환한 반복자에 대해 __next__(self) 함수가 순차적으로 불리워짐\n", " print(key, end=\" \")" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n" ] } ], "source": [ "print(type(d.items()))\n", "print(type(iter(d.items())))" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "one 1\n", "two 2\n", "three 3\n", "four 4\n", "five 5\n" ] } ], "source": [ "#python3.x\n", "for key, value in iter(d.items()): # 키에 대한 반복자, iter(d.keys()) 가 반환한 반복자에 대해 __next__(self) 함수가 순차적으로 불리워짐\n", " print(key, value)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2-4 파일 객체의 반복자" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 파일 객체는 그 자체가 반복자임\n", " - next() 함수에 의해 각 라인이 순차적으로 읽혀짐" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "next(f) - abc\n", "\n", "def\n", "\n", "ghi\n" ] } ], "source": [ "#python3.x\n", "f = open('readme.txt')\n", "\n", "print(\"next(f) - \", next(f))\n", "\n", "for line in f: # f.next() 가 순차적으로 호출됨\n", " print(line) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***\n", "## 3 발생자\n", "***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3-1 발생자란?\n", "- 발생자(Generator)\n", " - (중단됨 시점부터) 재실행 가능한 함수\n", " \n", "- 아래 함수 f()는 자신의 인수 및 내부 로컬 변수로서 a, b, c, d를 지니고 있다.\n", " - 이러한 a, b, c, d 변수들은 함수가 종료되고 반환될 때 모두 사라진다.\n", "- 발생자는 f()와 같이 함수가 (임시로) 종료될 때 내부 로컬 변수가 메모리에서 해제되는 것을 막고 다시 함수가 호출 될 때 이전에 수행이 종료되었던 지점 부터 계속 수행이 가능하도록 구현된 함수이다." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2 3\n" ] } ], "source": [ "def f(a, b):\n", " c = a * b\n", " d = a + b\n", " return c, d\n", "\n", "x, y = f(1, 2)\n", "print(x, y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- yield 키워드\n", " - return 대신에 yield에 의해 값을 반환하는 함수는 발생자이다.\n", " - yield는 return과 유사하게 임의의 값을 반환하지만 함수의 실행 상태를 보존하면서 함수를 호출한 쪽으로 복귀시켜준다.\n", " \n", " \n", "- 발생자는 곧 반복자이다.\n", " - 즉, 발생자에게 next() 호출이 가능하다." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2 3\n" ] }, { "ename": "StopIteration", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 12\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mStopIteration\u001b[0m: " ] } ], "source": [ "def f(a, b):\n", " c = a * b\n", " d = a + b\n", " yield c, d\n", "\n", "g = f(1, 2)\n", "\n", "x, y = next(g)\n", "print(x, y)\n", "\n", "x, y = next(g)\n", "print(x, y)" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2 3\n", "2 3\n" ] } ], "source": [ "def f(a, b):\n", " for _ in range(2):\n", " c = a * b\n", " d = a + b\n", " yield c, d\n", "\n", "g = f(1, 2)\n", "\n", "x, y = next(g)\n", "print(x, y)\n", "\n", "x, y = next(g)\n", "print(x, y)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "def generate_ints(N):\n", " for i in range(N):\n", " yield i" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "0\n", "1\n", "2\n" ] }, { "ename": "StopIteration", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgen\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# 발생자 실행 재개. yield에 의해 값 반환 후 다시 중단\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgen\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# 발생자 실행 재개. yield에 의해 값 반환 후 다시 중단\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgen\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# 발생자 실행 재개. yield에 의해 더 이상 반환할 값이 없다면 StopIteration 예외를 던짐\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m: " ] } ], "source": [ "gen = generate_ints(3) # 발생자 객체를 얻는다. generate_ints() 함수에 대한 초기 스택 프레임이 만들어지나 실행은 중단되어 있는 상태임\n", "print(gen)\n", "\n", "# print(gen.next()) \n", "# print(gen.next()) \n", "# print(gen.next()) \n", "# print(gen.next()) \n", "\n", "print(next(gen)) # 발생자 객체는 반복자 인터페이스를 가진다. 발생자의 실행이 시작됨. yield에 의해 값 반환 후 실행이 중단됨\n", "print(next(gen)) # 발생자 실행 재개. yield에 의해 값 반환 후 다시 중단\n", "print(next(gen)) # 발생자 실행 재개. yield에 의해 값 반환 후 다시 중단\n", "print(next(gen)) # 발생자 실행 재개. yield에 의해 더 이상 반환할 값이 없다면 StopIteration 예외를 던짐" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 위와 같은 세부 동작 방식을 이용하여, 다음과 같이 for ~ in 구문에 적용할 수 있다." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 2 3 4 " ] } ], "source": [ "for i in generate_ints(5):\n", " print(i, end=\" \")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 발생자 함수와 일반 함수의 차이점\n", " - 일반 함수는 함수가 호출되면 그 함수 내부에 정의된 모든 일을 마치고 결과를 반환함\n", " - 발생자 함수는 함수 내에서 수행 중에 중간 결과 값을 반환할 수 있음\n", " \n", " \n", "- 발생자가 유용하게 사용되는 경우\n", " - 함수 처리의 중간 결과를 다른 코드에서 참조할 경우\n", " - 즉, 모든 결과를 한꺼번에 반환 받는 것이 아니라 함수 처리 중에 나온 중간 결과를 받아서 사용해야 할 경우\n", " - 시퀀스 자료형을 효율적으로 만들고자 하는 경우" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3-2 발생자 구문\n", "- 리스트 내포(List Comprehension)\n", " - 리스트 객체의 새로운 생성\n", " - 메모리를 실제로 점유하면서 생성됨" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]\n" ] }, { "data": { "text/plain": [ "list" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = [k for k in range(100) if k % 5 == 0]\n", "print(a)\n", "type(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 리스트 내포 구문에 []가 아니라 () 사용\n", " - 리스트 대신에 발생자 생성\n", " - 즉, 처음부터 모든 원소가 생성되지 않고 필요한 시점에 각 원소가 만들어짐\n", " - 메모리를 보다 효율적으로 사용할 수 있음" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " at 0x105eec200>\n" ] }, { "data": { "text/plain": [ "generator" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = (k for k in range(100) if k % 5 == 0)\n", "print(a)\n", "type(a)" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "5\n", "10\n", "15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 " ] } ], "source": [ "# print(a.next())\n", "# print(a.next())\n", "# print(a.next())\n", "\n", "print(next(a))\n", "print(next(a))\n", "print(next(a))\n", "for i in a:\n", " print(i, end=\" \")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 아래 예는 sum 내장 함수에 발생자를 넣어줌\n", " - sum을 호출하는 시점에는 발생자가 아직 호출되기 직전이므로 각 원소들은 아직 존재하지 않는다.\n", " - sum 내부에서 발생자가 지니고 있는 next() 함수를 호출하여 각 원소들을 직접 만들어 활용한다.\n", " - 메모시 사용 효율이 높다." ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6\n" ] } ], "source": [ "a = [1, 2, 3]\n", "print(sum(a))" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "950\n" ] } ], "source": [ "a = (k for k in range(100) if k % 5 == 0)\n", "print(sum(a))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3-3 발생자의 활용 예 1 - 피보나치 수열" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 1 2 3 5 8 13 21 34 55 89 " ] } ], "source": [ "def fibonacci(a = 1, b = 1):\n", " while 1:\n", " yield a\n", " a, b = b, a + b\n", " \n", "for k in fibonacci(): # 발생자를 직접 for ~ in 구문에 활용\n", " if k > 100: \n", " break\n", " print(k, end=\" \")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3-4 발생자의 활용 예 2 - 홀수 집합 만들기" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 반복자를 활용한 예" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 3 5 7 9 11 13 15 17 19 \n", "[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]\n" ] } ], "source": [ "#python3.x\n", "class Odds:\n", " def __init__(self, limit = None): # 생성자 정의\n", " self.data = -1 # 초기 값\n", " self.limit = limit # 한계 값\n", " \n", " def __iter__(self): # Odds 객체의 반복자를 반환하는 특수 함수\n", " return self\n", "\n", " def __next__(self): # 반복자의 필수 함수\n", " self.data += 2\n", " if self.limit and self.limit <= self.data:\n", " raise StopIteration()\n", " return self.data\n", "\n", "for k in Odds(20):\n", " print(k, end=\" \")\n", " \n", "print()\n", "\n", "print(list(Odds(20))) # list() 내장 함수가 객체를 인수로 받으면 해당 객체의 반복자를 얻어와 __next__(self)를 매번 호출하여 각 원소를 얻어온다. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 발생자를 활용한 가장 좋은 예" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 \n", "3 \n", "5 \n", "7 \n", "9 \n", "11 \n", "13 \n", "15 \n", "17 \n", "19 \n", "[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]\n" ] } ], "source": [ "def odds(limit=None):\n", " k = 1\n", " while not limit or limit >= k:\n", " yield k\n", " k += 2\n", " \n", "for k in odds(20):\n", " print(k, end=\" \")\n", "\n", " print()\n", "\n", "print(list(odds(20))) # list() 내장 함수가 발생자를 인수로 받으면 해당 발생자의 next()를 매번 호출하여 각 원소를 얻어온다. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

참고 문헌: 파이썬(열혈강의)(개정판 VER.2), 이강성, FreeLec, 2005년 8월 29일

" ] } ], "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.6.8" } }, "nbformat": 4, "nbformat_minor": 1 }