공식가이드 PEP8에 따라 들여쓰기는 공백 4칸을 원칙으로 한다.
강제는 아니며, 얼마든지 선택적으로 적용 가능하다. 과거에는 인덴트를 탭으로 하거나 공백 2칸을 권할 때도 많았지만, PEP8 이후에는 이 기준을 준수한다. 이외에도 PEP8에는 다음과 같은 기준들이 있다.
fn = long_function_name(var_1, var_2
var_3, var_4)
#첫 번째줄에 파라미터가 있다면, 파라미터가 시작되는 부분에 보기 좋게 맞춘다.
def long_function_name(
var_1, var_2, var_3,
var_4):
print(var_1)
#첫 번째줄에 파라미터가 없다면, 공백 4칸 인덴트를 한번 더 추가하여 다른행과 구분되게 한다.
fn = long_function_name(
var_1, var_2,
var_3, var_4)
#여러 줄로 나눠쓸 경우 다음 행과 구분되도록 인덴트를 추가한다.
파이참을 사용하면 코딩 가이드를 자동으로 맞춰준다. 또는 파이참 내 Refotmat Code를 실행하면 PEP8 기준에 맞춰주기 때문에 매우 편리하다.
온라인 코딩 테스트를 풀이할 때도 가능하다면 파이참 같은 도구를 별도로 사용하자. 편리할 것!
파이썬은 변수명의 각 단어를 _ 로 구분하여 표기하는 스네이크 케이스(Snake Case) 를 따른다.
이는 함수명도 마찬가지다. 간혹 코딩테스트 문제에서 지키지 않은 경우도 많은데, 최소한 내가 풀고 작성한 코드는 소문자 변수명과 함수명을 기본으로 해야 한다.
혹시 면접관이 이런 질문을 하게 된다면 파이썬의 PEP 8 및 철학에 따라 스네이크 코딩을 지향한다고 얘기할 수 있어야 한다. 이 또한 파이참같은 IDE를 사용하면 네이밍 기준에 어긋날 경우, 자동으로 경고를 띄워주기 때문에 편리하다.
Camel Case
단어를 대소문자로 구분하여 섞어서 작명하는 방식으로, 자바의 대표적인 표기방식이다.
특이한 점은 단어의 첫 문자는 모두 대문자로 시작하지만 첫 단어의 시작 문자는 소문자로 표기하며, 첫 시작 문자까지도 대문자로 하는 방식은 Pascal Case라 한다.
ex) camelCase: int = 1
Snake Case
일반적으로 소문자로 표기하지만, 경우에 따라 시작 문자는 대문자로 표기하기도 한다.
ex) snake_case: int = 1
Python은 대표적인 동적 타이핑 언어임에도, 타입을 지정할 수 있다. 이 기능은 version 3.5 부터 사용 가능하다. CPython의 typing.py에는 선언할 수 있는 타입이 잘 명시되어 있으며, 다음과 같이 타입을 선언한다.
a: str = "1"
b: int = 1
그리고, 기존에 타입힌트를 사용하지 않는 파이썬 함수는 다음과 같이 정의해 사용해왔다.
def fn(a):
...
위와 같은 방법은 빠르게 정의해서 사용할 수 있다는 장점이 있지만 fn() 함수의 parameter a에는 어떤 타입을 넘겨야 하는지 전혀 알 수 없고, 리턴값이 무엇인지도 알 수 없다.
후에 프로젝트 규모가 커지게 되면 가독성을 떨어뜨리게 되며 만약 값을 잘못 넘기게 된다면 버그 유발의 주범이 된다. 그렇다면 다음 코드를 살펴보자.
def fn(a: int) -> bool:
...
이처럼 타입 힌트를 사용하게 되면 parameter a가 정수형임을 분명하게 하며, 리턴 값으로 True or False를 리턴할 것이라는 점도 확실히 알 수 있다.
물론 실제로는 강제 규약이 아니다 보니, 여전히 동적으로 할당될 수 있으므로 주의가 필요하다.
코딩 테스트는 일반적으로 짧은 알고리즘으로 끝나는 경우가 많고, 타입 또한 지정하지 않아도 한눈에 보일 만큼 명확하기 때문에 굳이 지정하지 않아도 문제는 없다. 그러나 코드를 정리할 때만이라도 타입을 모두 지정해서 보기 좋게 제출한다면, 코드 리뷰 시 면접관에게 좋은 점수를 받을 수 있을 것이다.
온라인 코딩 테스트 시에는 mypy를 사용하면 타입힌트에 오류 유무를 자동으로 확인할 수 있으므로 이를 통해 수정 후 코드를 제출할 수 있다. mypy는 pip으로 설치 가능하다.
$ pip install mypy
#설치
$ mypy solution.py
#사용
기존 리스트를 기반으로 새로운 리스트를 만들어내는 구문으로, 파이썬의 대표적인 특징이기도 하다.
ex
[n * 2 for n in range(1, 10 + 1 ) if n % 2 == 1]
version 2.7 이후에는 다음과 같이 리스트 외에도 딕셔너리 등이 가능하도록 추가됐다.
a = {}
for key, value in original.items():
a[key] = value
#아래처럼 처리
a = {key: value for key, value in original.items()}
리스트 컴프리헨션은 가독성이 좋은 편이지만 이 또한 무리하게 복잡하게 작성할 경우 오히려 가독성을 떨어뜨릴 수 있으므로 적절히 사용하는 게 중요하다. 대체로 표현식은 2개를 넘지 않아야 한다.
루프의 반복 동작을 제어할 수 있는 루틴 형태를 말한다.
(ex 메모리 상 보관이 아니라, 제너레이터만 생성해두고 필요할 때 언제든 만들어서 사용)
yield 구문을 사용하면, generator를 리턴할 수 있다. 기존의 함수는 return 구문을 만나면 값을 리턴하고 모든 함수의 동작을 종료하는 반면, yield는 제너레이터가 여기까지 실행중이던 값을 내보낸다는 의미로, 중간값을 리턴한 다음 함수는 종료되지 않고 계속해서 맨 끝에 도달할 때까지 실행된다.
def get_natural_number():
n = 0
while n < 100:
n += 2
yield n
>>> get_natural_number()
<generator object get_natural number at 0x.....>
만약 다음 값을 생성하려면 next()로 추출하면 된다.
>>> g = get_natural_number()
>>> for _ in range(0, 50):
... print(next(g))
...
2
4
6
8
...
98
100
다음과 같이 여러 타입의 값을 하나의 함수에서 생성하는 것도 가능하다.
def generator():
yield 1
yield 'string'
yield True
>>> g = generator()
>>> next(g)
1
>>> next(g)
'string'
>>> next(g)
True
제너레이터의 방식을 활용하는 대표적인 함수이며, 주로 for 문에서 쓰인다.
>>> list(range(5))
[0, 1, 2, 3, 4]
>>> type(range(5))
<class 'range'>
range() 는 range 클래스를 리턴하며, for 문에서 사용할 경우 내부적으로는 제너레이터의 next()를 호출하듯 매번 다음 숫자를 생성한다.
version 2.x 까지는 미리 생성해서 리스트로 리턴하는 방식이었고,
제너레이터를 리턴하는 방식은 xrange()라고 따로 존재했다.
version 3 이후, range() 함수가 제너레이터 역할을 하는 range 클래스를 리턴하는 형태로 변경됐고, xrange()는 사라졌다.
예를 들어 쉽게 이해하자.
>>> a = [n for n in range(1000000)]
>>> b = range(1000000)
a 에는 이미 생성된 값이 담겨 있고, b는 생성해야 한다는 조건만 존재한다.
>>> sys.getsizeof(a)
8697464
>>> sys.getsizeof(b)
48
메모리 점유율 비교를 통해 큰 차이를 알 수 있다.
생성 조건만 보관하기 때문에 100만이 아닌 1억개라도 메모리 점유율은 동일하다.
게다가 미리 생성하지 않은 값은 인덱스에 접근이 안 될거라 생각할 수 있으나, 인덱스로 접근시에는 바로 생성하도록 구현되어 있기 때문에 다음과 같이 리스트와 거의 동일한 느낌으로 불편 없이 사용 가능하다.
>>> b[999]
999
enumerate()는 '열거하다'는 뜻의 함수로, 순서가 있는 자료형(list, set, tuple 등)을 인덱스를 포함한 enumerate 객체로 리턴한다.
인덱스를 자동으로 부여해주기 때문에 매우 편리하게 활용할 수 있다.
>>> a = [1,10,100,1000]
>>> enumerate(a)
<enumerate object at 0x.....>
>>> list(enumerate(a))
[(0, 1), (1, 10), (2, 100), (3,1000)]
다음과 같이 리스트의 인덱스와 값을 함께 출력하려 할 때 깔끔하게 처리 가능하다.
for i, v in enumerate(a):
print(i, v)
다음은 version 3 이후 PEP 238에서 제시된 나눗셈 동작 방식이다.
// 나눗셈 연산자는 int(a / b)와 동일하다.
#Python 3+
>>> 5 / 3
1.666667
>>> type(5 / 3)
<class 'float'>
>>> 5 // 3
1
>>> type(5 //3)
<class 'int'>
>>> type(int(5 / 3))
<class 'int'>
(version 2 이전 / : 타입을 유지하는 특성을 가지고 있어 실수하기 쉬움)
다음 예에서도 정수형을 유지해 다음과 같이 1을 리턴한다.#Python 3+ >> 5 / 3 1.666667 #Python 2 >> 5 / 3 1
나머지를 구하는 연산자는 % 이다.
몫과 나머지를 동시에 구하려면 divmod() 함수를 사용하면 된다. 앞으로도 문제 풀이 시 자주 활용된다.
>>> divmod(5, 3)
(1, 2)
로컬 심볼 테이블 딕셔너리를 가져오는 메소드로 업데이트 또한 가능하다.
딕셔너리를 가져오는 부분에 집중해 살펴보자면, 우선 로컬에 선언된 모든 변수를 조회할 수 있는 강력한 명령이므로 디버깅에 많은 도움이 된다. 특히 로컬 스코프에 제한해 정보를 조회할 수 있기 때문에 클래스의 특정 메소드 내부에서나 함수 내부의 로컬 정보를 조회해 잘못 선언한 부분이 없는지 확인하는 용도로 활용 가능하다.
Leetcode 문제 풀이 중에도 코드 내부에 출력해 활용할 수 있다.
변수명을 일일이 찾을 필요 없어 편리하다.
pprint로 출력하게 되면 보기 좋게 줄바꿈 처리를 해줘 가독성이 높다.
import pprint
a = [2, 3, 5, 6]
b = 'hihi'
pprint.pprint(locals())

클래스 내부의 모든 로컬 변수를 출력해 주기 때문에 디버깅에 많은 도움이 된다.
코딩 테스트 문제 풀이 과정에서 디버깅을 할 때 가장 자주 쓰는 명령은 바로 print() 다.
(ver 2.x 까지는 print 절(statement)이었으나, 3.x 로 오면서 print() 함수로 바뀌었다.)
웬만한 대부분 코딩 테스트 플랫폼에서는 적어도 print()를 통해 stdout 출력 결과를 보여주어 디버깅 용도로 활용할 수 있도록 지원한다. 하지만 정답 제출시에는 print() 조차 보여주지 않는 경우가 있으니 유의해야 한다.
실무에서는 이 디버깅 방법을 추천하지 않는다. 하지만 코딩 테스트 시에는 디버거를 사용하거나 TDD 방식으로 접근하기도 어렵기 때문에, 사실상 print() 가 디버깅을 위해 제공되는 유일한 기능이기도 하다.
그렇기 때문에 다음과 같이 유용하게 활용할 수 있는 방법을 숙지하자.
>>> print('A1', 'B2')
A1 B2
가장 쉽게 출력하는 ','구분 방법이다. 이 경우 한 칸 공백이 디폴트로 설정되어 있으며, 띄어쓰기로 값을 구분해준다.
>>> print('A1', 'B2', sep=',')
A1,B2
sep 파라미터로 구분자를 ','로 지정해줄 수도 있다.
print() 함수는 항상 줄바꿈을 하기 때문에 긴 루프의 값을 반복적으로 출력하면 디버깅 하기 어려운데, end 파라미터를 공백으로 처리하여 줄바꿈을 하지 않도록 제한할 수 있다.
print('aa', end=' ')
print('bb')
------------------------
aa bb
리스트를 출력할 때는 join()으로 묶어서 처리한다.
>>> a = ['A', 'B', 'C']
>>>print('__'.join(a))
A__B__C
다음과 같이 idx와 fruit이 정의되어 있다고 할 때,
idx = 1 fruit = "Apple"idx 값에 1을 더해서 fruit과 함께 출력해보자.
>> print('{0}: {1}'.format(idx + 1, fruit)) 2: Apple다음과 같이 인덱스를 생략할 수 있다.
>> print('{}: {}'.format(idx + 1, fruit))
Python 3.6+ 에서만 지원하는 f-string(formated string literal) 방법
변수를 뒤에 별도로 부여할 필요 없이 마치 템플릿을 사용하듯 인라인으로 삽입할 수 있어 편리하게 사용할 수 있다. 무엇보다 기존의 %를 사용하거나 .format을 부여하는 방식에 비해 훨씬 간결하고 직관적이며 속도도 빠르다.>> print(f'{idx + 1}: {fruit}') 2: Apple
코딩 시 일단 코드의 전체 골격을 잡아 놓고 후에 내부 처리를 하고 싶은 경우에 사용한다.
파이썬에서 pass는 Null 연산으로, 아무것도 하지 않는 기능이다. 이처럼 아무 역할을 하지 않는 pass를 지정하면, 인덴트 오류 같은 불필요한 오류를 방지할 수 있다.
class MyClass(object):
def method_a(self):
pass
#오류방지
def method_b(self):
print("hi")
class MyClass()
이렇게 pass는 먼저 mockup 인터페이스부터 구현한 다음에 추후 구현을 진행할 수 있게 한다.
온라인 코딩 테스트 시에도 무척 유용하게 활용할 수 있다.