CodeOnWeb
로그인

챕터 3. 컴프리헨션(Comprehensions)

알고 나면 편한 컴프리헨션에 대해서 알아봅니다.

Park Jonghyun 2015/09/07, 19:56

내용

파이썬 3에 뛰어들기 (22)

챕터 -1. “파이썬 3로 뛰어들기”에서 달라진 점 챕터 0. 파이썬 설치하기 챕터 1. 첫 파이썬 프로그램 챕터 2. 고유 자료형 챕터 3. 컴프리헨션(Comprehensions) 챕터 4. 문자열(Strings) 챕터 5. 정규표현식(Regular Expressions) 챕터 6. 클로저와 제너레이터(Closures & Generators) 챕터 7. 클래스와 반복자(Classes & Iterators) 챕터 8. 고급 반복자(Advanced Iterators) 챕터 9. 단위 테스트(Unit Testing) 챕터 10. 리팩토링(Refactoring) 챕터 11. 파일 챕터 12. XML 챕터 13. 파이썬 객체 직렬화 챕터 14. HTTP 웹서비스 챕터 15. 사례 연구: chardet을 파이썬 3로 이식하기 챕터 16. 파이썬 라이브러리 패키징하기 부록 A. 2to3를 이용해서 코드를 파이썬 3로 이식하기 부록 B. 특수 함수 이름 부록 C. 이제 어디로 가야 할까요? 부록 D. 문제 해결

"인간의 상상력이란 존재하지 않는 가상의 것을 그릴 때가 아니라, 실존하는 무언가를 이해하려고 노력할 때 극대화된다."
— Richard Feynman

Diving In

모든 프로그래밍 언어는 저마다 멋진 기능으로 한껏 치장한 채 등장하곤 합니다. 복잡한 문제도 단 두 세 줄의 코딩으로 손쉽게 해결하는 그런 기능 말입니다. 곧 보시게 되겠지만 파이썬에도 그런 기능이 있습니다. 다른 언어를 쓰다가 오신 분은 어색할 수도 있습니다. 여러분이 쓰시던 오래된 언어에서는 존재하지 않는 방식이기 때문이죠. (그 언어의 창조주는 다른 것을 간단하게 하는 데 여념이 없어 여기까지는 신경을 못 쓰나 봅니다.) 아무튼요. 이번 장에서는 파이썬의 멋진 기능인 리스트 컴프리헨션(list comprehension), 사전 컴프리헨션(dictionary comprehension), 집합 컴프리헨션(set comprehension)에 대해 알아볼 것입니다. 컴프리헨션이라는 강력한 기능을 중심으로 한 연관 기능이죠. 하지만 먼저 파이썬 3에서 파일 시스템의 디렉토리와 파일을 다루는 방식에 대해 짚고 넘어가는 것이 좋을 것 같습니다.

Notice

이 챕터에는 OS의 내부 명령어를 다루는 예제가 많으므로 상당수의 코드가 CodeOnWeb에서 실행되지 않을 수 있습니다. 여기 나오는 리스트, 사전, 집합은 앞으로 계속 쓰이는 개념이니, 코드 실행은 하지 않더라도 한 번 읽어 보시는 것이 좋습니다. 로컬 컴퓨터에서 직접 실습해보시면 물론 더 좋습니다.

파일과 디렉토리 다루기

파이썬 3에는 os라는 모듈이 있습니다. os는 운영체제(operating system)를 의미하는 Operating System 의 약자이지요. os 모듈을 이용해 디렉토리, 파일, 프로세스, 환경 변수 등과 같은 정보를 얻거나 때로는 조작할 수 있습니다. 파이썬은 지원하는 모든 운영체제에서 플랫폼에 상관없이 최대한 일관성 있는 API를 제공하기 위해 많은 노력을 기울였습니다.

현재 작업 디렉토리

이제 막 파이썬에 입문 하셨다면 아마 대부분 파이썬 쉘을 가지고 연습하고 계실 겁니다. 제가 이제부터 책 전반에 걸쳐서 다양한 예제를 선보일텐데요, 예제들은 대략 이런 식으로 진행됩니다.

  1. 모듈 하나를 import 합니다
  2. 그 모듈에 있는 함수 중 하나를 호출할 거구요
  3. 실행 결과를 설명합니다

