설계와 역사 FAQ¶
파이썬은 왜 문장의 그룹화에 들여쓰기를 사용합니까?¶
Guido van Rossum은 그룹화에 들여쓰기를 사용하는 것이 매우 우아하고 일반적인 파이썬 프로그램의 명확성에 크게 기여한다고 믿습니다. 대부분의 사람은 시간이 좀 흐른 후에 이 기능을 사랑하는 법을 배웁니다.
시작/끝 괄호가 없기 때문에 구문 분석기와 사람 독자가 인식하는 그룹 간에 불일치가 있을 수 없습니다. 때때로 C 프로그래머는 다음과 같은 코드 조각을 만나게 됩니다:
if (x <= y)
x++;
y--;
z++;
Only the x++
statement is executed if the condition is true, but the
indentation leads you to believe otherwise. Even experienced C programmers will
sometimes stare at it a long time wondering why y
is being decremented even
for x > y
.
Because there are no begin/end brackets, Python is much less prone to coding-style conflicts. In C there are many different ways to place the braces. If you’re used to reading and writing code that uses one style, you will feel at least slightly uneasy when reading (or being required to write) another style.
많은 코딩 스타일은 시작/끝 괄호를 그 자신만의 줄에 배치합니다. 이로 인해 프로그램이 상당히 길어지고 귀중한 화면 공간이 낭비되어, 프로그램을 조망하기가 더 어려워집니다. 이상적으로는, 함수가 한 화면에 맞아야 합니다 (가령, 20–30줄). 20줄의 파이썬은 C의 20줄보다 훨씬 더 많은 작업을 수행할 수 있습니다. 이것은 시작/끝 괄호가 필요 없기 때문 만은 아닙니다만 – 선언이 없는 것과 고수준의 데이터형도 기여합니다 – 들여쓰기 기반 문법은 확실히 도움이 됩니다.
간단한 산술 연산으로 이상한 결과가 나오는 이유는 무엇입니까?¶
다음 질문을 보십시오.
부동 소수점 계산이 왜 그렇게 부정확합니까?¶
사용자는 종종 다음과 같은 결과에 놀라게 됩니다:
>>> 1.2 - 1.0
0.19999999999999996
그리고 이것을 파이썬의 버그라고 생각합니다. 그렇지 않습니다. 이것은 파이썬과 거의 관련이 없으며, 하부 플랫폼이 부동 소수점 숫자를 처리하는 방법과 훨씬 더 관련이 있습니다.
CPython의 float
형은 저장을 위해 C double
을 사용합니다. float
객체의 값은 고정 정밀도(일반적으로 53비트)의 이진 부동 소수점에 저장되고 파이썬은 부동 소수점 연산을 수행하는 데 C 연산을 사용하며, 이는 다시 프로세서의 하드웨어 구현에 의존합니다. 즉, 부동 소수점 연산에 관한 한, 파이썬은 C와 Java를 포함한 많은 널리 알려진 언어들처럼 작동합니다.
십진 표기법으로 쉽게 쓸 수 있는 많은 숫자가 이진 부동 소수점으로는 정확하게 표현할 수 없습니다. 예를 들어,:
>>> x = 1.2
이후에, x
에 대해 저장된 값은 10진수 값 1.2
에 대한 (매우 좋은) 근사치이지만, 정확히 같지는 않습니다. 일반적인 기계에서 실제 저장되는 값은 다음과 같습니다:
1.0011001100110011001100110011001100110011001100110011 (binary)
이것은 정확하게는 다음과 같습니다:
1.1999999999999999555910790149937383830547332763671875 (decimal)
53비트의 일반적인 정밀도는 파이썬 부동 소수점에 15–16자리의 10진수 정확도를 제공합니다.
자세한 설명은, 파이썬 자습서의 부동 소수점 산술 장을 참조하십시오.
파이썬 문자열이 불변인 이유는 무엇입니까?¶
몇 가지 장점이 있습니다.
하나는 성능입니다: 문자열이 불변임을 안다는 것은 만들 때 이를 위한 공간을 할당할 수 있다는 것을 의미하며, 스토리지 요구 사항은 고정되고 변경되지 않습니다. 이것은 또한 튜플과 리스트를 구분하는 이유 중 하나입니다.
또 다른 장점은 파이썬의 문자열을 숫자만큼 “기본적”으로 간주한다는 것입니다. 어떤 방법도 값 8을 다른 것으로 변경하지 않으며, 파이썬에서는 어떤 방법도 문자열 “eight”을 다른 것으로 변경하지 않습니다.
메서드 정의와 호출에서 ‘self’를 명시적으로 사용해야 하는 이유는 무엇입니까?¶
아이디어는 Modula-3에서 빌렸습니다. 여러 가지 이유로 매우 유용합니다.
첫째, 로컬 변수 대신 메서드나 인스턴스 어트리뷰트를 사용하고 있다는 것이 더 분명합니다. self.x
나 self.meth()
를 읽으면 클래스 정의를 기억하지 못하더라도 인스턴스 변수나 메서드가 사용된다는 것을 분명히 알 수 있습니다. C++에서는, 지역 변수 선언이 없는 것으로 구분할 수 있습니다 (전역은 드물거나 쉽게 인식할 수 있다고 가정할 때) – 하지만 파이썬에서는, 지역 변수 선언이 없어서, 확실히 하려면 클래스 정의를 찾아야 합니다. 일부 C++와 Java 코딩 표준에서는 인스턴스 어트리뷰트에 m_
접두어를 요청합니다, 따라서 이러한 명시성은 이런 언어들에서도 여전히 유용합니다.
둘째, 특정 클래스에서 메서드를 명시적으로 참조하거나 호출하려고 할 때 특별한 문법이 필요하지 않음을 의미합니다. C++에서, 파생 클래스에서 재정의된 베이스 클래스의 메서드를 사용하려면, ::
연산자를 사용해야 합니다 – 파이썬에서는 baseclass.methodname(self, <argument list>)
라고 쓸 수 있습니다. 이것은 __init__()
메서드에, 일반적으로 파생 클래스 메서드가 같은 이름의 베이스 클래스 메서드를 확장하려고 해서 어떻게든 베이스 클래스 메서드를 호출해야 하는 경우에 특히 유용합니다.
마지막으로, 인스턴스 변수의 경우 대입과 관련된 문법 문제를 해결합니다: 파이썬의 지역 변수는 (정의상!) 함수 본문에서 값이 대입되는 (그리고 전역으로 명시적으로 선언되지 않은) 변수이기 때문에, 인터프리터에게 대입이 지역 변수가 아니라 인스턴스 변수에 대한 대입이라는 것을 알릴 방법이 있어야 하고, (효율성의 측면에서) 구문적이면 좋습니다. C++는 선언을 통해 이 작업을 수행하지만, 파이썬에는 선언이 없어서 이러한 목적으로만 문법을 도입해야 하는 것은 유감입니다. 명시적 self.var
를 사용하면 이 문제가 잘 해결됩니다. 마찬가지로, 인스턴스 변수를 사용하는 경우, self.var
라고 써야 한다는 것은, 메서드 내에서 정규화되지 않은 이름에 대한 참조가 인스턴스의 디렉터리를 검색할 필요가 없음을 의미합니다. 다시 말해, 지역 변수와 인스턴스 변수는 두 개의 서로 다른 이름 공간에 있으며, 사용할 이름 공간을 파이썬에 알려야 합니다.
표현식에서 대입을 사용할 수 없는 이유는 무엇입니까?¶
파이썬 3.8부터, 가능합니다!
바다코끼리(walrus) 연산자 :=를 사용하는 대입 표현식은 표현식에 있는 변수를 대입합니다:
while chunk := fp.read(200):
print(chunk)
자세한 정보는 PEP 572를 참조하십시오.
파이썬은 왜 일부 기능(예를 들어 list.index())에는 메서드를 사용하고 다른 기능(예를 들어 len(list))에는 함수를 사용합니까?¶
Guido가 말했듯이:
(a) 일부 연산의 경우, 전위 표기법(prefix notation)이 후위(postfix) 표기법보다 더 잘 읽힙니다 – 전위 (그리고 중위(infix)!) 연산은 수학에서 오랜 전통을 가지고 있는데, 수학자가 문제에 대해 생각하는 데 시각적으로 도움이 되는 표기법을 좋아하는 곳입니다. x*(a+b) 와 같은 공식을 x*a + x*b 로 다시 작성하기 쉬운 것과 원시 OO 표기법을 사용하여 같은 작업을 수행할 때의 어색함을 비교해 보십시오.
(b) len(x) 라는 코드를 읽을 때 저는 무언가의 길이를 요구하고 있다는 것을 압니다. 이것은 저에게 두 가지를 알려줍니다: 결과는 정수이고, 인자는 일종의 컨테이너입니다. 반대로, x.len() 을 읽을 때, x가 인터페이스를 구현하거나 표준 len() 이 있는 클래스에서 상속하는 일종의 컨테이너라는 것을 이미 알고 있어야 합니다. 매핑을 구현하지 않는 클래스에 get() 이나 keys() 메서드가 있거나, 파일이 아닌 것에 write() 메서드가 있을 때 때때로 겪는 혼란을 보십시오.
—https://mail.python.org/pipermail/python-3000/2006-November/004643.html
join()이 리스트나 튜플 메서드가 아니라 문자열 메서드인 이유는 무엇입니까?¶
항상 string 모듈의 함수를 사용하여 제공되었던 것과 같은 기능을 제공하는 메서드가 추가된, 파이썬 1.6부터 문자열은 다른 표준형과 훨씬 더 비슷해졌습니다. 이러한 새로운 메서드의 대부분은 널리 받아들여졌지만, 일부 프로그래머가 불편해하는 것은 다음과 같습니다:
", ".join(['1', '2', '4', '8', '16'])
이것은 다음과 같은 결과를 제공합니다:
"1, 2, 4, 8, 16"
이 사용법에 대해 두 가지 자주 나오는 반론이 있습니다.
첫 번째는 “문자열 리터럴(문자열 상수)의 메서드를 사용하는 것이 정말 보기 흉합니다”라는 줄이 따라옵니다. 이에 대한 대답은 그럴 수 있지만, 문자열 리터럴은 그저 고정된 값일 뿐이라는 것입니다. 메서드가 문자열에 연결된 이름에 허용된다면, 리터럴에서 사용할 수 없게 만드는 논리적 이유가 없습니다.
두 번째 이의는 보통 다음과 같이 표현됩니다: “나는 시퀀스가 멤버를 문자열 상수로 연결하라고 말하고 있습니다”. 슬프게도, 당신은 그렇지 않습니다. 어떤 이유로 split()
를 문자열 메서드로 사용하는 데 훨씬 덜 어려움을 겪는 것 같습니다, 그럴 때 다음과 같은 표현이
"1, 2, 4, 8, 16".split(", ")
문자열 리터럴에게 주어진 구분자(또는, 기본적으로, 임의의 공백 연속)로 구분된 하위 문자열을 반환하도록 하는 명령임을 쉽게 알 수 있기 때문입니다.
join()
은 문자열 메서드입니다. 사용 시 구분자 문자열에게 문자열 시퀀스를 이터레이트 하고 인접한 요소 사이에 자신을 삽입하도록 지시하기 때문입니다. 이 메서드는 사용자가 직접 정의할 수 있는 새 클래스를 포함하여, 시퀀스 객체에 대한 규칙을 따르는 모든 인자와 함께 사용할 수 있습니다. 바이트열과 bytearray 객체에 대해 유사한 메서드가 존재합니다.
예외는 얼마나 빠릅니까?¶
예외가 발생하지 않으면 try/except 블록은 매우 효율적입니다. 실제로 예외를 잡는 것은 비용이 많이 듭니다. 2.0 이전의 파이썬 버전에서는 다음 관용구를 사용하는 것이 일반적이었습니다:
try:
value = mydict[key]
except KeyError:
mydict[key] = getvalue(key)
value = mydict[key]
이것은 딕셔너리에 거의 항상 키가 있을 것으로 예상했을 때만 의미가 있습니다. 그렇지 않으면, 다음과 같이 코딩했습니다:
if key in mydict:
value = mydict[key]
else:
value = mydict[key] = getvalue(key)
이 특정 경우에, value = dict.setdefault(key, getvalue(key))
를 사용할 수도 있지만, getvalue()
호출이 모든 경우에 평가되기 때문에 충분히 저렴한 경우에만 사용할 수 있습니다.
파이썬에 switch 나 case 문이 없는 이유는 무엇입니까?¶
if... elif... elif... else
시퀀스를 사용하면 이 작업을 쉽게 수행할 수 있습니다. switch 문 문법에 대한 몇 가지 제안이 있었지만, 범위 테스트를 수행할지와 방법에 대한 합의가 (아직) 없습니다. 자세한 내용과 현재 상태는 PEP 275를 참조하십시오.
매우 많은 가능성 중에서 선택해야 하는 경우에는, case 값을 호출할 함수에 매핑하는 딕셔너리를 만들 수 있습니다. 예를 들면:
def function_1(...):
...
functions = {'a': function_1,
'b': function_2,
'c': self.method_1, ...}
func = functions[value]
func()
객체에 대한 메서드 호출의 경우, getattr()
내장 함수를 사용하여 특정 이름을 가진 메서드를 꺼내어 더욱 단순화 할 수 있습니다:
def visit_a(self, ...):
...
...
def dispatch(self, value):
method_name = 'visit_' + str(value)
method = getattr(self, method_name)
method()
이 예제의 visit_
와 같이 메서드 이름에 접두사를 사용하는 것이 좋습니다. 이러한 접두사가 없으면, 값이 신뢰할 수 없는 소스에서 오는 경우, 공격자가 객체의 모든 메서드를 호출할 수 있습니다.
OS별 스레드 구현에 의존하는 대신 인터프리터에서 스레드를 에뮬레이션할 수 없습니까?¶
답변 1: 불행히도, 인터프리터는 각 파이썬 스택 프레임에 대해 적어도 하나의 C 스택 프레임을 푸시합니다. 또한, 확장은 거의 임의의 순간에 파이썬으로 콜백 할 수 있습니다. 따라서, 전체 스레드 구현에는 C에 대한 스레드 지원이 필요합니다.
답변 2: 다행히, C 스택을 피하도록 완전히 재설계된 인터프리터 루프가 있는 Stackless Python이 있습니다.
람다 표현식이 문장을 포함할 수 없는 이유는 무엇입니까?¶
파이썬의 구문 프레임워크는 표현식 내부에 중첩된 문장을 처리할 수 없기 때문에 파이썬 람다 표현식은 문장을 포함할 수 없습니다. 그러나, 파이썬에서, 이것은 심각한 문제가 아닙니다. 기능을 추가하는 다른 언어의 람다 형식과 달리, 파이썬 람다는 함수를 정의하기에 너무 게으른 경우를 위한 줄임 표기법일 뿐입니다.
함수는 이미 파이썬의 일급 객체이며, 지역 스코프에서 선언할 수 있습니다. 따라서 지역에 정의된 함수 대신 람다를 사용하는 유일한 이점은 함수의 이름을 만들 필요가 없다는 것입니다 – 하지만 그것은 단지 함수 객체(람다 표현식이 산출하는 것과 정확히 같은 형의 객체)가 대입되는 지역 변수일 뿐입니다!
파이썬을 기계 코드, C 또는 다른 언어로 컴파일 할 수 있습니까?¶
Cython은 수정된 버전의 파이썬을 선택적 주석이 있는 C 확장으로 컴파일합니다. Nuitka는 완전한 파이썬 언어 지원을 목표로 하는 파이썬을 C++ 코드로 변환하는 유망한 컴파일러입니다. Java로 컴파일하려면 VOC를 고려할 수 있습니다.
파이썬은 메모리를 어떻게 관리합니까?¶
파이썬 메모리 관리의 세부 사항은 구현에 따라 다릅니다. 파이썬의 표준 구현인 CPython은 참조 카운팅을 사용하여 액세스할 수 없는 객체를 감지하고, 또 다른 메커니즘을 사용하여 참조 순환을 수집하고, 액세스할 수 없는 순환을 찾고 관련된 객체를 삭제하는 순환 감지 알고리즘을 주기적으로 실행합니다. gc
모듈은 가비지 수집을 수행하고, 디버깅 통계를 얻고, 수거기의 매개 변수를 조정하는 함수를 제공합니다.
그러나, 다른 구현(가령 Jython이나 PyPy)은 완전한 가비지 수거기와 같은 다른 메커니즘에 의존할 수 있습니다. 이 차이는 파이썬 코드가 참조 카운팅 구현의 동작에 의존하는 경우 미묘한 이식 문제를 일으킬 수 있습니다.
일부 파이썬 구현에서, 다음 코드(CPython에서는 괜찮습니다)는 아마도 파일 기술자의 소진을 일으킵니다:
for file in very_long_list_of_files:
f = open(file)
c = f.read(1)
실제로, CPython의 참조 카운팅과 파괴자 체계를 사용할 때, f에 대한 각각의 새로운 대입은 이전 파일을 닫습니다. 그러나 전통적인 GC를 사용하면, 이러한 파일 객체는 다양하고 어쩌면 긴 간격으로만 수집(그리고 닫히게)됩니다.
모든 파이썬 구현에서 작동하는 코드를 작성하려면, 명시적으로 파일을 닫거나 with
문을 사용해야 합니다; 다음은 메모리 관리 체계와 관계없이 작동합니다:
for file in very_long_list_of_files:
with open(file) as f:
c = f.read(1)
CPython이 더 전통적인 가비지 수거 체계를 사용하지 않는 이유는 무엇입니까?¶
우선, 이것은 C 표준 기능이 아니라서 이식성이 없습니다. (예, 우리는 Boehm GC 라이브러리에 대해 알고 있습니다. 대부분의 일반 플랫폼용 (그들 전부는 아닙니다) 어셈블러 코드가 있고, 대체로 투명하지만, 완전히 투명하지는 않습니다; 파이썬이 이것으로 작동하도록 하려면 패치가 필요합니다.)
전통적인 GC는 파이썬이 다른 응용 프로그램에 내장될 때도 문제가 됩니다. 독립형 파이썬에서는 표준 malloc()과 free()를 GC 라이브러리에서 제공하는 버전으로 대체해도 상관없지만, 파이썬을 내장하는 응용 프로그램은 malloc()과 free()에 대한 자신만의 대체를 원할 수 있습니다, 그리고 파이썬의 것을 원하지 않을 수 있습니다. 현재, CPython은 malloc()과 free()를 올바르게 구현하는 모든 것과 동작합니다.
CPython이 종료될 때 모든 메모리가 해제되지 않는 이유는 무엇입니까?¶
파이썬 모듈의 전역 이름 공간에서 참조된 객체는 파이썬이 종료될 때 항상 할당 해제되지는 않습니다. 순환 참조가 있으면 발생할 수 있습니다. C 라이브러리에 의해 할당된 해제가 불가능한 특정 메모리도 있습니다 (예를 들어 Purify와 같은 도구는 이에 대해 불평합니다). 그러나, 파이썬은 종료 시 메모리 정리에 적극적이며 모든 단일 객체를 파괴하려고 시도합니다.
할당 해제 시 파이썬이 특정 항목을 삭제하도록 강제하려면 atexit
모듈을 사용하여 해당 삭제를 강제하는 함수를 실행하십시오.
별도의 튜플과 리스트 데이터형이 있는 이유는 무엇입니까?¶
리스트와 튜플은, 여러 면에서 비슷하지만, 일반적으로 근본적으로 다른 방식으로 사용됩니다. 튜플은 파스칼 레코드나 C 구조체와 유사하다고 생각할 수 있습니다; 그룹으로 다뤄지는 다양한 형을 갖는 관련 데이터의 작은 모음입니다. 예를 들어, 직교 좌표는 2개나 3개의 숫자로 구성된 튜플로 적절하게 표시됩니다.
반면에, 리스트는 다른 언어의 배열과 더 비슷합니다. 이들은 모두 같은 형을 가지고 하나씩 다뤄지는 다양한 수의 객체를 보유하는 경향이 있습니다. 예를 들어, os.listdir('.')
은 현재 디렉터리의 파일을 나타내는 문자열 리스트를 반환합니다. 이 출력에 작동하는 함수는 디렉터리에 다른 파일 한두 개를 추가해도 일반적으로 오동작하지 않습니다.
튜플은 불변입니다. 즉, 일단 튜플이 만들어지면, 어느 요소도 새 값으로 바꿀 수 없습니다. 리스트는 가변이라서, 언제든지 리스트의 요소를 변경할 수 있습니다. 불변인 요소만 딕셔너리 키로 사용할 수 있어서, 리스트가 아니라 튜플만 키로 사용할 수 있습니다.
CPython에서 리스트는 어떻게 구현됩니까?¶
CPython의 리스트는 실제로는 가변 길이 배열입니다, Lisp 스타일의 연결 리스트(linked lists)가 아닙니다. 구현은 다른 객체에 대한 참조의 연속적인 배열을 사용하고, 이 배열에 대한 포인터와 배열의 길이를 리스트 헤드 구조체에 유지합니다.
이것은 리스트 인덱싱 a[i]
의 비용이 리스트의 크기나 인덱스의 값과 무관한 연산으로 만듭니다.
항목이 추가되거나 삽입되면, 참조 배열의 크기가 조정됩니다. 항목을 반복적으로 추가하는 성능을 향상하기 위해 약간 영리하게 처리합니다; 배열을 확장해야 할 때, 추가 공간이 할당되어 다음 몇 번에는 실제 크기 조정이 필요하지 않습니다.
CPython에서 딕셔너리는 어떻게 구현됩니까?¶
CPython의 딕셔너리는 크기 조정 가능한 해시 테이블로 구현됩니다. B-트리와 비교해, 대부분의 상황에서 조회(지금까지 가장 흔한 연산) 성능이 향상되고, 구현이 더 간단합니다.
딕셔너리는 hash()
내장 함수를 사용하여 딕셔너리에 저장된 각 키에 대한 해시 코드를 계산하여 작동합니다. 해시 코드는 키와 프로세스별 시드에 따라 크게 다릅니다; 예를 들어, “Python”은 -539294296으로 해시 할 수 있는 반면, 한 글자만 다른 문자열인 “python”은 1142331976로 해시 할 수 있습니다. 그런 다음 해시 코드는 값이 저장될 내부 배열의 위치를 계산하는 데 사용됩니다. 모든 해시값이 다른 키를 저장한다고 가정하면, 딕셔너리가 키를 검색하는 데 상수 시간이 걸린다는 뜻입니다 – Big-O 표기법으로 O(1).
딕셔너리 키가 불변이어야 하는 이유는 무엇입니까?¶
딕셔너리의 해시 테이블 구현은 키값에서 계산된 해시값을 사용하여 키를 찾습니다. 키가 가변 객체이면, 값이 변경될 수 있어서, 해시도 변경될 수 있습니다. 그러나 키 객체를 변경하는 주체는 그것이 딕셔너리 키로 사용되고 있음을 알 수 없기 때문에, 딕셔너리에서 항목을 이동할 수 없습니다. 그런 다음, 딕셔너리에서 같은 객체를 찾으려고 하면 해시값이 다르기 때문에 찾을 수 없습니다. 이전 값을 찾으려고 해도 해당 해시 저장소에서 발견된 객체의 값이 다르기 때문에 역시 찾을 수 없습니다.
딕셔너리를 리스트로 인덱싱하려면, 먼저 리스트를 튜플로 변환하십시오; tuple(L)
함수는 리스트 L
과 같은 항목을 가진 튜플을 만듭니다. 튜플은 불변이므로 딕셔너리 키로 사용할 수 있습니다.
제안되었지만 받아들여지지 않은 몇 가지 해법:
주소(객체 ID)로 리스트를 해시 합니다. 같은 값으로 새 리스트를 생성하면 찾을 수 없기 때문에 작동하지 않습니다; 예를 들어:
mydict = {[1, 2]: '12'} print(mydict[[1, 2]])
는 두 번째 줄에 사용된
[1, 2]
의 id가 첫 번째 줄의 것과 다르기 때문에KeyError
예외가 발생합니다. 즉, 딕셔너리 키는is
가 아니라==
를 사용하여 비교해야 합니다.리스트를 키로 사용할 때 사본을 만듭니다. 가변 객체인 리스트가 자신에 대한 참조를 포함할 수 있고, 복사 코드가 무한 루프에 빠질 수 있기 때문에 작동하지 않습니다.
리스트를 키로 허용하지만, 사용자에게 수정하지 않도록 지시합니다. 이것은 잊거나 실수로 리스트를 수정했을 때 프로그램에 추적하기 어려운 버그를 만듭니다. 또한 딕셔너리의 중요한 불변성을 깨뜨립니다;
d.keys()
의 모든 값은 딕셔너리의 키로 사용할 수 있다.딕셔너리 키로 사용되면 리스트를 읽기 전용으로 표시합니다. 문제는 그 값을 변경할 수 있는 것은 최상위 객체만이 아니라는 것입니다; 리스트를 포함하는 튜플을 키로 사용할 수 있습니다. 무엇이든 키로 딕셔너리에 입력하면 거기에서 도달할 수 있는 모든 객체를 읽기 전용으로 표시해야 합니다 – 그리고 다시, 자기 참조 객체는 무한 루프를 일으킬 수 있습니다.
필요하면 이 문제를 회피하는 트릭이 있지만, 위험을 감수하고 사용하십시오: __eq__()
와 __hash__()
메서드를 모두 가진 클래스 인스턴스 내부에 가변 구조를 래핑할 수 있습니다. 그런 다음 딕셔너리(또는 다른 해시 기반 구조)에 상주하는 모든 래퍼 객체의 해시값이 객체가 딕셔너리(또는 다른 구조)에 있는 동안 고정되도록 해야 합니다.
class ListWrapper:
def __init__(self, the_list):
self.the_list = the_list
def __eq__(self, other):
return self.the_list == other.the_list
def __hash__(self):
l = self.the_list
result = 98767 - len(l)*555
for i, el in enumerate(l):
try:
result = result + (hash(el) % 9999999) * 1001 + i
except Exception:
result = (result % 7777777) + i * 333
return result
해시 계산은 리스트의 일부 멤버가 해시 불가능할 가능성과 산술 오버플로의 가능성으로 인해 복잡함에 유의하십시오.
또한 객체가 딕셔너리에 있는지에 관계없이, 항상 o1 == o2
(즉 o1.__eq__(o2) is True
)이면 hash(o1) == hash(o2)
(즉 o1.__hash__() == o2.__hash__()
) 여야 합니다. 이러한 제한 사항을 충족하지 못하면 딕셔너리와 다른 해시 기반 구조가 오작동합니다.
ListWrapper의 경우, 래퍼 객체가 딕셔너리에 있을 때마다 래핑 된 리스트는 이상 동작을 피하려면 변경되지 않아야 합니다. 요구 사항과 이를 올바르게 충족하지 못한 결과에 대해 충분히 생각할 준비가 되어 있지 않은 한 이 작업을 수행하지 마십시오. 경고받았다고 생각하십시오.
list.sort()가 정렬된 리스트를 반환하지 않는 이유는 무엇입니까?¶
성능이 중요한 상황에서, 단지 정렬하기 위해 리스트를 복사하는 것은 낭비입니다. 따라서, list.sort()
는 리스트를 제자리에서 정렬합니다. 이 사실을 상기시키기 위해, 정렬된 리스트를 반환하지 않습니다. 이렇게 하면, 정렬된 복사본이 필요하지만 정렬되지 않은 버전을 유지해야 할 때 실수로 리스트를 덮어쓰지 않도록 합니다.
새 리스트를 반환하려면, 대신 내장 sorted()
함수를 사용하십시오. 이 함수는 제공된 이터러블에서 새 리스트를 만들고, 정렬한 다음 반환합니다. 예를 들어, 정렬된 순서로 딕셔너리의 키를 이터레이트 하는 방법은 다음과 같습니다:
for key in sorted(mydict):
... # do whatever with mydict[key]...
파이썬에서 인터페이스 명세를 어떻게 지정하고 강제합니까?¶
C++와 Java와 같은 언어에서 제공하는 모듈에 대한 인터페이스 명세는 모듈의 메서드와 함수에 대한 프로토타입을 설명합니다. 많은 사람은 컴파일 타임에 인터페이스 명세를 적용하는 것이 대규모 프로그램을 구축하는 데 도움이 된다고 생각합니다.
파이썬 2.6은 추상 베이스 클래스(ABC)를 정의할 수 있는 abc
모듈을 추가합니다. 그런 다음 isinstance()
와 issubclass()
를 사용하여 인스턴스나 클래스가 특정 ABC를 구현하는지 확인할 수 있습니다. collections.abc
모듈은 Iterable
, Container
및 MutableMapping
과 같은 유용한 ABC 집합을 정의합니다.
파이썬의 경우, 구성 요소에 대한 적절한 테스트 규율을 통해 인터페이스 명세의 많은 이점을 얻을 수 있습니다.
모듈에 대한 좋은 테스트 스위트는 회귀 테스트를 제공함과 동시에 모듈 인터페이스 명세와 예제 집합으로 사용할 수 있습니다. 많은 파이썬 모듈은 스크립트로 실행하여 간단한 “자체 테스트”를 제공할 수 있습니다. 복잡한 외부 인터페이스를 사용하는 모듈조차도 외부 인터페이스의 간단한 “스텁(stub)” 에뮬레이션을 사용하여 종종 격리 테스트 할 수 있습니다. doctest
와 unittest
모듈 또는 제삼자 테스트 프레임워크를 사용하여 모듈의 모든 코드 줄을 실행하는 포괄적인 테스트 스위트를 구축할 수 있습니다.
인터페이스 명세를 갖는 것뿐만 아니라 적절한 테스트 규율은 파이썬으로 복잡한 대규모 응용 프로그램을 빌드하는 데 도움이 될 수 있습니다. 실제로, 인터페이스 명세는 프로그램의 특정 속성을 테스트할 수 없기 때문에 이것이 더 좋을 수 있습니다. 예를 들어, append()
메서드는 일부 내부 리스트 끝에 새 요소를 추가해야 합니다; 인터페이스 명세는 append()
구현이 실제로 이를 올바르게 수행하는지 테스트할 수 없지만, 테스트 스위트에서 이 속성을 확인하는 것은 간단합니다.
Writing test suites is very helpful, and you might want to design your code with an eye to making it easily tested. One increasingly popular technique, test-directed development, calls for writing parts of the test suite first, before you write any of the actual code. Of course Python allows you to be sloppy and not write test cases at all.
goto가 없는 이유는 무엇입니까?¶
In the 1970s people realized that unrestricted goto could lead
to messy “spaghetti” code that was hard to understand and revise.
In a high-level language, it is also unneeded as long as there
are ways to branch (in Python, with if
statements and or
,
and
, and if-else
expressions) and loop (with while
and for
statements, possibly containing continue
and break
).
One can also use exceptions to provide a “structured goto” that works even across function calls. Many feel that exceptions can conveniently emulate all reasonable uses of the “go” or “goto” constructs of C, Fortran, and other languages. For example:
class label(Exception): pass # declare a label
try:
...
if condition: raise label() # goto label
...
except label: # where to goto
pass
...
이것은 당신이 루프의 중간으로 점프하는 것을 허용하지 않지만, 어쨌든 그것은 일반적으로 goto의 남용으로 간주합니다. 아껴서 사용하십시오.
날 문자열(r-strings)이 역 슬래시로 끝날 수 없는 이유는 무엇입니까?¶
더 정확하게는, 홀수 개의 역 슬래시로 끝날 수 없습니다: 끝의 쌍이 없는 역 슬래시는 닫는 따옴표 문자를 이스케이프 하여, 끝나지 않은 문자열을 남깁니다.
날 문자열은 자체 역 슬래시 이스케이프 처리를 수행하려는 프로세서(주로 정규식 엔진)에 대한 입력을 쉽게 만들 수 있도록 설계되었습니다. 이러한 프로세서는 일치하지 않는 후행 역 슬래시를 에러로 간주하므로, 날 문자열은 이를 허용하지 않습니다. 그 대가로, 역 슬래시로 이스케이프 하여 문자열 따옴표 문자를 전달할 수 있습니다. 이 규칙은 r-문자열이 의도된 목적으로 사용될 때 잘 작동합니다.
윈도우 경로명을 빌드하려는 경우, 모든 윈도우 시스템 호출은 슬래시도 허용함에 유의하십시오:
f = open("/mydir/file.txt") # works fine!
DOS 명령에 대한 경로명을 빌드하려는 경우, 예를 들어, 다음 중 하나를 시도하십시오
dir = r"\this\is\my\dos\dir" "\\"
dir = r"\this\is\my\dos\dir\ "[:-1]
dir = "\\this\\is\\my\\dos\\dir\\"
왜 파이썬에는 어트리뷰트 대입을 위한 “with” 문이 없습니까?¶
Python has a ‘with’ statement that wraps the execution of a block, calling code on the entrance and exit from the block. Some language have a construct that looks like this:
with obj:
a = 1 # equivalent to obj.a = 1
total = total + 1 # obj.total = obj.total + 1
파이썬에서는, 이러한 구조가 모호해집니다.
오브젝트 파스칼, 델파이 및 C++와 같은 다른 언어는 정적 형을 사용하므로, 어떤 멤버가 대입되고 있는지 명확하게 알 수 있습니다. 이것이 정적 타이핑의 요점입니다 – 컴파일러는 항상 컴파일 시점에 모든 변수의 스코프를 알고 있습니다.
파이썬은 동적 형을 사용합니다. 실행 시간에 어떤 어트리뷰트가 참조되는지 미리 알 수 없습니다. 멤버 어트리뷰트는 실행 중에 객체에 추가하거나 제거할 수 있습니다. 이로 인해 단순한 읽기만으로는 어떤 어트리뷰트가 참조되는 중인지 알 수 없습니다: 지역 변수, 전역 변수 또는 멤버 어트리뷰트?
예를 들어, 다음과 같은 불완전한 스니펫을 보십시오:
def foo(a):
with a:
print(x)
스니펫은 “a” 에 “x” 라는 멤버 어트리뷰트가 있어야 한다고 가정합니다. 그러나, 파이썬에는 인터프리터에게 이것을 알려주는 것이 없습니다. 가령, “a”가 정수이면 어떻게 됩니까? “x” 라는 전역 변수가 있으면, with 블록 내에서 사용됩니까? 보시다시피, 파이썬의 동적 특성은 이런 선택을 훨씬 더 어렵게 만듭니다.
그러나, “with”와 유사한 언어 기능의 주요 이점(코드 볼륨 감소)은 대입을 통해 파이썬에서 쉽게 달성할 수 있습니다. 다음과 같이 하는 대신에:
function(args).mydict[index][index].a = 21
function(args).mydict[index][index].b = 42
function(args).mydict[index][index].c = 63
이렇게 작성하십시오:
ref = function(args).mydict[index][index]
ref.a = 21
ref.b = 42
ref.c = 63
이름 연결은 파이썬에서 실행 시간에 결정되는데, 두 번째 버전은 확인을 한 번만 수행하면 되므로 실행 속도를 높이는 부작용도 있습니다.
if/while/def/class 문에 콜론이 필요한 이유는 무엇입니까?¶
콜론은 주로 가독성을 높이기 위해 필요합니다 (실험적 ABC 언어의 결과 중 하나입니다). 이걸 고려해보십시오:
if a == b
print(a)
와
if a == b:
print(a)
두 번째 것이 어떻게 약간 더 읽기 쉬운지 주목하십시오. 이 FAQ 답변에서 콜론이 어떻게 예제를 시작하는지도 주목하십시오; 영어의 표준 사용법입니다.
또 다른 사소한 이유는 콜론이 구문 강조 표시가 있는 편집기를 도와준다는 것입니다: 들여쓰기를 늘려야 하는 때를 결정하기 위해, 프로그램 텍스트를 더 정교하게 구문 분석하는 대신 콜론을 찾을 수 있습니다.
파이썬은 왜 리스트와 튜플 끝에 쉼표를 허용합니까?¶
파이썬은 리스트, 튜플 및 딕셔너리 끝에 후행 쉼표를 추가할 수 있도록 합니다:
[1, 2, 3,]
('a', 'b', 'c',)
d = {
"A": [1, 5],
"B": [6, 7], # last trailing comma is optional but good style
}
이를 허용하는 데에는 몇 가지 이유가 있습니다.
리스트, 튜플 또는 딕셔너리에 대한 리터럴 값이 여러 줄에 걸쳐 있는 경우, 이전 줄에 쉼표를 추가할 필요가 없기 때문에 요소를 추가하기가 더 쉽습니다. 문법 에러를 만들지 않고 줄을 재정렬할 수도 있습니다.
실수로 쉼표를 누락하면 진단하기 어려운 에러가 발생할 수 있습니다. 예를 들면:
x = [
"fee",
"fie"
"foo",
"fum"
]
이 리스트에는 네 개의 요소가 있는 것처럼 보이지만, 실제로는 세 가지 요소만 있습니다: “fee”, “fiefoo” 및 “fum”. 항상 쉼표를 추가하면 이러한 에러 원인을 피할 수 있습니다.
후행 쉼표를 허용하면 프로그래밍적인 코드 생성이 더 쉬워질 수도 있습니다.