.. _language: RPython 언어 ============ 정의 ---- RPython은 정적 분석이 가능한 Python의 제한된 부분 집합입니다. 언어에 추가된 요소들이 있고 일부 사항은 예상 외로 동작할 수도 있지만, 이는 고려해야 할 제약 사항들을 대략적으로 정리한 목록입니다. 진행하다 보면 마주치게 될 특수한 경우의 제약 사항이 매우 많다는 점에 유의하십시오. 정확한 정의는 "RPython은 저희의 번역(translation) 툴체인이 받아들일 수 있는 모든 것"입니다 :) 흐름 제한사항 ------------- **변수** 변수는 각 제어 흐름 지점에서 `객체 제약`_\ 에 설명된 대로 최대 하나의 타입 값을 가져야 합니다. 즉, 예를 들어 문자열과 정수를 모두 담는 동일한 변수를 사용해 제어 경로를 합치는 것은 피해야 함을 의미합니다. None(기본적으로 널 포인터 역할)을 래핑된 객체, 클래스 인스턴스, 리스트, 딕셔너리, 문자열 등 여러 다른 타입과 섞는 것은 허용되지만, 정수, 부동소수점, 튜플과는 *허용되지 않습니다*. **상수** 모든 모듈 전역 변수는 상수로 간주됩니다. 이들의 바인딩은 실행 중에 변경되어서는 안 됩니다. 게다가, 전역(즉, 미리 만들어진) 리스트와 딕셔너리는 불변이어야 합니다: 예를 들어 전역 리스트를 수정하면 일관성 없는 결과가 발생합니다. 하지만 전역 인스턴스에는 이러한 제약이 없으므로, 가변 전역 상태가 필요하다면 미리 만들어진 싱글턴 인스턴스의 속성에 저장하십시오. **제어 구조** 모두 허용되며, ``for`` 루프는 내장 타입으로 제한되고, 제너레이터는 매우 제한적\ 입니다. **범위** ``range``\ 와 ``xrange``\ 는 동일합니다. ``range``\ 는 결과가 수정되는 경우에만 배열을 생성하며, 항상 배열을 생성하는 것은 아닙니다. 이는 어디서나 허용되며 완전히 구현되어 있습니다. CPython과의 유일하게 눈에 띄는 차이는 ``xrange``\ 의 start, stop, step 필드에 접근할 수 없다는 점입니다. **정의** 클래스나 함수의 실행 시점(run-time) 정의는 허용되지 않습니다. **제너레이터** 제너레이터는 지원되지만, 그 정확한 범위는 매우 제한적입니다. 하나의 제어 지점에서 서로 다른 두 제너레이터를 병합할 수는 없습니다. **예외** 완전히 지원됩니다. 내장 연산에서 발생하는 예외에 대한 제약 사항은 아래 `예외 규칙`_\ 을 참고하십시오. 객체 제약 --------- 우리는 사용하고 있습니다 **정수, 실수, 불리언** 동작합니다. **문자열** 많은, 하지만 전부는 아닌 문자열 메서드가 지원되며, 지원되는 메서드라도 반드시 모든 인자를 받아들이는 것은 아닙니다. 인덱스는 음수일 수 있습니다. 인덱스가 음수가 아닌 경우, 번역기가 해당 인덱스가 음수가 아님을 증명할 수 있다면 약간 더 효율적인 코드를 얻게 됩니다. 문자열을 슬라이싱할 때는 슬라이스의 시작 및 종료 인덱스가 음수가 아님을 증명해야 합니다. 암묵적인 str-to-unicode 캐스트는 어디에도 없습니다. ``%`` 연산자를 사용한 단순 문자열 포매팅은 포맷 문자열이 번역(translation) 시점에 알려져 있는 한 동작합니다. 지원되는 포매팅 지정자는 ``%s``, ``%d``, ``%x``, ``%o``, ``%f``\ 뿐이며, 여기에 더해 ``%r``\ 도 지원되지만 사용자 정의 인스턴스에 한해서만 지원됩니다. 변환 플래그(conversion flag), 정밀도(precision), 길이(length) 등의 수식자는 지원되지 않습니다. 또한 포매팅 시 유니코드와 문자열을 혼용하는 것은 금지됩니다. **튜플** 가변 길이 튜플은 없으며, 값의 쌍이나 n-튜플을 저장하거나 반환하는 데 사용합니다. 요소의 타입과 길이의 각 조합은 서로 별개이며 혼용할 수 없는 타입을 구성합니다. 리스트를 튜플로 변환하는 일반적인 방법은 없는데, 결과의 길이를 정적으로 알 수 없기 때문입니다. (물론 ``lst``\ 가 항목을 3개 가지고 있다는 것을 알고 있다면 ``t = (lst[0], lst[1], lst[2])``\ 라고 작성할 수 있습니다.) **lists** 리스트는 할당된 배열로 사용됩니다. 리스트는 초과 할당되므로, list.append()는 상당히 빠릅니다. 그러나 크기가 고정된 리스트를 사용하면, 코드가 더 효율적입니다. Annotator는 리스트 컴프리헨션(list comprehension)을 사용하더라도, 대부분의 경우 리스트가 크기 고정임을 알아낼 수 있습니다. 음수 인덱스나 범위를 벗어난 인덱스는 다음과 같이 가장 일반적인 연산에 대해서만 허용됩니다: - *indexing*: 양수 및 음수 인덱스가 허용됩니다. 인덱스는 IndexError 예외 절에서 요청하는 경우 검사됩니다. - *슬라이싱*: 슬라이스 시작은 범위 내에 있어야 합니다. 끝은 그럴 필요가 없지만, 시작보다 작아서는 안 됩니다. [:-1]이라는 특수한 경우를 제외하고, 모든 음수 인덱스는 허용되지 않습니다. 스텝은 지원되지 않습니다. 슬라이스 삭제도 동일한 규칙을 따릅니다. - *슬라이스 할당*: ``len(sublist) == y - x``\ 인 경우에만 ``lst[x:y] = sublist``\ 를 지원합니다. 즉, 슬라이스 할당은 리스트의 전체 길이를 변경할 수 없으며, 항목만 교체합니다. - *기타 연산자*: ``+``, ``+=``, ``in``, ``*``, ``*=``, ``==``, ``!=``\ 는 예상대로 동작합니다. - *메서드*: append, index, insert, extend, reverse, pop. pop()에서 사용되는 인덱스는 위의 *인덱싱*\ 과 동일한 규칙을 따릅니다. insert()에서 사용되는 인덱스는 범위 내에 있어야 하며 음수여서는 안 됩니다. **딕셔너리(dicts)** 해시 가능하기만 하면 고유한 키 타입만 갖는 딕셔너리입니다. 사용자 정의 해시 함수와 사용자 정의 동등성 비교는 반영되지 않습니다. 사용자 정의 해시 함수를 사용하려면 ``rpython.rlib.objectmodel.r_dict``\ 를 사용하십시오. **집합** RPython\ 에서는 집합(set)이 직접 지원되지 않습니다. 대신 일반 dict를 사용하고 값을 None으로 채워야 합니다. 그 dict의 값은 공간을 차지하지 않습니다. **리스트 컴프리헨션** 할당되고 초기화된 배열을 생성하는 데 사용할 수 있습니다. **함수** + 함수 선언은 기본값과 ``*args``\ 를 사용할 수 있지만, ``**keywords``\ 는 사용할 수 없습니다. + 함수 호출은 알려진 함수뿐 아니라 변수로 지정된 함수, 또는 메서드에 대해서도 이루어질 수 있습니다. 위치 인자와 키워드 인자로 호출할 수 있으며, ``*args`` 인자(반드시 튜플이어야 함)를 전달할 수도 있습니다. + 위에서 설명했듯이, 튜플은 가변 길이가 아닙니다. 가변 개수의 인자로 함수를 호출해야 한다면, 함수 자체를 일반 리스트인 단일 인자를 받도록 리팩터링하십시오. + 동적 디스패치는 호출 가능한 모든 함수에 대해 동일하거나, 적어도 "충분히 호환되는" 시그니처의 사용을 강제합니다. 이는 주로 메서드가 오버라이드되거나 서로 다른 클래스에서 어떤 식으로든 다른 정의가 주어지는 경우, 메서드 호출과 관련이 있습니다. 이는 또한 명시적으로 조작되는 함수 객체라는 더 드문 경우와도 관련이 있습니다. 정확한 호환성 규칙을 설명하는 일은 다소 복잡합니다(하지만 이를 어길 경우, 알 수 없는 크래시가 아니라 rtyper로부터 명시적인 오류를 받게 될 것입니다). **내장 함수** 다수의 내장 함수를 사용할 수 있습니다. 정확한 목록은 :source:`rpython/annotator/builtin.py`\ 에서 찾을 수 있습니다(``def builtin_xxx()``\ 를 참고하십시오). 다만 일부 내장 함수는 지원하는 범위가 제한적일 수 있습니다. ``int, float, str, ord, chr``...는 간단한 변환 함수로 사용할 수 있습니다. ``int, float, str``...는 isinstance 내부에서만 타입으로서 특별한 의미를 갖는다는 점에 유의하십시오. **클래스** + 메서드와 다른 클래스 속성은 시작 이후에 변경되지 않습니다 + 단일 상속은 완전히 지원됩니다 + 클래스 본문에서 `rpython.rlib.objectmodel.import_from_mixin(M)`\ 을 사용하여 클래스 `M`\ 의 전체 내용을 복사합니다. 이는 믹스인(mixin)을 구현하는 데 사용할 수 있습니다: 함수와 정적 메서드는 복제되고(그 외의 클래스 속성은 수정 없이 그대로 복사됩니다). + 클래스도 일급 객체입니다 **객체** 일반 규칙이 적용됩니다. 지원되는 특수 메서드는 ``__init__``, ``__del__``, ``__len__``, ``__getitem__``, ``__setitem__``, ``__getslice__``, ``__setslice__``, ``__iter__``\ 뿐입니다. 슬라이싱을 처리하려면 ``__getslice__``\ 와 ``__setslice__``\ 를 사용해야 합니다. 슬라이싱에 ``__getitem__``\ 과 ``__setitem__``\ 을 사용하는 것은 지원되지 않습니다. 추가로, ``__getslice__``\ 를 사용하더라도 슬라이싱에 음수 인덱스를 사용하는 것은 여전히 지원되지 않습니다. 소멸자(destructor) ``__del__``\ 은 `simple operations`__\ 만 포함해야 한다는 점에 유의하십시오. 더 복잡한 종류의 소멸자가 필요한 경우, 대신 ``rpython.rlib.rgc.FinalizerQueue``\ 를 사용하는 것을 고려하십시오. .. __: garbage_collection.html 이러한 레이아웃 덕분에 신경 써야 할 타입의 수가 상당히 제한됩니다. 정수형(Integer Types) --------------------- 정수 타입을 구현하는 과정에서, 현재 CPython에서 정수가 상당히 유동적인 상태라는 문제에 부딪혔습니다. Python 2.4부터 정수는 오버플로가 발생하면 long으로 변합니다. 이와 달리, 기본적으로는 랩어라운드(wrap-around) 방식의 머신 크기 연산을 수행하면서도, 필요할 때는 명시적으로 오버플로를 검사할 수 있는 방법이 필요합니다. 게다가, 번역(translation) 전후로 일관된 동작이 필요합니다. 저희는 부호 있는 산술 연산에 일반적인 정수를 사용합니다. 이는 번역(translation) 이전에는 오버플로우가 발생할 경우 long을 얻고, 번역(translation) 이후에는 조용한 래핑(wrap-around)이 발생함을 의미합니다. 더 많은 제어가 필요할 때마다, 저희는 :source:`rpython/rlib/rarithmetic.py`\ 에 있는 다음 헬퍼들을 사용합니다: **ovfcheck()** 이 특수 함수는 ``z = ovfcheck(x+y)``\ 와 같이 인자로 단일 산술 연산만을 사용해야 합니다. 이 함수의 의도된 의미는 지정된 연산을 오버플로 검사 모드로 수행하는 것입니다. 실행 시점(run-time)에, Python에서는 ovfcheck() 함수 자체가 결과를 검사해서, 그것이 ``long``\ 이면 OverflowError를 발생시킵니다. 하지만 코드 생성기들은 ovfcheck()를 힌트로 사용합니다: 즉 ``ovfcheck(x+y)`` 표현식 전체를 C에서의 단일 오버플로우 검사 덧셈으로 대체합니다. **intmask()** 이 함수는 랩어라운드(wrap-around) 산술 연산에 사용됩니다. 이 함수는 인자의 하위 비트를 반환하며, C의 "signed long int"에 들어맞지 않는 부분은 마스킹하여 제거합니다. 이 함수의 목적은 Python에서 이전 연산의 결과로 생긴 Python ``long``\ 을 다시 Python ``int``\ 로 변환하는 것입니다. 코드 생성기는 intmask()를 완전히 무시하는데, 이는 어차피 기본적으로 항상 랩어라운드 부호 있는 산술 연산을 수행하고 있기 때문입니다. (현재 우리는 C의 "int"와 "long int" 구분에 대응하는 것이 없으며, 어디서나 "long int"라고 가정합니다.) **r_uint** 몇몇 경우(예: 해시 테이블 조작)에는 머신 크기의 부호 없는 산술 연산이 필요합니다. 이런 경우를 위해 r_uint 클래스가 있으며, 이는 오버플로 시 아무 경고 없이 순환(wrap around)하는 워드 크기의 부호 없는 정수를 순수 Python으로 구현한 것입니다. ("워드 크기"와 "머신 크기"는 같은 의미로 사용되며, C에서 "unsigned long"을 써서 얻는 네이티브 크기를 뜻합니다.) 이 클래스의 목적은 (위에서 언급한 헬퍼 함수와 달리) 일관된 타입 지정에 있습니다: Python과 애노테이터 모두 프로그램 내에서 r_uint 인스턴스를 전파하며, 이들 사이의 모든 연산을 부호 없는 것으로 해석합니다. r_uint의 인스턴스는 코드 생성기에 의해 특별하게 처리되어 적절한 저수준 타입과 연산을 사용합니다. (부호 있는) 정수와 r_uint를 연산에서 혼합하면 부호 없는 결과를 의미하는 r_uint가 생성됩니다. r_uint에서 부호 있는 정수로 다시 변환하려면 intmask()를 사용하십시오. 타입 강제 및 검사 ----------------- RPython은 함수 인자에 RPython 수준의 타입을 강제하는 헬퍼 데코레이터를 제공합니다. ``enforceargs()``\ 라는 이 데코레이터는 함수의 인자와 일치할 것으로 예상되는 타입들을 매개변수로 받습니다. ``enforceargs()``\ 로 데코레이트된 함수는 임포트 시점에 함수 시그니처가 분석되고 RPython 수준의 타입이 추론됩니다(RPython에서 수행되는 번역(translation)의 방식에 대한 자세한 내용은 `Annotation pass documentation`_\ 를 참고하십시오). RPython에서 지원하지 않는 타입을 만나면 ``TypeError``\ 가 발생합니다. ``enforceargs()``\ 는 기본적으로 함수가 호출될 때마다 매개변수 타입의 타입 검사도 수행합니다. 이 동작을 비활성화하려면, 데코레이터에 ``typecheck=False`` 매개변수를 전달하면 됩니다. .. _Annotation pass documentation: http://rpython.readthedocs.io/en/latest/translation.html#annotator 예외 규칙 --------- 기본적으로 단순한 경우에는 예외가 생성되지 않습니다.:: #!/usr/bin/python lst = [1,2,3,4,5] item = lst[i] # this code is not checked for out-of-bound access try: item = lst[i] except IndexError: # complain 예외 처리기가 없는 코드는 예외를 발생시키지 않습니다(즉, 번역(translation)이 이루어진 후에는 그렇다는 뜻입니다. CPython 위에서 실행하면 물론 예외를 발생시킬 수 있습니다). 예외 처리기를 제공하면 오류 검사를 요청하는 것입니다. 제공하지 않으면 해당 연산이 실패할 수 없음을 시스템에 보장하는 것입니다. 이 규칙은 *함수 호출*\ 에는 적용되지 않습니다: 호출된 함수는 어떤 예외든 발생시킬 수 있다고 가정됩니다. 예를 들어:: x = 5.1 x = x + 1.2 # not checked for float overflow try: x = x + 1.2 except OverflowError: # float result too big 하지만:: z = some_function(x, y) # can raise any exception try: z = some_other_function(x, y) except IndexError: # only catches explicitly-raised IndexErrors in some_other_function() # other exceptions can be raised, too, and will not be caught here. 위에서 설명한 ovfcheck() 함수도 동일한 규칙을 따릅니다: 오버플로가 발생하면 어디서든 잡을 수 있는 OverflowError를 명시적으로 발생시킵니다. 명시적으로 발생(raise)되거나 다시 발생(re-raise)된 예외는 항상 생성됩니다. PyPy는 CPython 위에서 디버깅이 가능합니다 ----------------------------------------- PyPy는 표준 CPython에서 실행 가능하다는 장점이 있습니다. 이는 곧 모든 예외 처리를 활성화한 상태로 PyPy 전체를 실행할 수 있다는 의미이며, 이를 통해 암묵적 가정(assertion)을 지키지 못한 경우를 포착할 수 있습니다.