[2부] 03장 파이썬
파이썬 알고리즘 인터뷰 책 환경
- CPython (파이썬의 공식 인터프리터)
- 파이썬 버전 3.7
- PEP8
파이썬 문법
인덴트 (Indent)
- 파이썬 Indent는 공식 가이드인 PEP8에 따라 공백 4칸을 원칙으로 함.
- 구글 파이썬 가이드 라인 또한 공백 4칸 들여쓰기가 원칙.
- 강제는 아니며, 얼마든지 선택적으로 적용 가능, 이외에도 다음과 같은 기준이 있음.
- 첫 번째 줄에 파라미터가 있다면, 파라미터가 시작되는 부분에 맞춤.
- 첫 번째 줄에 파라미터가 없다면 공백 4칸 인덴트를 한 번 더 추가함.
foo = long_function_name(var_one, var_two,
var_three, var_four)
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
파이참 커뮤니티 에디션과 같은 개발 도구를 활용하면 별도록 신경쓰지 않아도 코딩 가이드를 자동으로 맞춰줌.
네이밍 컨벤션
- 카멜 케이스 : 단어의 첫문자는 모두 대문자로 시작, 첫 단어의 시작 문자는 소문자. 자바의 대표적인 표기 방식 (e.g. camelCase)
- 스네이크 케이스 : 각 단어를 언더스코어 (_)로 구분, (e.g. snake_case)
- 파스칼 케이스 : 첫 단어의 시작 문자도 대문자, 단어의 첫 문자도 모두 대문자로 시작 (e.g. PascalCase)
PEP8 에 따라 snake case 스타일로 코딩하는 것을 지향함.
타입 힌트
파이썬은 대표적인 동적 타이핑 언어임에도, 타입을 지정할 수 있는 타입 힌트 (Type Hint)가 PEP 484에 추가됨.
이 기능은 파이썬 버전 3.5 부터 사용할 수 있음.
예를 들어,
기존의 타입 힌트를 사용하지 않는 파이썬 함수의 경우
def fn(a):
...
- 빠르게 정의해서 사용할 수 있다는 장점이 있지만,
- fn() 함수의 파라미터 a에는 숫자를 넘겨야 하는지, 문자를 넘겨야 하는지 전혀 알 수 없으며, 이 함수의 리턴값이 무엇인지 알 수 없음!
- 버그 유발의 주범.
타입 힌트를 사용한다면
def fn(a: int) -> bool:
...
- fn() 함수의 파라미터 a가 정수형임을 분명하게 알 수 있으며, 리턴 값으로 True, False를 리턴함을 알 수 있음.
- 물론 실제로는 강제 규약이 아니다 보니, 여전히 동적으로 할당될 수 있으므로 주의 필요.
- 온라인 코딩 테스트 시에는 mypy를 사용하면 타입 힌트에 오류가 없는지 자동으로 확인 가능.
리스트 컴프리헨션 (List comprehension)
기존 리스트를 기반으로 새로운 리스트를 만들어내는 구문.
- 람다 표현식에 map, filter를 섞어서 사용하는 것에 비해 가독성이 높음.
n * 2 for n in range(1, 10 + 1) if n % 2 == 1
- 홀수인 경우 2를 곱해 출력하라는 리스트 컴프리헨션
리스트 외에도 딕셔너리 등도 가능함.
제너레이터
루프의 반복 (iteration 동작) 동작을 제어할 수 있는 루틴 형태
- 예를 들어, 숫자 1억 개를 만들어내 계산하는 프로그램을 작성해야 함. 제너레이터가 없다면 메모리 어딘가에 만들어낸 숫자 1억 개를 보관하고 있어야 함.
- 그러나 제너레이터를 이용하면, 단순히 제너레이터만 생성해두고 필요할 때 언제든 숫자를 만들어낼 수 있음.
즉, 제너레이터 객체는 모든 값을 메모리에 올려두고 이용하는 것이 아니라, 필요할 때마다 생성해서 반환하는 일을 함.
- 반복자와 동일한 일을 하는 것처럼 보이지만, 미리 메모리에 만들어 두는 것이 아니라, 값을 필요로 할 때마다 제너레이터에서부터 값을 받아오며 메모리에서 보관하지 않음 (이를 lazy evaluation이라고 함)
- 제터레이터는 여러 타입의 값을 하나의 함수에서 생성하는 것도 가능함.
range
range(): 제너레이터 방식을 활용하는 대표적인 함수
- 숫자 100000000 개를 생성할 수 있는 방법은 2가지.
- a : 에는 생성된 값을 담아두며,
- b : 에는 새롭게 생성해야 함, 이는 제너레이터를 리턴하듯 range 클래스를 리턴하는 방식임.
- 똑같이 숫자 100000000개를 갖고 있으나, range 클래스를 이용하는 b 변수의 메모리 점유율이 훨씬 더 작음. b 변수는 생성 조건만 보관하고 있기 때문.
- 또한 미리 생성하지 않은 값은 인덱스에 접근이 안 될 거라 생각할 수 있으나, 인덱스로 접근 시에는 바로 생성하도록 구현되어 있기 때문에 다음과 같이 리스트와 거의 동일한 느낌으로 불편 없이 사용할 수 있음.
enumerate
enumerate(): '열거하다'는 뜻의 함수. 순서가 있는 자료형 (list, set, tuple 등)을 인덱스를 포함한 enumerate 객체로 리턴함.
- list() 로 결과를 추출할 수 있는데, 인덱스를 자동으로 부여해 주기 때문에 매우 편리함.
//나눗셈 연산자
- / 기본 나눗셈 연산자
- // 몫 을 구하는 연산자 (정수형을 나눗셈할 때 동일한 정수형을 결과로 리턴하면서 내림 연산자의 역할)
- % 나머지 를 구하는 연산자
- divmod() 함수 : 몫과 나머지를 한 번에 구할 때
코딩 테스트 문제 풀이 과정에서 디버깅을 할 때 가장 자주 쓰는 명령
CODE
print('a1', 'b1') # , 사용: 한 칸 공백이 디폴트로 설정
print('a1', 'b1', sep=',') # sep 파라미터로 구분자를 콤마(,)로 지정
print('aa', end = ' ')
print('bb) # end 파라미터를 공백으로 처리하여 줄바꿈을 하지 않음
a = ['A', 'B']
print(' '.join(a)) # 리스트를 출력할 때는 join() 으로 묶어서 처리
idx = 1
fruit = "Apple"
print('{0}: {1}'.format(idx + 1, fruit))
print('{}: {}'.format(idx + 1, fruit))
print(f'{idx + 1}: {fruit}') # f-string(formated string literal)
# 변수를 뒤에 별도로 부여할 필요 없이 인라인으로 삽입할 수 있어 편리하게 사용할 수 있음. 3.6 + 에서만 지원함.
OUTPUT
a1 b1
a1,b1
aa bb
A B
2: Apple
2: Apple
2: Apple
pass
널 연산 (Null Operation) 으로 아무것도 하지 않는 기능.
- 아무 역할을 하지 않는 pass를 지정하면, 앞서 발생한 인덴트 오류 같은 불필요한 오류를 방지할 수 있음.
- 이는 목업 (mockup) 인터페이스부터 구현한 다음에 추후 구현을 진행할 수 있게 함.
locals
local symbol table을 dictionary 형태로 반환
- symbol table: 현재 프로그램에 대한 필요한 정보가 담김
- cf) globals() 함수: 전역 기호 테이블을 dictionary 형태로 변환.
locals() 함수: 현재의 local 변수를 dict type으로 던져주는 내장 함수.
- 로컷에 선언된 모든 변수를 조회할 수 있기 때문에 디버깅에 많은 도움이 됨.
- 특히, 로컬 스코프에 제한해 정보를 조회할 수 있기 때문에 클래스의 특정 메소드 내부에서나 함수 내부의 로컬 정보를 조회해 잘못 선언한 부분이 없는지 확인 가능.
코딩 스타일
좋은 코드에 정답은 없지만, 많은 사람이 선호하는 방식은 있음.
책에서 추천하는 책
- Clean Code 클린 코드 (로버트 C.마틴 지음, 박재호 외 옮김, 인사이트, 2013)
- 프로그래밍 수련법 (브라이언 W.커니핸, 롭 파이크 지음, 김정민 옮김, 인사이트, 2008)
- PEP 8 (실용적인 관점)
- 구글의 파이썬 스타일 가이드 (실용적인 관점)
변수명과 주석
좋지 않은 코드)
변수명이 무엇을 의미하는지 이해하기 어렵고, 알고리즘에 대한 주석이 없어서 어덯게 동작하는지 파악하기 어려움.
좋은 코드)
- (저자 개인적인 의견으로는) 간단한 주석을 부여하는 편이 훨씬 더 가독성이 높다고 봄.
- 엉클 밥은 Clean Code 책에서 코드에 주석을 달지 말 것을 주장함.
- 변수명: 의미 없는 이름보다는 각각의 의미를 부여해 작명
- 파이썬 PEP 8 문서 기준에 따라 (모두 스네이크 케이스로) 작성
리스트 컴프리헨션
리스트 컴프리헨션은 파이썬을 대표하는 특징 중 하나로, 매우 강력한 기능을 가지지만,
특유의 문법과 의미를 축약하여 나타내는 특징 탓에, 지나치게 남발하면 파이썬의 가독성을 떨어트리는 요인이 됨.
str1s = [str1[i:i + 2].lower() for i in range(len(str1) -1) if re.findall('[a-z]{2}', str1[i:i + 2].lower())]
Solution 1)
한 줄 풀이에 집착하기보다는 라인을 좀 더 여유있게 활용하여 가독성을 높이는 것이 좋음.
또한 표현식이 2개를 넘지 않는게 좋음.
str1s = [
str1[i:i + 2].lower() for i in range(len(str1) -1)
if re.findall('[a-z]{2}', str1[i:i + 2].lower())
]
Solution 2)
필요에 따라 모두 풀어서 쓰는 것도 가독성을 위해서라면 괜찮음.
str1s = []
for i in range(len(str1) - 1):
if re.findall('[a-z]{2}', str1[i:i + 2].lower()):
str1s.append(str1[i:i + 2].loser())
구글 파이썬 스타일 가이드
파이썬 공식 스타일 가이드인 PEP 8 외의 구글에서 정한 파이썬 스타일 가이드.
- PEP 8 에서 설명하지 않는 좋은 코드를 위한 지침들이 여럿 있음. 이를 소개하고자 함.
1. 함수의 기본 값 (default value)로 가변 객체 (Mutable Object)를 사용하지 않아야 함.
=> 함수가 객체를 수정하면 (e.g. 리스트에 아이템을 추가하는 경우) 기본값이 변경되지 때문. 따라서 다음과 같이 기본값으로 []나 {}를 사용하는 것은 지양해야 함.
No: def foo(a, b=[]):
...
No: def foo(a, b: Mapping = {}):
...
대신 다음과 같이 불변 객체 (Immutable Object)를 사용함. None을 명시적으로 할당하는 것도 좋은 방법
Yes: def foo(a, b=None):
if b is None:
b = []
Yes: def foo(a, b: Oprional[Sequence] = None):
if b is None:
b = []
2. True, False를 판별할 때는 암식적 (implicit)인 방법을 사용하는 편이 좋음.
=> 즉, 굳이 False 임을 if foo != []: 같은 형태로 판별할 필요 없이, if foo: 로 충분함.
3. 아래 코드 확인
Yes: if not users: # len(users) == 0, 길이가 없다는 말은 값이 없다는 뜻이먀, not users로 충분.
print('no user')
if foo == 0: # 비교 대상이 되는 정수값을 직접 비교하는 편이 안전함.
self.handle_zero()
if i % 10 == 0: # i % 10 == 0 과 같이 명시적으로 값을 비교하는 편이 좋음.
self.handle_multiple_of_ten()
No: if len(users) == 0:
print('no users')
if foo is not None and not foo:
self.handle_zero()
if not i % 10:
self.handle_multiple_of_ten()
4. 세미콜론으로 줄을 끝내서는 안되고, 세미콜론을 사용해 같은 줄에 두 문장을 써도 안 됨.
5. 최대 줄 길이는 80자.