썸네일

💡우아하게, 파이썬스럽게 코드를 작성해보자.

본 포스팅은 코딩테스트를 염두에두고 작성한 파이썬 코드 작성법이며, 파이썬 알고리즘 인터뷰(박상길 지음, 2020)를 읽으며 정리한 내용임을 밝힙니다.


이전 포스팅에 이어서, 본 포스팅에서는 리스트 컴프리헨션, 제너레이터, Range, Enumerate에 대해서 알아보도록 하겠습니다.

다시한번 강조드리지만, 기존에 다 알고 있던 기능이라도 한번쯤 되돌아 보시면서 파이썬스럽게, 우아하게 코드를 작성하는 법을 익히는 시간이였으면 좋겠습니다.

리스트 컴프리헨션

파이썬은 map, filter와 같은 함수형 기능을 지원하고, lambda expression 또한 지원합니다.

>>> list(map(lambda x: x + 10, [1,2,3]))
[11, 12, 13]

이는 아주 오래전부터(1994년) 람다를 지원해왔고, 이보다 더 유용한건 리스트 컴프리헨션입니다. 리스트 컴프리헨션이란 무엇일까요?

기존 리스트를 기반으로 새로운 리스트를 만드는 구문

파이썬 2.0부터 지원되었으며, 위 처럼 함수형 기능을 사용하는 것 보다 훨씬 가독성이 좋습니다.

>>> [n*2 for n in range(1, 10+1) if n % 2 == 1]
[2, 6, 10, 14, 18]

파이썬 2.7부터는 리스트 말고도 딕셔너리 등도 가능하도록 추가되었습니다.

a = {}
for key, value in original.items():
  a[key] = value

==

a = {key: value for key, value in original.items()}

제너레이터

제너레이터란,

루프의 반복(Iteration)동작을 제어할 수 있는 루틴의 형태

를 의미합니다.

예를 들어서 숫자 1억개를 만들어 계산하는 프로그램을 만든다고 가정하면, 제너레이터가 없을시 메모리 어딘가에 이 숫자 1억개를 저장해놓고 계산할 때 사용해야 합니다.

제너레이터를 이용하면 제너레이터만 생성해두고 필요할 때 숫자를 만들 수 있게 됩니다.

yield 구문을 이용하면 제너레이터 Object를 리턴할 수 있게 되는데, yield를 사용하면 실행중이던 값을 내보낸다는 의미로,

실행중이던 값을 리턴한 이후에 함수는 종료되지 않고 맨끝에 도달할 때 까지 실행상태에 있습니다.

def get_number():
  n = 0
  while True:
    n += 1
    yield n

g = get_number()
for _ in range(100):
  print(next(g))

출력:
1
2
3
...
99
100

위와 같이 next()에 Generator Object를 인자로 넣어주면 다음 값을 리턴해주며, 위처럼 출력이 나오게 됩니다.

Range

위 Generator의 방식을 활용한 대표적인 함수가 range()입니다.

다들 사용 방법은 아실 것이라고 생각하여 사용방법은 생략하겠습니다.

중요한 점은, range()는 range 클래스를 리턴하고 반복문에서 사용하는 경우 내부적으로는 제너레이터의 next()를 호출하듯 매번 다음 숫자를 생성하게 됩니다.

숫자를 미리 생성하여 메모리에 적재하는 방식이 아니라는 말입니다.

다음 예제를 봅시다.

>>> list_a = [n for in range(10000)]
>>> list_b = [range(10000)]

위 두 리스트를 shell에서 len()으로 비교를 해보면,

>>> len(list_a)
10000
>>> len(list_b)
10000
>>> len(list_a) == len(list_b)
True

위 두 리스트의 큰 차이는, list_a 에는 실제로 숫자 10000개가 생성되어 메모리를 점유하고 있고, list_b에는 위 숫자 0개부터 10000개까지의 리스트를 생성하기 위한 생성조건만을 담고 있기 때문에 실제로 메모리를 점유하고 있는건 그 조건 밖에 없습니다.

>>> sys.getsizeof(list_a)
85176
>>> sys.getsizeof(list_b)
48

미리 생성되고있지 않더라도 인덱스로 접근 시 바로 생성되도록 구현되어 있기 때문에 리스트와 거의 동일한 느낌으로 사용할 수 있습니다.

>>> b[999]
999

Enumerate

