├── .gitignore ├── .travis.yml ├── 2018coupang ├── README.md └── python │ ├── homework0.md │ ├── homework1.md │ ├── homework2.md │ ├── lecture0.md │ ├── lecture1.md │ ├── lecture2.md │ └── test_homework2.py ├── 2018fall ├── conftest.py ├── homework0.md ├── homework1.md ├── homework2.md ├── homework3.md ├── lecture1.md ├── lecture2.md ├── lecture3.md ├── lecture4.md ├── requirements.txt ├── solutions │ ├── __init__.py │ ├── homework1_jungheelee.py │ ├── homework1_suminb.py │ ├── homework1_ysunmi0427.py │ └── homework2_ysunmi0427.py ├── test_homework1.py ├── test_homework2.py └── webapp.py ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # IDE vscode 107 | .vscode 108 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | 4 | python: 5 | - "3.7-dev" 6 | 7 | install: 8 | - pip install -r 2018fall/requirements.txt 9 | 10 | script: 11 | - export PYTHONPATH=$PYTHONPATH:. 12 | - ls fall2018/solutions/homework*.py | awk -F'[_.]' '{print "pytest -v fall2018/test_" substr($1, 20) ".py --username=" $2}' | bash 13 | -------------------------------------------------------------------------------- /2018coupang/README.md: -------------------------------------------------------------------------------- 1 | # SB Coding Workshop: Coupang 2018 2 | 3 | 쿠팡 내부의 소프트웨어 엔지니어들을 대상으로 파이썬 워크샵을 진행합니다. 4 | -------------------------------------------------------------------------------- /2018coupang/python/homework0.md: -------------------------------------------------------------------------------- 1 | # Homework 0 2 | 3 | 원활한 워크샵 진행을 위해 환경 설정을 미리 해 오는 것이 이번 숙제의 목표이다. 준비가 되지 않으면 진행이 어려울 수 있다. 4 | 5 | ## Problem 1: Python 6 | 7 | 파이썬 3.7 버전을 권장하지만, 3.6도 괜찮다. 8 | 9 | $ python --version 10 | Python 3.7.0 11 | 12 | 파이썬 인터프리터가 준비 되었다면 파이썬 패키지 관리자인 `pip`를 준비하도록 한다. 13 | 14 | $ pip --version 15 | pip 18.0 from /usr/local/lib/python3.7/site-packages/pip (python 3.7) 16 | 17 | 참고할만한 문서: https://pip.pypa.io/en/stable/installing/ 18 | 19 | `pip`가 준비 되었다면 파이썬 가상 환경과 관리를 위한 패키지를 설치한다. 20 | 21 | pip install virtualenv 22 | 23 | 마지막으로, `virtualenv`를 조금 더 쉽게 사용할 수 있도록 패키징 해놓은 `virtualenvwrapper`를 설치한다. 24 | 25 | pip install virtualenvwrapper 26 | 27 | 안타깝게도 위 명령어만으로는 설치가 완벽하게 마무리 되지 않는다. 가상환경 디렉토리와 환경변수 설정 등을 직접 해주어야 합니다. [이 문서](https://virtualenvwrapper.readthedocs.io/en/latest/)를 참고하여 설치를 깔끔하게 마무리하도록 한다. 28 | 29 | ## Problem 2: Docker 30 | 31 | 도커를 설치한다. 32 | 33 | $ docker --version 34 | Docker version 18.06.1-ce, build e68fc7a 35 | 36 | ## Got Issues? 37 | 38 | `#python_seminar` 채널에 물어보자. -------------------------------------------------------------------------------- /2018coupang/python/homework1.md: -------------------------------------------------------------------------------- 1 | # Homework 1 2 | 3 | List comprehension을 이용한다면 모두 한 줄로 해결할 수 있는 문제들이다. 여기서 *한 줄*이라 함은 문자 그대로 코드를 한 줄에 우겨 넣는 것이 중요한 것이 아니라 하나의 표현식(expression)으로 해결 가능하다는 것이 주안점이다. 가독성을 위해서라면 하나의 표현식을 여러 줄에 걸쳐 써도 무방하다. 4 | 5 | 참고: [List comprehension](https://www.programiz.com/python-programming/list-comprehension)으로 해결하기 어렵다면 먼저 `for`, `while` 등의 반복문으로 해결을 한 다음 다시 list comprehension 으로 해결해 보는 것을 추천한다. 6 | 7 | 참고: 문제를 단순화하기 위해서 입력값은 모두 올바른 값이 주어진다고 가정하고 진행해도 좋다. 예를 들어서, 하나 이상의 원소가 있어야 하는 문제에 빈 리스트가 주어지거나, 정수형 리스트를 기대하는 함수에 문자열이 주어지는 경우는 없다. 8 | 9 | ## Problem 1 10 | 11 | ```python 12 | def square(xs): 13 | # Put your code here 14 | pass 15 | ``` 16 | 17 | 정수 리스트가 주어졌을 때 각 원소의 제곱을 가지는 새로운 리스트를 반환하는 함수를 작성하여라. 다음과 같이 작동해야 한다. 18 | 19 | ``` 20 | >>> square([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 21 | [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] 22 | ``` 23 | 24 | ## Problem 2 25 | 26 | ```python 27 | def even(xs): 28 | # Put your code here 29 | pass 30 | ``` 31 | 32 | 임의의 정수 리스트가 주어졌을 때 짝수인 원소만 담고 있는 새로운 리스트를 반환하는 함수를 작성하여라. 33 | 34 | ``` 35 | >>> even([1, 4, 9, 16, 25, 36, 49, 64, 81, 100]) 36 | [4, 16, 36, 64, 100] 37 | ``` 38 | 39 | ## Problem 3 40 | 41 | ```python 42 | def count_other_words(sentence, exclude): 43 | # Put your code here 44 | pass 45 | ``` 46 | 47 | 주어진 문장에서 특정 단어를 제외한 나머지 단어의 개수를 세는 코드를 작성하여라. 리스트 원소의 합을 구할 때에는 `sum()` 함수를 쓰면 편리하다. 48 | 49 | ``` 50 | >>> count_other_words('the quick brown fox jumps over the lazy dog', 'the') 51 | 7 52 | ``` 53 | 54 | ## Problem 4 55 | 56 | ```python 57 | def delta(xs): 58 | # Put your code here 59 | pass 60 | ``` 61 | 62 | 정수 또는 실수 원소를 가지는 리스트가 주어졌을 때 바로 이웃한 원소들간의 차를 구하는 코드를 작성하여라. 63 | 64 | ``` 65 | >>> delta([1, 2, 3, 4]) 66 | [1, 1, 1] 67 | 68 | >>> delta([4, 1, 1, 2, 4, 9]) 69 | [-3, 0, 1, 2, 5] 70 | ``` 71 | 72 | ## Problem 5 73 | 74 | 5.1, 5.2는 `set`을 사용하지 않고 구현해보는 것이 목적이다. 75 | 76 | ### Problem 5.1 77 | 78 | ```python 79 | def intersection(xs, ys): 80 | # Put your code here 81 | pass 82 | ``` 83 | 84 | 다음과 같이 두 개의 정수 리스트가 주어졌을 때 두 리스트 모두에 존재하는 원소들만 반환하는 코드를 작성하여라. 85 | 86 | ``` 87 | >>> xs = [5, 10, 15, 20, 25, 30, 35, 40] 88 | >>> ys = [9, 2, 3, 8, 10, 5, 21, 7] 89 | >>> intersection(xs, ys) 90 | [5, 10] 91 | ``` 92 | 93 | ### Problem 5.2 94 | 95 | ```python 96 | def union(xs, ys): 97 | # Put your code here 98 | pass 99 | ``` 100 | 101 | 두 개의 정수 리스트가 주어졌을 때 값이 중복되지 않도록 합치는 함수를 작성하여라. 102 | 103 | ``` 104 | >>> xs = [1, 2, 3, 4] 105 | >>> ys = [3, 4, 5, 6] 106 | >>> union(xs, ys) 107 | [1, 2, 3, 4, 5, 6] 108 | ``` 109 | 110 | 반환값의 순서는 관계 없다. 111 | 112 | ## Problem 6 113 | 114 | ```python 115 | def net_asset_value(inventory, prices): 116 | # Put your code here 117 | pass 118 | ``` 119 | 120 | 상품의 재고(`inventory`) 정보와 가격(`prices`) 정보가 주어졌을 때 총 재고 가치를 산출하는 함수를 작성하여라. 121 | 122 | ```python 123 | inventory = { 124 | 'avocado': 236, 125 | 'apple': 0, 126 | 'orange': 172, 127 | 'mango': 368, 128 | } 129 | 130 | prices = { 131 | 'avocado': 0.99, 132 | 'apple': 0.69, 133 | 'orange': 0.33, 134 | 'mango': 0.79 135 | } 136 | ``` 137 | 138 | 위와 같은 경우 총 재고 가치는 `581.12` 이다. 코드를 너무 복잡하게 만드는 것을 방지하기 위해서 `prices`는 항상 `inventory`에 있는 모든 아이템에 대한 가격 정보를 담고 있다고 가정해도 좋다. 139 | 140 | ``` 141 | >>> net_asset_value(inventory, prices) 142 | 581.12 143 | ``` 144 | 145 | ## Problem 7 146 | 147 | ```python 148 | def is_monotonic(xs): 149 | # Put your code here 150 | pass 151 | ``` 152 | 153 | 주어진 리스트의 monotonic 여부를 판단하는 코드를 작성하여라. Monotonic 리스트는 다음 두 가지 조건 중 하나를 만족하는 리스트이다. 154 | 155 | - Non-increasing: 원소의 값이 증가하지 않음 (같거나 감소함, *ai ≥ ai+1*) 156 | - Non-decreasing: 원소의 값이 감소하지 않음 (같거나 증가함, *ai ≤ ai+1*) 157 | 158 | ``` 159 | >>> is_monotonic([1, 2, 3, 4]) 160 | True 161 | 162 | >>> is_monotonic([4, 3, 2, 1]) 163 | True 164 | 165 | >>> is_monotonic([0, 0, 0, 0]) 166 | True 167 | 168 | >>> is_monotonic([1, 2, 1, 2]) 169 | False 170 | ``` 171 | 172 | ## Problem 8 (Bonus) 173 | 174 | 참고: 길이가 정해지지 않은 인자를 받으려면 다음과 같이 코드를 작성할 수 있다. 175 | 176 | ```python 177 | def func(*args): 178 | for x in args: 179 | # Do something with x 180 | pass 181 | ``` 182 | 183 | 함수의 시그니처(signature)가 위와 같다면 다음과 같이 호출하는 것이 가능하다. 184 | 185 | ``` 186 | func(x) 187 | func(x, y) 188 | func(x, y, z) 189 | ... 190 | ``` 191 | 192 | ### Problem 8.1 193 | 194 | ```python 195 | def zip(*args): 196 | # Put your code here 197 | pass 198 | ``` 199 | 200 | 파이썬에서 제공하는 `zip()` 함수를 구현하여라. 201 | 202 | ``` 203 | >>> list(zip([1, 2, 3], 'abc')) 204 | [(1, 'a'), (2, 'b'), (3, 'c')] 205 | ``` 206 | 207 | 전달되는 리스트의 길이는 동일하지 않을 수도 있으며, 그런 경우 가장 짧은 리스트를 기준으로 결과를 반환한다. 208 | 209 | ``` 210 | >>> list(zip([1, 2, 3, 4], 'abc', [9.0, 8.0])) 211 | [(1, 'a', 9.0), (2, 'b', 8.0)] 212 | ``` 213 | 214 | ### Problem 8.2 215 | 216 | ```python 217 | def unzip(iterables): 218 | # Put your code here 219 | pass 220 | ``` 221 | 222 | 8.1에서 구현한 `zip()`의 결과를 다시 원래대로 되돌려놓는 함수를 작성하여라. 223 | 224 | ``` 225 | >>> list(unzip([(1, 'a'), (2, 'b'), (3, 'c')])) 226 | [(1, 2, 3), ('a', 'b', 'c')] 227 | ``` 228 | 229 | ## 제출 230 | 231 | 코드를 제출하는 방법에 대해서는 아직 고민중이다. `#python_seminar` 채널을 통해서 공지할 예정이다. -------------------------------------------------------------------------------- /2018coupang/python/homework2.md: -------------------------------------------------------------------------------- 1 | # Homework 2 2 | 3 | ## Problem 1: Vector 4 | 5 | 벡터(`Vector`) 객체를 구현하는 것이 이 문제의 목표이다. 스켈레톤(skeleton) 구현은 다음과 같으며, 구현에 어려움이 있다면 이 섹션 맨 아래쪽에 있는 힌트를 참고해도 좋다. 힌트를 보기 전에 최대한 혼자 힘으로 해결해보는 것을 추천한다. 6 | 7 | 파일 이름은 `homework2.py` 로 한다. 8 | 9 | ```python 10 | class Vector: 11 | 12 | def __init__(self, *elements): 13 | self.elements = elements 14 | 15 | def __eq__(self, other): 16 | return all([x == y for x, y in zip(self.elements, other.elements)]) 17 | 18 | # ... 19 | ``` 20 | 21 | ### Problem 1.1 22 | 23 | 벡터는 최소 두 개 이상의 원소로 이루어진 값이다. 다음과 같이 생성할 수 있다. 24 | 25 | ``` 26 | >>> Vector(1, 2, 3, 4) 27 | <__main__.Vector object at 0x1063640b8> 28 | ``` 29 | 30 | 두 개 미만의 원소로 벡터를 생성하려고 시도하면 다음과 같이 `ValueError`가 발생한다. 31 | 32 | ``` 33 | >>> Vector(1) 34 | Traceback (most recent call last): 35 | File "...", line 7, in __init__ 36 | raise ValueError('At least two elements are required') 37 | ValueError: At least two elements are required 38 | ``` 39 | 40 | ### Problem 1.2 41 | 42 | ``` 43 | >>> Vector(1, 2, 3, 4) 44 | <__main__.Vector object at 0x1063640b8> 45 | ``` 46 | 47 | 위와 같이 메모리 주소가 나오는 것 보다는 48 | 49 | ``` 50 | >>> Vector(1, 2, 3, 4) 51 | [1, 2, 3, 4] 52 | ``` 53 | 54 | 이처럼 쉽게 인지할 수 있는 형식으로 출력하는 것이 여러모로 도움이 된다. 이렇게 나올 수 있도록 built-in 메소드를 오버라이드 해보자. 55 | 56 | ### Problem 1.3 57 | 58 | 벡터 객체를 생성했다면 크기를 알 수 있어야 한다. 59 | 60 | ``` 61 | >>> v = Vector(3, 5, 7) 62 | >>> len(v) 63 | 3 64 | ``` 65 | 66 | ### Problem 1.4 67 | 68 | 기본적인 동작 중 하나인 negation 을 구현하여라. 69 | 70 | ``` 71 | >>> v = Vector(1, -2, 3) 72 | >>> -v 73 | [-1, 2, -3] 74 | ``` 75 | 76 | ### Problem 1.5 77 | 78 | 벡터의 덧셈 연산을 구현하여라. 79 | 80 | ``` 81 | >>> u = Vector(5, 0, 4) 82 | >>> v = Vector(1, -2, 3) 83 | >>> u + v 84 | [6, -2, 7] 85 | ``` 86 | 87 | 만약 두 벡터의 길이가 같지 않다면 `ValueError` 를 내야 한다. 88 | 89 | ``` 90 | >>> Vector(1, 2, 3) + Vector(1, 2, 3, 4) 91 | Traceback (most recent call last): 92 | File "...", line 45, in ... 93 | raise ValueError(f'A vector of size {len(self)} is expected') 94 | ValueError: A vector of size 3 is expected 95 | ``` 96 | 97 | ### Problem 1.6 98 | 99 | 벡터의 뺄셈 연산을 구현하여라. 100 | 101 | ``` 102 | >>> u = Vector(5, 0, 4) 103 | >>> v = Vector(1, -2, 3) 104 | 105 | >>> u - v 106 | [4, 2, 1] 107 | 108 | >>> v - u 109 | [-4, -2, -1] 110 | ``` 111 | 112 | 뺄셈 연산도 덧셈 연산과 마찬가지로 양쪽 항 벡터의 크기가 같아야 한다. 113 | 114 | ### Problem 1.7 115 | 116 | 벡터의 곱셈을 구현하여라. 두 가지 종류의 곱셈 연산을 지원한다. 117 | 118 | ``` 119 | >>> u = Vector(1, 3, -5) 120 | >>> v = Vector(4, -2, -1) 121 | ``` 122 | 123 | 위와 같이 벡터 `u`, `v`가 정의된다고 할 때, 다음과 같이 scalar 값과의 곱셈 연산을 지원해야 한다. 124 | 125 | Bonus problem: `2 * u` 와 같은 형태의 연산을 지원하려면 어떻게 해야 할까? 126 | 127 | ``` 128 | >>> u * 2 129 | [2, 6, -10] 130 | ``` 131 | 132 | 벡터끼리 곱하는 것은 [dot product](https://en.wikipedia.org/wiki/Dot_product)로 정의된다. 133 | 134 | ``` 135 | >>> u * v 136 | 3 137 | ``` 138 | 139 | ### Problem 1.8 140 | 141 | 벡터끼리의 나눗셈은 정의되지 않는다. 142 | 143 | ``` 144 | >>> u / v 145 | Traceback (most recent call last): 146 | File "", line 1, in 147 | TypeError: unsupported operand type(s) for /: 'Vector' and 'Vector' 148 | ``` 149 | 150 | 정수형 나눗셈의 경우에도 마찬가지다. 151 | 152 | ``` 153 | >>> u // v 154 | Traceback (most recent call last): 155 | File "", line 1, in 156 | TypeError: unsupported operand type(s) for //: 'Vector' and 'Vector' 157 | ``` 158 | 159 | 단, scalar 값으로 나누는 경우는 다룰 수 있어야 한다. 160 | 161 | ``` 162 | >>> u / 2 163 | [1.0, 1.5, 2.0, 2.5] 164 | ``` 165 | 166 | 정수형 나눗셈의 경우도 마찬가지다. 167 | 168 | ``` 169 | >>> u // 2 170 | [1, 1, 2, 2] 171 | ``` 172 | 173 | ### Hints for Problem 1 174 | 175 | - Problem 1.2: [`__repr__()`](https://docs.python.org/3/reference/datamodel.html#object.__repr__) 176 | - Problem 1.3: [`__len__()`](https://docs.python.org/3/reference/datamodel.html#object.__len__) 177 | - Problem 1.4: [`__neg__()`](https://docs.python.org/3/reference/datamodel.html#object.__neg__) 178 | - Problem 1.5: [`__add__()`](https://docs.python.org/3/reference/datamodel.html#object.__add__) 179 | - Problem 1.6: [`__sub__()`](https://docs.python.org/3/reference/datamodel.html#object.__sub__) 180 | - Problem 1.7: [`__mul__()`](https://docs.python.org/3/reference/datamodel.html#object.__mul__) 181 | - Problem 1.8: [`__truediv__()`](https://docs.python.org/3/reference/datamodel.html#object.__truediv__), [`__floordiv__()`](https://docs.python.org/3/reference/datamodel.html#object.__floordiv__), [`NotImplemented`](https://docs.python.org/3/library/constants.html#NotImplemented), [`isinstance`](https://docs.python.org/3.7/library/functions.html#isinstance) 182 | 183 | ## Problem 2.1 184 | 185 | 파이썬의 `range()` 함수는 다음과 같이 유용하게 사용할 수 있다. 186 | 187 | ``` 188 | >>> [x for x in range(0, 10, 2)] 189 | [0, 2, 4, 6, 8] 190 | ``` 191 | 192 | 이것과 비슷한 기능을 제공하는 `Range` 클래스를 작성하여라. 단, `range()` 를 사용하지 않고 구현해야 한다. 또한, 복잡도를 줄이기 위해 `range(10)` 처럼 하나의 인자만 전달하는 것은 지원하지 않는다. 193 | 194 | 다음과 같이 시작과 끝을 명시하거나, 195 | 196 | ``` 197 | >>> [x for x in Range(0, 5)] 198 | [0, 1, 2, 3, 4] 199 | ``` 200 | 201 | 다음과 같이 시작과 끝, 그리고 스텝 크기를 명시할 수 있어야 한다. 202 | 203 | ``` 204 | >>> [x for x in Range(0, 10, 3)] 205 | [0, 3, 6, 9] 206 | ``` 207 | 208 | 세번째 인자인 `step`의 값은 `0`이 아닌 값이어야 하고, 만약 `0`이 주어진다면 `ValueError`를 내야 한다. 209 | 210 | ``` 211 | >>> Range(0, 0, 0) 212 | Traceback (most recent call last): 213 | File "", line 1, in 214 | File ".../homework2_suminb.py", line 23, in ... 215 | raise ValueError('`step` cannot be zero') 216 | ValueError: `step` cannot be zero 217 | ``` 218 | 219 | 다음의 파이썬 함수들이 도움이 될 수 있다. 220 | 221 | - [`__iter__()`](https://docs.python.org/3/reference/datamodel.html#object.__iter__) 222 | - [`__next__()`](https://docs.python.org/3/library/stdtypes.html#iterator.__next__) 223 | 224 | ## Problem 2.2 225 | 226 | 다음의 동작을 지원하도록 `Range` 클래스를 확장하여라. 227 | 228 | ``` 229 | >>> [x for x in reversed(Range(0, 5))] 230 | [4, 3, 2, 1, 0] 231 | ``` 232 | 233 | ``` 234 | >>> [x for x in reversed(Range(0, 10, 2))] 235 | [8, 6, 4, 2, 0] 236 | ``` 237 | 238 | ## 제출 239 | 240 | 답안을 따로 제출하지는 않고 각자 채점하기로 한다. 채점은 다음과 같이 할 수 있다. 241 | 242 | pytest -v test_homework2.py 243 | 244 | `Vector`, `Range` 클래스를 구현한 파일 이름은 `homework2.py` 로 한다. -------------------------------------------------------------------------------- /2018coupang/python/lecture0.md: -------------------------------------------------------------------------------- 1 | # Lecture 0 2 | 3 | 참여자들에게 물어보았다. "파이썬을 배우는 이유가 무엇인가?" 4 | 5 | - 머신러닝쪽에서 핫한 언어이기 때문에 6 | - 자바와 비교하여 *사상적*으로 어떻게 다른가 7 | - 익숙한 언어 하나 더 늘리기 8 | - TensorFlow 체험해보기 9 | 10 | ## Introduction 11 | 12 | - SB Coding Workshop 의 유래 13 | - 나는 파이썬을 어떤 목적으로 사용하는가? 14 | - 고통의 총량은 정해져있다 (Python vs. Scala) 15 | - "파이썬 문법 완벽 가이드"가 아닌 파이썬 개발자로서 첫 걸음을 내딛는데 필요한 것들을 조금씩 같이 체험해볼 예정이다. 16 | 17 | ## 환경 설정 18 | 19 | - 파이썬 3.7 버전을 추천하지만 3.6도 괜찮다. 2.x 버전은 많은 부분에서 다르기 때문에 워크샵에서는 다루지 않을 예정이다. 20 | - `pip` 설치 21 | 22 | ## 파이썬 살짝 체험해보기 23 | 24 | - 파이썬 프로젝트의 구성 25 | - REPL vs. Script File 26 | - `pip` 문법(?) 27 | - [Semantic versioning](https://semver.org/) 28 | - 패키지 설치해보기 (requests, flask) 29 | - 점심 뭐 먹을까? `random.choice(['맥도날드', '치폴레', '베트남 쌀국수'])` 30 | - `requests.get()`, `json.loads()` 체험해보기 31 | 32 | ## 다음 시간에 이야기 할 것 33 | 34 | - 가상 환경 (virtualenv) 35 | - List comprehension 36 | -------------------------------------------------------------------------------- /2018coupang/python/lecture1.md: -------------------------------------------------------------------------------- 1 | Lecture 1 2 | ========= 3 | 4 | List comprehension 5 | -------------------------------------------------------------------------------- /2018coupang/python/lecture2.md: -------------------------------------------------------------------------------- 1 | # Lecture 2 2 | 3 | - EAFP (Easier to ask for forgiveness) 4 | - Class definitions 5 | - Special methods 6 | - `self` 7 | - `__repr__` 8 | - `__iter__`, `__repl__` 9 | - `__enter__`, `__exit__` 10 | - http://www.diveintopython3.net/special-method-names.html 11 | - Duck typing 12 | - `for` loop example 13 | - Private variables 14 | - `type()` 15 | -------------------------------------------------------------------------------- /2018coupang/python/test_homework2.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from homework2 import Range, Vector 4 | 5 | 6 | @pytest.mark.parametrize('n', range(2, 100, 17)) 7 | def test_problem1_1(n): 8 | v = Vector(*list(range(n))) 9 | 10 | 11 | def test_problem1_1_invalid(): 12 | with pytest.raises(ValueError): 13 | v = Vector() 14 | with pytest.raises(ValueError): 15 | v = Vector(1) 16 | 17 | 18 | @pytest.mark.parametrize('xs, expected', [ 19 | ([97, 53, 5, 33, 65, 62, 51, 100], '[97, 53, 5, 33, 65, 62, 51, 100]'), 20 | ([1.0, 2.5, 3.8], '[1.0, 2.5, 3.8]'), 21 | (['a', 'b', 'c'], '[a, b, c]'), 22 | ]) 23 | def test_problem1_2(xs, expected): 24 | v = Vector(*xs) 25 | assert str(v) == expected 26 | 27 | 28 | @pytest.mark.parametrize('n', range(2, 100, 17)) 29 | def test_problem1_3(n): 30 | v = Vector(*list(range(n))) 31 | assert len(v) == n 32 | 33 | 34 | @pytest.mark.parametrize('xs, expected', [ 35 | ([76, 49, 40], [-76, -49, -40]), 36 | ([4, 10, 89, 69], [-4, -10, -89, -69]), 37 | ([77, 70, 75, 36, 56], [-77, -70, -75, -36, -56]), 38 | ([90, 67, 35, 66, 30, 27, 86, 75], [-90, -67, -35, -66, -30, -27, -86, -75]), 39 | ([75, 80, 42, 24, 31, 2, 93, 34, 14], [-75, -80, -42, -24, -31, -2, -93, -34, -14]), 40 | ]) 41 | def test_problem1_4(xs, expected): 42 | v = Vector(*xs) 43 | assert -v == Vector(*expected) 44 | assert -(-v) == Vector(*xs) 45 | 46 | 47 | @pytest.mark.parametrize('xs, ys, expected', [ 48 | ([100, 18], [5, 73], [105, 91]), 49 | ([50, 11, 47], [4, 77, 2], [54, 88, 49]), 50 | ([23, 91, 15, 61], [93, 7, 86, 2], [116, 98, 101, 63]), 51 | ([79, 12, 33, 8, 28], [82, 38, 44, 55, 23], [161, 50, 77, 63, 51]), 52 | ([64, 59, 5, 76, 12, 89], [25, 33, 45, 93, 60, 72], [89, 92, 50, 169, 72, 161]), 53 | ]) 54 | def test_problem1_5(xs, ys, expected): 55 | u, v = Vector(*xs), Vector(*ys) 56 | assert u + v == Vector(*expected) 57 | 58 | 59 | def test_problem1_5_invalid(): 60 | u, v = Vector(1, 2), Vector(1, 2, 3) 61 | with pytest.raises(ValueError): 62 | w = u + v 63 | 64 | 65 | @pytest.mark.parametrize('xs, ys, expected', [ 66 | ([89, 86], [98, 7], [-9, 79]), 67 | ([20, 43, 67], [15, 76, 56], [5, -33, 11]), 68 | ([1, 60, 87, 52], [83, 45, 49, 84], [-82, 15, 38, -32]), 69 | ([19, 71, 88, 1, 58], [42, 94, 5, 69, 35], [-23, -23, 83, -68, 23]), 70 | ([30, 97, 61, 45, 78, 36], [75, 81, 79, 16, 91, 39], [-45, 16, -18, 29, -13, -3]) 71 | ]) 72 | def test_problem1_6(xs, ys, expected): 73 | u, v = Vector(*xs), Vector(*ys) 74 | assert u - v == Vector(*expected) 75 | 76 | 77 | def test_problem1_6_invalid(): 78 | u, v = Vector(1, 2), Vector(1, 2, 3) 79 | with pytest.raises(ValueError): 80 | w = u - v 81 | 82 | 83 | @pytest.mark.parametrize('xs, n, expected', [ 84 | ([-92, 2], 79, [-7268, 158]), 85 | ([97, 69, 81], -89, [-8633, -6141, -7209]), 86 | ([14, -84, -34, 79], -60, [-840, 5040, 2040, -4740]), 87 | ([35, 24, 43, 54, 93], -100, [-3500, -2400, -4300, -5400, -9300]), 88 | ([26, -17, -21, 19, -88, 6], -52, [-1352, 884, 1092, -988, 4576, -312]), 89 | ]) 90 | def test_problem1_7_scalar(xs, n, expected): 91 | v = Vector(*xs) 92 | assert v * n == Vector(*expected) 93 | 94 | 95 | @pytest.mark.parametrize('xs, ys, expected', [ 96 | ([100, -23], [22, -9], 2407), 97 | ([49, -45, 29], [-65, -28, -65], -3810), 98 | ([93, -76, 58, -36], [36, 80, 54, -63], 2668), 99 | ([-21, -75, 86, -82, 75], [-16, 20, 43, -75, -10], 7934), 100 | ([11, -20, 56, 63, -48, 41], [22, 13, 33, -34, -85, 40], 5408), 101 | ]) 102 | def test_problem1_7_dot_product(xs, ys, expected): 103 | u, v = Vector(*xs), Vector(*ys) 104 | assert u * v == expected 105 | 106 | 107 | def test_problem1_7_invalid(): 108 | u, v = Vector(1, 2), Vector(1, 2, 3) 109 | with pytest.raises(ValueError): 110 | w = u * v 111 | 112 | 113 | @pytest.mark.parametrize('xs, n, expected', [ 114 | ([-38, -96], 8.7, [-4.367816091954023, -11.03448275862069]), 115 | ([-31, -71, 80], -4.4, [7.045454545454545, 16.136363636363637, -18.18181818181818]), 116 | ([-5, -57, -15, 9], -8.5, [0.5882352941176471, 6.705882352941177, 1.7647058823529411, -1.0588235294117647]), 117 | ([-75, 100, -63, 78, -44], -8.9, [8.42696629213483, -11.235955056179774, 7.078651685393258, -8.764044943820224, 4.943820224719101]), 118 | ([46, 62, 36, 54, 74, -82], -9.4, [-4.8936170212765955, -6.595744680851063, -3.829787234042553, -5.74468085106383, -7.872340425531915, 8.72340425531915]), 119 | ]) 120 | def test_problem1_8_truediv(xs, n, expected): 121 | v = Vector(*xs) 122 | assert v / n == Vector(*expected) 123 | 124 | 125 | @pytest.mark.parametrize('xs, n, expected', [ 126 | ([-95, -51], -53, [1, 0]), 127 | ([83, -69, 22], -47, [-2, 1, -1]), 128 | ([86, -85, 73, -95], 39, [2, -3, 1, -3]), 129 | ([8, 58, -75, -34, -83], -44, [-1, -2, 1, 0, 1]), 130 | ([-82, 65, -23, -11, 11, -54], -85, [0, -1, 0, 0, -1, 0]), 131 | ]) 132 | def test_problem1_8_floordiv(xs, n, expected): 133 | v = Vector(*xs) 134 | assert v // n == Vector(*expected) 135 | 136 | 137 | def test_problem1_8_invalid(): 138 | u, v = Vector(1, 2), Vector(1, 2, 3) 139 | with pytest.raises(TypeError): 140 | w = u / v 141 | with pytest.raises(TypeError): 142 | w = u // v 143 | 144 | 145 | @pytest.mark.parametrize('start, end', [ 146 | (0, 0), 147 | (1, 2), 148 | (-10, 5), 149 | (-20, 0), 150 | (-30, -10), 151 | ]) 152 | def test_range_1(start, end): 153 | assert list(Range(start, end)) == list(range(start, end)) 154 | 155 | 156 | @pytest.mark.parametrize('start, end, step', [ 157 | (0, 0, 1), 158 | (1, 2, 1), 159 | (3, 20, 1), 160 | (-10, 10, 3), 161 | (-10, 20, 100), 162 | ]) 163 | def test_range_2(start, end, step): 164 | assert list(Range(start, end, step)) == list(range(start, end, step)) 165 | 166 | 167 | @pytest.mark.parametrize('start, end, step', [ 168 | (0, 0, 1), 169 | (1, 2, 1), 170 | (3, 20, 1), 171 | (-10, 10, 3), 172 | (-10, 20, 100), 173 | ]) 174 | def test_range_3(start, end, step): 175 | assert list(reversed(Range(start, end, step))) == list(reversed(range(start, end, step))) 176 | 177 | 178 | def test_ragne_4(): 179 | with pytest.raises(ValueError): 180 | _ = Range(0, 0, 0) -------------------------------------------------------------------------------- /2018fall/conftest.py: -------------------------------------------------------------------------------- 1 | def pytest_addoption(parser): 2 | parser.addoption('--username', action='store') 3 | -------------------------------------------------------------------------------- /2018fall/homework0.md: -------------------------------------------------------------------------------- 1 | Prep Homework 2 | ============= 3 | 4 | 다음의 명령어가 실행될 수 있는 환경을 구축하세요. 맥 사용자라면 [Homebrew 를 이용하여 설치](https://docs.brew.sh/Homebrew-and-Python)할 수 있습니다. 윈도우 사용자라면 [Anaconda](https://www.anaconda.com/download/)를 사용하는 것이 편리할 수도 있습니다. 직접 설치해도 무방합니다. 5 | 6 | ``` 7 | $ python --version 8 | Python 3.7.0 9 | ``` 10 | 11 | 참고: `3.6`도 괜찮긴 하지만, `3.7`과 미묘하게 달라서 추가적으로 문제 해결을 할 일이 생길 수도 있습니다. 12 | 13 | ``` 14 | $ pip --version 15 | pip 18.0 from /usr/local/lib/python3.7/site-packages/pip (python 3.7) 16 | ``` 17 | 18 | `pip`는 [이 문서](https://pip.pypa.io/en/stable/installing/)를 참고해서 설치해주세요. 우분투 리눅스 사용자라면 `apt install python-pip` 명령어로 간편하게 설치할 수 있습니다. 19 | 20 | `pip`를 설치했다면 파이썬 가상 환경 구성과 관리를 위한 패키지를 설치하도록 합니다. (Anaconda를 설치했다면 이 과정은 생략해도 좋습니다.) 21 | 22 | ``` 23 | pip install virtualenv 24 | ``` 25 | 26 | 마지막으로, `virtualenv`를 조금 더 쉽게 사용할 수 있도록 패키징 해놓은 `virtualenvwrapper`를 설치합니다. 27 | 28 | ``` 29 | pip install virtualenvwrapper 30 | ``` 31 | 32 | 안타깝게도 위 명령어만으로는 설치가 완벽하게 마무리 되지 않습니다. 가상환경 디렉토리와 환경변수 설정 등을 직접 해주어야 합니다. [이 문서](https://virtualenvwrapper.readthedocs.io/en/latest/)를 참고하여 설치를 깔끔하게 마무리하도록 합니다. 33 | -------------------------------------------------------------------------------- /2018fall/homework1.md: -------------------------------------------------------------------------------- 1 | 코드를 길게 작성해서 풀어도 상관 없지만, 파이썬의 [list comprehension](https://www.programiz.com/python-programming/list-comprehension)을 이용하면 단 한줄로 해결할 수 있는 문제들입니다. 2 | 3 | ## Problem 1.1 4 | 5 | ```python 6 | def square(xs): 7 | # 여기에 여러분의 코드를 작성하세요 8 | pass 9 | ``` 10 | 11 | 정수 리스트가 주어졌을 때 각 원소의 제곱을 가지는 새로운 리스트를 반환하는 함수를 작성하십시오. 12 | 13 | xs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 14 | 15 | 예를 들어서, 위와 같은 리스트가 주어졌을 때 다음과 같은 리스트가 반환되어야 합니다. 16 | 17 | [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] 18 | 19 | ## Problem 1.2 20 | 21 | ```python 22 | def even(xs): 23 | # 여기에 여러분의 코드를 작성하세요 24 | pass 25 | ``` 26 | 27 | 임의의 정수 리스트가 주어졌을 때 짝수인 원소만 담고 있는 새로운 리스트를 반환하는 함수를 작성하십시오. 예를 들어서, 문제 1.1의 결과 리스트가 (`[1, 4, 9, 16, ...]`) 주어졌을 때 다음과 같은 리스트가 반환되어야 합니다. 28 | 29 | [4, 16, 36, 64, 100] 30 | 31 | ## Problem 2 32 | 33 | 친구들에게 단체 이메일을 보내려고 하는데, 이메일 주소 전체가 아닌 유저 이름 부분만 담긴 텍스트 파일이 주어졌습니다. 34 | 35 | alejandro, britney, christina, dennis, emily 36 | 37 | 유저 이름 뒤에 `@gmail.com`을 붙여야 하는데, 이걸 수동으로 하기에 우리 인생은 너무 짧습니다. 이것을 다음과 같이 변환하는 코드를 작성하십시오. 38 | 39 | alejandro@gmail.com; britney@gmail.com; christina@gmail.com; dennis@gmail.com; emily@gmail.com 40 | 41 | 여러분이 할 일은 `homework1.py`에 있는 `convert()` 함수를 구현하는 것입니다. `text`가 주어졌을 때 세미콜론(`;`)으로 구분된 이메일 주소들을 반환하는 함수입니다. 이 함수가 반환한 문자열을 이메일 주소 칸에 그대로 붙여넣으면 단체 메일을 보낼 수 있습니다. 42 | 43 | ``` 44 | def convert(text): 45 | # 여기에 여러분의 코드를 작성하세요 46 | pass 47 | ``` 48 | 49 | ## Problem 3 50 | 51 | 3.1, 3.2는 `set`을 사용하지 않고 구현해봅시다. 52 | 53 | ## Problem 3.1 54 | 55 | ```python 56 | def intersection(xs, ys): 57 | # 여기에 여러분의 코드를 작성하세요 58 | pass 59 | ``` 60 | 61 | 다음과 같이 두 개의 정수 리스트가 주어졌을 때 두 리스트 모두에 존재하는 원소들만 반환하는 코드를 작성하십시오. 62 | 63 | xs = [5, 10, 15, 20, 25, 30, 35, 40] 64 | ys = [9, 2, 3, 8, 10, 5, 21, 7] 65 | 66 | 여러분이 작성할 함수는 인자를 두 개 받는 함수입니다. 기대되는 반환값은 다음과 같습니다. 67 | 68 | [5, 10] 69 | 70 | ## Problem 3.2 71 | 72 | ```python 73 | def union(xs, ys): 74 | # 여기에 여러분의 코드를 작성하세요 75 | pass 76 | ``` 77 | 78 | 두 개의 정수 리스트가 주어졌을 때 값이 중복되지 않도록 합치는 함수를 작성하십시오. 79 | 80 | xs = [1, 2, 3, 4] 81 | ys = [3, 4, 5, 6] 82 | 83 | 반환값의 순서는 관계 없습니다. 84 | 85 | [1, 2, 3, 4, 5, 6] 86 | 87 | ## Problem 4 88 | 89 | ```python 90 | def net_asset_value(inventory, prices): 91 | # 여기에 여러분의 코드를 작성하세요 92 | pass 93 | ``` 94 | 95 | 상품의 재고(`inventory`) 정보와 가격(`prices`) 정보가 주어졌을 때 총 재고 가치를 산출하는 함수를 작성하십시오. 96 | 97 | ```python 98 | inventory = { 99 | 'avocado': 236, 100 | 'apple': 0, 101 | 'orange': 172, 102 | 'mango': 368, 103 | } 104 | 105 | prices = { 106 | 'avocado': 0.99, 107 | 'apple': 0.69, 108 | 'orange': 0.33, 109 | 'mango': 0.79 110 | } 111 | ``` 112 | 113 | 위와 같은 경우 총 재고 가치는 `581.12` 입니다. 코드를 너무 복잡하게 만드는 것을 방지하기 위해서 `prices`는 항상 `inventory`에 있는 모든 아이템에 대한 가격 정보를 담고 있다고 가정해도 좋습니다. 114 | 115 | ## Problem 5 116 | 117 | ```python 118 | def invert(index): 119 | # 여기에 여러분의 코드를 작성하세요 120 | pass 121 | ``` 122 | 123 | 딕셔너리의 키와 값을 바꾸는 코드를 작성하십시오. 124 | 125 | ```python 126 | index = { 127 | 'transparency': 37, 128 | 'composibility': 5, 129 | 'immutability': 40, 130 | 'idempotency': 14 131 | } 132 | ``` 133 | 134 | 기대되는 `invert()` 함수의 출력값은 다음과 같습니다. 135 | 136 | ```python 137 | {37: 'transparency', 5: 'composibility', 40: 'immutability', 14: 'idempotency'} 138 | ``` 139 | 140 | 코드를 복잡하게 만드는 것을 방지하기 위해 원본 딕셔너리의 키-값은 1:1 관계라고 가정합니다. (i.e., 같은 값을 가지는 중복되는 키가 없습니다.) 141 | 142 | ## Homework 6.1 (Bonus) 143 | 144 | ```python 145 | def zip(*args): 146 | # 여기에 여러분의 코드를 작성하세요 147 | pass 148 | ``` 149 | 150 | 파이썬에서 제공하는 `zip()` 함수를 구현하십시오. 151 | 152 | >>> list(zip([1, 2, 3], 'abc')) 153 | [(1, 'a'), (2, 'b'), (3, 'c')] 154 | 155 | 전달되는 리스트의 길이는 동일하지 않을 수도 있으며, 그런 경우 가장 짧은 리스트를 기준으로 결과를 반환합니다. 156 | 157 | >>> list(zip([1, 2, 3, 4], 'abc', [9.0, 8.0])) 158 | [(1, 'a', 9.0), (2, 'b', 8.0)] 159 | 160 | ## Homework 6.2 (Bonus) 161 | 162 | ```python 163 | def unzip(iterables): 164 | # 여기에 여러분의 코드를 작성하세요 165 | pass 166 | ``` 167 | 168 | 6.1에서 구현한 `zip()`의 결과를 다시 원래대로 되돌려놓는 함수를 작성하십시오. 169 | 170 | >>> list(unzip([(1, 'a'), (2, 'b'), (3, 'c')])) 171 | [(1, 2, 3), ('a', 'b', 'c')] 172 | 173 | ## 제출 174 | 175 | 답안은 `solutions/homework1_(GitHub 아이디).py` 파일로 제출해주십시오. 예를 들어서, GitHub 사용자 이름이 `suminb`라고 가정한다면 파일 이름은 `homework1_suminb.py`가 되어야 하고, `solutions` 디렉토리 안에 위치시키면 됩니다. 176 | 177 | ## 자동 채점 178 | 179 | 코드를 테스트 하기 위해서는 `pytest` 패키지가 필요합니다. 다음의 명령어를 실행하여 설치하도록 합니다. 180 | 181 | pip install pytest 182 | 183 | 패키지가 설치되면 다음과 같이 테스트 파일을 실행해서 여러분이 작성한 코드가 제대로 작동되는지 검증하도록 합니다. 184 | 185 | pytest -v test_homework1.py --username (GitHub 아이디) 186 | 187 | ## 참고할만한 자료 188 | 189 | - https://www.datacamp.com/community/tutorials/data-structures-python 190 | - https://docs.python-guide.org/ 191 | -------------------------------------------------------------------------------- /2018fall/homework2.md: -------------------------------------------------------------------------------- 1 | ## Problem 1: JavaScript Dictionary 2 | 3 | 자바스크립트의 경우 `dict[key]` 또는 `dict.key` 를 구분하지 않고 사용할 수 있다. 반면, 파이썬에서는 이 둘의 용법이 명확하게 구분된다. 4 | 5 | ``` 6 | >>> d = {'key': 'value'} 7 | 8 | >>> d['key'] 9 | 'value' 10 | 11 | >>> d.key 12 | Traceback (most recent call last): 13 | File "", line 1, in 14 | AttributeError: 'dict' object has no attribute 'key' 15 | ``` 16 | 17 | 18 | ## Problem 1.1 19 | 20 | ``` 21 | >>> d = DictWrapper({'key': 'value'}) 22 | 23 | >>> d['key'] 24 | value 25 | 26 | >>> d.key 27 | value 28 | ``` 29 | 30 | 위와 같이 이 둘을 명확하게 구분하지 않고 사용할 수 있도록 만들어주는 `DictWrapper` 클래스를 구현하여라. 31 | 32 | ```python 33 | class DictWrapper(object): 34 | 35 | def __init__(self, dict_) 36 | pass 37 | 38 | def __getattr__(self, key): 39 | pass 40 | 41 | def __getitem__(self, key): 42 | pass 43 | ``` 44 | 45 | 참고: 이것보다 훨씬 간단하게 구현할 수 있는 방법이 존재한다. 힌트는 '상속'. 46 | 47 | ## Problem 1.2 48 | 49 | 다음과 같이 값을 쓸 수 있도록 `DictWrapper`를 확장하여라. 50 | 51 | ``` 52 | >>> d = DictWrapper({'key': 'value'}) 53 | 54 | >>> d['key'] = 'new value' 55 | >>> d['key'] 56 | new value 57 | 58 | >>> d.key = 'another value' 59 | >>> d['key'] 60 | another value 61 | ``` 62 | 63 | ## Problem 1.3 64 | 65 | 키와 값의 관계가 1:1인지 (i.e., injective) 확인하는 메소드를 작성하여라. 66 | 67 | ```python 68 | class DictWrapper(object): 69 | 70 | def injective(self): 71 | return False 72 | ``` 73 | 74 | 예를 들어서, 1:1의 관계를 가지는 딕셔너리가 주어진다면 다음과 같은 결과가 나올 것이다. 75 | 76 | ``` 77 | >>> d = DictWrapper({'a': 1, 'b': 2, 'c': 3}) 78 | >>> d.injective() 79 | True 80 | ``` 81 | 82 | 반면, 1:1의 관계가 아니라면 다음과 같은 결과가 나와야 한다. 83 | 84 | ``` 85 | >>> d = DictWrapper({'a': 1, 'b': 2, 'c': 2}) 86 | >>> d.injective() 87 | False 88 | ``` 89 | 90 | Injective functions 에 대한 더 자세한 내용은 [위키피디아 항목](https://en.wikipedia.org/wiki/Injective_function)을 참조하면 좋다. 91 | 92 | ## Problem 1.4 93 | 94 | 키와 값의 관계가 1:1일 때 (i.e., injective) 키와 값을 맞바꾸는 메소드를 작성하여라. 95 | 96 | ```python 97 | class DictWrapper(object): 98 | 99 | def invert(self): 100 | pass 101 | ``` 102 | 103 | ``` 104 | >>> d = DictWrapper({'a': 1, 'b': 2, 'c': 3}) 105 | >>> d.invert() 106 | {1: 'a', 2: 'b', 3: 'c'} 107 | ``` 108 | 109 | 만약 1:1 관계가 성립하지 않는다면 `ValueError`를 내야 한다. 110 | 111 | ``` 112 | >>> d = DictWrapper({'a': 1, 'b': 2, 'c': 2}) 113 | >>> d.invert() 114 | Traceback (most recent call last): 115 | File "", line 1, in 116 | File ".../homework2_suminb.py", line 16, in invert 117 | raise ValueError('Dictionary is not injective, hence cannot be inverted') 118 | ValueError: Dictionary is not injective, hence cannot be inverted 119 | ``` 120 | 121 | `invert()`가 반환하는 객체는 `dict` 타입이 아닌 `DictWrapper` 타입이어야 한다. 122 | 123 | ## Problem 2.1 124 | 125 | 파이썬의 `range()` 함수는 다음과 같이 유용하게 사용할 수 있다. 126 | 127 | ``` 128 | >>> [x for x in range(0, 10, 2)] 129 | [0, 2, 4, 6, 8] 130 | ``` 131 | 132 | 이것과 비슷한 기능을 제공하는 `Range` 클래스를 작성하여라. 단, `range()` 를 사용하지 않고 구현해야 한다. 또한, 복잡도를 줄이기 위해 `range(10)` 처럼 하나의 인자만 전달하는 것은 지원하지 않는다. 133 | 134 | 다음과 같이 시작과 끝을 명시하거나, 135 | 136 | ``` 137 | >>> [x for x in Range(0, 5)] 138 | [0, 1, 2, 3, 4] 139 | ``` 140 | 141 | 다음과 같이 시작과 끝, 그리고 스텝 크기를 명시할 수 있어야 한다. 142 | 143 | ``` 144 | >>> [x for x in Range(0, 10, 3)] 145 | [0, 3, 6, 9] 146 | ``` 147 | 148 | 세번째 인자인 `step`의 값은 `0`이 아닌 값이어야 하고, 만약 `0`이 주어진다면 `ValueError`를 내야 한다. 149 | 150 | ``` 151 | >>> Range(0, 0, 0) 152 | Traceback (most recent call last): 153 | File "", line 1, in 154 | File ".../homework2_suminb.py", line 23, in ... 155 | raise ValueError('`step` cannot be zero') 156 | ValueError: `step` cannot be zero 157 | ``` 158 | 159 | 다음의 파이썬 함수들이 도움이 될 수 있다. 160 | 161 | - [`__iter__()`](https://docs.python.org/3/reference/datamodel.html#object.__iter__) 162 | - [`__next__()`](https://docs.python.org/3/library/stdtypes.html#iterator.__next__) 163 | 164 | ## Problem 2.2 165 | 166 | 다음의 동작을 지원하도록 `Range` 클래스를 확장하여라. 167 | 168 | ``` 169 | >>> [x for x in reversed(Range(0, 5))] 170 | [4, 3, 2, 1, 0] 171 | ``` 172 | 173 | ``` 174 | >>> [x for x in reversed(Range(0, 10, 2))] 175 | [8, 6, 4, 2, 0] 176 | ``` 177 | 178 | ## 제출 179 | 180 | 답안은 `solutions/homework2_(GitHub 아이디).py` 파일로 제출하면 된다. 예를 들어서, GitHub 사용자 이름이 `suminb`라고 가정한다면 파일 이름은 `homework2_suminb.py`가 되어야 하고, 해당 파일을 `solutions` 디렉토리 안에 위치시키면 된다. 181 | 182 | ## 자동 채점 183 | 184 | 코드를 테스트 하기 위해서는 `pytest` 패키지가 필요하다. 다음의 명령어를 실행하여 설치하도록 한다. 185 | 186 | pip install pytest 187 | 188 | 패키지가 설치되면 다음과 같이 테스트 파일을 실행해서 여러분이 작성한 코드가 제대로 작동되는지 검증하도록 한다. 189 | 190 | pytest -v test_homework2.py --username (GitHub 아이디) -------------------------------------------------------------------------------- /2018fall/homework3.md: -------------------------------------------------------------------------------- 1 | # Homework 3 2 | 3 | 이번 과제는 [이미 만들어진 파이썬 프로젝트][transporter]에서 각자 한 가지 이상의 기능을 구현해 보는 것이 목표이다. 4 | 5 | ## Problem 1 6 | 7 | 본격적으로 작업을 진행하기 전에 추가적인 환경 설정이 필요하다. 8 | 9 | ### Setting Up Docker 10 | 11 | 도커(Docker)를 설치해야 한다. 이미 설치 되어있다면 바로 다음 단계로 넘어간다. 12 | 13 | - https://docs.docker.com/docker-for-mac/install/ 14 | - https://docs.docker.com/docker-for-windows/install/ 15 | 16 | 명령창에서 `docker ps` 혹은 `docker images` 명령어를 수행했을 때 오류 메시지가 나오지 않고 (예를 들면 파일 권한 문제 등) 빈 리스트가 나오는 상태로 만들면 된다. 17 | 18 | ➜ transporter git:(develop) ✗ docker images 19 | REPOSITORY TAG IMAGE ID CREATED SIZE 20 | 21 | ### Redis Server 22 | 23 | [Transporter 프로젝트][transporter]는 효율적이고 빠른 데이터 캐싱을 위해 Redis를 사용한다. Redis 서버는 다음과 같이 실행할 수 있다. 24 | 25 | docker run -d -p 6379:6379 -p 6380:6380 redis:5.0 26 | 27 | `redis:5.0` 이미지가 로컬 시스템에 존재하지 않으면 자동으로 원격 저장소에서 받아오도록 되어있다. 만약 도커가 자동으로 이미지를 받아오지 않는다면 다음 명령어를 실행해서 받아오도록 한다. 28 | 29 | docker pull redis:5.0 30 | 31 | ### PostgreSQL 32 | 33 | 데이터베이스 서버가 재시작 될 때마다 데이터가 초기화 되면 효율적으로 작업하기 어려울 수 있기 때문에 영속적인 저장소를 사용하도록 한다. 도커로 실행되는 PostgreSQL 서버의 데이터 디렉토리를 호스트 머신의 디렉토리에 매핑(mapping)함으로써 해결한다. 매핑을 하기 위해서 홈 디렉토리에 `postgres` 디렉토리를 만들어야 한다. 34 | 35 | docker run -d \ 36 | -p 5432:5432 \ 37 | -e POSTGRES_USER=postgres \ 38 | -e POSTGRES_PASSWORD=qwerasdf \ 39 | -e POSTGRES_DB=transporter \ 40 | -v $HOME/postgres:/var/lib/postgresql/data \ 41 | -t postgres:9.6 42 | 43 | ### 확인 44 | 45 | PostgreSQL 서버에는 `psql` 명령어를 이용해서 접속할 수 있다. 다른 PostgreSQL 클라이언트를 사용해도 무방하다. 46 | 47 | ``` 48 | ➜ transporter git:(develop) ✗ psql -h localhost -U postgres 49 | Password for user postgres: 50 | psql (10.5, server 9.6.10) 51 | Type "help" for help. 52 | 53 | postgres=# 54 | ``` 55 | 56 | 위와 같이 `postgres:///postgres@localhost`에 접속할 수 있다면 정상이다. 57 | 58 | Redis 는 별도의 클라이언트를 준비하여 접속하거나 다음과 같이 Telnet 으로 접속할 수 있다. `localhost:6379`에 접속해서 `PING` 명령어를 날려보고 응답으로 `PONG` 메시지가 돌아오면 정상이다. 59 | 60 | ``` 61 | ➜ transporter git:(develop) ✗ telnet localhost 6379 62 | Trying ::1... 63 | Connected to localhost. 64 | Escape character is '^]'. 65 | PING 66 | +PONG 67 | ``` 68 | 69 | ## Problem 2 70 | 71 | 저번 시간에는 클래스에서 SQLite 에 데이터를 읽고 쓰는 것을 연습해보았는데, 이번에는 PostgreSQL 에 데이터를 읽고 쓰는 것을 연습해 보는 것이 목표이다. 사실, SQLAlchemy 같은 ORM 을 이용한다면 어떤 데이터베이스를 이용하든 코드 레벨에서 크게 달라질 것은 없다. 72 | 73 | 저번 워크샵 시간에 다같이 만들었던 간단한 CRUD 웹 앱 코드를 정리해서 [`webapp.py`](https://github.com/suminb/sbcw/blob/master/2018fall/webapp.py) 를 만들어두었다. 이 코드를 그대로 사용하는 것을 추천하지만, 저번 시간에 만들었던 코드에서 그대로 이어 나가도 괜찮다. 74 | 75 | 위 코드에서 의존하는 패키지는 다음과 같다. 76 | 77 | - `flask` 78 | - `flask-sqlalchemy` 79 | 80 | 코드를 보면 다음과 같이 데이터베이스 연결을 위한 URI를 명시하는 설정 변수가 있다. 81 | 82 | ```python 83 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///posts.db' 84 | ``` 85 | 86 | 이 부분을 변경해서 1번 문제에서 만든 PostgreSQL 서버에 접속할 수 있도록 만드는 것이 이 문제의 요구사항이다. 87 | 88 | SQLite 와 마찬가지로 PostgreSQL 데이터베이스에서도 최초에 한 번 테이블을 생성해주어야 한다. SQLite 에서는 하나의 파일이 하나의 데이터베이스에 대응되는 방식이었지만, PostgreSQL 에는 여러개의 데이터베이스, 여러명의 유저가 동시에 호스팅 될 수 있기 때문에 데이터베이스도 생성해주어야 한다. 89 | 90 | psql -h localhost -U postgres -c "CREATE DATABASE webapp" 91 | 92 | `webapp` 이라는 이름을 가진 데이터베이스를 생성했지만, 다른 이름으로 해도 무방하다. 그런 다음, 파이썬 REPL 을 띄워서 다음의 코드를 실행시키면 필요한 테이블이 생성된다. 93 | 94 | ```python 95 | from webapp import app, db 96 | 97 | with app.app_context(): 98 | db.create_all() 99 | ``` 100 | 101 | 마지막으로 `webapp`이 기존의 동작들을 잘 수행하는지 확인해본다. 102 | 103 | - 포스트 만들기 104 | - 포스트 읽어오기 105 | - 포스트 업데이트 106 | - 포스트 삭제 107 | 108 | ### 유용한 도구들 109 | 110 | 이 글에서는 PostgreSQL에 접속하기 위한 도구로 명령창 도구인 `psql`를 사용하지만, 명령창 사용이 익숙하지 않다면 GUI 도구를 이용해도 좋다. 111 | 112 | - https://dbeaver.io/ 113 | - https://www.pgadmin.org/ 114 | 115 | 116 | `GET` 요청을 제외한 다른 형식의 요청들은 웹브라우저를 이용해서 테스트하기 쉽지 않기 때문에 다음의 도구 중 하나를 골라 사용하는 것을 권장한다. 117 | 118 | - https://www.getpostman.com/ 119 | - https://curl.haxx.se/ 120 | 121 | 웹브라우저 확장 기능도 있으니 참고하면 좋다. (직접 테스트 해보지는 못했다.) 122 | 123 | - https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo 124 | - https://addons.mozilla.org/en-US/firefox/addon/restclient/ 125 | 126 | [transporter]: https://github.com/suminb/transporter -------------------------------------------------------------------------------- /2018fall/lecture1.md: -------------------------------------------------------------------------------- 1 | # Lecture 1 2 | 3 | 세상은 [멱함수(power law)](https://en.wikipedia.org/wiki/Power_law)로 설명할 수 있는 것들이 많습니다. [파레토 법칙](https://en.wikipedia.org/wiki/Pareto_principle)이라고 설명하는 현상도 비슷한 맥락입니다. 20%의 인구가 80%의 부를 점유하고 있다거나, 80%의 매출이 20%의 고객으로부터 나온다는 이야기는 여러번 들어보셨을겁니다. 그뿐만이 아닙니다. 세상에는 수많은 과학자들이 존재하지만, 학계에 이름을 남기는 사람들은 소수에 불과합니다. 대중들에게 알려지는 사람은 그것보다 훨씬 극소수입니다. 사전에 존재하는 수많은 단어 중 우리가 일상 생활에서 사용하는 단어의 수는 제한적입니다. 특정 인구수를 가지는 도시들의 숫자는 인구수의 거듭제곱에 반비례하여 나타납니다. 4 | 5 | 6 | 7 | 이번 코딩 워크샵의 목표는 여러분에게 파이썬의 심오한 세계 중 20%를 소개함으로써 여러분이 꿈꾸는 일의 80%를 이룰 수 있도록 도와드리는 것입니다. 그런 의미에서 이 저장소에 올릴 강의 노트도 제가 실제 강의에서 이야기 할 내용의 20%만 올려놓도록 하겠습니다. 8 | 9 | ## 파이썬 코드 실행하기 10 | 11 | 파이썬 코드를 실행하는 방법에는 크게 두 가지가 있습니다. `python (파일 이름).py` 처럼 파이썬 코드 파일을 실행시키는 방법과 REPL에 코드를 한줄씩 실행하는 방법이 있습니다. 물론 [Flask의 디버거](http://werkzeug.pocoo.org/docs/0.14/debug/) 또는 [Jupyter Notebook](http://jupyter.org/)과 같은 다른 실행환경도 존재하지만, 표준 환경의 연장선이기 때문에 따로 구분하지는 않겠습니다. 12 | 13 | ### Python Read–Eval–Print Loop (REPL) 14 | 15 | `python` 명령어에 파일 이름 등 별다른 인자를 주지 않고 실행하면 다음과 같이 REPL이 실행됩니다. 16 | 17 | Python 3.7.0 (default, Jun 29 2018, 20:13:13) 18 | [Clang 9.1.0 (clang-902.0.39.2)] on darwin 19 | Type "help", "copyright", "credits" or "license" for more information. 20 | >>> 21 | 22 | 여기서 파이썬의 모든 기능을 사용할 수 있습니다. 아주 간단한 작업을 처리하거나, 익숙하지 않은 라이브러리르 사용할 때 한줄씩 실행 결과를 확인해 가면서 코드를 작성할 때에 유용합니다. 다음과 같이 계산기로 사용하거나, 23 | 24 | >>> 1000 * .425 / 365 * 14 25 | 16.301369863013697 26 | 27 | 점심밥으로 무엇을 먹을 것인지 골라주는 코드를 작성해볼 수도 있습니다. 28 | 29 | >>> import random 30 | >>> random.choice(['페퍼로니 피자', '빅맥', '한방삼계탕', '똠양궁', '자루소바']) 31 | 페퍼로니 피자 32 | 33 | `_`는 바로 직전 결과 값을 나타냅니다. 34 | 35 | >>> 3 * 5 36 | 15 37 | >>> _ - 4 38 | 11 39 | 40 | `dir()` 함수를 호출하여 특정 모듈이나 클래스에 어떤 멤버들이 있는지 알아볼 수 있습니다. 41 | 42 | >>> dir(random) 43 | ['BPF', 'LOG4', 'NV_MAGICCONST', 'RECIP_BPF', 'Random', 'SG_MAGICCONST', 'SystemRandom', 'TWOPI', '_BuiltinMethodType', '_MethodType', '_Sequence', '_Set', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_acos', '_bisect', '_ceil', '_cos', '_e', '_exp', '_inst', '_itertools', '_log', '_os', '_pi', '_random', '_sha512', '_sin', '_sqrt', '_test', '_test_generator', '_urandom', '_warn', 'betavariate', 'choice', 'choices', 'expovariate', 'gammavariate', 'gauss', 'getrandbits', 'getstate', 'lognormvariate', 'normalvariate', 'paretovariate', 'randint', 'random', 'randrange', 'sample', 'seed', 'setstate', 'shuffle', 'triangular', 'uniform', 'vonmisesvariate', 'weibullvariate'] 44 | 45 | `type()` 함수도 유용하게 사용할 수 있습니다. 46 | 47 | >>> type(42) 48 | 49 | >>> type(42.0) 50 | 51 | >>> type('42') 52 | 53 | 54 | `help()` 함수는 파이썬 docstring의 내용을 보여줍니다. 55 | 56 | >>> help(random.randint) 57 | Help on method randint in module random: 58 | 59 | randint(a, b) method of random.Random instance 60 | Return random integer in range [a, b], including both end points. 61 | 62 | 종료하는 방법을 몰라서 당황할 수도 있는데, `exit()` 함수를 실행해서 종료하거나, 63 | 64 | >>> exit() 65 | 66 | 혹은, `ctrl + D` 키를 눌러서 [`EOF`](https://en.wikipedia.org/wiki/End-of-file)를 표준 입력으로 보냄으로써 인터프리터를 종료할 수도 있습니다. 67 | 68 | ### 파이썬 코드 파일 실행 69 | 70 | 매번 같은 코드를 REPL에 입력하는 것은 고통스러운 일이기 때문에 많은 경우 실행할 코드를 `.py` 파일에 저장해놓습니다. 예를 들어서, 파일 이름이 `test.py` 라고 가정할 때 다음과 같이 실행할 수 있습니다. 71 | 72 | python test.py 73 | 74 | ## 파이썬 코드 구성 75 | 76 | ### Entry point 77 | 78 | C/C++, Java에는 `main()` 함수가 진입점이지만, 파이썬에는 그런 명시적인 진입점이 존재하지 않습니다. `python test.py` 와 같이 실행을 할 경우 `test.py`의 내용이 실행됩니다. 79 | 80 | `test1.py`: 81 | ```python 82 | import os 83 | 84 | print(os.path.abspath(__file__)) 85 | ``` 86 | 87 | 실행을 하게 되면 다음과 같이 `test1.py` 스크립트의 절대경로가 출력됩니다. 88 | 89 | ``` 90 | $ python test1.py 91 | /home/.../sbcw/fall2018/test1.py 92 | ``` 93 | 94 | NOTE: 참고로 REPL에서 같은 코드를 실행할 경우 `__file__`이 정의되지 않았기 때문에 오류가 발생합니다. 95 | 96 | ``` 97 | >>> os.path.abspath(__file__) 98 | Traceback (most recent call last): 99 | File "", line 1, in 100 | NameError: name '__file__' is not defined 101 | ``` 102 | 103 | 파이썬 코드의 진입점은 코드를 어떻게 실행하는지에 따라 달라질 수 있습니다. 104 | 105 | `test2.py`: 106 | ```python 107 | import os 108 | 109 | print(__name__) 110 | ``` 111 | 112 | 다음과 같이 파이썬 인터프리터를 통해서 실행하게 되면 `__name__`의 값이 `__main__`이 됩니다. 113 | 114 | ``` 115 | $ python test2.py 116 | __main__ 117 | ``` 118 | 119 | 하지만 다른 코드에서 `test2.py`를 임포트(import) 하게 되면 `__name__`은 해당 모듈의 이름이 됩니다. 120 | 121 | ``` 122 | >>> import test2 123 | test2 124 | ``` 125 | 126 | 이러한 차이점을 이용해서 다른 모듈에서 임포트 되었을 때에는 실행하지 않고, 파이썬 인터프리터를 통해서 실행했을 때에만 수행되는 코드를 작성할 수도 있습니다. 127 | 128 | ```python 129 | if __name__ == '__main__': 130 | main() 131 | ``` 132 | 133 | ### Modules 134 | 135 | 파이썬의 가장 큰 특징과 장점 중 하나는 풍부한 기본 라이브러리 이외에도 수많은 3rd-party 라이브러리가 있다는 점입니다. 어떤 일을 하고자 할 때 직접 만들기보다는 구글 검색 한번이면 필요한 기능을 찾을 수 있을 가능성이 매우 높습니다. 136 | 137 | > There is ~~an app~~ a library for that. 138 | 139 | ``` 140 | parent/ 141 | __init__.py 142 | one/ 143 | __init__.py 144 | two/ 145 | __init__.py 146 | three/ 147 | __init__.py 148 | ``` 149 | 150 | Importing `parent.one` will implicitly execute `parent/__init__.py` and `parent/one/__init__.py`. Subsequent imports of `parent.two` or `parent.three` will execute `parent/two/__init__.py` and `parent/three/__init__.py` respectively. 151 | 152 | - `import` statement 153 | - `__import__()` function 154 | - `importlib.import_module()` 155 | 156 | Regular package: `module.py` or `module/__init__.py` 157 | 158 | Refer [this](https://docs.python.org/3/reference/import.html) for more details. 159 | 160 | ### `import` statements 161 | 162 | 스타일 1: 163 | 164 | ```python 165 | import os 166 | ``` 167 | 168 | 스타일 2: 169 | 170 | ```python 171 | from datetime import time 172 | ``` 173 | 174 | `import`문은 한 줄에 하나씩, 알파벳 순서대로 기술합니다. 175 | 176 | Yes: 177 | ```python 178 | import os 179 | import sys 180 | ``` 181 | 182 | No: 183 | ```python 184 | import sys, os 185 | ``` 186 | 187 | #### `import` orders 188 | 189 | 1. Standard library imports. 190 | 2. Related third party imports. 191 | 3. Local application/library specific imports. 192 | 193 | ```python 194 | import os 195 | import sys 196 | 197 | from flask import Flask 198 | import requests 199 | 200 | from app import utils 201 | ``` 202 | 203 | #### `import` paths 204 | 205 | 1. Current module 206 | 2. `site-packages` of the current environment 207 | 3. Global environment 208 | 209 | https://www.python.org/dev/peps/pep-0008/#imports 210 | 211 | ### White-space matters 212 | 213 | 파이썬에는 코드 블럭을 구분하는 장치가 공백 문자열입니다. `{}`를 사용하는 C/C++, Java 등의 언어와는 다른 점입니다. [PEP8](https://www.python.org/dev/peps/pep-0008/#indentation)에 따르면 코드 블럭을 구분하기 위해서 4개의 공백 문자열을 사용합니다. 214 | 215 | ```python 216 | def withdraw(amount, balance): 217 | if amount > balance: 218 | # withdraw 219 | else: 220 | raise Exception('Insufficient balance') 221 | ``` 222 | 223 | 224 | ## Primitive Types 225 | 226 | 파이썬에는 `int`, `long`, `float`, `bool`, `str` 등의 기본 타입이 있습니다. 이 부분에 대해서는 파이썬의 타입 시스템에 대해서 다룰 때 더 자세하게 설명할 예정입니다. 227 | 228 | ## Collections 229 | 230 | - `list` 231 | - `tuple` 232 | - `set` 233 | - `frozenset` 234 | - `dict` 235 | 236 | Q: `frozendict` 타입은 왜 없을까? 237 | 238 | ### List Indexing 239 | 240 | xs = [1, 2, 3, 4, 5, 6, 7] 241 | 242 | Accessing indivdual elements: 243 | 244 | >>> xs[0] 245 | 1 246 | >>> xs[1] 247 | 2 248 | >>> xs[-1] 249 | 7 250 | 251 | Slicing: 252 | 253 | >>> xs[2:4] 254 | [3, 4] 255 | >>> xs[0:3] 256 | [1, 2, 3] 257 | >>> xs[0:-1] 258 | [1, 2, 3, 4, 5, 6] 259 | 260 | Implicit slicing: 261 | 262 | >>> xs[3:] 263 | [4, 5, 6, 7] 264 | >>> xs[:2] 265 | [3, 4, 5, 6, 7] 266 | 267 | Stepping: 268 | 269 | >>> xs[0:5:2] 270 | [1, 3, 5] 271 | 272 | List concat: 273 | 274 | >>> [1, 2, 3] + [4, 5] 275 | [1, 2, 3, 4, 5] 276 | 277 | Q: Explain how this works: 278 | 279 | >>> xs[::-1] 280 | [7, 6, 5, 4, 3, 2, 1] 281 | 282 | Q: Why wouldn't it work for `set`s? 283 | 284 | >>> xs = set([1, 2, 3, 4, 5, 6, 7]) 285 | >>> xs[0] 286 | Traceback (most recent call last): 287 | File "", line 1, in 288 | TypeError: 'set' object does not support indexing 289 | 290 | ## Control Statements 291 | 292 | ### `if` statement 293 | 294 | ```python 295 | if expr1: 296 | # do something 297 | pass 298 | elif expr2: 299 | # do something 300 | pass 301 | else: 302 | # do something 303 | pass 304 | ``` 305 | 306 | ```python 307 | y = 'pass' if x else 'fail' 308 | ``` 309 | 310 | #### Explicit 311 | 312 | ```python 313 | if x is not None: 314 | pass 315 | 316 | if x != '': 317 | pass 318 | 319 | if len(x) > 0: 320 | pass 321 | 322 | if x != []: 323 | pass 324 | 325 | if x != {}: 326 | pass 327 | ``` 328 | 329 | #### Implicit 330 | 331 | ```python 332 | if x: 333 | pass 334 | ``` 335 | 336 | ### `for` loop 337 | 338 | ```python 339 | for var in iterable: 340 | # do something 341 | pass 342 | ``` 343 | 344 | >>> for i in range(5): 345 | ... print(i) 346 | ... 347 | 0 348 | 1 349 | 2 350 | 3 351 | 4 352 | 353 | `str`s are essentially `list`s: 354 | 355 | >>> for c in 'test': 356 | ... print(c) 357 | ... 358 | t 359 | e 360 | s 361 | t 362 | 363 | ### `while` loop 364 | 365 | ```python 366 | while expr: 367 | # do something 368 | pass 369 | ``` 370 | 371 | ### Other Control Statements 372 | 373 | - `try`/`except` statements 374 | - `with` statement 375 | - `switch` 는 없습니다. 하지만 만들 수는 있습니다. https://drive.google.com/file/d/1y9oBuTEdKYg3aphWO-R1u-15_1K2e29x/view 376 | 377 | 이건 다음 시간에 적절한 예제를 가지고 설명하도록 합시다. 378 | 379 | ## List Comprehension 380 | 381 | >>> xs = [1, 2, 3, 4, 5, 6, 7] 382 | >>> [x for x in xs] 383 | [1, 2, 3, 4, 5, 6, 7] 384 | 385 | ### Expressions 386 | 387 | >>> [x + 1 for x in xs] 388 | [2, 3, 4, 5, 6, 7, 8] 389 | 390 | ### Unpacking 391 | 392 | >>> xs = [1, 2, 3, 4] 393 | >>> ys = [1, 4, 9, 16] 394 | >>> [(x + y) for x, y in zip(xs, ys)] 395 | [2, 6, 12, 20] 396 | 397 | zip? https://docs.python.org/3/library/functions.html#zip 398 | 399 | ### Nested list comprehension 400 | 401 | >>> [(x, y) for x in xs for y in ys] 402 | [(1, 1), (1, 4), (1, 9), (1, 16), (2, 1), (2, 4), (2, 9), (2, 16), (3, 1), (3, 4), (3, 9), (3, 16), (4, 1), (4, 4), (4, 9), (4, 16)] 403 | 404 | https://www.python.org/dev/peps/pep-0202/ 405 | 406 | ## Dict Comprehension 407 | 408 | ```python 409 | items = ['apple', 'avocado', 'mango', 'orange'] 410 | quantities = [300, 1280, 600, 500] 411 | ``` 412 | 413 | >>> inventory = {k: v for k, v in zip(items, quantities)} 414 | >>> inventory 415 | {'apple': 300, 'avocado': 1280, 'mango': 600, 'orange': 500} 416 | 417 | >>> inventory.items() 418 | dict_items([('apple', 300), ('avocado', 1280), ('mango', 600), ('orange', 500)]) 419 | 420 | ## Generators 421 | 422 | Generator functions allow you to declare a function that behaves like an iterator. 423 | 424 | - Iterator 를 반환합니다. 425 | - 느슨하게 평가(lazy evalution) 됩니다. 426 | - Iterator 를 끝까지 소모하면 더이상 사용할 수 없습니다. 427 | 428 | ```python 429 | def generator(): 430 | yield 1 431 | yield 2 432 | yield 3 433 | ``` 434 | 435 | `next()` 함수를 호출하면 제네레이터가 생성한 원소를 하나씩 꺼내올 수 있습니다. 436 | 437 | g = generator() 438 | >>> next(g) 439 | 1 440 | >>> next(g) 441 | 2 442 | >>> next(g) 443 | 3 444 | >>> next(g) 445 | Traceback (most recent call last): 446 | File "", line 1, in 447 | StopIteration 448 | 449 | 반복문이나 list comprehension 과 함께 사용할 수 있습니다. 450 | 451 | >>> for x in generator(): 452 | ... print(x) 453 | ... 454 | 1 455 | 2 456 | 3 457 | 458 | >>> [x for x in generator()] 459 | [1, 2, 3] 460 | 461 | >>> list(generator()) 462 | [1, 3, 3] 463 | 464 | ```python 465 | def infinite_list(): 466 | v = 0 467 | while True: 468 | yield v 469 | v += 1 470 | ``` 471 | 472 | 느슨하게 평가 된다고 했으니 무한대 길이를 가지는 리스트를 만들어봅시다. 473 | 474 | >>> inf = infinite_list() 475 | 476 | >>> inf 477 | 478 | 479 | >>> [next(inf) for _ in range(5)] 480 | [0, 1, 2, 3, 4] 481 | 482 | Iterator 가 소진되면 더이상 사용할 수 없습니다. 483 | 484 | >>> g = generator() 485 | >>> [x for x in g] 486 | [1, 2, 3] 487 | >>> [x for x in g] 488 | [] 489 | 490 | Q. Why would you want to use generators? 491 | 492 | ## Functions 493 | 494 | - Positional arguments 495 | - Keyword arguments 496 | - Arbitrary argument list 497 | - Arbitrary keyword argument dictionary 498 | 499 | ## Taste of Real World 500 | 501 | (판사님, 이 코드는 고양이가 만들었습니다.) 502 | 503 | ## 만약 시간이 허락한다면 504 | 505 | - Coroutine: https://docs.python.org/3/library/asyncio-task.html 506 | - Integer division vs. floating point division 507 | - break, continue -------------------------------------------------------------------------------- /2018fall/lecture2.md: -------------------------------------------------------------------------------- 1 | # Lecture 2 2 | 3 | 이번 강의부터는 낭비되는 바이트로 인한 통신장비의 과부하를 방지하고 저장장치에 사용되는 반도체 사용량을 줄임으로써 탄소배출량(carbon footprint)을 저감하기 위해 [하십시오체](https://ko.dict.naver.com/detail.nhn?docid=44848960) 대신 [해체](https://ko.dict.naver.com/detail.nhn?docid=41959900)를 사용하기로 한다. 4 | 5 | ## EAFP 6 | 7 | (NOTE: 수강생이 소프트웨어 엔지니어라서 이 이야기를 먼저 할 수 있다. 클래스를 모르는 비개발자를 대상으로 할 때에는 이 섹션을 뒤로 보내야 할 것 같다.) 8 | 9 | Q. 세입자와 파이썬 프로그래머의 공통점? 10 | 11 | Easier to ask for forgiveness (EAFP). 허락을 구하기보단 용서를 구하라. 12 | 13 | ### 허락을 구하는 코드 1 14 | 15 | ```python 16 | if len(xs) > i: 17 | print(xs[i]) 18 | else: 19 | print('i is out of bound', file=sys.stderr) 20 | ``` 21 | 22 | ### 용서를 구하는 코드 1 23 | 24 | ```python 25 | try: 26 | print(xs[i]) 27 | except IndexError: 28 | print('i is out of bound', file=sys.stderr) 29 | ``` 30 | 31 | ### 허락을 구하는 코드 2 32 | 33 | ```python 34 | if isinstance(x, Duck): 35 | x.quack() 36 | else: 37 | x.meow() 38 | ``` 39 | 40 | ### 용서를 구하는 코드 2 41 | 42 | ```python 43 | try: 44 | x.quack() 45 | except AttributeError: 46 | x.neow() 47 | ``` 48 | 49 | ### 허락을 구하는 코드 3 50 | 51 | ```python 52 | if os.access(csv_file, os.R_OK): 53 | with open(csv_file) as fin: 54 | print(fin.read()) 55 | else: 56 | print('File could not be accessed', file=sys.stderr) 57 | ``` 58 | 59 | ### 용서를 구하는 코드 3 60 | 61 | ```python 62 | try: 63 | fin = open(csv_file) 64 | except IOError: 65 | print('File could not be accessed', file=sys.stderr) 66 | else: 67 | with fin: 68 | print(fin.read()) 69 | ``` 70 | 71 | ## Duck Typing 72 | 73 | ```python 74 | cat = Cat() 75 | ``` 76 | 77 | `cat`은 고양이 유전자(`class Cat`)를 물려받아서 고양이인가, 아니면 고양이처럼 행동하기(`Cat.meow()`) 때문에 고양이인가? 78 | 79 | ## Classes 80 | 81 | **클래스**란 무엇인가? 섣불리 한마디로 정의하려고 했다가는 여기저기 박제되어 온갖 욕을 얻어먹을 것이 불보듯 뻔하기 때문에 일단 [파이썬 공식 문서](https://docs.python.org/3/tutorial/classes.html)의 첫 문단을 인용하기로 한다. 82 | 83 | > Classes provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state. Class instances can also have methods (defined by its class) for modifying its state. 84 | 85 | 더 자세하고 재미있는 이야기는 강의시간에 하도록 한다. 86 | 87 | ### Namesapce and Scope 88 | 89 | (TODO: 설명 쓰기) 90 | 91 | > Although scopes are determined statically, they are used dynamically. At any time during execution, there are at least three nested scopes whose namespaces are directly accessible: 92 | > 93 | > * the innermost scope, which is searched first, contains the local names 94 | > * the scopes of any enclosing functions, which are searched starting with the nearest enclosing scope, contains non-local, but also non-global names 95 | > * the next-to-last scope contains the current module’s global names 96 | > * the outermost scope (searched last) is the namespace containing built-in names 97 | 98 | > It is important to realize that scopes are determined textually: the global scope of a function defined in a module is that module’s namespace, no matter from where or by what alias the function is called. On the other hand, the actual search for names is done dynamically, at run time — however, the language definition is evolving towards static name resolution, at “compile” time, so don’t rely on dynamic name resolution! (In fact, local variables are already determined statically.) 99 | 100 | > A special quirk of Python is that – if no global statement is in effect – assignments to names always go into the innermost scope. Assignments do not copy data — they just bind names to objects. The same is true for deletions: the statement del x removes the binding of x from the namespace referenced by the local scope. 101 | 102 | * `global` 103 | * `nonlocal` 104 | 105 | ```python 106 | def scope_test(): 107 | def do_local(): 108 | spam = 'local spam' 109 | 110 | def do_nonlocal(): 111 | nonlocal spam 112 | spam = 'nonlocal spam' 113 | 114 | def do_global(): 115 | global spam 116 | spam = 'global spam' 117 | 118 | spam = 'test spam' 119 | do_local() 120 | print('After local assignment:', spam) 121 | do_nonlocal() 122 | print('After nonlocal assignment:', spam) 123 | do_global() 124 | print('After global assignment:', spam) 125 | 126 | scope_test() 127 | print('In global scope:', spam) 128 | ``` 129 | 130 | https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces 131 | 132 | ### Class Definitions 133 | 134 | 클래스는 다음과 같이 정의할 수 있다. 135 | 136 | ```python 137 | class Cat: 138 | pass 139 | ``` 140 | 141 | `if`, `try`/`except` 등 제어문 안쪽이나 함수 내부에 선언하는 것도 가능하다. 142 | 143 | ```python 144 | try: 145 | from sbcw import Cat 146 | except ImportError: 147 | class Cat: 148 | pass 149 | ``` 150 | 151 | 함수와 마찬가지로 클래스가 정의될 때 클래스를 위한 네임스페이스가 생겨난다. 그리고 클래스를 정의하는 코드 블럭이 끝날 때 클래스 객체가 생성된다. 파이썬에서는 클래스도 객체다. 152 | 153 | ### Legacy Notes 154 | 155 | 사실, 파이썬에서 클래스를 정의하는 방법은 두 가지가 있다. 156 | 157 | #### *Classic* Style 158 | 159 | ```python 160 | class Cat: 161 | pass 162 | ``` 163 | 164 | #### *New* Style 165 | 166 | ```python 167 | class Cat(object): 168 | pass 169 | ``` 170 | 171 | 둘 간의 차이가 있는가? 172 | 173 | - 요약하자면, Python 2.x 버전에서는 차이가 있지만, 3.x에서는 두 스타일이 완전히 동일한 결과를 만들어낸다. 174 | - 더 자세한 내용은 [StackOverflow 페이지](https://stackoverflow.com/questions/4015417/python-class-inherits-object)를 참고. 175 | 176 | #### Python 2.7 177 | 178 | ``` 179 | >>> class Cat: 180 | ... pass 181 | ... 182 | >>> Cat.__bases__ 183 | () 184 | ``` 185 | 186 | ``` 187 | >>> class Cat(object): 188 | ... pass 189 | ... 190 | >>> Cat.__bases__ 191 | (,) 192 | ``` 193 | 194 | #### Python 3.7 195 | 196 | ``` 197 | >>> class Cat: 198 | ... pass 199 | ... 200 | >>> Cat.__bases__ 201 | (,) 202 | ``` 203 | 204 | ``` 205 | >>> class Cat(object): 206 | ... pass 207 | ... 208 | >>> Cat.__bases__ 209 | (,) 210 | ``` 211 | 212 | ### Class Objects 213 | 214 | (TODO: 내용 채워넣기) 215 | 216 | ### Methods 217 | 218 | ```python 219 | class Cat(object): 220 | 221 | def __init__(self, name): 222 | self.name = name 223 | 224 | def greet(self): 225 | return f'Hi there! My name is {self.name}' 226 | 227 | def attack(self): 228 | raise NotImplemented 229 | ``` 230 | 231 | ### 클래스의 비밀(?) 232 | 233 | ```python 234 | tom = Cat('Tom') 235 | tom.greet() 236 | ``` 237 | 238 | ```python 239 | tom = Cat('Tom') 240 | Cat.greet(tom) 241 | ``` 242 | 243 | ### Special Method for String Representation 244 | 245 | ```python 246 | class Cat(object): 247 | 248 | def __init__(self, name): 249 | self.name = name 250 | 251 | def __repr__(self): 252 | return f'Cat-{hash(self):x} ({self.name})' 253 | ``` 254 | 255 | ``` 256 | >>> cat = Cat('May') 257 | >>> cat 258 | Cat-106af147 (May) 259 | ``` 260 | 261 | ### Special Methods for Iterator 262 | 263 | ```python 264 | class Series(object): 265 | 266 | def __init__(self, lower_bound, upper_bound): 267 | self.lower_bound = lower_bound 268 | self.upper_bound = upper_bound 269 | self.current = lower_bound 270 | 271 | def __iter__(self): 272 | return self 273 | 274 | def __next__(self): 275 | if self.current > self.upper_bound: 276 | raise StopIteration 277 | else: 278 | self.current += 1 279 | return self.current - 1 # C/C++처럼 `return self.current++` 표현을 사용할 수 있었다면... 280 | ``` 281 | 282 | ### Special Methods for `with` Statement 283 | 284 | ```python 285 | class Session(object): 286 | 287 | def __enter__(self): 288 | pass 289 | 290 | def __exit__(self, type, value, traceback): 291 | pass 292 | 293 | def execute(self, query): 294 | pass 295 | ``` 296 | 297 | ```python 298 | s = Session() 299 | with s: 300 | s.execute(query) 301 | ``` 302 | 303 | ### More Special Methods 304 | 305 | http://www.diveintopython3.net/special-method-names.html 306 | 307 | 308 | ### Inheritance 309 | 310 | ```python 311 | class Amniota(Tetrapod): 312 | pass 313 | 314 | class Mammal(Amniota): 315 | pass 316 | 317 | class Cat(Mammal): 318 | pass 319 | ``` 320 | 321 | ### Multiple Inheritance 322 | 323 | 324 | 325 | ```python 326 | class Building(Unit): 327 | pass 328 | 329 | class CommandCenter(Building, AirUnit): 330 | pass 331 | ``` 332 | 333 | ### Private Variables 334 | 335 | 파이썬에는 언어 레벨에서 강제할 수 있는 private 변수가 없다. 컨벤션으로만 존재한다. 336 | 337 | > “Private” instance variables that cannot be accessed except from inside an object don’t exist in Python. However, there is a convention that is followed by most Python code: a name prefixed with an underscore (e.g. _spam) should be treated as a non-public part of the API. 338 | 339 | ## Metaclasses 340 | 341 | ``` 342 | >>> class Cat: 343 | ... pass 344 | ... 345 | 346 | >>> cat = Cat() 347 | 348 | >>> type(cat) 349 | 350 | 351 | >>> type(Cat) 352 | 353 | 354 | >>> type(type) 355 | 356 | ``` 357 | 358 | ```python 359 | Cat = type('Cat', (object,), {'name': 'Tom'}) 360 | ``` 361 | 362 | ``` 363 | >>> Cat.name 364 | Tom 365 | ``` 366 | 367 | ```python 368 | class Meta(type): 369 | 370 | def __new__(cls, name, bases, attrs): 371 | return super().__new__(cls, name, bases, attrs) 372 | 373 | 374 | class Cat(metaclass=Meta): 375 | 376 | def __init__(self, name): 377 | self.name = name 378 | ``` 379 | 380 | https://realpython.com/python-metaclasses/ 381 | -------------------------------------------------------------------------------- /2018fall/lecture3.md: -------------------------------------------------------------------------------- 1 | # Lecture 3 2 | 3 | 이번 시간의 목표는 간단한 파이썬 프로젝트를 직접 만들어 보는 것이다. 파이썬의 문법을 이해하는 것도 중요하지만, 실제로 파이썬으로 코딩을 하려고 하면 문법 이외의 부분에서 막히는 경우가 많다. 그 중 하나가 프로젝트 구성이다. 직접 간단한 프로젝트를 구성해봄으로써 프로젝트 디렉토리의 구조를 이해하고, 소프트웨어 프로젝트가 온전하게 작동하는데 필요한 구성 요소들(e.g., 패키지 의존성 관리, 테스트, CI 등)을 어떻게 배치하는지 체험해 보는 것이 주요 내용이다. 4 | 5 | ## Python Project Structure 6 | 7 | - Requirements file (`requirements.txt`) 8 | - README 9 | - License 10 | - Setup script (`setup.py`) 11 | - Documentation 12 | - Test suite 13 | - Continuous integration (CI) 14 | 15 | https://docs.python-guide.org/writing/structure/ 16 | 17 | ### Requirements file 18 | 19 | ``` 20 | flask 21 | flask==1.0.2 22 | flask>=1.0.0 23 | flask>=1.0.0,<=1.0.2 24 | git+https://github.com/pallets/flask.git@master 25 | ``` 26 | 27 | ``` 28 | pip install -r requirements.txt 29 | ``` 30 | 31 | #### `install_requires` vs Requirements Files 32 | 33 | - `requirements.txt` 파일 대신 `setup.py`에 의존성을 명시할 수 있다. 34 | - Semantic versioning 35 | - 하지만 특정 버전으로 고정하는건 일반적으로 좋은 관습이 아니다. 36 | 37 | https://packaging.python.org/discussions/install-requires-vs-requirements/ 38 | 39 | ### Setup Script 40 | 41 | #### `distutils` vs. `setuptools` 42 | 43 | https://stackoverflow.com/questions/25337706/setuptools-vs-distutils-why-is-distutils-still-a-thing 44 | 45 | #### `setup.py` 예제 46 | 47 | ```python 48 | #!/usr/bin/env python 49 | 50 | from distutils.core import setup 51 | from setuptools import find_packages 52 | 53 | import finance 54 | 55 | 56 | def readme(): 57 | try: 58 | with open('README.rst') as f: 59 | return f.read() 60 | except: 61 | return '(Could not read from README.rst)' 62 | 63 | 64 | setup( 65 | name='finance', 66 | version=finance.__version__, 67 | description='Personal Finance Project', 68 | long_description=readme(), 69 | author=finance.__author__, 70 | author_email=finance.__email__, 71 | url='http://github.com/suminb/finance', 72 | license='BSD', 73 | packages=find_packages(), 74 | entry_points={ 75 | 'console_scripts': [ 76 | 'finance = finance.__main__:cli' 77 | ] 78 | }, 79 | ) 80 | ``` 81 | 82 | https://docs.python.org/2/distutils/setupscript.html 83 | 84 | ### Test Suite 85 | 86 | 파이썬에 기본으로 포함된 [`unittest`](https://docs.python.org/3/library/unittest.html) 패키지 대신 [`pytest`](https://docs.pytest.org)를 이용한다. `pytest`는 테스트 코드에서 파이썬의 `assert` 구문을 그대로 이용할 수 있고, 픽스쳐(fixture) 관련 고급 기능들을 제공하고, 쉽게 확장할 수 있는 등 여러가지 유용한 기능들을 제공한다. 87 | 88 | - `tests` 디렉토리 안에 있는 `test_*.py` 파일들 89 | - `test_` 로 시작하는 함수들 90 | 91 | https://docs.pytest.org/en/latest/getting-started.html 92 | 93 | ### Continuous Integration (CI) 94 | 95 | https://docs.travis-ci.com/user/getting-started/ 96 | 97 | ## 간단한 웹 애플리케이션 만들어보기 98 | 99 | 댓글 서비스를 만들어보자. 기초적인 CRUD 작업을 지원하는 웹 애플리케이션이다. 100 | 101 | - Flask 102 | - SQLAlchemy -------------------------------------------------------------------------------- /2018fall/lecture4.md: -------------------------------------------------------------------------------- 1 | # Lecture 4 2 | 3 | 참고: 이 강의 노트의 상당 부분은 https://realpython.com/primer-on-python-decorators/ 페이지를 참고하여 만들었다. 4 | 5 | ## Functions 6 | 7 | - Functions vs. procedures 8 | - Pure functions vs. non-pure functions 9 | - Referential transparency 10 | 11 | ### Functions Are First-Class Objects 12 | 13 | ```python 14 | def square(x): 15 | return x * x 16 | 17 | def cube(x): 18 | return x ** 3 19 | ``` 20 | 21 | ``` 22 | >>> square 23 | 24 | 25 | >>> square.__name__ 26 | 'square' 27 | ``` 28 | 29 | ```python 30 | if exp == 2: 31 | func = square 32 | elif exp == 3: 33 | func = cube 34 | 35 | func(x) 36 | ``` 37 | 38 | ```python 39 | def power(base, exp): 40 | if exp == 2: 41 | return square 42 | elif exp == 3: 43 | return cube 44 | else: 45 | raise NotImplemented 46 | ``` 47 | 48 | ### Inner Functions 49 | 50 | (첫 시간에 간단하게 소개하고 넘어갔듯이) 파이썬에서는 함수 안에 함수를 정의하는 것이 가능하다. 51 | 52 | ```python 53 | def parent(): 54 | print('Printing from the parent() function') 55 | 56 | def first_child(): 57 | print('Printing from the first_child() function') 58 | 59 | def second_child(): 60 | print('Printing from the second_child() function') 61 | 62 | second_child() 63 | first_child() 64 | ``` 65 | 66 | 내부 함수가 정의된 순서와는 관계 없이 호출한 순서대로 결과가 나오는 것을 확인할 수 있다. 67 | 68 | ``` 69 | >>> parent() 70 | Printing from the parent() function 71 | Printing from the second_child() function 72 | Printing from the first_child() function 73 | ``` 74 | 75 | 글로벌 스코프에서 내부 함수의 이름만 가지고 접근하는건 불가능하다. 76 | 77 | ``` 78 | >>> first_child() 79 | Traceback (most recent call last): 80 | File "", line 1, in 81 | NameError: name 'first_child' is not defined 82 | ``` 83 | 84 | ### Returning Functions From Functions 85 | 86 | ```python 87 | def parent(): 88 | print('Printing from the parent() function') 89 | 90 | def first_child(): 91 | print('Printing from the first_child() function') 92 | 93 | return first_child 94 | ``` 95 | 96 | ``` 97 | >>> first_child = parent() 98 | Printing from the parent() function 99 | 100 | >>> first_child() 101 | Printing from the first_child() function 102 | ``` 103 | 104 | ## Simple Decorators 105 | 106 | ```python 107 | def decorator(func): 108 | def wrapper(): 109 | print('Something is happening before the function is called.') 110 | func() 111 | print('Something is happening after the function is called.') 112 | return wrapper 113 | 114 | def meow(): 115 | print('Meow!') 116 | 117 | meow = decorator(meow) 118 | ``` 119 | 120 | ``` 121 | >>> meow() 122 | Something is happening before the function is called. 123 | Meow! 124 | Something is happening after the function is called. 125 | ``` 126 | 127 | 간단하게 얘기해서: 데코레이터는 함수를 감싸서 함수의 동작을 변경할 수 있는 장치이다. 128 | 129 | ### Syntactic Sugar 130 | 131 | `meow = decorator(meow)` 와 같이 수동으로 래핑하는 것은 아름답지 못하다. 함수를 래핑하기 용이하도록 [pie syntax](https://www.python.org/dev/peps/pep-0318/#background) 라고 불리는 문법 요소를 제공한다. 132 | 133 | ```python 134 | def decorator(func): 135 | def wrapper(): 136 | print('Something is happening before the function is called.') 137 | func() 138 | print('Something is happening after the function is called.') 139 | return wrapper 140 | 141 | @decorator 142 | def meow(): 143 | print('Meow!') 144 | ``` 145 | 146 | ``` 147 | >>> meow() 148 | Something is happening before the function is called. 149 | Meow! 150 | Something is happening after the function is called. 151 | ``` 152 | 153 | ### Reusing Decorators 154 | 155 | 다음과 같은 데코레이터를 `utils.py` 에 정의해놓았다고 가정한다. 156 | 157 | ```python 158 | def repeat(func): 159 | def wrapper(): 160 | func() 161 | func() 162 | return wrapper 163 | ``` 164 | 165 | ```python 166 | from utils import repeat 167 | 168 | @repeat 169 | def meow(): 170 | print('Meow!') 171 | ``` 172 | 173 | ``` 174 | >>> meow() 175 | Meow! 176 | Meow! 177 | ``` 178 | 179 | ### Decorating Functions With Arguments 180 | 181 | 우리가 지금까지 사용했던 `meow()` 함수는 인자를 하나도 받지 않았지만, 인자를 받는 함수에 데코레이터를 적용한다면 문제가 발생한다. 182 | 183 | ```python 184 | @repeat 185 | def meow(name): 186 | print(f"Meow! I'm {name}") 187 | ``` 188 | 189 | ``` 190 | >>> meow('Tom') 191 | Traceback (most recent call last): 192 | File "", line 1, in 193 | TypeError: wrapper() takes 0 positional arguments but 1 was given 194 | ``` 195 | 196 | 데코레이터에서 인자를 하나도 전달해주지 않았기 때문인데, 다음과 같이 수정해서 해결할 수 있다. 197 | 198 | ```python 199 | def repeat(func): 200 | def wrapper(*args, **kwargs): 201 | func(*args, **kwargs) 202 | func(*args, **kwargs) 203 | return wrapper 204 | ``` 205 | 206 | ``` 207 | >>> meow('Tom') 208 | Meow! I'm Tom 209 | Meow! I'm Tom 210 | ``` 211 | 212 | ### Returning Values From Decorated Functions 213 | 214 | 우리가 지금까지 정의했던 `meow()` 함수는 아무 값도 반환하지 않는다. `print()` 함수로 문자열을 출력하는 대신 문자열을 반환하고 싶다면 어떻게 해야 할까. 215 | 216 | ```python 217 | @repeat 218 | def meow(name): 219 | return f"Meow! I'm {name}" 220 | ``` 221 | 222 | 이렇게 할 경우 다음과 같이 `None` 값을 반환하는 함수가 만들어진다. (파이썬에서는 아무 값도 반환하지 않는 함수를 정의하면 기본값으로 `None`을 반환한다.) 223 | 224 | ``` 225 | >>> meow('Tom') is None 226 | True 227 | ``` 228 | 229 | 위에서 데코레이터에서 아무런 값도 반환하지 않도록 정의했기 때문이다. 다음과 같이 수정해서 해결할 수 있다. 230 | 231 | ```python 232 | def repeat(func): 233 | def wrapper(*args, **kwargs): 234 | return func(*args, **kwargs), func(*args, **kwargs) 235 | return wrapper 236 | ``` 237 | 238 | ``` 239 | >>> meow('Tom') 240 | ("Meow! I'm Tom", "Meow! I'm Tom") 241 | ``` 242 | 243 | ### Introspection Issues 244 | 245 | NOTE: Introspection is the ability of an object to know about its own attributes at runtime. 246 | 247 | 일반적으로 함수의 속성은 런타임에 명확하게 알 수 있다. 248 | 249 | ``` 250 | >>> print 251 | 252 | 253 | >>> print.__name__ 254 | 'print' 255 | 256 | >>> help(print) 257 | Help on built-in function print in module builtins: 258 | 259 | print(...) 260 | 261 | ``` 262 | 263 | 하지만 데코레이터로 감싼 함수의 경우 다음과 같이 정체성 혼란을 야기할 수 있다. 264 | 265 | ``` 266 | >>> meow 267 | .wrapper at 0x106ae7378> 268 | 269 | >>> meow.__name__ 270 | 'wrapper' 271 | 272 | >>> help(meow) 273 | Help on function wrapper in module __main__: 274 | 275 | wrapper(*args, **kwargs) 276 | ``` 277 | 278 | 위와 같은 정체성 문제는 [`@functools.wraps`](https://docs.python.org/library/functools.html#functools.wraps) 데코레이터를 이용해서 해결할 수 있다. 279 | 280 | ```python 281 | import functools 282 | 283 | def repeat(func): 284 | @functools.wraps(func) 285 | def wrapper(*args, **kwargs): 286 | return func(*args, **kwargs), func(*args, **kwargs) 287 | return wrapper 288 | ``` 289 | 290 | ``` 291 | >>> meow 292 | 293 | 294 | >>> meow.__name__ 295 | 'meow' 296 | 297 | >> help(meow) 298 | Help on function meow in module __main__: 299 | 300 | meow(name) 301 | ``` 302 | 303 | ### Taking Parameters 304 | 305 | ```python 306 | @repeat(3) 307 | def meow(name): 308 | return f"Meow! I'm {name}" 309 | ``` 310 | 311 | 위와 같이 데코레이터에서 인자를 받고 싶으면 어떻게 해야 할까. 312 | 313 | ```python 314 | import functools 315 | 316 | def repeat(n=2): 317 | def decorator(func): 318 | @functools.wraps(func) 319 | def wrapper(*args, **kwargs): 320 | return [func(*args, **kwargs) for _ in range(n)] 321 | return wrapper 322 | return decorator 323 | ``` 324 | 325 | ``` 326 | >>> meow('Tom') 327 | ["Meow! I'm Tom", "Meow! I'm Tom", "Meow! I'm Tom"] 328 | ``` 329 | 330 | 331 | ## Real World Examples 332 | 333 | - 334 | - 335 | 336 | ## General Template 337 | 338 | ```python 339 | import functools 340 | 341 | def decorator(func): 342 | @functools.wraps(func) 343 | def wrapper(*args, **kwargs): 344 | # Do something before 345 | value = func(*args, **kwargs) 346 | # Do something after 347 | return value 348 | return wrapper 349 | ``` -------------------------------------------------------------------------------- /2018fall/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | -------------------------------------------------------------------------------- /2018fall/solutions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suminb/sbcw/16aa198f52a554f2e94a27a0b669814811aa2633/2018fall/solutions/__init__.py -------------------------------------------------------------------------------- /2018fall/solutions/homework1_jungheelee.py: -------------------------------------------------------------------------------- 1 | # Problem 1.1 2 | def square(xs): 3 | return [x * x for x in xs] 4 | 5 | 6 | # Problem 1.2 7 | def even(xs): 8 | return [x for x in xs if x % 2 == 0] 9 | 10 | 11 | # Problem 2 12 | def convert(text): 13 | return '; '.join([x.strip() + '@gmail.com' for x in text.split(',')]) 14 | 15 | 16 | # Problem 3.1 17 | def intersection(xs, ys): 18 | return [x for x in xs if x in ys] 19 | 20 | 21 | # Problem 3.2 22 | def union(xs, ys): 23 | return xs + [y for y in ys if y not in xs] 24 | 25 | 26 | # Problem 4 27 | def net_asset_value(inventory, prices): 28 | return sum(prices[item] * quantity for item, quantity in inventory.items()) 29 | 30 | 31 | # Problem 5 32 | def invert(index): 33 | return {val: it for it, val in zip(index.keys(), index.values())} 34 | 35 | 36 | # Homework 6.1 (Bonus) 37 | def zip(*args): 38 | # 여기에 여러분의 코드를 작성하세요 39 | pass 40 | 41 | 42 | # Homework 6.2 (Bonus) 43 | def unzip(iterable): 44 | # 여기에 여러분의 코드를 작성하세요 45 | pass 46 | -------------------------------------------------------------------------------- /2018fall/solutions/homework1_suminb.py: -------------------------------------------------------------------------------- 1 | def square(xs): 2 | return [x * x for x in xs] 3 | 4 | 5 | def even(xs): 6 | return [x for x in xs if x % 2 == 0] 7 | 8 | 9 | def convert(text): 10 | return '; '.join([x.strip() + '@gmail.com' for x in text.split(',')]) 11 | 12 | 13 | def intersection(xs, ys): 14 | return [x for x in xs if x in ys] 15 | 16 | 17 | def union(xs, ys): 18 | return xs + [y for y in ys if y not in xs] 19 | 20 | 21 | def net_asset_value(inventory, prices): 22 | return sum(prices[item] * quantity for item, quantity in inventory.items()) 23 | 24 | 25 | def invert(index): 26 | return {v: k for k, v in index.items()} 27 | 28 | 29 | def zip(*iterables): 30 | return [tuple(x[j] for x in iterables) for j in range(min(len(i) for i in iterables))] 31 | 32 | 33 | def unzip(iterable): 34 | return zip(*iterable) 35 | -------------------------------------------------------------------------------- /2018fall/solutions/homework1_ysunmi0427.py: -------------------------------------------------------------------------------- 1 | # Problem 1 2 | def square(xs): 3 | return [x ** 2 for x in xs] 4 | 5 | def even(xs): 6 | return [x for x in xs if x % 2 == 0] 7 | 8 | # Problem 2 9 | def convert(text): 10 | # return text.replace(',', '@gmail.com;') + '@gmail.com' 11 | return '; '.join([x + '@gmail.com' for x in text.split(', ')]) 12 | 13 | # Problem 3 14 | def intersection(xs, ys): 15 | return [x for x in xs if x in ys] 16 | 17 | def union(xs, ys): 18 | return [x for x in xs if x not in ys] + ys 19 | 20 | # Problem 4 21 | def net_asset_value(inventory, prices): 22 | return sum([inventory[i] * prices[i] for i in inventory.keys()]) 23 | 24 | # Problem 5 25 | def invert(index): 26 | # return {index[k]:k for k in index.keys()} 27 | return {v: k for k, v in index.items()} # items()는 (key, value) 튜플을 줌 28 | 29 | # Problem 6 30 | def zip(*args): 31 | return [tuple([a[i] for a in args]) for i in range(min(len(l) for l in args))] 32 | 33 | def unzip(iterables): 34 | return [tuple([i[l] for i in iterables]) for l in range(min(len(i) for i in iterables))] 35 | -------------------------------------------------------------------------------- /2018fall/solutions/homework2_ysunmi0427.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | # Problem 1 4 | # Problem 1.1 5 | # class DictWrapper(object): 6 | # def __init__(self, dict_): 7 | # self.d = dict_ 8 | 9 | # def __getattr__(self, key): 10 | # return self.d[key] 11 | 12 | # def __getitem__(self, key): 13 | # return self.d[key] 14 | 15 | # Simpler way 16 | class DictWrapper(dict): 17 | # Problem 1.1 18 | def __getattr__(self, key): 19 | # return self.__getitem__(key) 20 | return self[key] 21 | 22 | # Problem 1.2 23 | def __setattr__(self, key, value): 24 | self[key] = value 25 | 26 | # Problem 1.3 27 | def injective(self): 28 | return len(self.keys()) == len(set(self.values())) 29 | 30 | # Problem 1.4 31 | def invert(self): 32 | if self.injective(): 33 | return DictWrapper({v:k for k, v in self.items()}) 34 | else: 35 | raise ValueError('Dictionary is not injective, hence cannot be inverted') 36 | 37 | # Problem 2 38 | # class Range(object): 39 | # # Problem 2.1 40 | # def __init__(self, start, stop, step=1): 41 | # self.start = start 42 | # self.stop = stop 43 | # self.step = step 44 | # self.current = start 45 | # if step == 0: 46 | # raise ValueError('`step` cannot be zero') 47 | # def __iter__(self): 48 | # return self 49 | # def __next__(self): 50 | # # Assumption of this approach: start is always smaller than stop 51 | # if self.current >= self.stop: 52 | # raise StopIteration 53 | # else: 54 | # self.current += self.step 55 | # return self.current - self.step 56 | # # Problem 2.2 57 | # def __reversed__(self): 58 | # return reversed(list(Range(self.start, self.stop, self.step))) 59 | 60 | # Problem 2 61 | class Range(object): 62 | # Problem 2.1 63 | def __init__(self, start, stop, step=1): 64 | self.start = start 65 | self.stop = stop 66 | self.step = step 67 | self.current = start 68 | if step == 0: 69 | raise ValueError('`step` cannot be zero') 70 | def __iter__(self): 71 | return self 72 | def __next__(self): 73 | # Generalized version 74 | if (self.step > 0 and self.current >= self.stop) or (self.step < 0 and self.current <= self.stop): 75 | raise StopIteration 76 | else: 77 | self.current += self.step 78 | return self.current - self.step 79 | 80 | # Problem 2.2 81 | def __reversed__(self): 82 | # simpler than below but.. lol 83 | # return reversed(list(Range(self.start, self.stop, self.step))) 84 | length = math.ceil((self.stop - self.start) / self.step) 85 | return Range(self.start + self.step * (length-1), self.start-1, -self.step) -------------------------------------------------------------------------------- /2018fall/test_homework1.py: -------------------------------------------------------------------------------- 1 | import math 2 | import os 3 | import random 4 | import re 5 | 6 | import pytest 7 | 8 | 9 | random.seed(0) 10 | solution = None 11 | list_length = (5, 25) 12 | 13 | 14 | def setup_module(module): 15 | username = pytest.config.getoption('username') 16 | solution_module = f'homework1_{username}' 17 | 18 | try: 19 | namespace = __import__(f'solutions.{solution_module}') 20 | except ImportError: 21 | pytest.exit(f'{solution_module}.py does not exist') 22 | else: 23 | global solution 24 | solution = getattr(namespace, solution_module) 25 | 26 | 27 | @pytest.fixture 28 | def random_list(): 29 | def make(min_len, max_len): 30 | return random.sample(range(100), random.randint(min_len, max_len)) 31 | return make 32 | 33 | 34 | @pytest.mark.parametrize('_', range(10)) 35 | def test_problem1_1(_, random_list): 36 | xs = random_list(*list_length) 37 | # NOTE: We decided to call `sqrt()` so that we don't leak the solution for 38 | # this problem 39 | assert all([float(x) == math.sqrt(y) for x, y in zip(xs, solution.square(xs))]) 40 | 41 | 42 | @pytest.mark.parametrize('_', range(10)) 43 | def test_problem1_2(_, random_list): 44 | xs = random_list(*list_length) 45 | assert all([x % 2 == 0 for x in solution.even(xs)]) 46 | 47 | 48 | def test_problem2(): 49 | text = 'alejandro, britney, christina, dennis, emily' 50 | expected = \ 51 | 'alejandro@gmail.com; britney@gmail.com; christina@gmail.com; ' \ 52 | 'dennis@gmail.com; emily@gmail.com' 53 | assert solution.convert(text) == expected 54 | 55 | 56 | @pytest.mark.parametrize('_', range(10)) 57 | def test_problem3_1(_, random_list): 58 | xs = random_list(*list_length) 59 | ys = random_list(*list_length) 60 | assert set(solution.intersection(xs, ys)) == set(xs).intersection(ys) 61 | 62 | 63 | @pytest.mark.parametrize('_', range(10)) 64 | def test_problem3_2(_, random_list): 65 | xs = random_list(*list_length) 66 | ys = random_list(*list_length) 67 | assert set(solution.union(xs, ys)) == set(xs).union(ys) 68 | 69 | 70 | @pytest.mark.parametrize('inventory, prices, nav', [ 71 | ({}, {}, 0), 72 | ( 73 | {'banana': 1000}, 74 | {'banana': 0.90}, 75 | 900 76 | ), 77 | ( 78 | {'avocado': 236, 'apple': 0, 'orange': 172, 'mango': 368}, 79 | {'avocado': 0.99, 'apple': 0.69, 'orange': 0.33, 'mango': 0.79}, 80 | 581.12 81 | ) 82 | ]) 83 | def test_problem4(inventory, prices, nav): 84 | assert solution.net_asset_value(inventory, prices) == nav 85 | 86 | 87 | @pytest.mark.parametrize('index, inverted_index', [ 88 | ({}, {}), 89 | ( 90 | {'a': 1, 'b': 2, 'c': 3}, 91 | {1: 'a', 2: 'b', 3: 'c'}, 92 | ), 93 | ( 94 | {'transparency': 37, 'composibility': 5, 'immutability': 40, 'idempotency': 14}, # noqa 95 | {37: 'transparency', 5: 'composibility', 40: 'immutability', 14: 'idempotency'}, # noqa 96 | ) 97 | ]) 98 | def test_problem5(index, inverted_index): 99 | assert solution.invert(index) == inverted_index 100 | 101 | 102 | @pytest.mark.parametrize('_', range(10)) 103 | def test_problem6_1(_, random_list): 104 | m = random.randint(1, 10) # Number of lists 105 | lists = [random_list(0, 25) for _ in range(m)] 106 | assert list(zip(*lists)) == list(solution.zip(*lists)) 107 | 108 | 109 | @pytest.mark.parametrize('_', range(10)) 110 | def test_problem6_2(_, random_list): 111 | m = random.randint(1, 10) # Number of lists 112 | lists = [random_list(0, 25) for _ in range(m)] 113 | assert list(zip(*lists)) == list(solution.unzip(lists)) 114 | -------------------------------------------------------------------------------- /2018fall/test_homework2.py: -------------------------------------------------------------------------------- 1 | import math 2 | import os 3 | import random 4 | import re 5 | 6 | import pytest 7 | 8 | 9 | random.seed(0) 10 | solution = None 11 | list_length = (5, 25) 12 | 13 | 14 | @pytest.fixture 15 | def random_word(): 16 | def make(min_length=2, max_length=10): 17 | min_, max_ = ord('a'), ord('z') 18 | length = random.randint(min_length, max_length) 19 | return ''.join([chr(random.randint(min_, max_)) for _ in range(length)]) 20 | return make 21 | 22 | 23 | @pytest.fixture 24 | def random_keys(random_word): 25 | def make(size=8): 26 | return [random_word() for _ in range(size)] 27 | return make 28 | 29 | 30 | @pytest.fixture 31 | def random_dict(): 32 | def make(keys): 33 | return {k: random.randint(0, 1000) for k in keys} 34 | return make 35 | 36 | 37 | def setup_module(module): 38 | username = pytest.config.getoption('username') 39 | solution_module = f'homework2_{username}' 40 | 41 | try: 42 | namespace = __import__(f'solutions.{solution_module}') 43 | except ImportError: 44 | pytest.exit(f'{solution_module}.py does not exist') 45 | else: 46 | global solution 47 | solution = getattr(namespace, solution_module) 48 | 49 | 50 | @pytest.mark.parametrize('_', range(8)) 51 | def test_dict_wrapper_1(random_keys, random_dict, _): 52 | keys = random_keys() 53 | d = solution.DictWrapper(random_dict(keys)) 54 | 55 | for key in keys: 56 | assert d[key] == getattr(d, key) 57 | 58 | 59 | def test_dict_wrapper_2(): 60 | d = solution.DictWrapper({'key': 'value'}) 61 | 62 | d['key'] = 'value2' 63 | assert d['key'] == 'value2' 64 | assert d.key == 'value2' 65 | 66 | d.key = 'value3' 67 | assert d['key'] == 'value3' 68 | assert d.key == 'value3' 69 | 70 | 71 | @pytest.mark.parametrize('dict_, injective', [ 72 | ({}, True), 73 | ({'wpn': 599, 'jk': 795, 'fusm': 170, 'czc': 441, 'xhbma': 196, 'mrq': 367, 'opzswv': 117, 'nclhi': 65}, True), 74 | ({'xw': 345, 'wuounlrfg': 861, 'sjaeeikk': 817, 'wckytbb': 999, 'ejdpxhbjfq': 351, 'jmk': 127, 'nddrpp': 490}, True), 75 | ({'wjmx': 561, 'ucatgwkf': 986, 'huomw': 742, 'bmwsnyvw': 85, 'fo': 857, 'iwf': 742}, False), 76 | ({'tvcadugtsd': 71, 'cldbtagf': 71, 'pgx': 71, 'va': 226}, False), 77 | 78 | ]) 79 | def test_dict_wrapper_3(dict_, injective): 80 | d = solution.DictWrapper(dict_) 81 | assert d.injective() == injective 82 | 83 | 84 | @pytest.mark.parametrize('original, inverted, injective', [ 85 | ({}, {}, True), 86 | ({'wpn': 599, 'jk': 795, 'fusm': 170, 'czc': 441, 'xhbma': 196, 'nclhi': 65}, 87 | {599: 'wpn', 795: 'jk', 170: 'fusm', 441: 'czc', 196: 'xhbma', 65: 'nclhi'}, True), 88 | ({'xw': 345, 'wuounlrfg': 861, 'sjaeeikk': 817, 'wckytbb': 999}, 89 | {345: 'xw', 861: 'wuounlrfg', 817: 'sjaeeikk', 999: 'wckytbb'}, True), 90 | ({'huomw': 742, 'bmwsnyvw': 85, 'fo': 857, 'iwf': 742}, None, False), 91 | ({'tvcadugtsd': 71, 'cldbtagf': 71, 'pgx': 71, 'va': 226}, None, False), 92 | 93 | ]) 94 | def test_dict_wrapper_4(original, inverted, injective): 95 | if injective: 96 | d = solution.DictWrapper(original) 97 | assert d.invert() == inverted 98 | else: 99 | with pytest.raises(ValueError): 100 | d = solution.DictWrapper(original) 101 | d.invert() 102 | 103 | 104 | @pytest.mark.parametrize('start, end', [ 105 | (0, 0), 106 | (1, 2), 107 | (-10, 5), 108 | (-20, 0), 109 | (-30, -10), 110 | ]) 111 | def test_range_1(start, end): 112 | assert list(solution.Range(start, end)) == list(range(start, end)) 113 | 114 | 115 | @pytest.mark.parametrize('start, end, step', [ 116 | (0, 0, 1), 117 | (1, 2, 1), 118 | (3, 20, 1), 119 | (-10, 10, 3), 120 | (-10, 20, 100), 121 | ]) 122 | def test_range_2(start, end, step): 123 | assert list(solution.Range(start, end, step)) == list(range(start, end, step)) 124 | 125 | 126 | @pytest.mark.parametrize('start, end, step', [ 127 | (0, 0, 1), 128 | (1, 2, 1), 129 | (3, 20, 1), 130 | (-10, 10, 3), 131 | (-10, 20, 100), 132 | ]) 133 | def test_range_3(start, end, step): 134 | assert list(reversed(solution.Range(start, end, step))) == list(reversed(range(start, end, step))) 135 | 136 | 137 | def test_ragne_4(): 138 | with pytest.raises(ValueError): 139 | _ = solution.Range(0, 0, 0) 140 | -------------------------------------------------------------------------------- /2018fall/webapp.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask import Flask, jsonify, request 4 | from flask_sqlalchemy import SQLAlchemy 5 | 6 | 7 | app = Flask(__name__) 8 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///posts.db' 9 | db = SQLAlchemy(app) 10 | 11 | 12 | class Post(db.Model): 13 | __tablename__ = 'posts' 14 | 15 | id = db.Column(db.Integer, primary_key=True) 16 | subject = db.Column(db.String) 17 | body = db.Column(db.String) 18 | 19 | def __iter__(self): 20 | for column in self.__table__.columns: 21 | yield column.name, str(getattr(self, column.name)) 22 | 23 | 24 | @app.route('/post/') 25 | def view_post(post_id): 26 | post = Post.query.get_or_404(post_id) 27 | return jsonify(dict(post)) 28 | 29 | 30 | @app.route('/post', methods=['POST']) 31 | def new_post(): 32 | subject = request.form['subject'] 33 | body = request.form['body'] 34 | 35 | post = Post( 36 | subject=subject, 37 | body=body) 38 | 39 | db.session.add(post) 40 | db.session.commit() 41 | 42 | return str(post.id) 43 | 44 | 45 | @app.route('/post/', methods=['PUT']) 46 | def update_post(post_id): 47 | post = Post.query.get_or_404(post_id) 48 | 49 | post.subject = request.form['subject'] 50 | post.body = request.form['body'] 51 | 52 | db.session.add(post) 53 | db.session.commit() 54 | 55 | return '' 56 | 57 | 58 | @app.route('/post/', methods=['DELETE']) 59 | def delete_post(post_id): 60 | post = Post.query.get_or_404(post_id) 61 | 62 | db.session.delete(post) 63 | db.session.commit() 64 | 65 | return str(post.id) 66 | 67 | 68 | if __name__ == '__main__': 69 | port = int(os.environ.get('PORT', 8080)) 70 | debug = bool(os.environ.get('DEBUG', False)) 71 | app.run(port=port, debug=debug) 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sumin Byeon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SB Coding Workshop 2 | 3 | 소프트웨어 엔지니어들을 대상으로 파이썬을 가르치고 있습니다. --------------------------------------------------------------------------------