diff --git a/hong/python/effective-python/1. Pythonic Thinking.md b/hong/python/effective-python/1. Pythonic Thinking.md new file mode 100644 index 0000000..d881fc1 --- /dev/null +++ b/hong/python/effective-python/1. Pythonic Thinking.md @@ -0,0 +1,509 @@ +# Chapter 1: 파이썬답게 생각하기 + +파이썬다운 스타일은 컴파일러가 엄격히 통제하거나 사용하라고 강요하는 스타일이 아니다. 파이썬 언어를 사용하고 서로 협업하는 과정에서 자연스럽게 생겨난 스타일이다. + +파이썬 프로그래머는 + +1. 명시적인 것을 좋아하고, +2. 복잡한 것보다 단순한 것을 좋아하며, +3. 가독성을 최대한 높이려고 노력한다. + +![The Zen of Python](https://github.com/10000-Bagger/free-topic-study/assets/34956359/9f00f725-6873-4ecb-b60a-fc081f488314) + +### The Zen of Python + +```markdown +Beautiful is better than ugly. +Explicit is better than implicit. +Simple is better than complex. +Complex is better than complicated. +Flat is better than nested. +Sparse is better than dense. +Readability counts. +Special cases aren't special enough to break the rules. +Although practicality beats purity. +Errors should never pass silently. +Unless explicitly silenced. +In the face of ambiguity, refuse the temptation to guess. +There should be one -- and preferably only one -- obvious way to do it. +Altohough that way may not be obvious at first unless you're Dutch. +Now is better than never. +Although never is often better than right now. +If the implementation is hard to explain, it's a bad idea. +If the implementation is easy to explain, it may be a good idea. +Namespaces are one honking great idea -- let's do more of those! +``` + +[Python - 파이썬의 선(The Zen of python)](https://tyoon9781.tistory.com/entry/the-zen-of-python) + +## Better Way 1: 사용 중인 파이썬의 버전을 알아두라. + +이 책의 대부분의 예제 코드는 파이썬 3.7 구문을 따르고, 파이썬 3.8 구문으로 작선한 예제도 몇 가지 있다. + +다양한 버전의 표준 CPython이 미리 설치된 컴퓨터도 많이 있다. 하지만 명령줄에서 python을 실행할 때 티폴트로 어떤 버전이 실행될지는 명확하지 않다. `--version`플래그를 통해 어떤 python 버전을 사용하고 있는지 확인하자. + +### 기억해야 할 내용 + +- 파이썬 3는 파이썬 최신 버전이며 현재 가장 잘 지원되고 있다. 따라서 여러분은 프로젝트에서 파이썬 3을 써야 한다. +- 여러분의 시스템에 있는 파이썬 실행 파일이 여러분이 원하는 버전인지 확인하라. +- 파이썬 2는 사용하지 말라. 2020년 1월 1일부터 파이썬 2는 더 이상 지원되지 않는다. + +## Better Way 2: PEP 8 스타일 가이드를 따르라 + +파이썬 개선 제안 (Python Enhancement Proposal) #8은 파이썬 코드를 어떤 형식으로 작성할지 알려주는 스타일 가이드다. + +PEP 8은 깔끔한 파이썬 코드를 작성하는 방법을 아주 자세히 알려준다. 파이썬 언어가 개선되면 PEP 8도 계속 변하며, [온라인 가이드](https://peps.python.org/pep-0008/)는 한번 읽어볼 만하다. 다음은 여러분이 꼭 따라야 하는 규칙이다. + +### 공백 + +파이썬에서 공백(whitespace)은 중요한 의미가 있다. 파이썬 프로그래머들은 코드의 의미를 명확히 하는 데 공백이 미치는 영향에 특히 민감하다. + +공백과 관련한 다음 가이드라인을 따르라(다음 설명에서 공백은 탭, 스페이스, new line 등의 문자를 모두 합한 말이다). + +- 탭 대신 스페이스를 사용해 들여쓰기하라. +- 문법적으로 중요한 들여쓰기에는 4칸 스페이스를 사용하라. +- 라인 길이는 79개 문자 이하여야 한다. +- 긴 식을 다음 줄에 이어서 쓸 경우에는 일반적인 들여쓰기보다 4 스페이스를 더 들여써야 한다. +- 파일 안에서 각 함수와 클래스 사이에는 빈 줄을 두 줄 넣어라. +- 클래스 안에서 메서드와 메서드 사이에는 빈 줄을 한 줄 넣어라. +- 딕셔너리(dictionary)에서 key와 콜른(:) 사이에는 공백을 넣지 않고, 한 줄 안에 키와 값을 같이 넣는 경우에는 콜론 다음에 스페이스를 하나 넣는다. +- 변수 대입에서 = 전후에는 스페이스를 하나씩만 넣는다. +- 타입 표기를 덧붙이는 경우에는 변수 이름과 콜론 사이에 공백을 넣지 않도록 주의하고, 콜론과 타입 정보 사이에는 스페이스를 하나 넣어라. + +### 명명 규약 + +PEP 8은 파이션 언어의 여러 부분에 사용하는 이름을 어떻게 붙일지에 대한 고유 스타일을 제공한다. 이런 규약을 사용하면 코드를 읽을 때 각 이름이 어떤 유형에 속하는지 쉽게 구분할 수 있다. 이름과 관련해서는 다음 가이드라인을 따르라. + +- 함수, 변수, 애트리뷰트(attribute)는 lowercase_underscore처럼 소분자와 밑줄을 사용한다. +- 보호돼야 하는 인스턴스 애트리뷰트는 일반적인 애트리뷰트 이름 규칙을 따르되, _leading_underscore처럼 밑줄로 시작한다. +- 비공개(private)(한 클래스 안에서만 쓰이고 다른 곳에서는 쓰면 안 되는 경우) 인스턴스 애트리뷰트는 일반적인 애트리뷰트 이름 규칙을 따르되, __leanding_underscore처럼 밑줄 두 개로 시작한다. +- 클래스(예외도 포함한다)는 CapitalizedWord처럼 여러 단어를 이어 붙이되, 각 단어의 첫 글자를 대문자로 만든다. +- 모듈 수준의 상수는 ALL_CAPS처럼 모든 글자를 대문자로 하고 단어와 단어 사이를 밑줄로 연결한 형태를 사용한다. +- 클래스에 들어 있는 인스턴스 메서드는 호출 대상 객체를 가리키는 첫 번째 인자의 이름으로 반드시 self를 사용해야 한다. +- 클래스 메서드는 클래스를 가리키는 첫 번째 인자의 이름으로 반드시 cls를 사용해야 한다. + +### 식과 문 + +`The Zen of Python`에서는 `문제를 해결할 명백한 방법이 하나 있으며, 가급적이면 유일해야 한다`고 언급한다. PEP 8은 이런 가르침을 따라 식과 문장을 작성하는 스타일 규칙을 다음과 같이 정의했다. + +- 긍정적인 식을 부정하지 말고(`if not a is b`), 부정을 내부에 넣어라(`if a is not b`) +- 빈 컨테이너(container)나 시퀀스(sequence)([]나 ‘’ 등)를 검사할 때는 길이를 0과 비교(`if len(something == 0`)하지 말라. 빈 컨테이너나 시퀀스 값이 암묵적으로 `False`로 취급된다는 사실을 활용해 `if not container` 라는 조건 문을 써라. +- 마찬가지로 비어 있지 않은 컨테이너나 시퀀스([1] 이나 ‘hi’ 등)를 검사할 때도 길이가 0보다 큰지 비교하지 말라. 대신 if 컨테이너가 비어 있지 않은 경우 암묵적으로 `True`로 평가된단느 사실을 활용하라. +- 한 줄짜리 if 문이나 한 줄짜리 for, while 루프, 한 줄짜리 except 복합문을 사용하지 말라. 명확성을 위해 각 부분을 여러 줄에 나눠 배치하라. +- 식을 한 줄 안에 다 쓸 수 없는 경우, 식을 괄호로 둘러싸고 줄바꿈과 들여쓰기를 추가해서 일기 쉽게 만들라. +- 여러 줄에 걸쳐 식을 쓸 때는 줄이 계속된다는 표시를 하는 \ 문자보다는 괄호를 사용하라. + +### Import + +PEP 8은 모듈을 import해 코드에 사용하는 방법에 대해서도 가이드 라인을 제시한다. + +- import 문(from x import y도 포함)을 항상 파일 맨 앞에 위치시켜라. +- 모듈을 임포트할 때는 절대적인 이름(absolute name)을 사용하고, 현 모듈의 경로에 상대적인 이름은 사용하지 말라. 예: bar 패키지로부터 foo 모듈을 import 한다면 → `from bar import foo` 라고 해야하며 단지 `import foo` 라고 하면 안 된다. +- 반드시 상대적인 경로로 import 해야 하는 경우에는 `from . import foo` 처럼 명시적인 구문을 사용하라. +- import를 적을 때는 표준 라이브러리 모듈, 서드 파티 모듈, 여러분이 만든 모듈 순서로 섹션을 나눠라, 각 섹션에서는 알파벳 순서로 모듈을 임포트하라. + +** [Pylint](https://pypi.org/project/pylint/) 도구는 파이썬 소스 코드를 분석하는 유명한 정적 분석기다. PEP 8 스타일 가이드를 자동으로 실행해주고, 파이썬 프로그램에서 저지르기 쉬운 다양한 유형의 오류를 감지해준다. 여러 IDE와 editor도 자체 린트 도구나 린트와 비슷한 기능을 제공하는 프러그인을 지원한다. + +### 기억해야 할 내용 + +- 파이썬 코드를 작성할 때는 항상 PEP 8 스타일 가이드를 따르라. +- 큰 파이썬 커뮤니티와 공통된 스타일을 공유하면 다른 사람들과 협력할 때 도움이 된다. +- 일관성 있는 스타일을 사용하면 나중에 자신이 작성한 코드를 직접 수정할 때도 더 수월해진다. + +[PEP 8 – Style Guide for Python Code | peps.python.org](https://peps.python.org/pep-0008/) + +## Better Way 3: bytes와 str의 차이를 알아두라 + +- str 인스턴스에는 사람이 사용하는 언어의 문자를 표현하는 유니코드 코드 포인트(code point)가 들어있다. +- str 인스턴스에는 직접 대응하는 이진 인코딩이 없고, bytes에는 직접 대응하는 텍스트 인코딩이 없다. +- str에 bytes를, bytes에 str을 더할 수 없다. 당연히 비교도 안 된다. +- bytes를 str로, str를 bytes로 바꿀 수 있다. 바꾸는 코드는 아래와 같다. + +```python +a = b'h\x65llo' +a # output: b'hello' +type(a) # output: bytes +a.decode('utf-8') # output: 'hello' +type(a.decode('utf-8')) # output: str + +b = 'HELLO' +b.encode('utf-8') # b'HELLO' +type(b.encode('utf-8') # bytes +``` + +- 위처럼 bytes를 사용하는 방법은 문자열 앞에 `b` 를 붙여주면 된다. +- 유니코드 데이터를 이진 데이터로 변환하려면 str의 encode 메서드를 호출해야 하고, 이진 데이터를 유니코들 데이터로 변환하려면 bytes의 decode 메서드를 호출해야 한다. +- 그리고, bytes와 str 인스턴스끼리는 (>, ==, +, %와 같은) 연산자에 섞어서 사용할 수 있다. + +```python +print(b'one' + b'two') # Output: 'onetwo' + +print(b'one' + 'two') # output: TypeError: can't concat str to bytes + +``` + +### 기억해야 할 내용 + +- bytes에는 8비트 값의 시퀀스가 들어 있고, str에는 유니코드 코드 포인트의 시퀀스가 들어 있다. +- 처리할 입력이 원하는 문자 시퀀스(8비트 값, UTF-8로 인코딩된 문자열, 유니코드 코드 포인트들)인지 확실히 하려면 도우미 함수를 사용하라. +- bytes와 str 인스턴스를 (>, ==, + % 와 같은) 연산자에 섞어서 사용할 수 없다. +- 이진 데이터를 파일에서 읽거나 파일에 쓰고 싶으면 항상 이진 모드(’rb’나 ‘wb’)로 파일을 열어라. +- 유니코드 데이터를 파일에서 읽거나 파일에 쓰고 싶을 때는 시스템 디폴트 인코딩에 주의하라. 인코딩 차이로 놀라고 싶지 않으면 open에 encoding 파라미터를 면시적으로 전달하라. + +## Better Way 4: C 스타일 형식 문자열을 str.format으로 쓰기보다는 f-문자열을 통한 Interpolation을 사용하라 + +- 형식화(formatting): 미리 정의된 문자열에 데이터 값을 끼워 넣어서 사람이 보기 좋은 문자열로 저장하는 과정. +- 가장 일반적인 방법으로 % 형식화 연산자를 사용했었다. (과거형..) +- 그리고, 값이 여럿일 경우 tuple로 지정한다. + +```python +print('My %s is %d.' % ('age', 5)) # Output: My age is 50. +``` + +- 파이썬 3부터는 %를 사용하는 오래된 C 스타일 형식화 문자열보다 더 표현력이 좋은 고급 문자열 혁식화 기능(format 내장 함수)이 도입됐다. + +```python +print('My {} is {}.'.format('age', 5)) # Output: My age is 50. +``` + +- 파이썬 3.6부터 더 발전된 형태인 `Interpolation을 통한 형식 문자열(f-문자열)`이 도입됐다. +- f-문자열은 형식 문자열의 표현력을 극대화하고, 형식화 문자열에서 키와 값을 불필요하게 중복 지정해야하는 경우를 없애준다. +- f-문자열은 형식화 식 안에서 현재 파이썬 영역에서 사용할 수 있는 모든 이름(변수 등)을 자유롭게 참조할 수 있도록 허용함으로써 간결함을 제공한다. +- 아래 예제와 같이 형식 문자열 앞에 f 문자를 붙여 사용한다. + +```python +key = 'age' +value = 50 +print(f'My {key} is {value}.') # Output: My age is 50. +``` + +### 기억해야 할 내용 + +- % 연산자를 사용하는 C 스타일 형식화 문자열은 여러 가지 단점과 번잡성이라는 문제가 있다. +- str.format 메서드는 형식 지정자 미니 언어에서 유용한 개념 몇 가지를 새로 제공했다. 하지만 이를 제외하면 str.format 메서드도 C 스타일 형식 문자열의 문제점을 그대로 가지고 있으므로, 가능하면 str.format 사용을 피애햐 한다. +- f-문자열은 값을 문자열 안에 넣는 새로운 구문으로, C 스타일 형식화 문자열의 가장 큰 문제점을 해결해준다. +- f-문자열은 간결하지만, 위치 지정자 안에 임의의 파이썬 식을 직접 포함시킬 수 있으므로 매우 강력하다. + +## Better Way 5: 복잡한 식 대신 도우미 함수를 작성하자 + +파이썬은 문법이 간결하므로 상당한 로직이 들어가는 식도 한 줄로 매우 쉽게 작성할 수 있다. + +- 반복적인 if/else 문: + +```python +green_str = my_values.get('초록', ['']) +if green_str[0]: + green = int(green_str[0]) +else: + green = 0 +``` + +- 위 로직을 반복 적용하려면 (위 예제처럼 단지 두세 번에 불과할지라도) 꼭 도우미 함수를 작성해야 한다. + +```python +def get_first_int(values, key, default=0): + found = values.get(key, ['']) + if found[0]: + return int(found[0]) + return default +``` + +** `repr()` 함수 + +- The `repr()` function returns a printable representation of the given object. +- 객체를 문자열로 반환하는 함수이다. str()과 비슷하지만 약간의 차이가 있다. + +```python +# 숫자에서는 아무런 차이가 없다. +a = 123 +str(a) # Output: '123' +repr(a) # Output: '123' + +# 문자열의 경우, repr()은 따옴표(')로 감싼 형태의 문자열을 반환한다. +a = "Life is too short" +str(a) # Output: 'Life is too short' +repr(a) # Output: "'Life is too short'" + +# datetime 모듈을 출력할 경우, 아래와 같은 차이를 보여준다. +import datetime +a = datetime.datetime(2017, 9, 27) +str(a) # Output: '2017-09-27 00:00:00' +repr(a) # Output: 'datetime.datetime(2017, 9, 27, 0, 0)' +eval(repr(a)) # Output: datetime.datetime(2017, 9, 27, 0, 0) +``` + +| 구분 | str() | repr() | +| --- | --- | --- | +| 성격 | 비공식적인 문자열을 출력 | 공식적인 문자열을 출력 | +| 사용 목적 | 사용자가 보기 쉽도록 | 문자열로 객체를 다시 생성 | +| 누구를 위해 | 프로그램 사용자(end user) | 프로그램 개발자(developer) | +- repr()의 사용 목적을 보면 ‘문자열로 객체를 다시 생성’이라고 되어 있다. 문자열로 객체를 생성할 때는 eval() 함수를 사용한다. 즉, 다음과 같이 datetime 객체를 repr()로 생성한 문자열에 다시 eval() 함수를 실행하면 datetime 객체가 만들어져야 한다는 뜻. + +### 기억해야 할 내용 + +- 파이썬 문법을 사용하면 아주 복잡하고 읽기 어려운 한 줄짜리 식을 쉽게 작성할 수 있다. +- 복잡한 식을 도우미 함수로 옮겨라. 특히 같은 로직을 반복해 사용할 때는 도우미 함수를 꼭 사용하라. +- boolean 연산자 or나 and를 식에 사용하는 것보다 if/else 식을 쓰는 편이 더 가독성이 좋다. + +## Better Way 6: 인덱스를 쓰는 대신 대입을 사용해 데이터를 언패킹하자 + +- 튜플(tuple): list처럼 여러 개의 데이터를 저장할 때 사용 + +```python +working = { + '노트북': 0, + '마우스': 1, + '헤드셋': 2 +} # type: dictionary +print(working.items()) # items()는 키와 값을 함께 뽑아준다. Output: dict_items([('노트북', 0), ('마우스', 1), ('헤드셋', 0)]) +a = tuple(working.items()) # type: tuple +print(a) # Output: (('노트북', 0), ('마우스', 1), ('헤드셋', 2)) +``` + +- 튜플에 있는 값은 숫자 인덱스를 사용해서 접근할 수 있다. +- 인덱스를 통해 새 값을 대입하는 방식으로 튜플을 변경할 수는 없다. + +```python +pair = ('약과', '호박엿') +pair[0] = '타래과' +# Output: +# Traceback ... +# TypeError: 'tuple' object does not support item assignment +``` + +- 파이썬에는 언패킹(unpacking) 기능이 있는데, 언패킹 구문을 사용하면 한 문장 안에서 여러 값을 대입할 수 있다. +- list, sequence, iterable 안에 여러 계층으로 iterable이 들어간 경우 다양한 패턴을 언패킹 구문에 사용할 수 있다. + +```python +notebook, mouse, headset = a +print(notebook) # Ouptput: ('노트북', 0) +``` + +- 데이터를 언패킹 한다는 것은 묶인 것을 각각의 변수로 담는다는 것. 궅이 index를 쓸 필요가 없는 곳에 index를 써서 나타내면 가독성이 떨어지기 때문에 언패킹 방식을 사용하자. + +### 기억해야 할 내용 + +- 파이썬은 한 문장 안에서 여러 값을 대입할 수 있는 언패킹이라는 특별한 문법을 제공한다. +- 파이썬 언패킹은 일반화돼 있으므로 모든 이터러블에 적용할 수 있다. 그리고 이터러블이 여러 계층으로 내포된 경우에도 언패킹을 적용할 수 있다. +- 인덱스를 사용해 시퀀스 내부에 접근하는 대신 언패킹을 사용해 시각적인 잡음을 줄이고 코드를 더 명확하게 만들라. + +## Better Way 7: range 보다는 enumerate를 사용하자 + +- iterable: 반복 가능한 객체 + - `iter` 내장 함수를 쓴다. +- range: 어떤 정수 집합을 iteration하는 loop가 필요할 때 유용 + - range(start, end, step) + - start 숫자부터 end 숫자 바로 앞 숫자까지 컬랙션을 만든다. + +```python +for i in range(5): + print(i+1, end=' ') # print의 end default는 \n 이다. Output: 1 2 3 4 5 + +alist = ['h', 'e', 'l', 'l', 'o'] +for a int alist: + print(a, end='') # Output: hello +``` + +- enumerate: iterator를 지연 계산 제너레이터(lazy generator)로 감싼다. + - 느긋한 계산법(lazy evaluation): 계산의 결과값이 필요할 때까지 계산을 늦추는 기법이다. + - generator의 next가 호출되어야만 실제로 파일에서 한줄을 읽는 코드가 동작하는 방식 등을 지연 실행이라고 한다. + - 느긋하게 계산하면 필요없는 계산을 하지 않으므로 실행을 더 빠르게 할 수 있고, 복합 수식을 계산할 때 오류 상태를 피할 수 있고, 무한 자료 구조를 쓸 수 있고, 미리 정의된 것을 이용하지 않고 보통 함수로 제어 구조를 정의할 수 있다. +- enumerate는 루프 인텍스와 iterator의 다음 값으로 이뤄진 쌍을 넘겨준다. (yield) + - 기본적으로 인덱스와 원소로 이루어진 튜플을 만들어 준다. +- next 내장 함수를 사용해 다음 원소를 가져온다. + +```python +flavor_list = ['바닐라', '초콜릿', '피칸'] + +next(enumerate(flavor_list)) # output: (0, '바닐라') + +for i, flavor in enumerate(flavor_list): + print(i, flavor) +# Outputs: +# 0 바닐라 +# 1 초콜릿 +# 2 피칸 +``` + +- range를 이용해서 index로 접근하는 것보다, enumerate를 통해 index가 별개로 확인될 수 있는 것이 더 낫다. + +### 기억해야 할 내용 + +- enumerate를 사용하면 이터레이터에 대해 루프를 돌면서 이터레이터에서 가져오는 원소의 인텍스까지 얻는 코드를 간결하게 작성할 수 있다. +- range에 대해 루프를 돌면서 시퀀스의 원소를 인덱스로 가져오기보다는 enumerate를 사용하라. +- enumerate의 두 번째 파라미터로 어디부터 원소를 가져오기 시작할지 지정할 수 있다 (디폴트 값은 0이다). + +## Better Way 8: 여러 iterator에 대해 나란히 loop를 수행하려면 zip을 사용하자 + +- zip: 둘 이상의 iterator를 lazy generator를 사용해 묶어준다. +- zip generator는 각 iterator의 다음 값이 들어있는 tuple을 반환한다. +- input iterator의 길이가 서로 다르면 zip은 아무런 경고도 없이 가장 짧은 iterator 길이까지만 tuple을 내놓고 더 긴 iterator의 나머지 원소는 무시한다. + +```python +for i in zip('123','ABCD'): + print(i) +# Output: +# ('1', 'A') +# ('2', 'B') +# ('3', 'C') +``` + +- 가장 짧은 iterator에 맞춰 길이에 제한하지 않고, 길이가 서로 다른 iterator에 대해 루프를 수행하려면 `itertools` 내장 모듈의 `zip_longest` 함수를 사용한다. + +```python +import itertools + +for i in itertools.zip_longest('123', 'ABCD'): + print(i) +# Output: +# ('1', 'A') +# ('2', 'B') +# ('3', 'C') +# (None, 'D') +``` + +### 기억해야 할 내용 + +- zip 내장 함수를 사용해 여러 이터레이터를 나란히 이터레이션할 수 있다. +- zip은 튜플을 지연 계산하는 제너레이터를 만든다. 따라서 무한히 긴 입력에도 zip을 쓸 수 있다. +- 입력 이터레이터의 길이가 서로 다르면 zip은 아무런 경고도 없이 가장 짧은 이터레이터 길이까지만 튜플을 내놓고 더 긴 이터레이터의 나머지 원소는 무시한다. +- 가장 짧은 이터레이터에 맞춰 길이를 제한하지 않고 길이가 서로 다른 이터레이터에 대해 루프를 수행하려면 itertools 내장 모듈의 zip_longest 함수를 사용하라. + +## Better Way 9: for나 while 루프 뒤에 else 구문을 사용하지 말자 + +- 파이썬은 for나 while 루프에 속한 블록 바로 뒤에 else 블록을 허용하는 특별한 문법을 제공한다. + +```python +for i in range(3): + print('Loop', i) +else: + print('Else block!') +# Output: +# Loop 0 +# Loop 1 +# Loop 2 +# Else block! +``` + +- 이름과 다르게 else 블록은 루프가 끝나자마자 실행된다. +- 또한, 루프 뒤에 오는 else 블록은 루프가 반복되는 도중에 break를 만나는 경우 실행되지 않는다. + +```python +for i in range(3): + print('Loop', i) + if i == 1: + break +else: + print('Else block!') +# Output: +# Loop 0 +# Loop 1 +``` + +- 이 외에도 코드의 가독성을 낮추는 용례가 있지만, 결론은 for나 while 루프 뒤에 else 구문을 하용함으로써 얻을 수 있는 표현력보다는 미래에 이 코드를 이해하려는 사람들(자신 포함)이 느끼게 될 부담감이 더 크다는 것이다. +- 파이썽에서 루프와 같은 간단한 구성 요소는 그 자체로 의미가 명확해야 한다. 따라서 절대로 루프 뒤에 else 블록을 사용하지 말아야 한다. + +### 기억해야 할 내용 + +- 파이썬은 for나 while 루프에 속한 블록 바로 뒤에 else 블록을 허용하는 특별한 문법을 제공한다. +- 루프 뒤에 오는 else 블록은 푸르가 반복되는 도중에 break를 만나지 않은 경우에만 실행된다. +- 동작이 직관적이지 않고 혼동을 야기할 수 있으므로 루프 뒤에 else 블록을 사용하지 말라. + +## Better Way 10: 대입식을 사용해 반복을 피하자 + +- 대입식은 영어로 assignment expression이며 왈러스 연산자(walrus operator)라고도 부른다. (파이썬 3.8에서 새롭게 도입된 구문) + - `:=` 를 왈러스라 부른다. + + ![스크린샷 2024-04-16 143018](https://github.com/10000-Bagger/free-topic-study/assets/34956359/7996c9a9-e338-4597-b1d7-21296f8eb6e1) + +- 대입식은 대입문이 쓰일 수 없는 위치에서 변수에 값을 대입할 수 있으므로 유용하다. +- 하나의 식 안에서 변수에 값을 대입하면서 이 값을 평가할 수 있고, 중복을 줄일 수 있다. + - 한 줄에서 변수에 값을 할당하면서 동시에 이 값을 표현식의 일부로 사용할 수 있다. + +```python +# 기존 +flavor_list = ['바닐라', '초콜릿', '피칸'] + +n1 = len(flavor_list) + +if n1 < 5: + print(f'리스트 개수는 {n1}입니다.') # Output: 리스트 개수는 3입니다. + +# 왈러스 이용 +if (n2 := len(flavor_list)) < 5: + print(f'리스트 개수는 {n2}입니다.') # Output: 리스트 개수는 3입니다. +``` + +- 대입식을 사용해서 파이썬에서 지원하지 않는 switch/case 문을 더 가독성있게 만들 수 있다. +- 아래의 대십식을 사용하는 버전은 원래 문장보다 다섯 줄 정도 짧지만, 들여쓰기와 내포가 줄어서 가독성은 훨씬 좋다. + +```python +# 왈러스 사용 x +count = fresh_fruit.get('바나나', 0) +if count >= 2: + pieces = slice_bananas(count) + to_enjoy = make_smoothies(pieces) +else: + count = fresh_fruit.get('사과', 0) + if count >= 4: + to_enjoy = make_cider(count) + else: + count = fresh_fruit.get('레몬', 0) + if count: + to_enjoy = make_lemonade(count) + else: + to_enjoy = '아무것도 없음' + +# 왈러스 사용 o +if (count := fresh_fruit.get('바나나', 0)) >= 2: + pieces = slice_bananas(count) + to_enjoy = make_smoothies(pieces) +elif (count := fresh_fruit.get('사과', 0)) >= 4: + to_enjoy = make_cider(count) +elif (count := fresh_fruit.get('레몬', 0): + to_enjoy = make_lemonade(count) +else: + to_enjoy = '아무것도 없음' +``` + +- 대입식을 사용해서 파이썬에서 지원하지 않는 do/while 문을 더 가독성있게 만들 수 있다. +- 아래와 같이 왈러스 연산자를 사용하면 while 루프에서 매번 fresh_fruit 변수에 대입하고 조건을 검사할 수 있으므로 무한 루프-중간에서 끝내기 관용어의 필요성이 줄어든다. +- 이 해법이 더 짧고 읽기 쉽기 때문에 여러분은 코드에서 이 방식을 우선적으로 사용해야 한다. + +```python +# 왈러스 사용 x +bottles = [] +while True: # 무한 루프 + fresh_fruit = pick_fruit() + if not fresh_fruit: # 중간에서 끝내기 + break + for fruit, count in fresh_fruit.items(): + batch = make_juice(fruit, count) + bottles.extend(batch) + +# 왈러스 사용 o +bottles = [] +while fresh_fruit := pick_fruit(): + for fruit, count in fresh_fruit.items(): + batch = make_juice(fruit, count) + bottles.extend(batch) +``` + + +- 여러분의 코드에서 못생긴 요소가 보이기 시작하면 왈러스 연산자를 사용해 가능한 한 그런 요소를 없애기 위해 노력할 것을 권장한다. + + +**추가: 대입식은 리스트 안에서 사용할 때도 가능하다. +```python +a = 2 + +[y := a, y**2, y**3] # [2, 4, 8] +``` + + + +### 기억해야 할 내용 + +- 대입식에서는 왈러스 연산자(`:=`)를 사용해 하나의 식 안에서 변수 일므에 값을 대입하면서 이 값을 평가할 수 있고, 중복을 줄일 수 있다. +- 대입식이 더 큰 식의 일부분으로 쓰일 때는 괄호`()`로 둘러싸야 한다. +- 파이썬에서는 switch/case 문이나 do/while 루프를 쓸 수 없지만, 대입식을 사용하면 이런 기능을 더 깔끔하게 흉내 낼 수 있다.