diff --git a/hong/python/python-basics.md b/hong/python/python-basics.md new file mode 100644 index 0000000..1f5d6b2 --- /dev/null +++ b/hong/python/python-basics.md @@ -0,0 +1,425 @@ +# Python Basics + +## 1. CPython, Cython, Jython, IronPython 차이 + +### CPython + +Cpython은 python.org에서 배포되고 있는 Python을 의미한다. + +Python의 공식 github에 들어가면 python/cpython으로 프로젝트명이 되어 있는 것을 볼 수 있다. + +여기서 앞에 C는 C/C++언어로 구현된 Python을 의미한다. + +프로그래밍 언어를 구축할 때 해당 언어를 컴퓨터가 이해할 수 있게 번역해줄 compiler가 필요하고, 이때 + +1. self-hosted compiler: 자신 어어로 컴파일러를 작성한 언어 +2. Source-to-source compiler: 기존에 널리 사용되는 프로그래밍 언어를 활용해서 컴파일러를 작성 (대표적으로 Python) + +Python은 C언어로 작성된 컴페일러를 활용해서 라인단위로 Code를 인터프리팅 하여 실행한다. + +(때문에 CPython을 수정한 다음 컴파일을 하면 Python의 문법이 바뀐다!) + +그리고 C로 작성된 컴파일러이기 때문에, CPython의 Python C-API를 이용해서 C-Extension 모듈을 만들 수도 있다. https://devocean.sk.com/experts/techBoardDetail.do?ID=163718 + +예를 들어, Numpy package 같은 경우, 빠른 속도를 위해서 CPython으로 작성된 소스코드가 다수 존재한다. https://github.com/numpy/numpy + +** 추가 공부할 주제: Python GIL (Global Interpreter Lock) 문제, CPython의 GC, 컴파일 언어보다 느린 이유. CPython의 메모리 관리. + +### PyPy + +PyPy는 CPython의 컴파일러를 python으로 작성한 구현체 (self-hosted compiler). 최적화된 PyPy의 파이썬 구현이 현재의 C 구현보다 빠르도록 하는 것이 이 프로젝트의 목적. + +때문에 CPython으로 작성된 패키지가 포함된 라이브러리들이 정상적으로 작동하지 않는 경우가 있다. (예: 과거 numpy가 PyPy에서 정상동작하지 않았던 이유.) + +구현체가 다르기 때문에 PyPy를 활용하기 위해서는 별도의 PyPy로 된 Python 설치가 필요하다. + +### Cython + +Cython은 C와 유사한 문법으로 함수를 작성하고, 이를 CPython 패키지 형태로 만들어준다. + +공식 문서의 예시: + +```python +'''hello.pyx''' +def say_hello_to(name): + print(f"Hello {name}!") +``` + +```python +'''setup.py''' +from setuptools import setup +from Cython.Build import cythonize + +setup( + name='Hello world app', + ext_modules=cythonize("hello.pyx"), +) +``` + +위 파일들을 생성한 뒤, setup.py를 실행시키면 아래와 같은 C언어 파일이 생성되고, 아래의 파일 내에서 Python.h를 사용하는 것을 통해서 CPython Level의 패키지를 만들어 준다는 것을 유추할 수 있다. + +```c +/* Generated by Cython 0.29.33 */ + +/* BEGIN: Cython Metadata +{ + "distutils": { + "name": "test_foo", + "sources": [ + "test_foo.pyx" + ] + }, + "module_name": "test_foo" +} +END: Cython Metadata */ + +#ifndef PY_SSIZE_T_CLEAN +#define PY_SSIZE_T_CLEAN +#endif /* PY_SSIZE_T_CLEAN */ +#include "Python.h" +#ifndef Py_PYTHON_H +... +``` + +Pandas의 경우도 더 높은 성능을 원한느 경우 Cython을 이용해서 최적화된 함수를 사용할 수 있다. https://pandas.pydata.org/docs/user_guide/enhancingperf.html + +### Jython + +JVM을 활용한 Python의 구현체 중 하나로, Python syntax로 작성된 코드를 JVM이 이해할 수 있는 바이트코드로 만들고, 이 바이트 코드를 JVM이 실행하게 된다. + +참고: Java 코드가 JVM을 통해 프로그램이 실행되는 과정: + +`Java Source Code -> Java Compiler -> Java Bytecode -> JVM -> Java Interpreter -> 컴퓨터가 이해할 수 있는 기계어로 변환 -> OS를 통해 메모리 적재 후 PC (Program Counter) Register를 통해 시분할로 할당된 CPU 연산 처리` + +CPython 대비 속도의 차이점이 있을 수 있지만, 언어 사용자 입장에서는 큰 차이점을 느끼지 못한다. + +Jython의 특징으로 GC가 variable counting이 아닌 JVM에서 사용되는 GC로 구현되어 있기 때문에 GIL을 사용하지 않느다는 장점이 있다. + +Jython의 가장 큰 문제: 아직 Python 3버전을 지원하지 않는다… + +> The current release (Jython 2.7) only supports Python 2 (sorry). There is work towards a Python 3 in the project’s GitHub repository - [Jython.org](https://www.notion.so/Cracking-the-Coding-Interview-db3ebd2fd2c3434a9a3e7a17e58aa71b?pvs=21) +> + +### IronPython + +Jython이 JVM을 활용한 구현체인 것처럼, IronPython은 C#의 NET 프레임워크를 사용한 Python 구현체. + + + +## 2. Python 3.11의 주된 변경점 (Oct. 2022) + +### 배경 + +Python은 동적 타이핑 (Dynamically Typing), 인터프리터식, 그리고 객체지향 언어로 다양한 프레임워크 (장고, 플라스크, FastAPI) 등과 함께 딥러님과 여러 프로그램들과 함께 발전하고 있다. + +하지만 이런 Python은 속도가 느리다는 명확한 한계점이 있다. Python은 Java, C, C++ 만큼 빠르지 못하다. 이러한 한계점은 커뮤니티를 통해서 지적되어 왔고, Python 3.11은 그에 대한 첫 결과물로 볼 수 있다. + +### Python 3.11의 주된 변경점 + +- 적응형 전문 인터프리터 (PEP 659) +- 연속적으로 할당된 실행 프레임들 +- Zero cost try-except +- 더욱 정형화된 오브젝트 레이아웃 +- 레이지하게 생성하는 객체 Dict + +### Python 내부 데이터 구조 실행방식 + +**1. 각 메모리별 접근 속도** + + - Arithmetic operation: 1 cycle + - L1 cache latency: ~4 cycles + - L2 cache latency: ~10 cycles + - L3 cache latency: ~30 cycles + - RAM latency: ~200+ cycles +- 5Ghz CPU에서 위와 같은 메모리 접근성을 보인다. 물리적인 접근은 L1, L2, L3 그리고 RAM으로 갈수록 소통이 비싸진다. 즉, 어느 프로그램이든 메모리 접근이 있으면 물리적인 한계로 인해 프로그램이 느려질 수밖에 없다. 만약 다른 프로그램이 특정 메모리 주소를 접근하려고 할 때, 다른 프로그램이 그 메모리 주소를 점유하고 있다면 더욱더 느려질 수밖에 없을 것이다. + +**2. Linked List vs Array List** +- 3개 요소를 가지고 있는 Linked List와 Array List 비교. +- Linked List는 마지막 요소에 접근하기 위해 Head → 0번째 요소 → 1번째 요소 → 2번째 요소에 접근하여, 총 4번 메모리 접근을 해야 함. +- Array List는 Head → 2번째 요소에 바로 접근하여 요소의 수에 상관없이 항상 2번 메모리에 접근함. +- Frame stack은 Python 함수를 부를 때 사용되는 객체. 각각의 Python 함수를 부를 때마다, Frame Object를 stack에 넣는다. 이때 프레임은 지역 변수, 임시 값을 위한 공간, 이전 프레임, 전역 변수 그외 등등을 위한 참조, 그리고 디버깅 정보를 가지고 있다. + +**3. Python 3.10 (Oct. 2021) 이하 버전** +- 위의 Python frame은 Linked List로 연결되어 있었고, 이는 Top에 있는 스택만 가져와서 부르기에는 편했다. +- 하지만 다른 스택을 부르기에는 추가적인 비용도 들고 새로운 공간 할당을 위해 공간을 비워둬야 하는 등 위에서 말한 메모리 접근 측면에서 괸장히 큰 비용을 치르게 된다. +- 3.10 이하 버전에서는 링크 스택을 위해 많은 메모리 청크가 스레드마다 할당 되어 있었고, 만약 새로운 프레임이 생성될 경우 기존 메모리를 참고해야하는 경우가 생긴다. 이는 곧 L1 같은 빠른 메모리에서, L2, L3, 그리고 RAM 같은 느린 메모리로 접근할 때 많은 대가를 치르게 된다. + +**4. Python 3.11** +- Python 3.11에서는 이를 아주 큰 메모리 할당을 통해 해결하고자 했다. (일단 미리 큰 메모리를 할당해놓는 이유는 얼마나 큰 프레임 스택을 얻게 될지 몰라서이다.) +- 그리고 메모리에서 새로운 할당을 하기보다는, 재사용을 통해 메모리 활용성을 높이고자 함. +- 그리고 이러한 프레임 객체를 재활용할 때, Python 3.11에서는 Frame Object를 느리게 (lazily) 생성한다. 이는 곧 더 적은 메모리를 요구한다는 것. +- 물론 이러한 느린 (lazily) 생성은 무조건 모든 케이스에 맞는 것은 아니지만, 이러한 경우는 적기에 3.10 버전과 비교했을 때 많은 성능적 이득을 얻게 됨. +- 정리하면, 3.10 이하와 비교했을 때 3.11 버전은: + - 디버깅 정보가 느리게 생성된다. (이는 기본적으로 프레임 스택의 직접적인 파트가 아니기 때문,) + - Exception 스택이 버려짐 + Name space인 Dict가 키값을 가능할 때마다 공유함. + +**5. Zero Cost Exceptions** +- 메모리 절약을 위해서 Exception 스택이 없어졌기 때문에 그 대안으로 Exception 정보를 테이블에 저장하는 방식을 채택함. +- 3.10에서 try-except는 바이트코드에 명시적으로 구현되어 있음. + - Try는 내부 스택으로 자그마한 테이블을 넣고, 시스켐이 예외 처리를 위해 어디로 가야할지, 그리고 얼마나 많은 실행 스택을 사출해야할지 알려줌. + - 이는 160 Byte를 매 프레임 객체마다 소모하며, 특히 3.10 미만 버전은 240 byte나 소모함. + - 예: 21개의 try, except를 nested 구조로 짜면 21 x 160 byte로 메모리 이슈 발생 가능. +- 3.11에서는 Exception 정보를 태아불애 저장한다. + - 예외가 발생하지 않는 한, 아무것도 실행되지 않는다. + - 예외 발생 시, offset과 stack의 깊이는 테이블에서 조회함. +- 위 방식의 문제점 (Zero Cost는 아닌 이유) + - 코드 객체의 크기가 조금 늘어남. + - 예외 발새 시 조금 느려짐. + +### Dictionary 변화 + +**1. 일반적인 Python 객체** + +```python +class C: + def __init__(self, a, b): + self.a = a + self.b = b +``` + +- 위 코드는 두개의 attribute를 가지고 있으며, a, b를 할당한다. +- 위의 코드가 메모리 상에서 어떻게 접근하고 메모리를 점유하는지 이해해야 함. + - Python 객체는 고정된 크기를 가지고 있지 않다. + - 대부분의 모든 Python 객체는 `__dict__` 를 가지고 있고, 이떄 `__dict__` 속성은 거의 직접적으로 사용되지 않는다. (class의 instance가 가지고 있는 속성들을 모두 포함한다) + - 또한 `__slots__` 이라는 속성을 가지고 있을 수 있다. (클래스가 가진 속성을 제한할 때 사용된다. 이는 기존의 Dictionary로 관리한느 속성을 집합 형태의 Set으로 바꿔 메모리를 절햑하는 방식) + - 또는 `List` 처럼 built-in 타입들로부터 상속받아 사용될 수 있음. + - 이러한 특성들은 코드를 읽기 쉽고 직관적으로 작성할 수 있게 해준다. (예: `dict["attribute"]` 처럼 할 필요 없이 `dict.attribute` 로 사용할 수 있게 해줌) + - 정리하자면, python 객체의 크기는 가변적이라는 것 + 이러한 특성으로 인해 Python 객체의 실행은 느린 것. + +![스크린샷 2024-04-04 211134](https://github.com/10000-Bagger/free-topic-study/assets/34956359/2289e2e2-3c6c-4130-a77b-c02b9acf2199) + +- 위와 같은 특성들로 인해 실제 Python 객체 (object)의 메모리 구조는 위와 같이 구성되어 있다. + - object (흰색 박스)는 class로 향하는 pointer (`__class__`)를 가지고 있고, class (녹색 박스)도 객체를 가리키는 pointer (dict_offset)을 가지고 있다. (이유는 객체 사이즈가 가변적이기 때문) + - 따라서 dict_offset을 통해 `__dict__` 가 있는 곳을 찾는다. 이 class (녹색 박스)는 객체가 몇개가 있든 단 하나만 존재한다. (`__dict__` 주소 = object 주소 + dict_offset) + - 빨간 박스로 칠해진 dictionary(header, keys, values)는 class의 인자로 전달받아 저장된 키와 값을 저장하여 class와 dictionary 사이에 값을 공유할 수 있도록 구성되어 있음. (예: `self.a = a` 는 객체의 __dict__를 통해 key a와 values에 접근할 수 있음) (추가 공부 더 필요) + +**2. Python 3.11의 객체 구조** + +![스크린샷 2024-04-04 213055](https://github.com/10000-Bagger/free-topic-study/assets/34956359/c4454da5-6d2a-4b2b-bb6b-e3ff76bb708e) + +![스크린샷 2024-04-04 213120](https://github.com/10000-Bagger/free-topic-study/assets/34956359/d5a80744-4956-44d6-9e23-571ba7c72ebb) + + +- 포인터 위치가 Dictionary의 pointer `__dict__` 가 고정된 offset인 객체의 맨 앞으로 옮겨짐. +- 이로 인해서 class에서 dict_offset을 통해 위치를 객체의 `__dict__` 위치를 찾을 필요가 없어짐 +- 즉, 메모리 크기를 줄인 것은 아니지만, 메모리 접근, 메모리 사이클을 줄여 성능 개선! +- 또한 Dictionary를 줄여서 dictionary key를 class를 통해 접근 + 모든 값들이 object에 넣어졌을 때 접근 가능하도록 함 (추가 공부 필요) + +### 전문화된 적응형 인터프리터 (Specializing Adaptive Interpreter) + +**1. Python은 interpreter 언어인가 compile 언어인가?** + +- 간단하게 설명하면, 기계어와 소스 코드 사이의 중간 코드. +- Python의 경우 Interpreter처럼 한줄씩 읽고 실행할 수 있음. +- Interpreter와 compile은 실행 방식이지 언어의 구성 요소가 아님. +- 따라서, Python은 Interpreter 언어이기도 하고 compile 언어라고도 볼 수 있음. +- 예: + - CPython (C 기반의 bytecode compile 도구), Jython (JVM 기반의 bytecode compile 도구)를 사용한다면 Python을 bytecode로 변환함. + - PyPy를 사용한다면 Just-In-Time 컴파일러로 사용됨. + - 즉, 실행 방식에 따라 달라진다. + +** Interpreter: 원시 언어의 명령을 변역 실행하는 프로그램. 프로그래밍 언어의 소스 코드를 바로 실행하는 컴퓨터 프로그램 또는 환경. + +** Compiler: 특정 프로그래밍 언어로 쓰여 있는 문서를 다른 프로그래밍 언어로 옮기는 언어 번역 프로그램. Compiler는 소스 프로그램을 읽어서 즉시 결과를 출력하는 Interpreter와 구분됨. + +**2. Python에서의 Bytecode (CPython 기준)** + +> CPython은 bytecode로의 compile을 자동으로 해주는 Interpreter이다. +> +- Python의 dis 모듈을 이용하면 CPython bytecode를 역 어셈블하여 바이트코드 분석을 할 수 있다. (`import dis`) +- 일반적으로 `def add(a, b): return a+b` 라는 함수를 생성해서 실행했을 때, `LOAD_FAST 0(a)`, `LOAD_FAST 1(b)`, `BINARY_ADD`, `RETURN_VALUE` 순서로 bytecode가 작성된다. + +```python +def add(a, b): + return a + b +``` + +```python +dis.dis(add) +2 0 LOAD_FAST 0 (a) + 2 LOAD_FAST 1 (b) + 4 BINARY_ADD + 6 RETURN_VALUE +``` + +**3. Specializing Adaptive Interpreter** + +- 위의 add와 같은 함수가 여러번 호출된다면 매번 새롭게 생성한느 것보다 같은 것을 여러번 활용하는 것이 더 이득이기 때문에, Python 3.11은 코드가 일정 횟수 이상 호출되면 `최적화 - 적응화`를 수행한다. + +```python +for i in range (10): + add(3, 5) +``` + +- 위처럼 add 함수를 for 문으로 반복 호출했을 때 bytecode를 확인해보면 아래와 같이 출력된다. + +```python +dis.dis(add, adaptive=True) +1 0 RESUME_QUICK 0 + +2 2 LOAD_FAST__LOAD_FAST 0 (a) + 4 LOAD_FAST 1 (b) + 6 BINARY_OP_ADD_INT 0 (+) + 10 RETURN_VALUE +``` + +- `LOAD_FAST___LOAD_FAST`의 경우 두개의 instruction을 동시에 실행한다는 뜻. +- `BINARY_OP_ADD_INT` 는 더하기 연산자의 최적화된 버전으로, 여러번의 데이터 타입 체크가 이루어진 후, 만약 이 부분이 계속해서 이루어졌고 괜찮다고 판별될 경우, 타입 체크를 스킵하고 더하기 연산자를 바로 실행한다. +- 즉 여러 번의 코드가 실행되고 만약 이 작업이 더 이상 의미 없는 과정을 포함하고 있으면 과감하게 스킵해 버리는 것. + +### 향상된 에러 표시 + +- 에러 표시의 정확성이 향상됨. +- 기존에는 어느 부분에서 에러가 났는지 대충이나마 파악할 수 있었다면, 3.11에선느 더욱 세밇하게 파악할 수 있게 됨. +- 예: +- 3.10의 에러 표시: +```python +% python3.10 nondata.py +Traceback (most recent call last): + File "nonedata.py", line 7, in + print(data["a"]["b"]["c"]["d"]) +TypeError: 'NoneType' object is not subscriptable. + +``` + +- 3.11의 에러 표시: +```python +% python3.11 nondata.py +Traceback (most recent call last): + File "nonedata.py", line 7, in + print(data["a"]["b"]["c"]["d"]) + ~~~~~~~~~~~~~~^^^^^ +TypeError: 'NoneType' object is not subscriptable. +``` + +## 3. Python Data Model + +1. **Objects, values, types** + +> Every object has an identity, a type, and a value. An object’s *identity* never changes once it has been created; you may think of it as the object’s address in memory. The `is` operator compares the identity of two objects; the `id()` function returns an integer representing its identity. +> +- Objects: + - Objects는 Python이 data를 추상화(abstraction)한 것이다. + - Python의 모든 것은 객체나 객체 간의 관계로 표현된다 (코드 역시 객체로 표현됨) + - CPython의 경우, `id(x)`는 x가 저장된 메모리의 주소이다. +- Types: + - 객체의 형태는 객체가 지원한느 연산들을 정의하고 (예를 들어, “길이를 갖고 있는지”) 그 형의 객체들이 가질 수 있는 가능한 값들을 정의한다. + - `type()` 함수는 객체의 형태를 돌려준다. identity와 마찬가지로, 객체의 형태 (type) 역시 변경되지 않는다. +- values: + - 어떤 객체들의 값은 변경할 수 있다. 변경이 가능한 객체들을 가변(mutable)이라고 함. + - 일단 만들어진 후에 값을 변경헐 수 없는 객체들을 불변(immutable)이라고 함. + - 가변 객체에 대한 참조를 저장하고 있는 불변 컨테이너의 값은 가변 객체의 값이 변할 때 변경된다고 볼 수도 있지만, 저장하고 있는 객체들의 집합이 바뀔 수 없으므로 컨테이너는 여전히 불변이라고 여겨진다.) + - 객체의 가변성(mutability)는 객체의 type에 의해 결정됨. + - 예: 숫자, 문자열, tuple은 immutable이지만, dictionary와 list는 mutable. +- 객체는 절대 명시적으로 파괴될 수 없. 더이상 참조되지 않을 때 GC된다. (CPython은 reference-counting 방식을 사용하고, 설정 변경을 통해 GC 성능 튜닝이 가능함.) +- GC가 안될 수 있는 상황들: + - tracing / debugging facilities may keep objects alive that would normally be collectable. + - catching an exception with a `try … except` statement may keep object alive. + - referencing `“external” resources` such as open files or windows. (gc가 안될 수 있어서 `close()` method 또는 `try … finally`, `with` 을 사용해서 명시적으로 자원을 닫는 것을 강력하게 권장함.) +- 객체가 다른 객체에 대한 참조를 포함하는 경우, Container 라고 부른다. (예: Tuple, List, Dict 등) +- 이 참조들은 컨테이너 값의 일부이고, 대부분 컨테이너의 값을 말할 때는 객체들의 identity보다는 value를 말하기 때문에 Tuple 같은 불변 컨테이너에 속한 가변 객체가 변경되면 컨테이너의 값도 변경됨 (identity가 아닌 value가 변경!) +- type은 거의 모든 측면에서 객체가 동작하는 방법에 영향을 준다. + - immutable type의 경우, 새 값을 만드는 연산은 실제로는 이미 존재하는 객체 중에서 같은 type과 value를 갖는 것을 돌려줄 수 있음. + - mutable type 객체에서는 값 변경이 가능하기 때문에 위와 같은 것이 허용되지 않음. + - 예: `a = 1; b = 1` 후에, `a` 와 `b` 는 값 1을 갖는 같은 객체일 수도 있고, 아닐 수도 있음. 하지만, `c = []; d = []` 후에, `c` 와 `d` 는 다르고, 독립적이고, 새로 만들어진 빈 리스트임이 보장됨. (`c = d = []` 는 같은 객체를 `c` 와 `d` 에 적용하는 거라 다른 케이스) + +1. **The Standard type hierarchy** +- None +- NotImplemented +- Ellipsis +- numbers.Number + - numbers.Integral + - Integers (int) + - Booleans (bool) + - numbers.Real (float) + - numbers.Complex (complex) +- Sequnces + - Immutable sequences + - Strings + - Tuples + - Bytes + - Mutable sequences + - Lists + - Byte Arrays +- Set types + - Sets + - Frozen sets +- Mappings + - Dictionaries +- Callable types + - User-defined functions + - Special read-only attributes + - function.__globals__ + - function.__closure__ + - Special writable attributes + - function.__doc__ + - function.__name__ + - function.__qualname__ + - function.__module__ + - function.__defaults__ + - function.__code__ + - function.__dict__ + - function.__annotations__ + - function__kwdefaults__ + - Instance methods + - Special read-only attributes + - method.__self__ + - method.__func__ + - method.__doc__ + - method.__name__ + - method.__module__ + - Generator functions + - Coroutine functions + - Asynchronous generator functions + - Built-in functions + - Classes +- Modules +- Custom classes +- Class instances +- I/O objects (also known as file objects) +- Internal types + - Code objects + - Frame objects + - Traceback objects + - Static method objects + - Class method objects + +1. **Special method names** +- Basic customization +- Customizing attribute access + - Customizing module attribute access + - Implementing Descriptors + - Invoking Descriptors + - __slots__ +- Customizing class creation + - Metaclasses + - Resolving MRO entries (Method Resolution Order) + - Determining the appropriate metaclass + - Preparing the class namespace + - Executing the class body + - Creating the class object + - Uses for metaclasses +- Customizing instance and subclass checks +- Emulating generic types + - The purpose of __class_getitem__ + - __class_getitem__ versus __getitem__ +- Emulating callable objects +- Emulating container types +- Emulating numeric types +- With Statement Context Maangers +- Customizing positional arguments in class pattern matching +- Special method lookup + +**4. Coroutines** + +- Awaitable Objects +- Coroutine Objects +- Asynchronous Iterators +- Asynchronous Context Managers + +## References +[CPython, Jython, Cython.... 니들은 정체가 뭐니?](https://devocean.sk.com/blog/techBoardDetail.do?ID=164537) + +[파이썬 3.11은 어떻게 빨라질 수 있었을까? | 요즘IT](https://yozm.wishket.com/magazine/detail/2096/) + +[3. Data model](https://docs.python.org/3.11/reference/datamodel.html)