MicroPython vs Python 3 Differences
Compatibility Overview
MicroPython implements a substantial subset of Python 3 for microcontrollers and resource-constrained systems. Most everyday Python syntax is familiar, but library availability, memory management, hardware access and some language behaviours differ from standard CPython.
CPython is the reference implementation of Python, commonly used on Windows, Linux and macOS. MicroPython is a separate implementation with its own runtime, modules and platform-specific features.
| Feature | CPython 3 | MicroPython |
|---|---|---|
| Target environment | Desktop, server, embedded Linux | Microcontrollers and constrained systems |
| Core syntax | Full Python 3 implementation | Substantial Python 3 subset |
| Standard library | Extensive | Reduced or adapted |
| Memory management | Reference counting and cyclic GC in CPython | Garbage-collected managed heap |
| Hardware GPIO | Requires platform-specific libraries | machine.Pin |
| Hardware I2C/SPI/UART | External or platform libraries | machine interfaces |
| Multithreading | threading | Limited _thread support on selected ports |
| Async programming | asyncio | Lightweight asyncio implementation |
| Package installation | pip | mip, mpremote or manual installation |
| Native extensions | CPython extension interface | Different native integration mechanisms |
| Filesystem | Host operating system | Board-dependent storage |
| Memory capacity | Usually comparatively large | Often limited to kilobytes or megabytes |
| Startup | Script, module or application entry point | Commonly boot.py and main.py |
Compatibility depends on the MicroPython version, port and firmware build. A feature available on an ESP32 build may not be available on an RP2040, RP2350 or STM32 build.
Python Syntax Compatibility
Most common Python constructs work with little or no modification.
value = 25name = "Sensor"if value > 20: print(name, "active")for index in range(5): print(index)def square(number): return number * number
Commonly supported features
| Feature | MicroPython support |
|---|---|
| Variables and assignments | Yes |
| if, elif, else | Yes |
| for and while | Yes |
| Functions | Yes |
| Default arguments | Yes |
| Classes and inheritance | Yes |
| List comprehensions | Yes |
| Generators and yield | Yes |
| Exceptions | Yes |
| Context managers | Yes |
| Decorators | Yes, with implementation limitations |
| async and await | Supported by modern builds |
| Type annotations | Limited support; not full CPython compatibility |
| Structural pattern matching | Do not assume support |
| Metaclasses and advanced introspection | Reduced or implementation-dependent |
MicroPython does not implement every feature introduced in recent CPython releases. Code using newer syntax should be checked against the exact MicroPython firmware version.
Standard Library Differences
One of the most common migration problems is importing a module that exists in CPython but is absent or reduced in MicroPython.
Common module comparison
| CPython module | MicroPython equivalent or status |
|---|---|
| os | os, reduced functionality |
| sys | sys, reduced functionality |
| time | time, embedded timing extensions |
| json | json, reduced functionality |
| math | math, supported subset |
| struct | struct, supported subset |
| socket | socket, networking-dependent |
| select | select, platform-dependent |
| asyncio | Lightweight MicroPython implementation |
| threading | No equivalent full implementation |
| subprocess | Generally unavailable |
| multiprocessing | Generally unavailable |
| pathlib | Not normally included |
| sqlite3 | Not normally included |
| ctypes | Not supported as in CPython |
| logging | Not normally included as a full standard module |
| machine | MicroPython-specific |
| network | MicroPython-specific |
Older MicroPython releases and some compatibility libraries use names such as ujson, uos and utime. Modern MicroPython code generally uses the standard names where supported.
Check module availability
import sysprint(sys.platform)print(sys.version)help("modules")
This is more reliable than assuming every firmware build includes the same modules.
Data Types and Numeric Behaviour
Both implementations support integers, floating-point numbers, strings, bytes, lists, tuples and dictionaries.
The internal representations and limits may differ.
Integers
CPython integers have arbitrary precision, subject to available memory.
MicroPython supports small integers efficiently and can support larger integers through long-integer implementations. The available integer range depends on the firmware build and configuration.
number = 123456result = number * 1000
Do not assume that every MicroPython build supports arbitrarily large integer calculations.
Floating-point numbers
CPython normally uses IEEE 754 double-precision floating-point values.
MicroPython floating-point precision depends on its build configuration and architecture. Some builds use single precision; others use double precision.
value = 0.1 + 0.2print(value)
Floating-point calculations may produce different rounding results between CPython and MicroPython.
For sensor calibration, numerical filtering and accumulated calculations, determine the precision supported by the target firmware.
Numeric comparison
| Characteristic | CPython | MicroPython |
|---|---|---|
| Integer precision | Arbitrary precision | Build-dependent |
| Float precision | Normally 64-bit | Commonly 32-bit or 64-bit |
| Complex numbers | Supported | Not universally supported |
| Decimal arithmetic | decimal module | Usually unavailable |
| Large integer operations | Memory-limited | More constrained |
| Numeric formatting | Extensive | Some format features may differ |
Memory Management
CPython normally uses reference counting combined with cyclic garbage collection.
MicroPython uses a garbage-collected heap for dynamically allocated objects.
MicroPython memory inspection
import gcgc.collect()print("Free:", gc.mem_free())print("Allocated:", gc.mem_alloc())
The gc.mem_free() and gc.mem_alloc() functions are MicroPython-specific.
They report managed heap information rather than the total physical RAM available on the microcontroller.
Memory allocation differences
| Operation | CPython | MicroPython |
|---|---|---|
| Create list | Allocates memory | Allocates constrained heap memory |
| String concatenation | Allocates new objects | Can rapidly consume limited heap |
| Garbage collection | Mostly automatic | Automatic, with manual control available |
| Inspect free heap | External tools or runtime facilities | gc.mem_free() |
| Preallocate buffers | Useful optimisation | Often important for reliability |
| Memory fragmentation | Possible | Particularly significant on small heaps |
Avoid repeated allocations
Less suitable for a memory-constrained loop:
while True: data = bytearray(1024) process(data)
Here, process() represents an application-defined function.
A better arrangement is to allocate the buffer once:
buffer = bytearray(1024)while True: process(buffer)
The second version avoids repeatedly allocating the same buffer.
Where supported, use readinto() methods to fill existing buffers instead of creating new bytes objects.
Hardware Access
CPython running on a desktop computer does not provide a universal GPIO, ADC, PWM or microcontroller peripheral interface.
MicroPython exposes embedded hardware through modules such as machine.
MicroPython GPIO
from machine import Pinled = Pin(15, Pin.OUT)led.on()
The selected pin must be valid for the target board.
Equivalent CPython code on a Raspberry Pi running Linux would use a separate GPIO library, with different APIs and pin-numbering conventions.
Hardware module comparison
| Function | CPython | MicroPython |
|---|---|---|
| GPIO | Platform-specific library | machine.Pin |
| ADC | External hardware/library | machine.ADC |
| PWM | Platform-specific library | machine.PWM |
| I2C | Platform-specific library | machine.I2C |
| SPI | Platform-specific library | machine.SPI |
| UART | Serial library or OS interface | machine.UART |
| Hardware timer | Platform-specific interface | machine.Timer |
| Deep sleep | OS or platform-specific | machine.deepsleep() where supported |
The machine module is not available in ordinary CPython installations.
Time and Delay Functions
Both environments support delays through the time module.
import timetime.sleep(1)
MicroPython additionally provides embedded-oriented timing functions.
import timetime.sleep_ms(100)time.sleep_us(50)start = time.ticks_ms()
Timing API comparison
| Operation | CPython | MicroPython |
|---|---|---|
| Delay in seconds | time.sleep() | time.sleep() |
| Delay in milliseconds | time.sleep(0.1) | time.sleep_ms(100) |
| Delay in microseconds | time.sleep(0.00005) | time.sleep_us(50) |
| Monotonic elapsed time | time.monotonic() | time.ticks_ms() / ticks_us() |
| High-resolution timing | time.perf_counter() | time.ticks_us() / ticks_cpu() where supported |
| Tick wraparound handling | Usually unnecessary for monotonic clock | time.ticks_diff() |
Correct elapsed-time calculation
import timestart = time.ticks_ms()# Application workelapsed = time.ticks_diff(time.ticks_ms(), start)print(elapsed)
MicroPython tick counters wrap around. ticks_diff() handles this for intervals within its supported comparison range.
Do not directly subtract MicroPython tick values when wraparound is possible.
Neither CPython nor MicroPython guarantees precise real-time execution merely because a delay function accepts microsecond values.
Filesystem Differences
CPython normally accesses files through the host operating system.
MicroPython often uses internal flash storage, external flash, SD cards or another board-supported filesystem.
Portable basic file access
with open("settings.txt", "w") as file: file.write("interval=1000\n")
with open("settings.txt", "r") as file: print(file.read())
Filesystem comparison
| Feature | CPython | MicroPython |
|---|---|---|
| open() | Supported | Supported where filesystem exists |
| os.listdir() | Supported | Commonly supported |
| os.mkdir() | Supported | Commonly supported |
| os.stat() | Supported | Commonly supported |
| pathlib.Path | Standard library | Usually unavailable |
| File permissions | OS-dependent | Often limited or absent |
| Symbolic links | Filesystem-dependent | Usually unsupported |
| Storage capacity | Host-dependent | Frequently limited |
| Write endurance | Depends on storage | Important for onboard flash |
MicroPython applications should avoid unnecessary flash writes, especially when logging high-frequency sensor measurements.
Threads and Concurrency
CPython provides the threading module and other concurrency mechanisms.
MicroPython offers limited _thread support on selected ports, but its behaviour and available features differ from CPython.
Comparison
| Feature | CPython | MicroPython |
|---|---|---|
| threading.Thread | Supported | Not normally available |
| _thread.start_new_thread() | Supported | Port-dependent |
| threading.Lock | Supported | Use _thread facilities where available |
| multiprocessing | Supported on suitable systems | Generally unavailable |
| asyncio | Full standard-library implementation | Reduced embedded implementation |
| Hardware interrupt callbacks | Not a standard desktop Python feature | Supported by selected peripherals |
MicroPython asyncio
import asyncioasync def sensor_task(): while True: print("Sensor task") await asyncio.sleep_ms(1000)async def main(): await sensor_task()asyncio.run(main())
MicroPython asyncio uses cooperative scheduling. Tasks must yield execution using await for other tasks to run.
A blocking hardware operation or long computation can delay the entire event loop.
Interrupt Handling
MicroPython supports hardware interrupt callbacks on selected peripherals.
CPython does not provide an equivalent general-purpose microcontroller interrupt interface.
MicroPython GPIO interrupt
from machine import Pinevent_count = 0def on_edge(pin): global event_count event_count += 1input_pin = Pin(14, Pin.IN, Pin.PULL_UP)input_pin.irq( trigger=Pin.IRQ_FALLING, handler=on_edge)
This assumes GPIO14 supports the selected interrupt mode.
The callback increments a counter but does not debounce a mechanical switch.
Interrupt restrictions
MicroPython hard interrupt handlers must avoid operations that allocate heap memory.
Common restrictions include:
- Avoid allocating lists, dictionaries or strings.
- Avoid floating-point operations that may allocate.
- Avoid blocking delays.
- Avoid filesystem operations.
- Avoid network communication.
- Keep execution time short.
Some ports support soft interrupt callbacks or deferred execution using micropython.schedule().
CPython signal handlers and operating-system callbacks have different execution semantics and are not direct substitutes.
Package Installation
CPython commonly uses pip to install packages from Python package repositories.
MicroPython uses a different package ecosystem and deployment process.
CPython
python -m pip install requests
MicroPython
Modern MicroPython versions support mip for compatible packages.
From a MicroPython REPL on a device with suitable network access:
import mipmip.install("package_name")
Replace package_name with an actual compatible package.
mip is not available on every firmware build, and direct installation requires an appropriate network connection.
Packages may also be transferred using development tools such as mpremote.
A package designed for CPython is not automatically compatible with MicroPython.
Packages requiring CPython C extensions, substantial RAM, operating-system processes or unavailable standard-library modules generally cannot run unchanged.
JSON and Binary Data
Both environments provide JSON and binary-data handling, but MicroPython implementations may omit optional features.
JSON
import jsondata = {"temperature": 23.5}encoded = json.dumps(data)decoded = json.loads(encoded)
Common json.dumps() and json.loads() operations work on modern MicroPython builds.
Advanced formatting options, custom encoders and other CPython features may be absent.
Binary data
import structpacket = struct.pack("<HB", 1000, 25)value, status = struct.unpack("<HB", packet)
The format string specifies:
- < — little-endian byte order.
- H — unsigned 16-bit integer.
- B — unsigned 8-bit integer.
The example produces a three-byte packet.
MicroPython’s struct module supports common binary formats, but advanced features and platform-dependent representations should be checked before porting code.
Exception Handling Differences
The basic exception syntax is shared.
try: value = int("invalid")except ValueError: print("Conversion failed")
However, MicroPython may provide less detailed exception information because of memory constraints.
Some builds also omit or simplify traceback-related features.
MicroPython emergency exception buffer
import micropythonmicropython.alloc_emergency_exception_buf(100)
This reserves memory for exception information generated in hard interrupt contexts.
It has no direct equivalent in ordinary CPython programming.
The buffer size is application-dependent and consumes additional RAM.
Performance Differences
MicroPython is optimised for reduced memory consumption and embedded execution rather than matching CPython’s full feature set.
Performance varies considerably by processor, clock frequency, memory architecture and firmware configuration.
MicroPython-specific optimisation tools
| Feature | Purpose |
|---|---|
| @micropython.native | Compile supported functions to native code |
| @micropython.viper | Use typed native-code emitter |
| @micropython.asm_thumb | Inline Thumb assembly on supported Arm ports |
| const() | Define constants with MicroPython-specific optimisation |
| .mpy files | Precompiled MicroPython bytecode modules |
These facilities are not universally available across MicroPython ports.
Native emitter example
import micropython@micropython.nativedef add_values(a, b): return a + b
The native emitter can improve some workloads, but actual results depend on the function and target architecture.
It does not automatically make every function faster.
Constant definitions
from micropython import constLED_PIN = const(15)BUFFER_SIZE = const(128)
The const() facility allows MicroPython to optimise supported constant references.
Ordinary CPython does not include the micropython module.
Code Portability Techniques
Use shared Python syntax
Prefer ordinary functions, classes, loops and data structures where possible.
Avoid platform-specific code outside the sections that require hardware access.
Separate hardware interfaces
A sensor-processing function can be written independently of the hardware driver.
def celsius_to_fahrenheit(celsius): return celsius * 9 / 5 + 32
This function works in both CPython and MicroPython.
Conditional imports
import sysif sys.implementation.name == "micropython": from machine import Pinelse: Pin = None
This distinguishes the runtime without assuming a particular board.
The desktop branch must still provide an appropriate hardware abstraction if the application needs GPIO access.
Compatible JSON imports
For modern MicroPython and CPython versions:
import json
Prefer the standard module name rather than using older ujson naming unless compatibility with a specific firmware requires it.
Avoid unnecessary dependencies
A small embedded application may be easier to maintain with a few standard modules than with a large collection of third-party packages.
Check transitive dependencies as well as the package’s main API.
Runtime Detection
The sys module provides useful runtime information.
import sysprint(sys.implementation.name)print(sys.platform)print(sys.version)
Typical implementation names:
| Runtime | sys.implementation.name |
|---|---|
| CPython | cpython |
| MicroPython | micropython |
On MicroPython, sys.platform identifies the port, not necessarily the exact development board.
For instance, several different boards may report the same platform identifier.
Troubleshooting Ported Code
| Symptom | Likely cause | Corrective action |
|---|---|---|
| ImportError | Module missing | Check firmware and available modules |
| AttributeError | API differs | Inspect supported methods |
| MemoryError | Heap exhausted | Reduce allocations and buffer sizes |
| OverflowError | Numeric range exceeded | Check integer implementation and values |
| TypeError | Different API signature | Check target implementation |
| SyntaxError | Unsupported syntax | Replace newer Python constructs |
| Unexpected numeric result | Different float precision | Check rounding and numeric representation |
| Filesystem error | Path or storage unavailable | Check mounted filesystem |
| Package installation fails | Incompatible package or unavailable installer | Use a MicroPython-compatible package |
| Application stalls | Blocking call | Use cooperative scheduling or timeouts |
| Interrupt fails | Unsupported callback operation | Remove allocations and blocking work |
| GPIO code fails on desktop | machine unavailable | Use platform-specific hardware interface |
| Timing differs | Different scheduler or clock behaviour | Use monotonic timing and measure execution |
| Threading code fails | threading unavailable | Use asyncio or supported _thread APIs |
Compatibility Checklist
- Identify the CPython and MicroPython versions involved.
- Confirm the MicroPython port and firmware build.
- Check syntax support before transferring source files.
- Verify all imported modules.
- Check optional arguments and return types of library functions.
- Confirm integer range and floating-point precision requirements.
- Measure available heap memory.
- Replace desktop-specific hardware libraries with appropriate embedded interfaces.
- Review filesystem paths and storage assumptions.
- Replace unsupported threading and process-management code.
- Avoid memory allocation in hard interrupt callbacks.
- Check timing functions and tick-counter wraparound.
- Confirm that third-party packages support MicroPython.
- Separate portable application logic from hardware-dependent code.
- Test error handling, reset recovery and long-running memory behaviour on the target device.




