├── .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 | 소프트웨어 엔지니어들을 대상으로 파이썬을 가르치고 있습니다.
--------------------------------------------------------------------------------