Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 290 – 코드 마이그레이션과 현대화

Author:
Raymond Hettinger <python at rcn.com>
Status:
Active
Type:
Informational
Created:
06-Jun-2002
Post-History:


Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PEP는 더 새로운 버전의 Python이 설치되었을 때 Python 애플리케이션을 업데이트하기 위한 절차와 아이디어를 모아 놓은 것입니다.

마이그레이션 팁은 비호환성이 생길 수 있는 부분을 짚어 주고, 그러한 차이를 찾아내어 해결하는 방법을 제안합니다. 현대화 절차는 새로운 언어 기능을 활용하도록 오래된 코드를 갱신하는 방법을 보여 줍니다.

근거

이 절차 저장소는 알려진 마이그레이션 문제와 그 문제를 해결하기 위한 절차의 카탈로그 또는 체크리스트 역할을 합니다.

마이그레이션 문제는 여러 가지 이유로 발생할 수 있습니다. 일부 낡은 기능은 PEP 4의 지침에 따라 서서히 폐지됩니다. 또한 일부 코드는 버전 간에 바뀔 수 있는, 문서화되지 않은 동작에 의존합니다. 어떤 코드는 나중에 버그로 밝혀진 동작에 의존할 수 있으며, 그 버그가 수정되면 해당 동작이 바뀝니다.

현대화 옵션은 새 버전의 Python이 이전보다 더 나은 명확성이나 더 높은 성능을 가능하게 하는 기능을 추가할 때 생깁니다.

새 항목 작성 지침

커밋 권한이 있는 개발자는 이 PEP를 직접 갱신할 수 있습니다. 그 밖의 사람은 포함 여부를 검토받을 수 있도록 개발자에게 아이디어를 보낼 수 있습니다.

일관된 형식은 저장소를 더 쉽게 사용할 수 있게 해 주지만, 명확성을 높이기 위해 섹션을 자유롭게 추가하거나 제거하십시오.

유지 관리자가 업데이트할 만한 코드를 찾는 데 도움이 되는 도구로 grep 패턴을 제공할 수 있습니다. 그러나 완전히 자동화된 검색/치환 방식의 정규 표현식은 권장하지 않습니다. 대신 각 코드 조각을 개별적으로 평가해야 합니다.

새 항목에서 가장 중요한 부분은 금기 사항 섹션입니다. 이 섹션에는 갱신을 적용해서는 안 되는(SHOULD NOT) 것으로 알려진 상황을 나열합니다.

마이그레이션 문제

비교 연산자는 0이나 1을 만드는 지름길이 아님

Python 2.3 이전에는 비교 연산이 True나 False 대신 0이나 1을 반환했습니다. 일부 코드는 불리언 값이 적절하지 않은 곳에서 0이나 1을 만들어 내는 지름길로 이를 사용했을 수 있습니다. 예를 들면 다음과 같습니다.:

def identity(m=1):
    """Create and m-by-m identity matrix"""
    return [[i==j for i in range(m)] for j in range(m)]

Python 2.2에서 identity(2)를 호출하면 다음과 같은 결과가 나옵니다:

[[1, 0], [0, 1]]

Python 2.3에서 같은 호출을 하면 다음과 같은 결과가 나옵니다:

[[True, False], [False, True]]

불리언은 정수의 서브클래스이므로 행렬 계산은 계속 정상적으로 이루어지지만, 출력은 예상한 대로 나오지 않습니다. 리스트 컴프리헨션은 다음과 같이 바꿔야 합니다.:

return [[int(i==j) for i in range(m)] for j in range(m)]

True나 False 대신 숫자를 기대할 수 있는 다른 애플리케이션이 사용할 데이터를 저장할 때도 비슷한 문제가 있습니다.

현대화 절차

절차는 해당 현대화를 활용하는 데 필요한 Python 버전별로 묶여 있습니다.

Python 2.4 이상

