PEP 3126 – 암시적 문자열 연결 제거
- Author:
- Jim J. Jewett <JimJJewett at gmail.com>, Raymond Hettinger <python at rcn.com>
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 29-Apr-2007
- Post-History:
- 29-Apr-2007, 30-Apr-2007, 07-May-2007
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
거부 공지
이 PEP는 거부되었습니다. 찬성하는 지지가 충분하지 않았고, 제거하려는 기능이 그다지 해롭지 않으며, 오히려 더 어려워지는 사용 사례도 일부 있습니다.
초록
Python은 많은 구문 분석 규칙을 C에서 물려받았습니다. 이는 대체로 유용했지만, 일부 개별 규칙은 Python에서 덜 유용하므로 제거해야 합니다.
이 PEP는 리터럴이 인접해 있다는 것만으로 이루어지는 암시적 문자열 연결을 제거할 것을 제안합니다.
다음과 같이 작성하는 대신:
"abc" "def" == "abcdef"
작성자는 명시적으로 문자열을 더하거나:
"abc" + "def" == "abcdef"
결합해야 합니다:
"".join(["abc", "def"]) == "abcdef"
동기
Python 3000의 목표 중 하나는 불필요한 기능을 제거하여 언어를 단순화하는 것이어야 합니다. 암시적 문자열 연결은 없애고 기존 기법을 사용해야 합니다. 이렇게 하면 문법이 단순해지고 사용자가 머릿속에 그리는 Python의 모습도 단순해집니다. 후자는 언어가 “머릿속에 들어오도록” 하는 데 중요합니다. 현재 사용자 중 상당수는 암시적 연결의 존재조차 알지 못합니다. 이를 아는 사용자 중에서도 상당수는 전혀 사용하지 않거나 습관적으로 피합니다. 이를 알면서 사용하기도 하는 사용자 중에서, 암시적 연산자 우선순위가 어떠한지, 그리고 어떤 상황에서 정의가 컴파일될 때 계산되고 어떤 상황에서 실행될 때 계산되는지를 자신 있게 말할 수 있는 사람은 매우 적습니다.
과거인가 미래인가
Python의 많은 구문 분석 규칙은 의도적으로 C와 호환되도록 만들어졌습니다. 이는 유용한 기본 방침이지만, 특수한 경우(Special Cases)는 Python에서의 유용성을 근거로 정당화되어야 합니다. 이제 더 이상 Python 프로그래머가 C에도 익숙할 것이라고 가정해서는 안 되므로, 언어 간 호환성은 정당화의 근거가 아니라 판단이 팽팽할 때의 결정 기준으로 다루어야 합니다.
C에서 암시적 연결은 변수에 저장하기 위한 (실행 시간) 함수 호출을 사용하지 않고 문자열을 결합하는 유일한 방법입니다. Python에서는 +나 "".join과 같은 더 표준적인 Python 관용구를 사용하여 문자열을 결합할 수 있습니다(이렇게 해도 여전히 불변으로 인식됩니다).
문제
암시적 문자열 연결 때문에 튜플과 리스트가 보이는 것보다 짧아지며, 이는 다시 혼란스러운 오류나 심지어 조용히 발생하는 오류로 이어질 수 있습니다. 예를 들어, 여러 매개변수를 받으면서 그중 일부에는 기본값을 제공하는 함수가 있다고 합시다:
def f(fmt, *args):
print fmt % args
이는 올바른 호출처럼 보이지만, 실제로는 그렇지 않습니다:
>>> f("User %s got a message %s",
"Bob"
"Time for dinner")
Traceback (most recent call last):
File "<pyshell#8>", line 2, in <module>
"Bob"
File "<pyshell#3>", line 2, in f
print fmt % args
TypeError: not enough arguments for format string
이 함수를 호출하면 아무 경고 없이 잘못된 동작이 일어날 수 있습니다:
def g(arg1, arg2=None):
...
# silently transformed into the possibly very different
# g("arg1 on this linearg2 on this line", None)
g("arg1 on this line"
"arg2 on this line")
Jason Orendorff의 말을 인용하면 다음과 같습니다 [1]:
아. 방금 이런 일이 이곳에서 꽤 자주 일어난다는 것을 깨달았습니다. 제가 일하는 곳에서는 scons를 사용하는데, 각 SConscript에는 긴 파일 이름 목록이 있습니다:sourceFiles = [ 'foo.c' 'bar.c', #...many lines omitted... 'q1000x.c']쉼표를 빠뜨리는 것은 흔한 실수이며, 그러면 scons는 ‘foo.cbar.c’를 찾을 수 없다고 불평합니다. 이는 여러분이 Python 프로그래머이더라도 꽤 당혹스러운 동작이며, 이곳의 모든 사람이 Python 프로그래머인 것도 아닙니다.
해결책
Python에서 문자열은 객체이며 __add__ 연산자를 지원하므로, 다음과 같이 작성할 수 있습니다:
"abc" + "def"
이들은 리터럴이므로 이 덧셈은 여전히 컴파일러가 최적화하여 제거할 수 있습니다. CPython 컴파일러는 이미 그렇게 하고 있습니다. [2]
기존의 다른 대안으로는 여러 줄(삼중 따옴표) 문자열과 join 메서드가 있습니다:
"""This string
extends across
multiple lines, but you may want to use something like
Textwrap.dedent
to clear out the leading spaces
and/or reformat.
"""
>>> "".join(["empty", "string", "joiner"]) == "emptystringjoiner"
True
>>> " ".join(["space", "string", "joiner"]) == "space string joiner"
True
>>> "\n".join(["multiple", "lines"]) == "multiple\nlines" == (
"""multiple
lines""")
True
우려 사항
연산자 우선순위
Guido는 % 같은 다른 문자열 연산자와 관련된 몇 가지 경계 사례가 있으므로 이 변경은 PEP로 다루어야 한다고 밝혔습니다 [2]. (str %가 유지된다고 가정할 때의 이야기입니다 – 이 연산자는 PEP 3101 – Advanced String Formatting으로 대체되어 제거될 수도 있습니다. [3])
해결 방법은 괄호를 사용하여 우선순위를 강제하는 것입니다 – 이는 오늘날에도 사용할 수 있는 것과 같은 해결책입니다:
# Clearest, works today, continues to work, optimization is
# already possible.
("abc %s def" + "ghi") % var
# Already works today; precedence makes the optimization more
# difficult to recognize, but does not change the semantics.
"abc" + "def %s ghi" % var
다음과는 대조적으로:
# Already fails because modulus (%) is higher precedence than
# addition (+)
("abc %s def" + "ghi" % var)
# Works today only because adjacency is higher precedence than
# modulus. This will no longer be available.
"abc %s" "def" % var
# So the 2-to-3 translator can automatically replace it with the
# (already valid):
("abc %s" + "def") % var
긴 명령
… (제가 생각하기에) 읽기 쉬운 SQL 쿼리를 구성합니다 [4]:rows = self.executesql("select cities.city, state, country" " from cities, venues, events, addresses" " where cities.city like %s" " and events.active = 1" " and venues.address = addresses.id" " and addresses.city = cities.id" " and events.venue = venues.id", (city,))
여기서도 대안으로는 삼중 따옴표 문자열, +, .join이 있습니다:
query="""select cities.city, state, country
from cities, venues, events, addresses
where cities.city like %s
and events.active = 1"
and venues.address = addresses.id
and addresses.city = cities.id
and events.venue = venues.id"""
query=( "select cities.city, state, country"
+ " from cities, venues, events, addresses"
+ " where cities.city like %s"
+ " and events.active = 1"
+ " and venues.address = addresses.id"
+ " and addresses.city = cities.id"
+ " and events.venue = venues.id"
)
query="\n".join(["select cities.city, state, country",
" from cities, venues, events, addresses",
" where cities.city like %s",
" and events.active = 1",
" and venues.address = addresses.id",
" and addresses.city = cities.id",
" and events.venue = venues.id"])
# And yes, you *could* inline any of the above querystrings
# the same way the original was inlined.
rows = self.executesql(query, (city,))
정규 표현식
복잡한 정규 표현식은 때때로 암시적으로 연결된 여러 문자열로 작성되며, 각 정규식 구성 요소는 별도의 줄에 놓이고 그 뒤에 주석이 붙습니다. 여기에 더하기 연산자를 삽입할 수는 있지만, 그렇게 하면 정규식을 읽기가 더 어려워집니다. 한 가지 대안은 re.VERBOSE 옵션을 사용하는 것입니다. 또 다른 대안은 일련의 += 줄로 정규식을 구성하는 것입니다:
# Existing idiom which relies on implicit concatenation
r = ('a{20}' # Twenty A's
'b{5}' # Followed by Five B's
)
# Mechanical replacement
r = ('a{20}' +# Twenty A's
'b{5}' # Followed by Five B's
)
# already works today
r = '''a{20} # Twenty A's
b{5} # Followed by Five B's
''' # Compiled with the re.VERBOSE flag
# already works today
r = 'a{20}' # Twenty A's
r += 'b{5}' # Followed by Five B's
국제화
일부 국제화 도구 – 특히 xgettext – 는 이미 암시적 연결을 특별히 처리하도록 되어 있지만, Python의 명시적 연결은 그렇지 않습니다. [5]
이러한 도구는 (이미 적법한) 다음 코드를 추출하지 못합니다:
_("some string" +
" and more of it")
하지만 다음 코드에 대해서는 특별한 처리를 두는 경우가 많습니다:
_("some string"
" and more of it")
그냥 지나치게 긴 줄(xgettext는 메시지를 2048자로 제한하는데 [7], 이는 Python이 강제하는 제한보다 작습니다)이나 삼중 따옴표 문자열을 사용하는 것도 가능하겠지만, 이러한 해결책은 코드의 가독성을 어느 정도 희생합니다:
# Lines over a certain length are unpleasant.
_("some string and more of it")
# Changing whitespace is not ideal.
_("""Some string
and more of it""")
_("""Some string
and more of it""")
_("Some string \
and more of it")
이에 대한 좋은 단기적 해결책은 보이지 않습니다.
전환
제안된 새 구문은 현재 Python에서 이미 적법하며, 즉시 사용할 수 있습니다.
2 to 3 변환기가 다음 코드를:
"str1" "str2"
("line1" #comment
"line2")
다음과 같이 기계적으로 바꾸도록 만들 수 있습니다:
("str1" + "str2")
("line1" +#comments
"line2")
사용자가 다른 관용구 중 하나를 사용하고 싶다면 그렇게 할 수 있습니다. 이 관용구들은 모두 이미 Python 2에서 유효하므로, 번역기를 수정하는 대신 원본 소스를 편집하면 됩니다.
미해결 문제
외부 텍스트 추출 도구를 지원하는, 또는 적어도 특히 xgettext [6]를 지원하는 더 나은 방법이 있습니까?
참고 문헌
Copyright
This document has been placed in the public domain.