{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 3. bytes와 str의 차이를 알아두라" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- bytes 타입의 인스턴스에는 부호가 없는 8바이트 데이터가 그대로 들어감\n", "- 직접 대응하는 텍스트 인코딩이 없음" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "a = b'h\\x65llo'" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[104, 101, 108, 108, 111]\n" ] } ], "source": [ "print(list(a))" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "b'hello'\n" ] } ], "source": [ "print(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- str 인스턴스에는 사람이 사용하는 언어의 문자를 표현하는 유니코드 **코드 포인트**가 들어가 있다.\n", "- 직접 대응하는 이진 인코딩이 없음" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "a = 'a\\u0300 propos'" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['a', '̀', ' ', 'p', 'r', 'o', 'p', 'o', 's']\n" ] } ], "source": [ "print(list(a))" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "à propos\n" ] } ], "source": [ "print(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- unicode to bytes -> encode()\n", "- bytes to unicode -> decode()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "파이썬 프로그램을 작성할 떄 유니코드 데이터를 인코딩하거나 디코딩하는 부분을 인터페이스의 가장 먼 경계 지점에 위치시켜라.\n", "\n", "이런 방식을 **유니코드 샌드위치** 라고 부른다.\n", "\n", "str을 사용하자" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 문자 표현 방식에 의해 발생하는 두가지 상황\n", "- UTF-8(또는 다른 인코딩 방식)로 인코딩된 8비트 시퀀스를 그대로 사용하고 싶다.\n", "- 특정 인코딩을 지정하지 않은 유니코드 문자열을 사용하고 싶다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### bytes나 str 인스턴스를 받아서 항상 str 반환하는 함수" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "def to_str(bytes_or_str):\n", " if isinstance(bytes_or_str, bytes):\n", " value = bytes_or_str.decode('utf-8')\n", " else:\n", " value = bytes_or_str\n", " return value # str 인스턴스" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "'foo'\n" ] } ], "source": [ "print(repr(to_str(b'foo')))" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "'bar'\n" ] } ], "source": [ "print(repr(to_str('bar')))" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "'한'\n" ] } ], "source": [ "print(repr(to_str(b'\\xed\\x95\\x9c')))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### bytes나 str 인스턴스를 받아서 항상 bytes 반환하는 함수" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "def to_bytes(bytes_or_str):\n", " if isinstance(bytes_or_str, str):\n", " value = bytes_or_str.encode('utf-8')\n", " else:\n", " value = bytes_or_str\n", " return value # bytes 인스턴스" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "b'foo'\n" ] } ], "source": [ "print(repr(to_bytes(b'foo')))" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "b'bar'\n" ] } ], "source": [ "print(repr(to_bytes('bar')))" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "b'\\xed\\x95\\x9c\\xea\\xb8\\x80'\n" ] } ], "source": [ "print(repr(to_bytes('한글')))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 이진 8비트 값과 유니코드 문자여을 다룰 때 두가지 문제점\n", "### bytes와 str은 호환되지 않으므로 어떤 타입인지 알아야함\n", "\n", "'+' 연산자는 bytes는 bytes끼리, str은 str끼리 연산 가능\n", "\n", "다른 연산자도 마찬가지" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "b'onetwo'\n" ] } ], "source": [ "print(b'one' + b'two')" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "onetwo\n" ] } ], "source": [ "print('one' + 'two')" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "can't concat str to bytes", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m<ipython-input-21-7e91d857e3d1>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mb'one'\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m'two'\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: can't concat str to bytes" ] } ], "source": [ "print(b'one' + 'two')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "같은 문자를 갖는 bytes와 str 인스턴스가 같은지 비교하면 False" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "False\n" ] } ], "source": [ "print(b'foo' == 'foo')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### % 연산자에 대해 적용해보자" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "b'red blue'\n" ] } ], "source": [ "print(b'red %s' % b'blue')" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "red blue\n" ] } ], "source": [ "print('red %s' % 'blue')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "하지만 파이썬이 어떤 이진 텍스트 인코딩을 사용할지 알 수 없으므로 str 인스턴스를 bytes 형식화 문자열에 넘길 수는 없음" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "%b requires a bytes-like object, or an object that implements __bytes__, not 'str'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m<ipython-input-25-996799a8aced>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mb'red %s'\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;34m'blue'\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: %b requires a bytes-like object, or an object that implements __bytes__, not 'str'" ] } ], "source": [ "print(b'red %s' % 'blue')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "하지만 반대는 가능하다." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "red b'blue'\n" ] } ], "source": [ "print('red %s' % b'blue')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "여기서는 bytes 인스턴스의 __repr__ 메서드를 호출함" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 파일 핸들과 관련한 연산들이 디폴트로 유니코드 문자열을 요구하고 이진 바이트 문자열을 요구하지 않음" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "write() argument must be str, not bytes", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m<ipython-input-27-e42a59df12fe>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'data.bin'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'w'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwrite\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mb'\\xf1\\xf2\\xf3\\xf4\\xf5'\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: write() argument must be str, not bytes" ] } ], "source": [ "with open('data.bin', 'w') as f:\n", " f.write(b'\\xf1\\xf2\\xf3\\xf4\\xf5')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "'wb'가 아닌 'w'로 열었기 때문임" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "with open('data.bin', 'wb') as f:\n", " f.write(b'\\xf1\\xf2\\xf3\\xf4\\xf5')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "반대로 읽을 때도 비슷한 오류 발생" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "ename": "UnicodeDecodeError", "evalue": "'utf-8' codec can't decode byte 0xf1 in position 0: invalid continuation byte", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mUnicodeDecodeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m<ipython-input-29-e61b03babdec>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'data.bin'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'r'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\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;32m/usr/lib/python3.8/codecs.py\u001b[0m in \u001b[0;36mdecode\u001b[0;34m(self, input, final)\u001b[0m\n\u001b[1;32m 320\u001b[0m \u001b[0;31m# decode input (taking the buffer into account)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 321\u001b[0m \u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbuffer\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0minput\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 322\u001b[0;31m \u001b[0;34m(\u001b[0m\u001b[0mresult\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mconsumed\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_buffer_decode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0merrors\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfinal\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 323\u001b[0m \u001b[0;31m# keep undecoded input until the next call\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 324\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbuffer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mconsumed\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;31mUnicodeDecodeError\u001b[0m: 'utf-8' codec can't decode byte 0xf1 in position 0: invalid continuation byte" ] } ], "source": [ "with open('data.bin', 'r') as f:\n", " data = f.read()" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "with open('data.bin', 'rb') as f:\n", " data = f.read()" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "b'\\xf1\\xf2\\xf3\\xf4\\xf5'" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [], "source": [ "with open('data.bin', 'r', encoding='cp1252') as f:\n", " data = f.read()" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'ñòóôõ'" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 핸들이 텍스트 모드에 있으면 시스템의 디폴트 텍스트 인코딩을 bytes.encode (쓰기의 경우)와 str.decode (읽기의 경우)에 적용해서 이진데이터를 해석한다.\n", "-> python2 이야기인듯 하다.\n", "\n", "대부분 시스템 디폴트 인코딩은 utf-8 인데 이는 위 이진 데이터를 읽을 수 없음" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "따라서 디폴트 인코딩이 다를 수 있으므로 명시해주는게 좋음" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "UTF-8\n" ] } ], "source": [ "import locale\n", "print(locale.getpreferredencoding())" ] } ], "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 }