Mental model¶
PythonNative is a thin layer over native UIKit and Android view hierarchies, with a React-style component model on top. If you have worked with React Native, the surface API will feel familiar; the runtime, however, is meaningfully different.
TL;DR¶
@pn.componentfunctions return immutableElementdescriptors. Nothing is mounted until theReconcilercommits the tree.- Hooks (
use_state,use_effect, etc.) drive re-renders. State updates are batched per render pass. - Re-rendering produces a new tree; the reconciler diffs it against the previous one and applies the smallest set of native mutations.
- Native widgets are created and updated by Swift and Kotlin component managers that receive one serialized transaction per commit over the native bridge. Mobile applications embed CPython; the browser preview has its own JavaScript renderer.
The runtime in one diagram¶
@pn.component fn ---> Element tree ---> Reconciler ---> Native views
^ |
| v
set_state() <--------- schedule re-render batched ---> diff + patch
|
v
flush effects
Each render pass has three phases:
- Render: component functions run; hooks record state reads, queue effects, register memos. No native widgets change yet.
- Commit: the reconciler applies the diff to native views,
creating, updating, and removing widgets through the registered
ViewHandlerimplementations. - Effect: pending
use_effectcallbacks fire in depth-first order; cleanups from the previous render run before the new callbacks.
If an effect sets state, the loop kicks off again (with a safety cap that prevents render storms).
How PythonNative differs from React Native¶
| Concept | React Native | PythonNative |
|---|---|---|
| Component language | JavaScript / TypeScript | Python |
| Bridge | Fabric: one C++ shadow tree commit per render; TurboModules for device APIs | One JSON transaction per commit applied by Swift / Kotlin component managers; named native modules for device APIs |
| Threading | UI runs on the main thread; JS on a separate thread | Native UI on its platform thread; Python on a dedicated asyncio application thread |
| Distribution | Metro bundler ships a JS bundle | pn build bundles your app/ and the pythonnative package into the native project |
| Dev loop | Metro dev server; Expo Go / dev client on device; Fast Refresh | pn start dev server; debug builds and the --dev-client shell connect over WebSocket; Fast Refresh reloads .py modules in place |
| Preview without a device | Expo web / Snack | pn preview renders in a browser tab through the same bridge protocol the native runtimes speak |
| Native widgets | Wrapped by Fabric component managers | Wrapped by PNComponentManager (Swift) / ComponentManager (Kotlin) classes |
Python components run on a dedicated application thread. Native calls cross a versioned, serialized bridge and marshal widget operations to the platform UI thread. Native events are queued back to Python. Some module calls can return synchronously; asynchronous results and cancellation use the bridge's promise protocol. See The native bridge for the contract.
How PythonNative differs from Toga, BeeWare, Kivy¶
- Toga and Kivy are imperative widget toolkits. PythonNative is declarative: you describe the tree per render, and the reconciler figures out the diff.
- Kivy renders its own widgets via OpenGL. PythonNative renders real native widgets, so your buttons look like UIKit buttons on iOS and Material buttons on Android. Applications configure accessibility labels and roles and test interaction on each platform.
- BeeWare's Briefcase is a packaging story; PythonNative ships its
own
pnCLI for the same purpose, plus a UI runtime.
Mental shortcuts¶
When something feels surprising, fall back on these rules:
It's just Python
Application functions run in CPython. Native Swift, Kotlin, and C++
libraries are compiled when building the app.
print(x) reaches the device console; import foo runs the
bundled module. If a stack trace mentions pythonnative.*, you
can open that file in your editor and read the source.
Components are functions
Each call to a @component function should produce the same
output for the same input plus current hook state. Side effects
belong in use_effect.
Native widgets are real
A pn.Text becomes a UILabel or a TextView. Anything you can
do to those in their respective SDKs, you can usually do via a
custom ViewHandler.
Next steps¶
- See the runtime in detail: Architecture.
- Walk through a render: Lifecycle.
- Learn how trees are diffed: Reconciliation.