Skip to content

Commit 706404e

Browse files
authored
Merge pull request #140 from thisisthepy/feat/no-em-dash
Docs: Remove every em-dash and the last pip example
2 parents 452cc80 + 9b8fdb3 commit 706404e

126 files changed

Lines changed: 1865 additions & 1865 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎AGENTS.md‎

Lines changed: 32 additions & 32 deletions
Large diffs are not rendered by default.

‎PROJECT.md‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# python-multiplatform — 프로젝트 요점
1+
# python-multiplatform, 프로젝트 요점
22

33
Kotlin Multiplatform 에 CPython 을 임베딩해 **Kotlin 과 Python 이 서로의 라이브러리를 양방향으로**
44
쓰게 한다. 모든 지원 플랫폼에서 같은 방식으로 동작하고, 경계를 넘는 비용을 측정하며 줄인다.
@@ -35,7 +35,7 @@ Kotlin Multiplatform 에 CPython 을 임베딩해 **Kotlin 과 Python 이 서로
3535
| 플랫폼 | Kotlin 실행 환경 | 다운콜 | 업콜 진입 | 상태 |
3636
|---|---|---|---|---|
3737
| Desktop (macOS) | JVM | Panama FFM (`invokeExact`) | FFM upcall stub | 전체 스위트 · GraalVM 네이티브 이미지 업콜 검증 |
38-
| Desktop (Linux/Windows) | JVM | 동상 | 동상 | 배선만 — 실행 기록 없음 |
38+
| Desktop (Linux/Windows) | JVM | 동상 | 동상 | 배선만, 실행 기록 없음 |
3939
| Android | ART | JNI `RegisterNatives` | JNI | 기기 테스트 |
4040
| iOS | Kotlin/Native | cinterop | `@CName` | 시뮬레이터에서 공통 스위트 |
4141
| androidNative | Kotlin/Native | cinterop | `@CName` | 기기에서 공통 스위트 |
@@ -45,7 +45,7 @@ Kotlin Multiplatform 에 CPython 을 임베딩해 **Kotlin 과 Python 이 서로
4545