현재 작업 디렉토리에 대해 아직 모르신다면 1번 단계에서 ImportError 오류가 발생할 가능성이 많습니다. 파이썬은 import하는 모듈을 import 검색 경로에서 찾는데, 모듈이 존재하는 폴더가 검색 경로에 없다면 당연히 오류가 발생할 수밖에 없겠지요. 두 가지 정도 방법이 있습니다.

  1. 모듈이 있는 폴더를 import 검색 경로에 추가합니다.
  2. 현재 작업 디렉토리를 모듈이 있는 폴더로 바꿉니다.

파이썬은 실행되는 동안 현재 작업 경로를 항상 메모리에 올려두고 필요할 때마다 참조합니다. 파이썬 쉘을 사용하든, 커맨드라인에서 스크립트를 실행하든, 웹서버에서 파이썬 CGI 스크립트를 들리든, 현재 작업 경로는 언제나 메모리에 상주합니다.

os 모듈을 현재 작업 경로를 다루는 두 가지 함수를 제공합니다.

  1. os 모듈은 파이썬과 함께 설치되는 모듈입니다. 언제, 어디서든 불러올 수 있습니다.
  2. 현재 작업 경로를 조회하려면 os.getcwd() 함수를 쓰면 됩니다. 그래픽 파이썬 쉘을 쓰고 계신다면, 현재 작업 경로는 파이썬 쉘의 실행파일이 있는 곳이 됩니다. 윈도우즈에서는 파이썬을 설치한 경로가 됩니다. 바꾸지 않았다면 C:\Python31과 같은 경로겠지요. 커맨드라인에서 파이썬 쉘을 실행했다면, 파이썬 쉘을 시작한 그 폴더가 현재 작업 경로로 지정됩니다.
  3. 현재 작업 경로를 바꾸려면 os.chdir() 함수를 호출합니다.
  4. 저는 윈도우즈를 쓰고 있지만, os.chdir() 함수의 인자는 리눅스 형식(역슬래쉬 대신 슬래쉬를 썼고, 드라이브 문자도 없지요)으로 주었습니다. 운영체제에 따라 작동 방식이 달라지지 않게 하기 위한 방편이지요.

파일명과 디렉토리명 다루기

우리가 디렉토리에 대해 배우는 동안은 os.path 모듈에 주목하세요. os.path 모듈은 파일과 디렉토리 이름을 다루기 위한 함수를 가지고 있습니다.

  1. os.path.join() 함수는 하나 이상의 파일과 디렉토리 경로를 조합하는 데 사용합니다. 이 경우는 두 문자열을 그냥 붙여주네요.
  2. os.path.join() 함수는 필요할 경우 역슬래쉬() 문자를 알아서 추가해줍니다. 그냥 슬래쉬(/)가 아니라 역슬래쉬인 이유는 제가 윈도우즈에서 실행했기 때문입니다. 이 예제를 리눅스나 맥 OS X에서 실행하면 슬래쉬가 추가될거에요. 슬래쉬에 신경쓰지 말고 그냥 os.path.join()을 사용하세요. 파이썬이 알아서 적절하게 처리하니까요.
  3. os.path.expanduser() 함수는 ~ 문자를 현재 사용자의 홈 디렉토리로 바꾸어줍니다. 사용자의 홈 디렉토리가 있는 리눅스, 맥 OS, 윈도우즈 같은 운영체제에서 잘 작동됩니다. 반환되는 디렉토리 경로 맨 끝에 슬래쉬(혹은 역슬래쉬)가 붙지 않지만, os.path.join() 함수를 사용하면 걱정하지 않아도 됩니다.
  4. 지금까지 배운 내용을 조합하면 사용자의 홈 디렉토리를 포함하는 경로를 쉽게 만들어 낼 수 있습니다. os.path.join() 함수에 넘겨주는 인자의 수에는 제한이 없습니다. 다른 프로그래밍 언어에서는 addSlashIfNecessary() 같은 함수를 직접 만들어 사용해야 했는데, 이 함수를 보고 얼마나 기뻤는지 모릅니다. 파이썬에서는 그런 짜증나는 함수를 더 이상 만들지 마세요.
    똑똑한 사람들이 이미 알아서 처리 해두었으니까요.