리스트의 앞쪽에서 삽입하고 꺼내기

Python의 리스트는 오른쪽에서의 append와 pop에 가장 좋은 성능을 내도록 구현되어 있습니다. pop(0)이나 insert(0, x)를 사용하면 리스트 전체에 대해 O(n) 데이터 이동이 발생합니다. 이러한 필요에 대응하기 위해 Python 2.4에서는 왼쪽과 오른쪽 양쪽 모두에서 효율적인 append와 pop 연산을 제공하는 새 컨테이너 collections.deque()를 도입합니다(그 대가로 getitem/setitem 접근은 훨씬 느립니다). 새 컨테이너는 데이터 큐를 구현할 때 특히 유용합니다:

패턴:

c = list(data)   -->   c = collections.deque(data)
c.pop(0)         -->   c.popleft()
c.insert(0, x)   -->   c.appendleft()

찾기:

grep pop(0 or
grep insert(0

사용자 정의 정렬 단순화하기

Python 2.4에서는 리스트의 sort 메서드와 새로운 sorted 내장 함수 모두 정렬 키를 계산하기 위한 key 함수를 받습니다. 모든 비교마다 적용되는 cmp 함수와 달리, key 함수는 각 레코드에 한 번만 적용됩니다. key 함수는 cmp보다 훨씬 빠르며, 대개 더 적은 코드로 더 읽기 쉽습니다. key 함수는 정렬의 안정성도 유지합니다(같은 키를 가진 레코드는 원래 순서대로 남습니다.

비교 함수를 사용하는 원래 코드:

names.sort(lambda x,y: cmp(x.lower(), y.lower()))

명시적 데코레이션을 사용하는 또 다른 원래 코드:

tempnames = [(n.lower(), n) for n in names]
tempnames.sort()
names = [original for decorated, original in tempnames]

key 함수를 사용하도록 수정한 코드:

names.sort(key=str.lower)       # case-insensitive sort

찾기: grep sort *.py

흔한 람다 사용 대체하기

Python 2.4에서 operator 모듈에 lambda 키워드의 일반적인 용도를 대체할 수 있는 두 개의 새 함수 itemgetter()와 attrgetter()가 추가되었습니다. 새 함수들은 더 빠르게 실행되며, 가독성을 높여 준다고 보는 사람들도 있습니다.

패턴:

lambda r: r[2]      -->  itemgetter(2)
lambda r: r.myattr  -->  attrgetter('myattr')

전형적인 사용 맥락:

sort(studentrecords, key=attrgetter('gpa'))   # set a sort field
map(attrgetter('lastname'), studentrecords)   # extract a field

찾기: grep lambda *.py

단순해진 역방향 순회

Python 2.4에서는 역방향 이터레이션을 위한 reversed 내장 함수가 도입되었습니다. 기존의 역방향 순회 방식에는 장황함, 성능 문제(속도와 메모리 사용량), 명확성 부족 가운데 하나 이상의 문제가 있었습니다. 권장하는 방식은 시퀀스를 정방향으로 표현하고, 그 결과에 reversed를 적용한 다음, 그렇게 얻은 빠르고 메모리 효율적인 이터레이터를 순회하는 것입니다.

반개구간으로 표현한 원래 코드:

for i in range(n-1, -1, -1):
    print seqn[i]

여러 단계에 걸쳐 뒤집는 또 다른 원래 코드:

rseqn = list(seqn)
rseqn.reverse()
for value in rseqn:
    print value

확장 슬라이싱으로 표현한 또 다른 원래 코드:

for value in seqn[::-1]:
    print value

reversed 함수를 사용하도록 수정한 코드:

for value in reversed(seqn):
    print value

Python 2.3 이상

문자열 포함 여부 검사

Python 2.3에서는 string2 in string1에서 string2의 길이 제한이 없어졌습니다. 이제 string2는 길이에 관계없이 어떤 문자열이든 될 수 있습니다. 원래 문자열에서 부분 문자열의 위치는 상관없이 부분 문자열을 검색할 때 in 연산자를 사용하면 의미가 명확해집니다.

패턴:

string1.find(string2) >= 0   -->  string2 in string1
string1.find(string2) != -1  -->  string2 in string1

apply()를 직접 함수 호출로 바꾸기

Python 2.3에서 apply()는 Pending Deprecation(폐지 예정)으로 표시되었습니다. Python 1.6에서 함수 호출에 * 와 ** 가 도입되면서 더 이상 필요 없게 되었기 때문입니다. 직접 함수 호출은 내장 함수 조회를 생략하므로 항상 apply()보다 조금 더 빨랐습니다. 이제는 apply()가 warnings 모듈을 사용하기 때문에 더욱 느립니다.

패턴:

apply(f, args, kwds)  -->  f(*args, **kwds)

참고: Python 2.3.3에서 apply()의 Pending Deprecation(폐지 예정) 표시가 제거되었습니다. apply()의 대안이 없던 1.5.2까지 거슬러 올라가는 Python 버전에서 동작하는 코드를 유지 관리해야 하는 사람들에게 불편을 주기 때문입니다. 하지만 이 함수는 여전히 사용 중단된 상태입니다.

Python 2.2 이상

딕셔너리 포함 여부 검사

딕셔너리 포함 여부를 검사할 때는 ‘has_key()’ 메서드 대신 ‘in’ 키워드를 사용하십시오. 코드가 더 짧아지고 읽기 쉬워집니다. 리스트의 포함 여부 검사와 스타일도 일관됩니다. has_key는 속성 검색이 필요하고 비교적 비용이 큰 함수 호출을 사용하므로 ‘in’ 쪽이 약간 더 빠릅니다.

패턴:

if d.has_key(k):  -->  if k in d:

사용하지 말아야 할 경우:

  1. 일부 딕셔너리류 객체는 __contains__() 메서드를 정의하지 않을 수 있습니다:
    if dictlike.has_key(k)
    

찾는 방법: grep has_key

딕셔너리 순회하기

딕셔너리를 순회할 때는 새로운 iter 메서드를 사용하십시오. iter 메서드는 모든 키, 값 또는 항목을 완전히 복사한 새 리스트 객체를 만들 필요가 없으므로 더 빠릅니다. 필요에 따라 키, 값 또는 항목(키/값 쌍)만 선택하면 쓰고 버릴 객체 참조를 만드는 시간을 절약할 수 있고, 항목의 경우에는 키에 대한 두 번째 해시 조회도 절약할 수 있습니다.

패턴:

for key in d.keys():      -->  for key in d:
for value in d.values():  -->  for value in d.itervalues():
for key, value in d.items():
                          -->  for key, value in d.iteritems():

사용하지 말아야 할 경우:

  1. 리스트가 필요하다면 반환 타입을 바꾸지 마십시오:
    def getids():  return d.keys()
    
  2. 일부 딕셔너리류 객체는 iter 메서드를 정의하지 않을 수 있습니다:
    for k in dictlike.keys():
    
  3. 이터레이터는 슬라이싱, 정렬 및 기타 연산을 지원하지 않습니다:
    k = d.keys(); j = k[:]
    
  4. 딕셔너리 이터레이터를 사용하는 동안에는 딕셔너리를 수정할 수 없습니다:
    for k in d.keys(): del[k]
    

stat 메서드

stat 상수나 인덱스를 새로운 os.stat 속성과 메서드로 바꾸십시오. os.stat 속성과 메서드는 순서에 의존하지 않으며 stat 모듈을 임포트할 필요도 없습니다.

패턴:

os.stat("foo")[stat.ST_MTIME]  -->  os.stat("foo").st_mtime
os.stat("foo")[stat.ST_MTIME]  -->  os.path.getmtime("foo")

찾는 방법: grep os.stat 또는 grep stat.S

types 모듈에 대한 의존성 줄이기

types 모듈은 향후 폐지될 가능성이 높습니다. 대신 내장 생성자 함수를 사용하십시오. 내장 생성자 함수가 약간 더 빠를 수도 있습니다.

패턴:

isinstance(v, types.IntType)      -->  isinstance(v, int)
isinstance(s, types.StringTypes)  -->  isinstance(s, basestring)

이 기법을 완전히 활용하려면 Python 2.3 이상이 필요하지만(basestring은 Python 2.3에서 도입되었습니다), 대부분의 용도에는 Python 2.2로 충분합니다.

찾는 방법: grep types *.py | grep import

__builtins__ 모듈과 충돌하는 변수 이름 피하기

Python 2.2에서는 dict와 file에 대한 새 내장 타입이 추가되었습니다. 스크립트에서는 이러한 타입을 가리는 변수 이름을 할당하지 않아야 합니다. list와 같은 기존 내장 객체에도 같은 조언이 적용됩니다.

패턴:

file = open('myfile.txt') --> f = open('myfile.txt')
dict = obj.__dict__ --> d = obj.__dict__

찾는 방법: grep 'file ' *.py

Python 2.1 이상

whrandom 모듈 폐지

난수 관련 메서드는 모두 random 모듈 한곳에 모였습니다.

패턴:

import whrandom --> import random

찾는 방법: grep whrandom

Python 2.0 이상

문자열 메서드

문자열(string) 모듈은 향후 폐지될 가능성이 높습니다. 대신 문자열 메서드를 사용하십시오. 문자열 메서드가 더 빠르기도 합니다.

패턴:

import string ; string.method(s, ...)  -->  s.method(...)
c in string.whitespace                 -->  c.isspace()

찾는 방법: grep string *.py | grep import

startswith와 endswith 문자열 메서드

슬라이싱 대신 이러한 문자열 메서드를 사용하십시오. 슬라이스를 만들 필요가 없고 개수를 잘못 셀 위험도 없습니다.

패턴:

"foobar"[:3] == "foo"   -->  "foobar".startswith("foo")
"foobar"[-3:] == "bar"  -->  "foobar".endswith("bar")

atexit 모듈

atexit 모듈은 프로그램 종료 시 실행될 여러 함수를 지원합니다. 또한 매개변수가 있는 함수도 지원합니다. 안타깝게도 이 모듈의 구현은 종료 함수를 하나만 지원하는 sys.exitfunc 속성과 충돌합니다. sys.exitfunc에 의존하는 코드는 더 새롭고 다재다능한 atexit 모듈을 사용하기로 한 다른 모듈(라이브러리 모듈 포함)을 방해할 수 있습니다.

패턴:

sys.exitfunc = myfunc  -->  atexit.register(myfunc)

Python 1.5 이상

클래스 기반 예외

문자열 예외는 폐지 예정이므로 Exception 베이스 클래스에서 파생하십시오. 구식인 문자열 예외와 달리, 클래스 예외는 모두 다른 예외나 Exception 베이스 클래스에서 파생됩니다. 이를 통해 예외를 의미 있게 묶을 수 있습니다. 또한 “except Exception” 절로 모든 예외를 잡을 수 있습니다.

패턴:

NewError = 'NewError'  -->  class NewError(Exception): pass

찾는 방법: PyChecker를 사용하십시오.

모든 Python 버전

None 검사

None 객체는 하나뿐이므로 동등성을 동일성으로 검사할 수 있습니다. 동일성 검사는 동등성 검사보다 약간 더 빠릅니다. 또한 일부 객체 타입은 비교 연산을 오버로드할 수 있으므로 동등성 검사가 훨씬 더 느릴 수 있습니다.

패턴:

if v == None  -->  if v is None:
if v != None  -->  if v is not None:

찾는 방법: grep '== None' 또는 grep '!= None'