4646
| 경로 | 내용 |
4747
|---|---|
48-
| `python-multiplatform/` | 라이브러리 — FFI 계층(`python/native/ffi/`), 객체 모델(`python/multiplatform/ffi/`), 업콜 런타임 |
48+
| `python-multiplatform/` | 라이브러리, FFI 계층(`python/native/ffi/`), 객체 모델(`python/multiplatform/ffi/`), 업콜 런타임 |
4949
| `python-multiplatform-ksp/` | 업콜 테이블을 생성하는 KSP 프로세서 |
5050
| `python-multiplatform-gradle-plugin/` | `stagePythonHome`, 아티팩트 워커(빌드된 jar/klib 바인딩), `.pyi` 스텁 생성 |
5151
| `ksp-fixtures/` | 생성기 산출물을 실제로 쓰는 소비자 모듈 (`app`, `compose`, `artifact`, …) |
@@ -67,7 +67,7 @@ Kotlin Multiplatform 에 CPython 을 임베딩해 **Kotlin 과 Python 이 서로
6767
```
6868

6969
- 컴파일 확인은 세 타깃: `compileKotlinAndroidNativeArm64`(기본 루프), `compileKotlinIosSimulatorArm64`,
70-
`compileKotlinDesktop`. **androidNative 를 빼먹지 않는다** — `nativeMain` 을 iOS 와 공유한다.
70+
`compileKotlinDesktop`. **androidNative 를 빼먹지 않는다**, `nativeMain` 을 iOS 와 공유한다.
7171
- 생성기 소비자 검증은 **모듈마다 따로**: `:ksp-fixtures:app:desktopTest`,
7272
`:ksp-fixtures:compose:desktopTest`, `:ksp-fixtures:artifact:desktopTest`.
7373
- 결과는 `build/test-results/<target>/*.xml` 을 지우고 `--rerun` 으로 다시 돌려 센다.
@@ -84,15 +84,15 @@ Kotlin Multiplatform 에 CPython 을 임베딩해 **Kotlin 과 Python 이 서로
8484
KSP 업콜 테이블과 프록시(클래스·프로퍼티·companion·`suspend`·취소), jar 아티팩트 워커, Python 에서
8585
Compose 렌더(데스크톱), Android 부트스트랩, GraalVM 네이티브 이미지 업콜(데스크톱, 수동 검증).
8686
- **부분**: iOS·androidNative·wasm 의 업콜과 수명 검증, klib 워커(스캐너만), `.pyi` 스텁, GIL 해제,
87-
Linux/Windows, free-threaded 는 desktop 한정·옵트인(3.14t, `-PpythonFreeThreaded=true`, 236 테스트 0 실패 — ROADMAP §9).
87+
Linux/Windows, free-threaded 는 desktop 한정·옵트인(3.14t, `-PpythonFreeThreaded=true`, 236 테스트 0 실패, ROADMAP §9).
8888
- **계획**: Android/iOS 의 free-threaded(프리빌트 없음), Android/iOS/wasm 의 Compose, 네이티브 이미지 검증 자동화.
8989

9090
## 6. 큰 결정들
9191

9292
| 결정 | 근거 |
9393
|---|---|
94-
| Python→Kotlin 은 **런타임 리플렉션 아님** — 빌드 타임 테이블 | Kotlin/Native 에 리플렉션이 사실상 없고, GraalVM 네이티브 이미지는 closed-world 다 |
95-
| **JVM 메서드를 이름으로 찾지 않는다** — Kotlin 소스를 생성해 `kotlinc` 가 컴파일 | 값 클래스 맹글링 접미사가 겹쳐서 이름 조회는 원리적으로 불가능하다 |
94+
| Python→Kotlin 은 **런타임 리플렉션 아님**, 빌드 타임 테이블 | Kotlin/Native 에 리플렉션이 사실상 없고, GraalVM 네이티브 이미지는 closed-world 다 |
95+
| **JVM 메서드를 이름으로 찾지 않는다**, Kotlin 소스를 생성해 `kotlinc` 가 컴파일 | 값 클래스 맹글링 접미사가 겹쳐서 이름 조회는 원리적으로 불가능하다 |
9696
| 바인더는 **Kotlin 네임스페이스를 다른 이름으로 내보내지 않는다** | 사용자 규정. `androidx.*` 는 원본 Kotlin, `pythonx.*` 는 pythonx-compose 의 실제 패키지 |
9797
| 노출은 **블랙리스트** (`@PythonInternal`) | 화이트리스트는 붙이기를 잊으면 조용히 사라진다 |
9898
| 병렬성은 **free-threading**, 멀티 인터프리터 아님 | 인터프리터별 GIL 은 C 확장이 `Py_mod_multiple_interpreters` 를 선언해야 import 된다 |
@@ -103,11 +103,11 @@ Kotlin Multiplatform 에 CPython 을 임베딩해 **Kotlin 과 Python 이 서로
103103

104104
## 7. 열린 질문
105105

106-
`docs/SPEC.md` 끝의 "Outside intent — needs a decision" 이 원본이다. 요지:
106+
`docs/SPEC.md` 끝의 "Outside intent, needs a decision" 이 원본이다. 요지:
107107

108-
1. `.pyi` 스텁 생성기가 매니페스트 없이도 `androidx.*` 를 `pythonx.*` 로 바꿔 쓴다 — 네임스페이스
108+
1. `.pyi` 스텁 생성기가 매니페스트 없이도 `androidx.*` 를 `pythonx.*` 로 바꿔 쓴다, 네임스페이스
109109
개명 금지 규정과 충돌하는가.
110-
2. 라이브러리가 `sys.modules` 에 합성 `pythonx` 모듈(`__path__ = []`)을 넣는다 — 실제 `pythonx`
110+
2. 라이브러리가 `sys.modules` 에 합성 `pythonx` 모듈(`__path__ = []`)을 넣는다, 실제 `pythonx`
111111
패키지를 가리는가.
112112
3. 멤버 이름의 snake_case 변환은 의도된 것인가.
113113
4. 범용 `pythonx` 어댑터가 이 저장소에 있어야 하는가, pythonx-compose 에 있어야 하는가.

‎README.md‎

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ English | [한국어](docs/locale/README_ko.md)
44

55
# 🐍 python-multiplatform
66

7-
**Real CPython inside Kotlin Multiplatform — Kotlin calls Python, Python calls Kotlin, on every target.**
7+
**Real CPython inside Kotlin Multiplatform, Kotlin calls Python, Python calls Kotlin, on every target.**
88

99
[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
1010
[![Kotlin Multiplatform](https://img.shields.io/badge/Kotlin-Multiplatform-7F52FF.svg?logo=kotlin&logoColor=white)](https://kotlinlang.org/docs/multiplatform.html)
@@ -21,31 +21,31 @@ English | [한국어](docs/locale/README_ko.md)
2121

2222
## 💡 Why
2323

24-
Python has the libraries; Kotlin Multiplatform has the reach — desktop, Android, iOS, the web.
24+
Python has the libraries; Kotlin Multiplatform has the reach, desktop, Android, iOS, the web.
2525
python-multiplatform puts **the real CPython interpreter** inside a Kotlin Multiplatform app and
2626
builds a two-way bridge across it:
2727

2828
- Kotlin gets a **typed, Kotlin-idiomatic object model** over Python: a `PyList` *is* a `MutableList`,
2929
a `PyDict` *is* a `MutableMap`, a Python error *is* a `Throwable`.
3030
- Python gets **Kotlin classes as ordinary Python**: `Greeter('Kotlin').greet(2)`, `await` on a
31-
`suspend fun`, Kotlin extension functions as methods — all through a table generated at build time,
31+
`suspend fun`, Kotlin extension functions as methods, all through a table generated at build time,
3232
so it works where reflection does not: Kotlin/Native and GraalVM native images.
3333

3434
It is CPython itself, not a re-implementation, so C extensions keep working.
3535

3636
## ✨ Features
3737

38-
- 🔌 **Downcalls** — the CPython Stable ABI (~330 functions) as one `expect` surface with an `actual`
38+
- 🔌 **Downcalls**: the CPython Stable ABI (~330 functions) as one `expect` surface with an `actual`
3939
per platform: Panama `invokeExact` on desktop, `RegisterNatives` JNI on Android, cinterop on Native.
40-
- 🧩 **Object model** — `PyObject`, `PyType`, `PyException`, basic types, `PyList` / `PyDict` /
40+
- 🧩 **Object model**: `PyObject`, `PyType`, `PyException`, basic types, `PyList` / `PyDict` /
4141
`PySet` / `PyTuple` implementing Kotlin collection interfaces, modules, callables, conversion.
42-
- 🚀 **Upcalls** — a KSP processor generates a function table; Python constructs Kotlin classes, calls
42+
- 🚀 **Upcalls**: a KSP processor generates a function table; Python constructs Kotlin classes, calls
4343
methods, reads and writes properties, and `await`s `suspend` functions with cancellation.
44-
- 📦 **Bind prebuilt libraries** — the Gradle plugin walks jars (Compose included) and generates the
44+
- 📦 **Bind prebuilt libraries**: the Gradle plugin walks jars (Compose included) and generates the
4545
bindings and `.pyi` stubs, with default-argument omission and value-class support.
46-
- ♻️ **No manual memory management** — wrappers release their Python reference when collected; reference
46+
- ♻️ **No manual memory management**: wrappers release their Python reference when collected; reference
4747
cycles that cross the boundary are collected by Python's cyclic GC.
48-
- 🧪 **Measured** — every boundary mechanism ships with overhead tests; the cost table is rendered from
48+
- 🧪 **Measured**: every boundary mechanism ships with overhead tests; the cost table is rendered from
4949
real runs ([cost table](docs/investigations/cost-table.md)).
5050

5151
## 🚀 Quick start
@@ -118,7 +118,7 @@ name, unchanged.
118118
### Platform setup
119119

120120
<details>
121-
<summary><b>Desktop</b> — apply the Gradle plugin so <code>PYTHONHOME</code> has a stdlib</summary>
121+
<summary><b>Desktop</b>, apply the Gradle plugin so <code>PYTHONHOME</code> has a stdlib</summary>
122122

123123
```kotlin
124124
plugins {
@@ -138,7 +138,7 @@ prefix with `-Dpython.multiplatform.home=...`. See `docs/platforms/desktop-packa
138138
</details>
139139

140140
<details>
141-
<summary><b>Android</b> — call <code>PythonBootstrap.initialize</code></summary>
141+
<summary><b>Android</b>, call <code>PythonBootstrap.initialize</code></summary>
142142

143143
```kotlin
144144
import python.multiplatform.env.PythonBootstrap
@@ -172,7 +172,7 @@ flowchart LR
172172
T --> K
173173
```
174174

175-
Behind each platform's single entry point, **every platform uses the same generated table** — there is
175+
Behind each platform's single entry point, **every platform uses the same generated table**, there is
176176
no per-platform Python → Kotlin binding code. Details: [design notes](docs/design/).
177177

178178
## 🌍 Platforms
@@ -188,7 +188,7 @@ no per-platform Python → Kotlin binding code. Details: [design notes](docs/des
188188

189189
## 📊 Status
190190

191-
Honest summary — see the [roadmap](docs/roadmap/ROADMAP.md) for what is left to do.
191+
Honest summary, see the [roadmap](docs/roadmap/ROADMAP.md) for what is left to do.
192192

193193
| Area | State |
194194
|---|---|
@@ -213,10 +213,10 @@ Honest summary — see the [roadmap](docs/roadmap/ROADMAP.md) for what is left t
213213

214214
## 📖 Documentation
215215

216-
- 🌐 **[Guide](https://thisisthepy.github.io/python-multiplatform/)** (English / 한국어) — source in [`docs/guide/`](docs/guide/)
217-
- 🏗 [Design notes](docs/design/) — architecture, downcalls, upcalls, lifetime, threading, binding policy
218-
- 📱 [Platform notes](docs/platforms/) — Android FFM, wasm, CPython acquisition, GraalVM verification
219-
- 🔬 [Investigations](docs/investigations/) — measurements and audits
216+
- 🌐 **[Guide](https://thisisthepy.github.io/python-multiplatform/)** (English / 한국어), source in [`docs/guide/`](docs/guide/)
217+
- 🏗 [Design notes](docs/design/), architecture, downcalls, upcalls, lifetime, threading, binding policy
218+
- 📱 [Platform notes](docs/platforms/), Android FFM, wasm, CPython acquisition, GraalVM verification
219+
- 🔬 [Investigations](docs/investigations/), measurements and audits
220220
- 🗺 [Roadmap](docs/roadmap/ROADMAP.md)
221221
- 🇰🇷 [README in Korean](docs/locale/README_ko.md)
222222

‎benchmarks/cost_table.py‎

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,7 @@ def _samples(runs: list[dict[str, Any]], target: str, source: str, key: str,
749749

750750
def _fmt_range(values: list[float], unit: str) -> str:
751751
if not values:
752-
return "—"
752+
return ", "
753753
lo, hi = min(values), max(values)
754754
suffix = "x" if unit == "x" else " ns"
755755
if abs(hi - lo) < 0.005:
@@ -780,7 +780,7 @@ def render_markdown(report: dict[str, Any]) -> str:
780780
targets = report.get("targetsRequested") or DEFAULT_TARGETS
781781
lines: list[str] = []
782782

783-
dirty = " **(working tree dirty — these numbers are not reproducible from the commit alone)**" \
783+
dirty = " **(working tree dirty, these numbers are not reproducible from the commit alone)**" \
784784
if report.get("workingTreeDirty") else ""
785785
lines.append(f"_Generated by `benchmarks/cost_table.py` from "
786786
f"`{report.get('sourceFile', 'the newest JSON')}`._")
@@ -815,31 +815,31 @@ def render_markdown(report: dict[str, Any]) -> str:
815815
host = report["host"]
816816
warm = report.get("benchmarkTestWarmup", {})
817817
lines += [
818-
f"- **commit** `{report['commit'][:12]}` — {report.get('commitSubject', '')}{dirty}",
818+
f"- **commit** `{report['commit'][:12]}`, {report.get('commitSubject', '')}{dirty}",
819819
f"- **branch** `{report.get('branch', '?')}`, worktree `{report.get('worktree', '?')}`",
820820
f"- **captured** {report['capturedAt']}"
821-
+ (" — the first of several cuts; each one's own time is in the source table below"
821+
+ (", the first of several cuts; each one's own time is in the source table below"
822822
if len(report.get("sources") or []) > 1 else ""),
823823
f"- **host** {host.get('cpu', '?')}, {host.get('cores', '?')} cores, "
824824
f"{host.get('os', '?')} {host.get('machine', '')}",
825825
f"- **load average** {report.get('loadAtStart')} at start, {report.get('loadAtEnd')} at end "
826826
f"(ceiling {report['loadCeiling']}, guard `{report.get('loadGuard')}`)"
827-
+ (" — of the first cut only; per-cut figures below"
827+
+ (", of the first cut only; per-cut figures below"
828828
if len(report.get("sources") or []) > 1 else ""),
829-
f"- **runs per target** {report['runsRequested']} — ranges below are min–max over them",
829+
f"- **runs per target** {report['runsRequested']}, ranges below are min–max over them",
830830
f"- **`UpcallBoundaryCostTest`** warmup and iteration counts are self-reported per target "
831831
"in the table below (the test prints them)",
832832
f"- **`BenchmarkTest`** warmup, read from the source at this commit: "
833833
f"`WARMUP` = {warm.get('WARMUP', '?')}, `WARMUP_BULK` = {warm.get('WARMUP_BULK', '?')} "
834-
"(the ≥4096-char string rows use the second — see that file's own caveat)",
834+
"(the ≥4096-char string rows use the second, see that file's own caveat)",
835835
"- **suite scope** full task, never `--tests`-filtered: narrowing the suite changes what is "
836836
"being measured",
837837
"",
838838
]
839839

840840
sources = report.get("sources") or []
841841
if len(sources) > 1:
842-
lines += ["This table is merged from several cuts — which target came from which:", ""]
842+
lines += ["This table is merged from several cuts, which target came from which:", ""]
843843
lines += _table(
844844
["cut", "commit", "captured", "load at start", "contaminated", "targets"],
845845
[[f"`{s['file']}`", f"`{s['commit']}`", str(s.get("capturedAt")),
@@ -875,16 +875,16 @@ def render_markdown(report: dict[str, Any]) -> str:
875875
device = sim
876876
wall = "/".join(str(r.get("wallSeconds", "?")) for r in mine)
877877
counts = (f"{suite.get('tests', '?')}/{suite.get('failures', '?')}/{suite.get('skipped', '?')}"
878-
if suite else "—")
878+
if suite else ", ")
879879
rows.append([
880880
f"`{name}`",
881881
f"`{target.task}`" if target else "?",
882882
status if not reason else f"**{status}**",
883-
device or ("—" if not (target and target.avd) else "—"),
884-
platform_name or "—",
883+
device or (", " if not (target and target.avd) else ", "),
884+
platform_name or ", ",
885885
counts,
886-
wall or "—",
887-
conditions or "—",
886+
wall or ", ",
887+
conditions or ", ",
888888
])
889889
lines += _table(
890890
["target", "task", "status", "device / simulator", "platform, self-reported",
@@ -895,7 +895,7 @@ def render_markdown(report: dict[str, Any]) -> str:
895895
notes = [(n, r) for n, r in notes if r]
896896
if notes:
897897
lines += ["**Targets that did not produce numbers, and why:**", ""]
898-
lines += [f"- `{n}` — {r}" for n, r in notes]
898+
lines += [f"- `{n}`, {r}" for n, r in notes]
899899
lines.append("")
900900

901901
# ---- upcall --------------------------------------------------------------------
@@ -913,7 +913,7 @@ def render_markdown(report: dict[str, Any]) -> str:
913913
lines += _table(["target"] + [c for c, _ in UPCALL_COLUMNS], rows)
914914
lines.append("")
915915

916-
lines += ["Controls, from the same block — these are what say whether a row moved because the "
916+
lines += ["Controls, from the same block, these are what say whether a row moved because the "
917917
"boundary moved or because the whole host did:", ""]
918918
rows = []
919919
for name in targets:

0 commit comments

Comments
 (0)