os.path는 경로를 각각의 디렉토리나 파일로 나누어 주는 함수도 가지고 있습니다.

  1. split 함수는 파일의 전체 경로를 인자로 받은 뒤, 파일과 그 파일이 속해 있는 디렉토리로 나누어서 튜플 형식으로 반환해줍니다.
  2. 함수가 여러 개의 값을 반환할 때 여러 개의 변수로 나눠 담을 수 있던 것 기억하세요? os.path.split()이 바로 그런 함수입니다. split 함수가 반환하는 파일과 경로명을 두 변수를 포함하는 튜플로 받을 수 있습니다. 순서 또한 그대로 지켜집니다.
  3. 첫 번째 변수인 dirname에는 os.path.split() 함수가 반환하는 튜플의 첫 번째 원소인 파일 경로가 담깁니다.
  4. 두 번째 변수인 filename에는 os.path.split() 함수가 반환하는 튜플의 두 번째 원소인 파일명이 담깁니다.
  5. os.path 모듈에는 os.path.splitext() 라는 함수도 있습니다. 확장자를 가진 파일명을 인자로 받은 뒤, 확장자를 제외한 파일 이름과 확장자를 튜플로 반환합니다.

디렉토리 조회하기

glob 모듈 또한 파이썬 표준 라이브러리 중 하나로 디렉토리 내용을 검색하는데 사용됩니다. glob 모듈 내의 함수에 와일드카드도 이용할 수 있으므로 더욱 간편하게 디렉토리 내용을 검색할 수 있습니다.

  1. glob 모듈은 와일드카드 문자를 입력으로 받아 이에 해당하는 모든 파일과 디렉토리 경로를 반환합니다. 이 예제는 examples 폴더 내에 있는 확장자가 xml인 모든 파일들을 반환합니다.
  2. 이제 현재 작업 디렉토리를 examples 디렉토리로 변경해볼까요. os.chdir() 함수의 인자로 상대 경로를 줄 수도 있습니다.
  3. glob의 인자로 여러 개의 와일드카드 문자를 사용할 수도 있습니다. 이 예제는 현재 작업 디렉토리에서 이름에 test가 포함되어 있고 확장자가 py인 파일의 리스트를 반환합니다.

파일 메타 정보(metadata) 조회하기

모든 현대적 파일 시스템은 각 파일의 메타 정보도 함께 저장합니다. 메타 정보는 파일 생성일, 최종 수정일, 파일 크기 등의 파일과 관련된 정보를 말합니다. 파이썬에서 이 메타정보를 조회하려ᄆᆫ API 하나만 알면 됩니다. 파일을 열고 닫을 필요도 없고 파일 이름만 알면 됩니다.

  1. 현재 작업 디렉토리는 examples 폴더입니다.
  2. feed.xml은 examples 폴더에 있는 파일입니다. os.stat() 함수를 호출하면 feed.xml 파일의 각종 메타 정보를 담은 객체 하나를 반환합니다.
  3. st_mtime 은 파일의 수정 시각을 담고 있는데, 반환 결과가 썩 눈에 들어오는 형식은 아니네요. (좀 전문적으로 말하자면, 결과로 반환되는 저 숫자는 1970년 1월 1일(Epoch라고 부릅니다)부터 현재까지의 시간을 초 단위로 환산한 값입니다. 계산해 보세요. 진짜예요.)
  4. time 모듈 역시 파이썬의 표준 라이브러리 중 하나입니다. 이 모듈은 시간을 다양한 형식으로 출력한다거나, 타임존과 관련된 여러 가지 일을 처리하는 데 필요한 함수를 가지고 있습니다.
  5. time.localtime() 함수는 Epoch부터 초로 측정되어 있는 시간(3번 결과값 같이요)을 보다 알아보기 쉬운 객체(년, 월, 일, 시, 분, 초 등을 포함)로 바꿔줍니다. 이 파일은 2009년 7월 13일 오후 5:25 경에 마지막으로 수정되었네요.

  1. os.stat() 함수는 파일의 크기도 st_size 속성에 담아 반환합니다. feed.xml 파일의 크기는 3070 바이트네요.
  2. st_size 속성을 approximate_size() 함수에 인자로 넘겨서 좀 더 읽기 쉬운 포맷으로 변경해볼 수도 있겠네요.

파일 절대 경로 구하기