함수의 이름처럼, 열거하는 특징을 가지고 있고 여러 자료형(list, set, tuple …)을 인덱스를 포함한 enumerate 객체로 리턴합니다.

>>> list_a = [4,323,5,71,2354]
>>> list_a
[4, 323, 5, 71, 2354]
>>> enumerate(list_a)
<enumerate object at 0x10d57bb80>
>>> list(enumerate(list_a))
[(0, 4), (1, 323), (2, 5), (3, 71), (4, 2354)]

위와같이 인덱스를 자동으로 부여해줘서 굉장히 편히 활용할 수 있습니다. 이는 코딩테스트에서도 인덱스와 값 모두 활용해야 할때, 혹은 저장해야 할 때 유용하게 쓰입니다.

>>> a = ['ab', 'cd', 'ef']
>>> for index, value in enumerate(a):
...    print(index, value)

0 ab
1 cd
2 ef

추가내용

본문으로 다루기에는 큰 어려움이 없어 부록으로 남깁니다.

나눗셈

파이썬2에서의 / 연산자는 실수가 아닌 정수형을 리턴했고, 파이썬3부터 실수형을 리턴하도록 바뀌었습니다.

과거의 방식을 사용하려면 //연산자를 사용하면 되며, 몫을 내림연산 한다고 생각하면 됩니다.

추가로 나머지를 구하는 연산자는 %이며, 몫과 나머지를 모두 구하는 함수는 divmod()입니다.

print

실무에서는 print()로 디버깅하는건 좋은 방법이 아니나, 코딩테스트에서 TDD 방식이나 디버거로 문제해결에 접근하는게 어렵기 때문에

사실상 print()로 코딩테스트에서는 값을 찍어보면서 디버깅을 하곤 합니다.

밑 예제를 보시면서 print() 함수를 잘 활용할 수 있도록 하시면 좋겠습니다.

# 값을 콤마로 구분하여 출력
>>> print('A', 'B')
A B

# sep 파라미터로 구분자를 지정
>>> print('A', 'B', sep=',')
A,B

# 코딩테스트에서 디버깅 시 줄바꿈을 막아 도움이 되도록 출력
print('aa', end=' ')
print('bb')
-----------
aa bb

# 리스트를 출력할 땐 join()으로 묶어서 처리한다.
>>> a = ['A', 'B']
>>> print(' '.join(a))
A B

# f-string으로 문자열에 원하는 값을 인라인으로 삽입하여 출력
# format()은 속도도 느리고 직관적이지 않아 저는 선호하지 않습니다.
>>> A = 25
>>> B = 'CodeSik'
>>> print(f'{B}{A}살이다.')

pass

pass 는 Null 연산이고 아무것도 처리하지 않는 기능으로, 처음에 코드의 구조를 작성하거나 MockUp Interface 먼저 구현한 다음에 추후 구현을 진행할 수 있도록 합니다.

실제로 저도 많이 사용하는데 코딩테스트시 아주 유용하게 사용할 수 있습니다.

class Solution(object):
  def function_a(self):
    pass
  def function_b(self):
    print('function_b')

solution = Solution()

이렇게 구현을 해놓으면 미리 구현하지 못한 function_a 발생할 오류를 없애놓을 수 있습니다.

locals

locals()는 로컬 심볼 테이블 딕셔너리를 가져오는 메소드로 업데이트 또한 가능합니다. 여기서 가져오는 로컬 심볼 테이블 딕셔너리는, 로컬에 선언된 모든 변수를 조회할 수 있는 강력한 명령 입니다. 그래서 코딩테스트 시 디버깅에 아주 유용하게 사용할 수 있습니다.

import pprint
pprint.pprint(locals())

'pprint': <module 'pprint' from '/usr/local/Cellar/python@3.9/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/pprint.py'>,
 's': 'a',
 'sentence': 'A man, a plan, a canal: Panama',

마지막 포스팅에서는 코딩스타일에 대해서 다뤄보겠습니다. 코딩테스트에서 왜 코딩스타일이 중요하냐? 라는 의문이 들 수도 있는데,

저도 코딩테스트를 겪어보면서 면접자가 어떻게 채점하는지 그 과정을 알 수 있었고, 정성적인 기준으로 평가가 수행될 수 있다는 부분을 꼭 아셔야 합니다.

그럼 다음 포스팅에서 봐요!