Python3 List의 편리한 기능들
작성 계기
잠시 Python을 사용해본적은 있지만, 최근 프로젝트를 아예 Python을 사용하기 시작하면서 기존의 C++의 STL에서의 Vector와 많은 부부니 비교가 되었다. 특히, 알았을때와 몰랐을때의 생산성의 차이를 많이 느끼게 되었기 때문에 기록겸 정리를 해 놓았다. 본 내용들은 기본서에서는 잘 다루지는 않는 내용이지만, 그렇다고 어려운 것은 아니다.
List의 구조
기본적으로 C++ STL의 Vector와 거의 비슷한다. 배열처럼 사용이 가능하며, 동적으로 길이를 늘리고 줄일 수 가 있다. 하지만, 경험에 의한 것인지 좀 더 편리한 기능들이 많이 있다. 그리고 정적 언어와 달리 List는 특정한 자료형을 구분 하지 않고 담을 수가 있다. 물론 C++에서도 포인터를 이용하면 구현을 할 수 있겠지만, 별로 추천하는 방법은 아니다.
Python의 List의 특징
여기서는 주로 다른 프로그램 언어에서 보기 힘든 Python의 List의 특징을 작성하겠다.
원소를 포함한 비교 연산이 가능함
Python의 str 구조도 마찬가지이지만 C/C++ 언어에서는 문자열 두개를 비교할때 각 문자의 길이 하나하나를 비교를 해서 같아야만 같은 문자열로 판단하는 알고리즘을 메서드로 구현해서 판단을 해야 한다. Python의 경우 이러한 비교 연산을 위한 Hash라는 값을 인스턴스 혹은 객체를 생성할때 만들어 놓는데, 이 숫자가 같은지만 비교를 하여 비교연산에 소모되는 연산량을 줄였다. 클래스의 Hash에 대해서 자료를 찾아보면 자세한 내용을 알 수 있다.
예시)
a = [1, 'hello', 2]
b = [2, 'hello', 1]
c = [1, 'hello', 2]
a == b
# False
a == c
# True
위의 예시 코드를 Python idle shell에서 실행해서 간단히 확인 할 수 있다.
곱연산으로 반복적인 원소 채우기
Python에서 특이한 기능중 하나로 원소가 있는 리스트와 숫자를 곱 연산 할경우 해당 리스트를 반복적으로 숫자만큼 채우는 것이 가능하다. 이 기능은 Python의 기본 문자열 자료형인 str에서도 나타나는 특징이다.
예시)
a = [1, 2]*3
# a = [1, 2, 1, 2, 1, 2]
예시 코드를 Python idle shell에서 확인 해보면 된다.
원소를 배분
Python에서 가장 두드러지는 문법 중 하나로 배열의 길이 만큼 객체명을 좌항에 작성을 하면 우항에 그 크기에 많은 리스트를 놓으면 각 원소를 순서대로 대입이 되는 특징이 있다. 필자의 경우 이러한 특징을 곱연산의 특징과 응용해서 여려개의 객체명을 초기화 해야 할 경우에 간단하게 사용하곤 한다.
예시)
a1, a2, a3 = [None]*3
# a1 = None
# a2 = None
# a3 = None
예시 코드를 Python idle shell에서 확인 해보면 된다.
단, List의 길이와 좌항의 변수의 길이가 안 맞을 경우 에러가 생기기 때문에 일반화 방식으로 작성할 경우 개수를 검사하는 로직을 미리 추가 해주는 것이 좋다.
간단한 index 로 나누어 대입
이 기능은 다른 언어에서도 있기는 하지만, Python에서 문법적으로 간결하게 제공을 하고 있다. ':'을 대괄호 안에서 사용하여 범위를 자를 수 있다. 필자의 경우 특정 2차원 배열을 1차원 배열에 넣고 관리할때 응용해서 사용하기도 한다.
예시)
a = [1, 2, 3, 4, 5]
b = a[2:]
# b = [3, 4, 5]
c = a[:3]
# c = [1, 2, 3]
index는 많이 헷깔리는 경우가 있는데, python은 친 인간 언어적인 편이라서 사람이 일반적으로 세는 방식으로 자른다. 그러나 전통적인 배열로 사용할 경우에는 0부터 시작한다.
리스트간 병합(Merrge)
List를 초기 정의한 이후 원소를 추가를 할 때 일반적으로 append와 같은 C++의 STL 경우 push 메서드를 호출해서 한쪽으로 하나씩 넣는게 일반적이다. 그러나 List의 연산자 + 혹은 +=을 사용하면 리스트를 동등하게 머지하는 결과를 나타낸다. append와 비슷해 보이나 다른 방식이기 때문에 주의해야 한다.
예시)
a = [1, 2]
b = [3, 4]
c = a + b
# c = [1, 2, 3, 4]
리스트 컴프리헨션(Comprehensions)
공식 문서에서는 간단하게 리스트를 만드는 방법이라고 소개를 하고 있다. 이터러블의 멤버들에게 특정 연산을 사용하는 경우에 응용이 가능하다. 가장 쉽게 응용이 가능한 것은 기존에 어떤 수치 리스트를 파일에 저장하기 위해서 문자형으로 바꾸거나 혹은 반대로 어떤 파일을 읽어서 숫자로 된 리스트를 수치형 자료로 변경할 경우에 응용하면 편한 기능이다. 이 방법을 몰라도 형 변환은 못 하는 것은 아니다. 다만, 간결하고 편할 뿐이다.
예시)
squaresSrc = [1, 4, 8, 16]
squaresDst = [str(it) for it in squaresSrc]
# squaresDst = ['1', '4', '8', '16']
그리고 여기서 더 나아가 컴프리헨션을 통해 중첩과 if문을 응용이 가능하다. 여기서 중첩을 할 경우에는 실제 코드에서 2중 for 문을 사용한 것과 같다. 컴프리헨션에서는 실행되는 순서는 첫 for문 부터 읽어서 가장 마지막에 앞에 있는 연산을 수행한다.
예시)공식 문서 예제
>>>[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
이러한 기능은 편리하지만, 자신이 협업 혹은 유지보수하는 인력 수준에 따라서 중첩 혹은 if문이 추가된 방식에 대해서 가독성이 떨어진다면, 가능하면 사용을 자제하는 것이 좋을 것 같다.
기타 기본적인 재배열(sort) 관련 메서드
List가 선언된 객체에 자체적으로 오름차순과 내림차순으로 재배열할 경우 sort(), 그리고 현재 배열된 원소를 역으로 배열할 경우 reverse()를 기본적으로 지원을 한다. 또한 sort()의 경우 입력인자로는 정해진 flag를 넣어서 방식의 조절이 가능하다. 다만, 이 메서들을 사용시 주의해야 할 점은 List 객체 자체를 바꿔버리기 때문에 재배열을 하기 전의 데이터를 보존을 해야한다면, 깊은 복사를 하고 나서 해야한다는 단점이 있다.
이후 추가로 편의성 기준으로 정리가 필요하면 후에라도 추가를 하도록 하겠다.
참조자료
스택오브 플로워: list str to int