앞서 glob.glob() 함수가 파일을 검색하는 예를 보았죠? 첫 번째 예의 반환값에는 'examples\feed.xml'가 있었고, 두 번째 예의 반환값에는 'romantest1.py'같은 상대 경로가 있었습니다. 현재 작업 경로를 바꾸지 않았다는 가정 하에서, 이런 상대 경로는 파일을 열거나 파일의 메타 정보를 얻는 데 사용할 수 있습니다. 하지만 작업 경로를 바꿀 필요가 있는 경우 상대 경로는 사용할 수 없습니다. (상대 경로는 현재 디렉토리를 기준으로만 유효하기 때문이지요.) 이럴 경우 os.path.realpath() 함수를 사용해서 절대 경로를 구할 수 있습니다.

리스트 컴프리헨션 (List Comprehension)

리스트 컴프리헨션을 이용하면 한 리스트의 모든 원소 각각에 어떤 함수를 적용한 후, 그 반환값을 원소로 가지는 다른 리스트를 쉽게 만들 수 있습니다. (이런 것을 매핑(mapping)한다고 합니다.)

  1. 리스트 컴프리헨션은 오른쪽에서 왼쪽으로 읽으면 이해하기 쉽습니다. a_list는 함수가 적용될 원본 리스트입니다. 파이썬 해석기는 a_list를 순회하면서 각 원소를 elem이라는 임시 변수에 한 번에 하나씩 할당합니다. 할당된 각 elem 변수를 elem * 2 라는 함수에 적용한 후 그 결과를 새로운 리스트에 추가합니다.
  2. 리스트 컴프리헨션은 결과값으로 새로운 리스트를 반환합니다. 원래 리스트는 변경하지 않습니다.
  3. 결과값을 계속 사용하려면 변수에 저장해 두는 것이 좋습니다. 이 예에서는 기존 a_list가 리스트 컴프리헨션에 의해 반환된 리스트로 바뀌었습니다.

리스트 컴프리헨션에는 어떠한 파이썬 표현식도 사용 수 있습니다. os 모듈의 파일과 디렉토리를 다루는 함수도 리스트 컴프리헨션 안에서 사용할 수 있습니다.

  1. 현재 작업 디렉토리에 있는 파일 중 확장자가 .xml인 파일을 리스트 형태로 반환합니다.
  2. 확장자가 .xml인 파일의 목록을 가져와서 각 파일을 절대 경로를 원소로 가지는 리스트를 반환합니다.

리스트 컴프리헨션을 이용해 한 리스트에서 원하는 아이템만 뽑아낼(filter) 수도 있습니다.

  1. 원하는 아이템을 뽑아내려면 리스트 컴프리헨션 뒤에 if 문을 사용합니다. 리스트의 각 아이템이 사용될 때 if에 주어진 조건을 만족하는지 확인하고, 그 결과가 참인 경우만 새로운 리스트에 그 아이템을 포함합니다. 이 예에서는 .py라는 확장자를 가진 모든 파일 중 그 크기가 6000 바이트가 넘는 아이템만 결과로 반영합니다. 그런 파일이 6개가 있군요.

지금까지 본 예제는 숫자를 곱하거나, 단일 함수를 호출하는 등 비교적 간단한 것이었습니다. 좀 더 복잡한 예제도 보죠.

  1. 이 리스트 컴프리헨션은 현재 작업 경로에 있는 확장자가 .xml인 파일을 모두 찾고, 그 파일의 크기(os.stat() 호출)와 절대 경로(os.path.realpath() 호출)로 구성된 튜플을 만든 후 리스트로 반환합니다.
  2. 이 예제는 앞의 예제와 비슷한데, 반환되는 파일의 크기에 approximate_size() 함수를 적용하고 있습니다.

사전 컴프리헨션 (Dictionary Comprehension)

