IT/컴퓨터 > Python (4개의 글)

파이썬으로 영문 문자열 모든 대소문자 조합 구하기 

2020. 10. 23 IT/컴퓨터 > Python
홈페이지에 검색 기능을 구현하다 보니 한 가지 문제가 생겼는데, 영문 검색의 경우 입력한 키워드의 대소문자가 일치하지 않으면 검색이 되지 않는 것이었다. 예를 들어 글 제목을 검색할때 Hello 라고 검색하면 hello 라는 문자열이 들어 있는 경우에는 같은 단어인데도 검색이 되지 않는 식이다. 이런 동작은 바람직하지 않으므로 개선을 해 주고 싶었다. 물론 유저가 Hello, hello 두번 검색을 해 볼수도 있겠지만 꼭 첫글자만 대문자라는 법도 없고, 모든 경우의 수 (이 경우 2^5 = 32가지)를 사용자가 일일이 검색한다는것은 생각하기 어렵다. 따라서 입력한 키워드를 이용해 가능한 모든 영문 대소문자 조합을 만들고, 이를 모두 검색해서 일치하는 결과값이 있으면 표시해주기로 했다. 처음엔 for 반복문을 이용해서 구현해보려고 했지만 쉽지 않았다. 검색을 해 보니 다행히 파이썬은 itertools 라는 훌륭한 도구를 제공하고 있어 그것을 이용한 방법을 기술해 보겠다. 내용은 stack overflow의 아래 문답을 기초로 했다. https://stackoverflow.com/questions/11144389/find-all-upper-lower-and-mixed-case-combinations-of-a-string   일단 결론부터 써 보면   이렇게 하면    이렇게 'fox' 라는 단어의 각 글자를 대소문자로 조합한 결과를 모두 리턴받게 된다.   아래는 각 단계별로 하나하나 공부해 본 내용.   파이썬의 itertools 모듈에는 여러가지 조합을 만들어낼 수 있는 다양한 메서드를 제공하고 있는데, 내용이 많으므로 다른 것들은 생략하고.. product() 메서드에 대해서만 알아보면,  이 함수는 전달받은 임의의 개수의 인자에 대해서 각 인자의 원소들로 데카르트 곱(Cartesian product)을 구해 이를 튜플 형식의 원소를 가진 반복자로 반환해준다.   라고 써놓고 보니 오히려 더 복잡한데, 실제 동작을 보면 그렇게 어렵지는 않다. 데카르트 곱이라는것은 어떤 집합이 있을때 각 집합에서 원소를 하나씩 골라서 각각의 원소로 이루어진 쌍을 모두 구한다는 것이다.  즉 집합 A = {a,b}, 집합 B = {1,2,3} 이라고 할때 데카르트 곱을 A x B 라고 하면 결과는 A x B = {(a,1), (a,2), (a,3), (b,1), (b,2), (b,3)} 가 되는 것이다.   만약 집합 A, B, C 가 각각 A = {a, b}, B = {1,2}, C = {가,나} 라면 A x B x C 는 A x B x C = {(a,1,가), (a, 1, 나), (a, 2, 가), (a, 2, 나), ......... , (b, 2, 가), (b, 2, 나)} 가 될 것이다.   itertools.product() 메서드는 바로 이 동작을 하는 것이다. 즉 어떤 단어를 이루는 글자들의 모든 대소문자 조합을 구하려면, 글자 하나하나의 대소문자 쌍을 원소로 하는 집합들을 만들어 각 집합들에 대해 데카르트 곱을 구하면 되겠다. 원하는 단어가 'fox' 라면, (f, F), (o, O), (x, X) 세 집합의 데카르트 곱을 구하면 되는것이다.   위의 코드를 다시 한줄한줄 살펴 보면, 제너레이터 표현식을 이용해 fox라는 단어를 이루는 문자들의 대소문자를 원소로 갖는 튜플들을 생성해준다. 제너레이터를 리스트로 만들어 프린트해보면 원하는대로 각각의 글자의 대소문자 튜플들이 생성되어 있다.   튜플 언패킹을 이용해 제너레이터 안의 원소들을 itertools.product()에 전달해주면 각각의 원소들의 데카르트 곱을 구해준다. 프린트해보면 f, o, x 각각의 대소문자에 대한 8가지 조합을 모두 만들어준 것을 알 수 있다. 즉 위처럼 튜플을 언패킹해주는 것은 다음과 같은 동작이다.   이렇게 만들어진 letters 객체의 각 원소 튜플에서, 글자들을 join() 함수를 이용해 연결해주고, 즉 이렇게 조합된 단어들을 리스트 컴프리헨션을 이용해 리스트로 만들어 준다.     이렇게 해서 함수 리턴값으로 원래 단어에 대해 가능한 모든 대소문자 조합을 받아서, 사이트 검색시에 사용자가 무슨 단어를 입력했든지 대소문자 상관없이 검색해주는 기능에 이용하였다.   파이썬 반복자는 결과물이 바로 눈에 보이는것이 아니라 필요할때마다 하나씩 꺼내 쓰는 식이라 처음에는 직관적으로 이해하기가 어려운것같다. itertools 내의 함수들의 동작도 이와 관련되어 좀 머리가 아프지만 잘 이해하고 사용하면 복잡한 코딩을 피하고 요긴하게 쓸 일이 있을 듯하니 조금 더 공부를 해 봐야겠다.

파이썬 문자열(string) 다루기 - 메서드 정리 

