{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 34. send로 제너레이터에 데이터를 주입하지 말라"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "제너레이터를 이용할 때 양방향을 사용할 수 있으면 좋을 것 같다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def wave(amplitude, steps):\n",
    "    step_size = 2 * math.pi / steps\n",
    "    for step in range(steps):\n",
    "        radians = step * step_size\n",
    "        fraction = math.sin(radians)\n",
    "        output = amplitude * fraction\n",
    "        yield output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def transmit(output):\n",
    "    if output is None:\n",
    "        print(f'출력: None')\n",
    "    else:\n",
    "        print(f'출력: {output:>5.1f}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def run(it):\n",
    "    for output in it:\n",
    "        transmit(output)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "출력:   0.0\n",
      "출력:   2.1\n",
      "출력:   3.0\n",
      "출력:   2.1\n",
      "출력:   0.0\n",
      "출력:  -2.1\n",
      "출력:  -3.0\n",
      "출력:  -2.1\n"
     ]
    }
   ],
   "source": [
    "run(wave(3.0, 8))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "파이썬 제너레이터는 send 메서드를 지원한다.\n",
    "\n",
    "이 메서드는 yield 식을 양방향 채널로 격상시켜준다.\n",
    "\n",
    "send 메서드를 사용하면 입력을 제너레이터에 스트리밍하는 동시에 출력을 내보낼 수 이싿.\n",
    "\n",
    "일반적으로 제너레이터를 이터레이션할 때 yield 식이 반환하는 값은 None이다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def my_generator():\n",
    "    received = yield 1\n",
    "    print(f'받은 값 = {received}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "출력값 = 1\n"
     ]
    }
   ],
   "source": [
    "it = iter(my_generator())\n",
    "output = next(it)\n",
    "print(f'출력값 = {output}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "받은 값 = None\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    next(it)\n",
    "except StopIteration:\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "하지만 for 루프나 next 내장 함수로 제너레이터를 이터레이션하지 않고 send 메서드를 호출하면, 제너레이터가 재개될 때 yield가 send에 전달된 파라미터 값을 반환한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "출력값 = 1\n",
      "받은 값 = 안녕!\n"
     ]
    }
   ],
   "source": [
    "it = iter(my_generator())\n",
    "output = it.send(None)\n",
    "print(f'출력값 = {output}')\n",
    "\n",
    "try:\n",
    "    it.send('안녕!')\n",
    "except StopIteration:\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "def wave_modulating(steps):\n",
    "    step_size = 2 * math.pi / steps\n",
    "    amplitude = yield\n",
    "    for step in range(steps):\n",
    "        radians = step * step_size\n",
    "        fraction = math.sin(radians)\n",
    "        output = amplitude * fraction\n",
    "        amplitude = yield output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_modulating(it):\n",
    "    aplitudes = [\n",
    "        None, 7, 7, 7, 2, 2, 2, 2, 10, 10, 10, 10, 10\n",
    "    ]\n",
    "    for amplitude in aplitudes:\n",
    "        output = it.send(amplitude)\n",
    "        transmit(output)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "출력: None\n",
      "출력:   0.0\n",
      "출력:   3.5\n",
      "출력:   6.1\n",
      "출력:   2.0\n",
      "출력:   1.7\n",
      "출력:   1.0\n",
      "출력:   0.0\n",
      "출력:  -5.0\n",
      "출력:  -8.7\n",
      "출력: -10.0\n",
      "출력:  -8.7\n",
      "출력:  -5.0\n"
     ]
    }
   ],
   "source": [
    "run_modulating(wave_modulating(12))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "def complex_wave():\n",
    "    yield from wave(7.0, 3)\n",
    "    yield from wave(2.0, 4)\n",
    "    yield from wave(10.0, 5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "출력:   0.0\n",
      "출력:   6.1\n",
      "출력:  -6.1\n",
      "출력:   0.0\n",
      "출력:   2.0\n",
      "출력:   0.0\n",
      "출력:  -2.0\n",
      "출력:   0.0\n",
      "출력:   9.5\n",
      "출력:   5.9\n",
      "출력:  -5.9\n",
      "출력:  -9.5\n"
     ]
    }
   ],
   "source": [
    "run(complex_wave())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "def complex_wave_modulating():\n",
    "    yield from wave_modulating(3)\n",
    "    yield from wave_modulating(4)\n",
    "    yield from wave_modulating(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "출력: None\n",
      "출력:   0.0\n",
      "출력:   6.1\n",
      "출력:  -6.1\n",
      "출력: None\n",
      "출력:   0.0\n",
      "출력:   2.0\n",
      "출력:   0.0\n",
      "출력: -10.0\n",
      "출력: None\n",
      "출력:   0.0\n",
      "출력:   9.5\n",
      "출력:   5.9\n"
     ]
    }
   ],
   "source": [
    "run_modulating(complex_wave_modulating())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "def wave_cascading(amplitude_it, steps):\n",
    "    step_size = 2 * math.pi / steps\n",
    "    for step in range(steps):\n",
    "        radians = step * step_size\n",
    "        fraction = math.sin(radians)\n",
    "        amplitude = next(amplitude_it)\n",
    "        output = amplitude * fraction\n",
    "        yield output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "def complex_wave_cascading(amplitude_it):\n",
    "    yield from wave_cascading(amplitude_it, 3)\n",
    "    yield from wave_cascading(amplitude_it, 4)\n",
    "    yield from wave_cascading(amplitude_it, 5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_cascading():\n",
    "    amplitudes = [7, 7, 7, 2, 2, 2, 2, 10, 10, 10, 10, 10]\n",
    "    it = complex_wave_cascading(iter(amplitudes))\n",
    "    for amplitude in amplitudes:\n",
    "        output = next(it)\n",
    "        transmit(output)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "출력:   0.0\n",
      "출력:   6.1\n",
      "출력:  -6.1\n",
      "출력:   0.0\n",
      "출력:   2.0\n",
      "출력:   0.0\n",
      "출력:  -2.0\n",
      "출력:   0.0\n",
      "출력:   9.5\n",
      "출력:   5.9\n",
      "출력:  -5.9\n",
      "출력:  -9.5\n"
     ]
    }
   ],
   "source": [
    "run_cascading()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이 코드는 입력 제너레이터가 완전히 스레드 안전 하다고 가정한다는 단점이 있다.\n",
    "\n",
    "하지만 제너레이터가 항상 스레드 안전하지는 않다. \n",
    "\n",
    "따라서 스레드 경계를 넘나들면서 제너레이터를 사용해야 한다면 async 함수가 더 나은 해법일 수도 있다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 기억해야 할 내용\n",
    "- send 메서드를 사용해 데이터를 제너레이터에 주입할 수 있다. 제너레이터는 send로 주입된 값을 yield 식이 반환하는 값을 통해 받으며, 이 값을 변수에 저장해 활용할 수 있다.\n",
    "- send와 yield form 식을 함께 사용하면 제너레이터의 출력에 None이 불쑥불쑥 나타나는 의외의 결과를 얻을 수도 있다.\n",
    "- 합성할 제너레이터들의 입력을 이터레이터를 전달하는 방식이 send를 사용하는 방식보다 더 낫다. send는 가급적 사용하지 말라."
   ]
  }
 ],
 "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.8.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}