사저 컴프리헨션은 리스트 컴프리헨션과 거의 흡사합니다. 차이점은 실행 후 결과로 리스트 대신 사전을 반환하는 것 뿐입니다.

  1. 이것은 그냥 리스트 컴프리헨션입니다. 파일명에 test라는 문자열이 포함된 모든 python 파일(.py)을 찾은 뒤, 파일명과 메타데이타(os.stat() 함수를 사용)의 튜플을 원소로 가지는 리스트를 반환합니다. metadata 라는 변수에 이 리스트를 저장합니다.
  2. 리스트 내의 각 아이템은 튜플입니다.
  3. 이것이 사전 컴프리헨션입니다. 리스트 컴프리헨션과 비슷하지만 두 가지 차이가 있습니다. 먼저 리스트 컴프리헨션은 대괄호([])로 감싸는 데 반해, 사전 컴프리헨션은 중괄호({})로 감쌉니다. 두 번째 차이는, 리스트 컴프리헨션에서는 각각의 아이템에 대해 하나의 표현식을 사용하지만, 사전 컴프리헨션에서는 :(colon)을 기준으로 두개의 표현식을 사용합니다. 즉 하나는 키(key)이고 다른 하나는 값(value)입니다. 이 예에서는 f가 키, os.stat(f)가 값입니다.
  4. 사전 컴프리헨션은 사전을 반환합니다. (당연하겠죠.)
  5. 결과로 반환된 사전 키는 glob.glob('test.py') 호출을 통해 반환 된 파일명이네요.
  6. 결과로 반환된 사전의 값은 os.stat() 함수를 통해 반환된 값입니다. 그 말은, 이제 파일이름을 키로 해서 해당파일의 메타 정보를 손쉽게 조회할 수 있있다는 것입니다. 그 예 중 하나는 st_size를 조회하는 것이겠군요. alphameticstest.py 파일의 크기는 2509 바이트입니다.

리스트 컴프리헨션에과 같이 사전 컴프리헨션에서도 if 문을 추가하여 결과값에 필터를 적용할 수 있습니다.

  1. 이 예제의 사전 컴프리헨션은, 현재 작업 디렉토리에 있는 모든 파일의 이름과(glob.glob('*')) 메타 정보(os.stat(f))를 각각 키와 값으로 가지는 사전을 만듭니다.
  2. 이 사전 컴프리헨션은, 1에서 생성된 metadata_dict 사전의 아이템 중에서 파일의 크기가 6000 바이트 보다 큰(if meta.st_size > 6000) 파일만 고른 후, 확장자를 제외한 파일명(os.path.splitext(f)[0])과 대략적인 파일 크기(human size.approximate_size(meta.t_size))를 키와 값으로 하는 사전을 반환합니다.
  3. 이 사전에는 6개의 아이템이 있네요. 6000 바이트 보다 크기가 큰 파일이 6개라는 건 좀전에도 확인했죠?
  4. 각 키에 해당하는 값은 approximate_size() 함수에 의해 반환된 문자열입니다.

사전 컴프리헨션을 이용한 다른 재미있는 것들

알고 있으면 언젠가는 유용하게 쓸 테크닉 하나를 소개합니다. 사전의 키과 값을 서로 바꿔야 할 때 다음과 같이 해보세요.

물론 사전 내의 값이 문자열이나 튜플처럼 변하지 않는(immutable) 값일 때만 쓸 수 있습니다. 만약 리스트를 값으로 가지는 사전에 이 테크닉을 쓰면 오류가 발생합니다. (리스트는 mutable이죠.)

집합 컴프리헨션 (Set Comprehension)

집합도 자체 컴프리헨션 문법이 있습니다. 사전 컴프리헨션과 아주 흡사하죠. 유일하게 다른 점은 키:값 쌍 대신 값만 가진다는 것입니다.

  1. 집합 컴프리헨션은 집합을 입력으로 받을 수 있습니다. 이 예제의 집합 컴프리헨션은 0부터 9까지의 숫자를 가진 집합의 각 원소에 제곱을 합니다.
  2. 집합 컴프리헨션도 다른 컴프리헨션과 마찬가지로 if 문을 사용해 결과를 필터링할 수 있습니다.
  3. 입력이 꼭 집합일 필요는 없습니다. 어떤 시퀀스(이 예에서는 리스트)라도 가능합니다.

더 공부할 내용

이 강의는 영어로 된 원문한글화 프로젝트를 기초로 작성되었으며, Creative Commons Attribution Share-Alike 라이센스하에 자유로운 변경, 배포가 가능합니다

11881 읽음
이전 챕터 2. 고유 자료형
다음 챕터 4. 문자열(Strings)

저자

토론이 없습니다

Please log in to leave a comment

16.5.11618.20190612.allo
문제 보고 · 사용조건 · 개인정보보호
래블업 주식회사 · 대한민국 서울 테헤란로 145 · 대표: 신정규 · 사업자번호: 864-88-00080 · +82 70-8200-2587

거절 확인

닫기
좋아요 책갈피 토론

아래 주소를 복사하세요