2020. 04. 23 IT/컴퓨터 > Python
  파이썬을 다루다 보면 가장 많이 하게 되는 작업 중 하나가 문자열을 다듬는 작업일 것이다. 문자열 관련된 메서드들을 정리해 본다. 파이썬의 문자열은 작은따옴표(' ') 또는 큰따옴표(" ") 사이에 원하는 문자열을 입력해서 생성할수 있고, 여러 줄로 된 문자열을 한번에 생성하고 싶은 경우 작은따옴표나 큰따옴표 3개를 시작과 끝 부분에 입력해서 생성할 수 있다.   파이썬의 문자열은 불변(immutable) 이므로 메서드가 원래의 문자열을 변경하는 경우는 없다. 메서드는 항상 새로운 문자열을 반환하므로 이 새로운 문자열을 저장해 줄 새로운 변수를 마련해 주어야 한다. 예전에 어떤 함수 안에서 text = text.replace('/', '') 식으로 새로 변경된 문자열을 다시 변수에 할당해 주어야 하는데 무의식중에 원래의 텍스트를 변경하는 것으로 생각했는지 text.replace('/', '') 라고만 적어놓고선 프로그램이 작동하지 않아서 한참 고생한 적이 있다.   str.capitalize() 문자열의 첫 글자만 대문자로 만들고 나머지는 소문자로 된 문자열을 반환한다. 이때 첫 문자가 영문 알파벳이 아닌 경우는 변환되지 않은 원래 문자 그대로이며 나머지 문자들만 소문자로 바뀐다.   str.lower() 원래 문자열에서 영문 알파벳 문자를 모두 소문자로 바꾼 문자열을 반환한다. 영문 알파벳이 아닌 문자는 무시한다.   str.upper() 원래 문자열에서 영문 알파벳 문자를 모두 대문자로 바꾼 문자열을 반환한다. 영문 알파벳이 아닌 문자는 무시한다.   str.title() 문자열을 타이틀 규칙에 맞게 바꾼 새 문자열을 반환한다. 영문 알파벳이 아닌 글자 다음에 나오는 첫 알파벳 문자는 대문자로, 알파벳 문자 뒤에 나오는 알파벳 문자는 소문자로 바꾸는 것이다. 보통은 공백 문자 다음에 나오는 알파벳을 바꾸는 목적이지만, 공백문자가 아닌 어포스트로피(') 등의 문자 뒤에 따라오는 문자도 대문자로 바꾸어 버리기 때문에 그다지 쓸모있는 메서드는 아닌것 같다. 공식 문서에서는 정규표현식을 사용해 딱 빈칸 뒤에 오는 알파벳만 대문자로 바꾸는 방법이 있다고 소개하고 있지만 그렇게 하면 배보다 배꼽이 더 커지는것같다..   str.casefold() 전부 소문자로 된 문자열을 반환한다. str.lower()와 비슷하지만 더욱 엄격하게 적용된다고 하는데, 영어가 아닌 다른 언어에서 차이점을 나타내는 경우가 있는 듯하다. 예를 들어 독일어 ß 의 경우 lower() 로는 그대로 ß 이지만 casefold() 를 적용하는 경우 'ss' 로 바뀐다. 영어 이외의 라틴어권 문자를 다룰 경우 쓸 일이 있을지도 모르겠다.   str.swapcase() 영문 대문자는 소문자로, 소문자는 대문자로 바뀐 문자열을 반환한다. 영문 알파벳이 아닌 문자는 변하지 않는다.     str.center(width[, fillchar]) width 에 지정해준 길이만큼의 빈 문자열을 만들고 원래의 문자열을 이 가운데 위치시킨다. 옵션으로 fillchar 를 지정해 주면 빈 문자 대신 fillchar 로 공간을 채운다. width 가 원래 문자열의 길이보다 작게 지정되었다면 원래의 문자열을 그대로 반환한다.   str.ljust(width[, fillchar]) 원래의 문자열을 왼쪽 정렬하고 width 로 주어진 길이만큼 공백 문자로 채운 문자열을 반환한다. fillchar를 따로 지정해주면 공백 대신 그 문자로 공간을 채운다. width 가 원래 문자열의 길이보다 작은 숫자이면 원래 문자열을 반환한다.   str.rjust(width[, fillchar]) 원래의 문자열을 오른쪽 정렬하고 width 로 주어진 길이만큼 공백 문자로 채운 문자열을 반환한다. fillchar를 따로 지정해주면 공백 대신 그 문자로 공간을 채운다. width 가 원래 문자열의 길이보다 작은 숫자이면 원래 문자열을 반환한다.     str.count(sub[, start[, end]]) 특정 문자열이 원래 문자열 안에서 몇 번이나 등장하는지를 알려 준다. 옵션으로 start와 end를 지정할 수 있는데, 일반적인 문자열 슬라이스 할때와 같이 인덱스를 지정해주면 된다. start와 end를 지정해주면 그 범위 안에서만 검색한다.     str.startswith(prefix[, start[, end]]) 문자열이 prefix로 주어진 문자열로 시작되는지 검사해서 True 또는 False 를 반환해준다. Prefix 는 하나의 문자열일수도 있고, 튜플로 여러 문자열을 검사하도록 할 수도 있다. 튜플로 검사하는 경우 원소 중 하나만 일치하면 True가 반환된다. 옵션으로 start, end 인덱스를 주면 start 인덱스에서 검사를 시작하고 end 인덱스까지만 검사한다.   str.endswith(suffix[, start[, end]]) 원래 문자열이 suffix 로 주어진 문자열로 끝나는지를 검사해서 True 또는 False 를 반환한다. Suffix 는 하나의 문자열일수도 있고, 튜플로 여러 문자열을 검사하도록 할 수도 있다. 튜플로 검사하는 경우 원소 중 하나만 일치하면 True가 반환된다. 옵션으로 start, end 인덱스를 주면 start 인덱스에서 검사를 시작하고 end 인덱스까지만 검사한다 (오른쪽부터).     str.find(sub[, start[, end]]) 원래 문자열에서 sub 으로 주어진 문자열을 찾는다. sub 문자열이 있는 경우 그 문자열이 시작되는 지점의 index를 반환해준다. sub 문자열을 찾지 못한 경우 -1 이 반환된다. 만약 문자열이 시작되는 위치를 찾고 싶은것이 아니라 그냥 문자열이 포함되어 있는지 여부만 알고 싶다면 find() 대신 'in' 연산자를 사용하는 것이 더 간단할 수도 있다.   str.index(sub[, start[, end]]) find() 와 같은 기능이지만, sub 문자열이 없는 경우 ValueError 를 일으킨다.   str.rfind(sub[, start[, end]]) 왼쪽에서부터 검색하는 find() 와 반대로, 원래 문자열에서 sub 으로 주어진 문자열을 오른쪽에서부터 찾아 인덱스를 반환한다 (즉 가장 마지막에 sub 문자열이 나오는 곳의 인덱스). sub 문자열을 찾지 못한 경우 -1 이 반환된다.   str.rindex(sub[, start[, end]]) rfind() 와 같은 기능이지만, sub 문자열이 없는 경우 ValueError 를 일으킨다.     str.isalnum() 문자열이 알파벳과 숫자로만 이루어져있으면 True, 아니면 False 를 반환한다. 구체적으로는 문자열의 각 글자가 isalpha(), isdecimal(), isdigit(), isnumeric() 중 하나에 해당하면 해당 글자는 isalnum() 에 대해서 True 이다. 한글로 해 보면 한글도 True를 반환한다.   str.isalpha() 문자열이 알파벳으로만 이루어져 있으면 True, 한 글자라도 다른 글자가 들어 있으면 False를 반환한다.   str.isascii() 파이썬 3.7 에서 새로 생긴 메서드. 문자열의 모든 문자가 ASCII 문자이면 True, 아니면 False를 반환한다. 공백 문자도 True를 반환한다.   str.isdecimal() 문자열의 모든 문자가 10진수 숫자이면 True, 아니면 False를 반환한다.   str.isdigit() 문자열의 모든 문자가 숫자로만 이루어져 있으면 True, 아니면 False를 반환한다. 10진수를 구성하는 0~9까지의 숫자 이외에도 다른 언어 체계에서 숫자를 나타내는 특별한 문자들도 True를 반환한다고 하는데, 공식 홈페이지에서는 Kharosthi 문자라는걸 예로 들고 있어서 찾아 보았더니 고대 인도(?) 문자라고 한다..이런걸 쓸 일이 있을지.. 아마 일상적인 용도에서는 isdecimal()과 똑같은 용도로 쓰면 될 듯.   str.isnumeric() 문자열의 모든 문자가 numeric 문자이면 True, 아니면 False를 반환한다. isdigit() 에 포함된 문자 이외에도 Unicode에서 numeric 값을 가진 문자들이 모두 포함된다고 한다. 10진수를 다루는 일상적인 용도에서는 역시 isdecimal() 과 차이 없을듯.   str.islower() 문자열의 영문 문자가 모두 소문자이면 True, 아니면 False를 반환한다. 알파벳이 아닌 문자는 무시한다. 알파벳 문자가 하나도 포함되어 있지 않으면 False를 반환한다.   str.isspace() 문자열의 모든 문자가 공백 문자 ' ' 이면 True, 아니면 False를 반환.   str.istitle() 문자열이 title 형식에 맞으면 True, 아니면 False를 반환. 구체적으로는 영문 대문자 앞에는 대문자/소문자가 아닌 문자만 와야 하고 (대문자 앞에 공백문자가 오는 것이 일반적이지만, 한글 문자 등 알파벳이 아닌 문자가 있는 경우에도 True를 반환한다.), 영문 소문자 앞에는 꼭 영문 소문자 또는 대문자가 있어야 한다.   str.isupper() 문자열의 영문 문자가 모두 대문자이면 True, 아니면 False를 반환한다. 알파벳이 아닌 문자는 무시한다. 알파벳 문자가 하나도 포함되어 있지 않으면 False를 반환한다.     str.join(iterable) 반복 가능한 객체(리스트, 튜플 등)을 인자로 주면 객체의 각 원소를 주어진 string 을 이용해 연결해서 새로운 문자열을 반환해 준다. 반복 객체의 원소가 문자열 타입이 아닌 경우에는 TypeError 가 발생한다.   str.split(sep=None, maxsplit=-1) 주어진 문자열을 sep 문자열이 나오는 곳마다 잘라서 잘린 문자열들로 이루어진 리스트를 반환한다. sep을 따로 지정하지 않으면 공백 문자가 나오는 곳마다 자른다. 이 때 공백문자가 연속해서 나오는 것은 무시하고 공백문자를 제거하고 잘려서 남은 문자열들만 리스트의 원소로 집어넣는다. sep을 지정해주면 이 문자열을 기준으로 자르는데, 주어진 문자가 연속해서 나오는 경우에 연속된 sep 문자 사이사이에 빈 문자열이 있는것으로 간주해 빈 문자열 (공백 문자가 아니라 empty string) 를 리스트의 원소로 집어넣는다. (즉 default 인 공백 문자를 기준으로 split할때와 동작이 다름) maxsplit 을 지정해주면 왼쪽부터 정해진 횟수만큼만 자르는 작업을 해서 리스트로 반환한다.   str.rsplit(sep=None, maxsplit=-1) 주어진 문자열을 sep 문자열이 나오는 곳마다 잘라서 잘린 문자열들로 이루어진 리스트를 반환한다. sep을 따로 지정하지 않으면 공백 문자가 나오는 곳마다 자른다. maxsplit 을 지정해주면 오른쪽부터 정해진 횟수만큼만 자르는 작업을 해서 리스트로 반환한다. 오른쪽부터 자르는 점만 제외하면, 왼쪽부터 잘라 나가는 split() 와 동작은 동일하다.   str.splitlines([keepends]) 문자열 중에 개행 문자를 찾아 이를 기준으로 문자열을 잘라서 리스트로 반환해준다. 디폴트는 개행 문자는 제거한 리스트를 반환해 주지만, keepends=True 를 주면 개행문자가 포함된 원소들로 이루어진 리스트를 반환해준다. 자르는 기준이 되는 개행 문자들은 다음과 같다. \n : Line Feed \r : Carriage Return \r\n : Carriage Return + Line Feed \v or \x0b : Line Tabulation \f or \x0c : Form Feed \x1c : File Separator \x1d : Group Separator \x1e : Record Separator \x85 : Next Line (C1 Control Code) \u2028 : Line Separator \u2029 : Paragraph Separator     str.strip([chars]) 문자열의 좌우에 있는 공백 문자들을 제거한 문자열을 반환해준다. 따로 chars 문자열을 지정해주면 해당 문자열의 문자들로 조합할 수 있는 모든 문자열에 대해 왼쪽과 오른쪽에서 제거를 시도한다.   str.lstrip([chars]) 원래 문자열에서 왼쪽에 있는 공백 문자를 전부 제거한 문자열을 반환한다. 문자열을 인수로 전달하면 공백문자 대신 해당 문자열을 제거한다. 이 때 정확히 해당 문자열을 제거하는것이 아니고, 주어진 문자열을 조합해서 나올 수 있는 모든 문자열에 대해 제거를 시도한다. (메서드를 사용하기 위해서 문자의 조합이 어떤 것이 나올수 있는지 계산해 봐야 하는것인가..) 유용하게 쓰는 경우는 웹에서 텍스트를 크롤링했을때 원하는 텍스트 앞뒤로 빈칸이 있는 경우가 많은데 이때 lstrip(), rstrip(), strip() 등을 이용해 빈칸이 제거된 깔끔한 문자열을 얻을수 있다.   str.rstrip([chars]) 원래 문자열에서 오른쪽에 있는 공백 문자를 전부 제거한 문자열을 반환한다. 문자열을 인수로 전달하면 공백문자 대신 해당 문자열을 제거한다. 이 때 정확히 해당 문자열을 제거하는것이 아니고, 주어진 문자열을 조합해서 나올 수 있는 모든 문자열에 대해 제거를 시도한다. 오른쪽에서 제거하는 점을 빼면 lstrip() 과 같다.     str.partition(sep) sep 으로 주어진 문자열이 처음 나오는 곳에서 문자열을 3개로 잘라 튜플로 반환해 준다(sep 의 앞부분, sep, sep의 뒷부분). 원래 문자열에 sep 문자열이 없는 경우에는 원래 문자열과 빈 문자열 2개로 된 튜플을 반환한다.   str.rpartition(sep) sep 으로 주어진 문자열이 처음 나오는 곳에서 문자열을 3개로 잘라 튜플로 반환해 준다(sep 의 앞부분, sep, sep의 뒷부분). 원래 문자열에 sep 문자열이 없는 경우에는 빈 문자열 2개와 원래 문자열로 된 튜플을 반환한다.     str.replace(old, new[, count]) 문자열 안에서 특정 문자열을 전부 찾아 새로운 문자열로 바꿔 준다. 개인적으로 프로젝트 관련해서 디렉토리 내의 파일 이름을 대량으로 바꾸는 작업을 할 때가 가끔 있는데 이때 유용하게 쓰는 메서드이다. 옵션으로 count 를 주면 count 에 주어진 횟수만큼만 바꾸고 작업을 멈춘다.   str.zfill(width) 원래의 문자열 왼쪽에 숫자 '0' 을 채워 width 만큼의 길이를 가진 문자열을 만들어 반환한다. width 가 원래 문자열 길이보다 작으면 원래 문자열을 그대로 반환한다. 특이점은 문자열이 + 또는 - 기호로 시작하는 경우에는 숫자 0 을 맨 왼쪽부터 채우는 것이 아니고 + 또는 - 기호가 가장 먼저 나오고 그 다음 0을 채우고, 그 후에 원래 문자열의 나머지 부분이 나온다.   이상으로 파이썬 문자열 관련된 메서드들을 정리해 보았다. 이외에도 몇가지 메서드들이 더 있지만 유니코드 등 관련된 전문적인 작업을 하는 경우가 아니면 별로 쓸 일이 없을것같아 생략했다. 문자열 포매팅 관련된 format() 메서드는 내용이 많아 나중에 따로 정리해 보려고 한다.

파이썬 datetime 모듈로 날짜, 시간 다루기 

2020. 04. 03 IT/컴퓨터 > Python
파이썬으로 코딩을 하다 보면 날짜, 시간에 관련 처리를 할 일이 많다. 이때 사용하게 되는것이 datetime 모듈이다. 시간을 처리하는 다른 모듈은 time 모듈이 있는데, time 모듈은 좀 더 근본적으로 시간에 관한 여러 가지 연산을 지원하고, datetime 은 기본적인 연산들은 지원하지만 주로 시간이나 날짜를 사용자에게 보여지는 형태로 포매팅하는데 중점을 둔 모듈이라고 한다.   Datetime 모듈의 하위 클래스는 datetime.date datetime.time datetime.datetime datetime.timedelta datetime.tzinfo datetime.timezone 이 있다. 시간대 적용은 pytz 모듈로 하는것이 편하므로 이 중 tzinfo 와 timezone 은 생략하고 나머지 모듈에 대해서 정리해 본다. 더 자세한 내용은 공식 문서 참조.   #1. datetime.date 이 클래스의 객체는 년월일 정보를 담고 있는 객체이다. 특정한 날짜의 객체를 만들려면 년, 월, 일을 전달해주면 되고, 오늘 날짜를 얻으려면 today() 를 이용한다.   today() 에서는 현재 작업중인 시스템의 로컬 날짜가 반환되게 된다.   date 객체에서 년, 월, 일 을 추출하려면 객체의 year, month, day 속성을 가져오면 된다.  와 같이 한다. 이때 반환되는 값은 int 타입이다.   date.weekday() 는 요일 정보를 숫자로 반환해준다. 월요일이 0이고 순서대로 올라가 일요일이 6이다. date.isoweekday() 도 같은 기능인데, 차이점은 월요일이 1이고 일요일이 7이다.   년월일 정보가 YYYY-MM-DD 식으로 포매팅된 것을 ISO 포맷이라고 하는데 이런 string을 date 객체로 변환하려면  과 같이 하면 된다. 이때 월/일 숫자가 한자리수인 경우 앞에 0 이 붙어있지 않으면 제대로 처리가 안된다. (즉 2020-01-01 은 되지만 2020-1-1 은 처리가 안됨) 반대로 날짜 객체를 ISO 포맷의 문자열로 반환하려면 와 같이 할 수 있다.   date.replace()를 이용하면 객체의 년월일 정보를 바꿀수 있다. date.strftime() 메서드를 이용해 객체를 다양한 방법으로 문자열로 변환할 수 있다. strftime() 포매팅 관련 옵션은 포스트 하단 참조.     #2. datetime.time 이 클래스의 객체는 특정 날짜와 무관한 시간 정보를 담고 있는 객체이다.  특정 시간의 객체를 생성하려면 인자를 직접 전달해주면 된다. 인자를 따로 전달하지 않으면 디폴트 값이 전달된다. hour는 0~23, minute은 0~59, second는 0~59, microsecond는 0~999,999 까지의 정수 값이 허용된다. fold는 서머타임 등 물리적으로 같은 시간에 대해 2가지 다른 시간이 표시될때 조정해주는 값으로 0 또는 1이 사용되는데, 보통은 기본값 0으로 두면 되는듯하다. tzinfo는 시간대 정보를 전달해 준다. 시간대는 별도의 pytz 모듈을 이용해서 지정해주면 편하다. pytz는 따로 설치를 해주어야 한다. pytz에서 지원하는 모든 시간대 정보는 로 확인할 수 있다. 파이썬 날짜/시간 관련 개체는 크게 naive와 aware 객체로 구분할수 있다. 간단히 말하면 시간대 정보를 담고 있으면 aware, 시간대 정보가 담겨있지 않으면 naive 객체이다. 그런데 naive 객체는 시간대 정보가 없어 혼란을 초래할 수 있으므로 가능하면 모든 객체를 시간대를 지정해 aware 객체로 만들어 주는것이 좋다. 그리고 특별한 경우가 아니면 모든 시간대는 UTC 로 통일하고, 특정 지역 사용자에게 보여질때 해당 시간대로 변환해서 보여주는것이 좋다고 한다.     어떤 time 객체의 시, 분, 초, 마이크로초 및 시간대 정보는 각 속성을 입력하면 확인할 수 있다. 여기서 나머지는 int type으로 숫자를 반환하고, tzinfo는 pytz.timezone 객체를 반환한다. 만약 시간대의 이름을 문자열로 얻고싶다면 tzname() 메서드를 호출하면 된다.   date 객체에서와 마찬가지로 time.replace()를 이용하면 time 객체의 시간 정보를 바꿀 수 있다.   time.isoformat() 메서드는 시간 정보를 ISO 포맷의 문자열로 출력해준다. 다만 날짜 데이터와 달리 시간 데이터의 ISO 포맷은 읽기에 불편해 실제 사용할 일이 많지 않을것같다.   time.strftime() 은 date에서와 마찬가지로 다양한 옵션으로 사용자에게 읽기 편한 문자열 출력을 제공한다. 옵션은 역시 포스트 하단의 표 참조.     #3. datetime.datetime 이 클래스의 객체는 날짜와 시간 정보를 모두 담고 있는 객체이다.  객체를 생성하려면 으로 생성하면 된다. 생성된 객체의 년월일, 시분초 등 정보는 위에서와 같은 방법으로 확인할 수 있다.   UTC 기준으로 현재 시간을 담은 객체를 생성하려면 아래와 같이 한다. 현재 시각을 생성하는 메서드는 datetime.now() datetime.today() datetime.utcnow()  가 있는데, 아래의 2가지는 naive datetime 객체를 생성하므로 가능하면 아래 2가지보다 now()를 사용하는것이 권장되고, now()를 쓸 때 시간대 정보를 전달해서 aware 객체를 생성하는 것이 좋다.   date와 time 객체가 따로 존재한다면 datetime.combine() 을 이용해서 결합해줄 수 있다. 이때 별도로 시간대 정보를 전달해줄 수 있다.   또 datetime 객체에서 날짜 정보를 따로 객체로 만들려면 date()을 이용한다. 시간 객체는 time() 혹은 timetz() 이용하면 되는데, 후자는 시간대 정보를 포함하고 있다.   date나 time 객체에서와 동일한 방법으로 datetime.replace() 를 이용하면 날짜와 시간 정보를 수정할 수 있다. datetime.weekday()와 datetime.isoweekday()는 date 객체에서와 동일하다.   datetime.strptime() 을 이용하면 strftime() 에서 사용하는 포매팅 옵션을 이용해서 datetime 객체를 만들어줄 수 있다.   date나 time 과 동일하게 datetime.strftime() 을 이용해서 다양한 포맷으로 출력할 수 있다. 옵션은 포스트 하단 참조,     #4. datetime.timedelta 이 클래스의 객체는 두 날짜 혹은 시간의 차이를 나타내는 객체이다. 객체를 생성하려면 days, seconds, microseconds, milliseconds, minutes, hours, weeks 인자를 전달해주면 된다. 디폴트값은 0 이다. 실제 객체에 저장되는것은 days, seconds와 microseconds 이고, 나머지 인자들은 내부적으로 계산이 이루어져 변환된다. timedelta 개체끼리는 사칙연산과 크기 비교, 절대값 산출 등 기본적인 수학적 처리가 제공된다.   실제로는 timedelta 에 인자를 직접 전달하기보다는 두 datetime 객체의 차이를 구하는데 이용될 것이다. datetime 객체의 차이를 구하면 자동으로 timedelta 객체가 반환된다. datetime 객체끼리 더하는 연산은 지원되지 않아 TypeError 가 발생한다. 위의 예처럼 과거 날짜에서 현재 날짜를 빼는 연산도 가능하다. 그러나 과거 날짜에서 현재 날짜를 빼는것이 논리적으로 어떤 의미인지 명확하지 않고 계산도 복잡해지므로, 항상 현재에 더 가까운 날짜에서 오래된 날짜를 빼는것이 좋을 것같다.   datetime 객체에 timedelta 객체를 더하거나 빼서 특정 시간 이후나 이전의 시간을 구하는것도 가능하다.     이상으로 datetime 모듈 관련 주로 많이 쓰게 되는 내용들을 정리해 보았다. 아래는 strftime() 포매팅 관련 옵션.   %a 현재 로케일에 따른 요일 이름 약자 Sun, Mon, …, Sat (en_US); So, Mo, …, Sa (de_DE) %A 현재 로케일에 따른 요일 이름 Sunday, Monday, …, Saturday (en_US); Sonntag, Montag, …, Samstag (de_DE) %w 10진수로 표현된 요일 번호 (일요일이 0, 토요일이 6임) 0, 1, …, 6 %d 10진수로 표현된 날짜 (한자리수인 경우 0이 앞에 붙음) 01, 02, …, 31 %b 현재 로케일에 따른 월 이름 약자 Jan, Feb, …, Dec (en_US); Jan, Feb, …, Dez (de_DE) %B 현재 로케일에 따른 월 이름 January, February, …, December (en_US); Januar, Februar, …, Dezember (de_DE) %m 10진수로 표현된 월 (한자리수인 경우 0이 앞에 붙음) 01, 02, …, 12 %y 앞 2자리수룰 제외하고 10진수로 표현된 년도 (한자리수인 경우 0이 앞에 붙음) 00, 01, …, 99 %Y 10진수로 표현된 년도 0001, 0002, …, 2013, 2014, …, 9998, 9999 %H 10진수로 표현된 시간 (24시간제) 00, 01, …, 23 %I 10진수로 표현된 시간 (12시간제) 01, 02, …, 12 %p 현재 로케일에 따른 오전, 오후 표시 AM, PM (en_US); am, pm (de_DE) %M 10진수로 표현된 분 (한자리수인 경우 0이 앞에 붙음) 00, 01, …, 59 %S 00, 01, …, 59 %f 10진수로 표현된 마이크로초 (자리수에 따라 0이 앞에 붙음) 000000, 000001, …, 999999 %Z 시간대 (시간대 정보가 없는 naive 객체인 경우 None). (empty), UTC, EST, CST %j 10진수로 표현된 년중 날짜 (1월1일이 001 임) 001, 002, …, 366 %U 10진수로 표현된 년중 주수 (일요일을 한 주의 첫날로 정함. 처음으로 일요일이 포함된 주가 01주, 일요일이 포함되지 않은 날짜들이 속한 그 전주는 00주임) 00, 01, …, 53 %W 10진수로 표현된 년중 주수 (월요일을 한 주의 첫날로 정함. 처음으로 일요일이 포함된 주가 01주, 월요일이 포함되지 않은 날짜들이 속한 그 전주는 00주임) 00, 01, …, 53 %c 현재 로케일 설정에 따라 요일과 년월일, 시간을 출력 Tue Aug 16 21:30:00 1988 (en_US); Di 16 Aug 21:30:00 1988 (de_DE) %x 현재 로케일 설정에 따라 년월일을 출력 08/16/88 (None); 08/16/1988 (en_US); 16.08.1988 (de_DE) %X 현재 로케일 설정에 따라 시간을 출력 21:30:00 (en_US); 21:30:00 (de_DE)    

파이썬 가상환경이 필요한 이유와 사용법 (venv, virtualenv) 

2020. 03. 11 IT/컴퓨터 > Python
  파이썬에서 프로젝트를 진행할 때는 각각의 프로젝트 별로 가상환경을 만들어서 진행해 주는 것이 좋다. 사실 프로젝트라 할만한 것을 제대로 진행해 보기 전, 책을 보면서 코어 파이썬 문법을 배우고 예제만 따라해 보는 수준에서는 왜 굳이 귀찮게 가상환경을 설치하는것인지 잘 이해가 되지 않았다. 처음 파이썬을 배울때 아나콘다(Anaconda) 배포판을 이용해서 파이선을 설치했는데, 아나콘다를 설치하면 수백가지의 패키지가 같이 설치된다. 이후로도 이것저것 배우면서 설치한 패키지들이 많이 있었는데, 이렇게 한번 설치해 놓으면 패키지를 아무거나 필요한 대로 불러서 쓸 수 있는데 굳이 왜 가상환경을 만들어서 이미 설치했던 패키지를 또 설치하라는건지 이해가 되지 않았다. 그런데 Django를 배우면서 직접 프로젝트들을 한두 개 진행하다 보니 왜 가상환경이 필요한지 저절로 알게 되었다.    1. 프로젝트를 배포하면 원격 서버에 따로 패키지들을 설치해 줘야 하는데, 내가 이 프로젝트만을 위해서 설치한 패키지들이 어떤 것들이 있는지 알 수가 없었다. 내가 설치한 아나콘다 배포판에는 기본적으로 수많은 패키지가 포함되어 있고 그 환경에 내가 추가로 설치한 패키지들이 뒤섞여 있는 상태이다.  한편 배포하려는 서버에는 처음에는 아무것도 깔려 있지 않기 때문에 필요한 것들을 설치해 주어야 하는 상황이다. 그런데 유료로 사용하는 서버에 공간만 차지하고 쓰지도 않을 패키지들을 설치할 필요는 없으니 딱 내가 사용한 패키지들만 설치를 해야 하는데 그걸 구분하는게 문제였다. 물론 어떤 패키지를 설치할 때마다 따로 적어 놓는다든지 할 수도 있겠지만, 프로그래밍을 배운다는 입장에서 그런 원시적인 짓을 할수는 없지 않나. 그리고 pip 명령을 이용해서 어떤 패키지를 설치하면 딱 그 패키지만 설치되는게 아니고 딸려 있는 여러가지 이름이 다른 패키지들도 자동으로 설치가 되는 경우가 많기 때문에, 그런것들까지 다 확인한다는것은 사실상 불가능하다. 그리고 서버에 일일이 그 패키지들을 설치해주는 것이 혹시 가능하다 하더라도 정확히 내가 사용했던 버전과 버전이 맞는지 일일이 확인해야 할텐데, 그걸 수작업으로 한다는것도 역시 원시적이고 말이 안된다. 그래서 미리 가상환경을 만들어 내가 실제로 이 프로젝트에 사용한 패키지들만 설치를 해야 할 필요성을 느꼈다.   2. 그리고 시간이 지남에 따라 패키지들이 업데이트되는데, 이것저것 업데이트를 하다 보면 서로 의존적인 패키지들 사이에 버전이 맞지 않아 호환이 되지 않는 경우들이 생긴다. 그렇다고 특정한 프로젝트 하나를 위해 언제 지원이 끊길지 모르는 예전 버전의 패키지를 로컬에 계속 유지할 수도 없고, 패키지가 업데이트되어 호환성 문제가 생길 때마다 프로젝트의 코드를 일일이 수정하는 것도 실질적으로 불가능하다. 아예 마음먹고 프로젝트를 메이저 버전업을 하면서 갈아 엎어버리는 경우라면 모를까, 구 버전에서 잘 작동하는 프로젝트의 코드를 개별 패키지들의 마이너 버전업 때마다 체크하고 수정하는 것도 불가능하고, 가능하더라도 시간낭비인 경우가 많을것이다. 그래서 한 프로젝트를 위해 확실히 작동하는 버전의 여러 패키지들을 한데 모아서 관리하기 위해서도 가상환경이 필요하다.   3. 파이썬 버전 자체가 다른 환경인 경우도 있겠다. 사실 나는 처음부터 파이썬 3로 입문해서 로컬에서 작업할 때는 파이썬 3만 쓰기 때문에 문제가 없지만, 프로젝트를 배포하려는 서버에는 파이썬 2만 설치된 경우도 있고, 2와 3이 같이 있는 경우도 있는 등 다양한 환경이다. 그런데 예를 들어 최신버전의 Django 같은 경우에는 파이썬 2에서는 아예 동작을 하지 않기 때문에 명시해서 파이썬 3를 설치해 주고 파이썬 3에 맞는 가상환경을 설정해 주지 않으면 프로젝트 구동 자체가 불가능하다.   이외에도 여러가지 이유가 있을 수 있겠지만, 하여튼 연습으로 끄적거리는게 아니라 제대로 구동되는 프로젝트를 목표로 하는 경우라면 프로젝트와 그 프로젝트에 사용된 패키지들은 언제나 한 묶음으로 움직이는 것이 좋겠다. 가상환경을 구성해 주는 유틸리티도 여러가지가 있는 모양이지만, 아래의 virtualenv 혹은 venv 정도면 일단은 무난히 사용할 수 있는것 같다.   #1. Virtualenv 이전 버전의 파이썬부터 많이 사용하던 프로그램이다. 를 이용해서 설치하고, 그 virtualenv 를 이용해서 가상환경을 만들고 관리한다. 프로그램이 설치되면 식으로 원하는 환경의 이름을 지정해서 구동해 주면 가상환경이 만들어진다. 가상환경에 필요한 파일들이  현재 위치 아래의 project_env 라는 디렉토리 안에 설치가 되는 식이다. 특정한 디렉토리에 설치하려면 절대 경로를 전부 입력해 주면 된다. 경험상은 프로젝트 루트 디렉토리 바로 아래에 만들어주면 관리가 편한것 같다.   만들어진 가상환경을 활성화하려면 activate 명령을 입력해야 한다. 리눅스의 경우는 경우에는 project_env/bin 아래에 activate 명령어 파일이 존재하므로 다음과 같이 입력한다. 성공적으로 활성화가 된 경우에는 프롬프트 앞에 (project_env) 와 같이 가상환경의 이름이 표시되는 것을 볼 수 있다.   윈도우의 경우는 project_env/scripts 폴더 안에 activate 명령어 파일이 존재하므로 아래와 같이 입력하면 활성화가 된다. 마찬가지로 프롬프트 앞에 가상환경의 이름이 보인다.   이후 pip list 를 입력해 보면 기본 파이썬만 설치된 리스트를 확인할 수 있다. 가상환경에서 빠져나오는 경우에는 를 입력하면 된다.   #2. Venv 그런데 virtualenv 는 동작이 좀 불안정한 경우가 많은것 같다. 정확히 원인이 뭔지 아직 찾아내지 못했지만 내가 사용하는 데스크탑만의 문제라고 생각했는데 DigitalOcean 에서 만든 우분투 서버에서도 비슷한 일이 일어나는걸로 봐서는 뭔가 virtualenv 설치 과정이나 activation 과정에서 문제가 있는것 같은데.. Activate를 해도 (가상환경이름) 이 보이지 않는 경우도 있고 (이때 pip list 해보면 파이썬만 설치되어 있는 것으로 나오는 걸로 봐서는 뭔가 작동은 하는데 정상 작동이 아닌것같다), 이런 경우에 pip install 을 이용해서 무슨 패키지를 설치하면 설치가 되긴 되는데, 가상환경에 설치되지가 않고 바깥의 원래 환경에만 설치되는 현상이 발생한다. 즉 설치 후 pip list 를 해 보아도 가상환경 안에는 설치한 패키지가 추가되어 있지 않고, 다시 pip install 을 해 보려고 하면 패키지가 이미 설치되어 있다는 메세지만 나온다.  그리고 이런 현상이 일단 발생하면 deactivate를 해도 이전의 환경으로 돌아가지 않는다. Deactivate 해도 아무 에러 메세지는 발생하지 않는데, 이후에 pip list 해보면 정상적으로 가상환경이 종료되었다면 원래 환경에 설치된 수많은 패키지들이 보여야 하는데 하나도 안 보이고 기본 파이썬 패키지만 보인다. 일단 문제의 원인을 찾지 못해서 다른 대안을 찾아 보았는데, 다행히 파이썬 3.4 부터는 venv 라는 패키지가 기본으로 포함되어 있어서 따로 virtualenv를 설치하지 않아도 가상환경을 이용할 수 있다.   venv 를 이용해 가상환경을 만들려면 다음 명령어를 사용한다.   가상환경 활성화 시키는 방법은 virtualenv 와 동일하다. 리눅스: 윈도우:   venv 를 이용해 가상환경을 만든 경우에는 virtualenv 에서와 같은 문제를 아직 겪지 않았다. 어차피 파이썬에 내장된 기능이라면 굳이 virtualenv를 추가로 설치하는 번거로움을 피할 수도 있으니 요즘은 주로 venv 를 이용하고 있다.   #3. 가상환경에서의 패키지 관리 우선 로컬에서 가상환경을 활성화시키고 필요한 모든 패키지들이 설치되었으면, 어느 환경에서든 같은 패키지들이 한 묶음으로 설치되도록 requirements.txt 를 만들어 주는 것이 좋다. 일일이 손으로 적는게 아니라  를 입력하면 현재 환경에서 설치된 모든 패키지들의 이름과 버전이 명시된 requirements.txt 라는 파일이 자동으로 만들어진다.  그리고 원격 서버에서 이 파일을 이용해 패키지들을 일괄 설치하려면 여기서 -r 은 requirement 를 설치할때는 붙이라고 되어 있는데 공식 documentation 을 보아도 아주 속시원한 설명은 없는 듯하다. 각종 옵션에 이런것들이 많은데 다소 심오한 파이썬 언어의 코어에 가까운 이야기들이라 일단은 그냥 시키는대로 하자..   위의 명령어로 필요한 패키지들을 일괄 설치해 주면 로컬과 원격지에서 똑같은 환경으로 작업이 가능하다. 다만 requirement.txt 를 이용한 설치 동작은 서로 의존적인 패키지들간에 간섭이 있어 잘 설치되지 않거나 에러가 나는 경우도 종종 있는 것 같다. 이런 경우에는 프로젝트 안의 패키지들을 pip list 로 다시 확인해서 잘 설치되지 않은 패키지들은 번거롭지만 수작업으로 설치해줘야 할 때도 있다.   사실 처음에는 좀 번거롭다고 생각되기도 했는데, 익숙해지니 가상환경을 만들지 않고 작업하는 것은 생각하기 어렵게 되었다. 깔끔한 작업 환경을 위해서 항상 가상환경을 먼저 구성하도록 하자.
  • 1 (current)