Language
The Python subset
What compiles and runs today, construct by construct, each giving CPython's answer.
On this page
Waspy compiles a typed subset of Python ahead of time. The rule it holds to: a construct either gives CPython's answer or is refused at compile time, never a quietly different one. Integer width is the one deliberate exception (see Numbers). The modules board tracks each feature, every one backed by a test asserting CPython's answer.
Syntax and control flow
- Functions, several per module, calling each other; multiple files compile into one module
- Control flow:
if/else,whileandforloops withbreakandcontinue, andelse:clauses onfor,while, andtry, run exactly when CPython runs them - Conditional expressions (
a if cond else b), evaluating only the arm the condition picks. Both arms must read as one type: two classes share their nearest common base, a class andNonemake anOptional, an empty collection takes the other arm's element type, and an int widens only where a float is expected; arms that would read differently (1 if c else 2.5,True if c else 0,"a" if c else None) are refused - Variables, assignment, and type annotations on parameters and return values. For a collection passed into or out of a function, the parameterised form (
List[str],Dict[str, int],Tuple[int, str]) is what gives the compiler its element type; a barelistordictthere carries only ints, bools, and None (see Limitations) - Calls pass arguments by position or by keyword (
f(1, c=7),Point(x=1)), with defaults filled in for the parameters skipped. Functions and__init__take keyword-only parameters (def f(x, *, factor=2)),*args: T(a tuple ofT), and**kwargs: T(adict[str, T]), andf(*xs)passes a list or tuple on as*args. A call to one of the module's own functions is checked against its signature, so a surplus or missing argument is a compile-timeTypeError assert test, msgraisesAssertionErrorwhen the test is false, evaluating the message only thenglobal namein a function or method rebinds the module definition for every reader; a rebinding keeps the definition's typedel xs[i],del xs[a:b], anddel d[k], raisingIndexError/KeyErrorfor what is not there- Tuple unpacking types each target from the value, and a value of the wrong length raises
ValueError. Extended unpacking (a, *b, c = xs) binds the starred target to the middle slice as a real list - Augmented assignment through a name, an attribute, or a subscript (
counts[w] += 1,self.items[k].qty += n), each target evaluated once, and annotated fields (self.items: Dict[str, Item] = {}) - Unsupported syntax is rejected up front with a located error and a hint, instead of failing deep in code generation
Numbers and operators
- Types: integers (32-bit, see Numbers), floats, booleans, strings, bytes, and the collections below
- Arithmetic (
+,-,*,/,%,//,**), comparisons (==,!=,<,<=,>,>=), boolean operators (and,or, short-circuiting), and bitwise operators (&,|,^,<<,>>)
Strings and formatting
- String operations: slicing (with any step,
s[::-1]included), concatenation, repetition (s * n), 20+ methods, and formatting: f-strings,.format(),format(), and%, all through CPython's format specification mini-language (fill, alignment, sign,z,#,0, width,,/_grouping, precision, and every type letter), digit for digit with CPython..format()takes automatic, positional, and named fields, attribute and index lookups ({0.x},{1[2]},{d[key]}), conversions, and specs with nested fields;%takes every conversion with its flags, a tuple operand, and%(key)sover a dict - String methods run on a string built at run time, not only on a literal: case and trim transforms,
find/count/startswith/endswith,split/join/replace, the layout and predicate methods. A method folded on a literal answers what it answers at run time. Strings compare by content everywhere, so a key built at run time matches an equal one already in a dict - A
stris a sequence of code points, as in CPython:len, indexing, slicing,find/index, padding, iteration, and comprehensions count characters, not UTF-8 bytes. Case conversion (upper,lower,capitalize,title, with CPython's final-sigma and titlecase rules andßtoSS) and theis*predicates (isalpha,isalnum,isdigit,isdecimal,isnumeric,isspace,isupper,islower) cover the whole of Unicode, from tables generated from CPython 3.13 (Unicode 15.1), and so dostrip()andsplit()'s idea of whitespace - F-strings interpolate every placeholder, rendering each value with
str().{x!r},{x!a}, and{x:spec}(with nested fields,{x:{w}.{p}f}) format as CPython does. Constant placeholders fold into the literal at compile time str()andrepr()of every value: a float by its shortest round-trip digits (CPython's repr, exponent notation from 1e16), bools, None, quoted and escaped strings and bytes, ranges, nested lists, tuples, dicts, and sets, and instances through their__str__and__repr__.float()parses a string correctly rounded, as CPython does, and raises a catchableValueErroron bad input
Built-in functions
- Built-in functions:
int()(including parsing a string, with a catchableValueErroron bad input),float(),str(),repr(),ascii(),format(),bool(),len()(of strings, collections, and ranges),abs(),round(x[, ndigits])(rounding the exact binary value half-to-even, as CPython does),min()/max()(several arguments, all ints, all floats, or all strings, or one list, tuple, or range, withkey=anddefault=),sum()over lists and tuples (with an optional start value),isinstance()against a class, a builtin type, or a tuple of them,sorted(iterable[, key][, reverse]),dict(mapping)(a copy),dict(pairs)anddict(**kwargs), andreversed(seq)where it is walked (a loop, a comprehension,list(),sorted(),str.join()).print()writes to standard output (withsep,end,file=sys.stderr, andflush), andinput()reads a line of standard input, both through WASI (see Running compiled modules)
Collections
- Collections: lists, dicts, sets, tuples, and ranges: literals, indexing, slicing, methods, and membership (
in/not in), with full-precision f64 elements and hash-table sets; lists, dicts, and sets grow at run time. A collection's elements live in a block its header points at, so growing one never moves the collection: a list grown inside a function it was passed to is grown for every other name for it too. An empty collection is falsy as in Python - List and tuple methods:
sort,reverse,pop(i),index,count,append,extend,insert,remove. Lists and tuples concatenate (xs + ys) and repeat ([0] * n,(a, b) * 3) - Sets take
|,&,^, and-(and their in-place forms) and the full method set (union,intersection,difference,symmetric_difference, the_updateforms,issubset,issuperset,isdisjoint, taking a set, list, or tuple;copy,pop,clear;set(iterable)). A set iterates in CPython's order: its hashes for ints, bools, floats, and tuples of them, and its table algorithm, are CPython's, so a loop over a set,pop(), and the order a set prints in agree with CPython 3.13 for those members. A set of strings has no reproducible order in CPython, whose string hash is keyed per process; here the order is fixed but unspecified - Dicts merge with
d1 | d2and|=in CPython's order and haveget(key, default),keys,values,items,update,pop,popitem,setdefault,copy,clear, anddict.fromkeys.dict.get(key)without a default needs instance values (see Limitations) - A walk over a collection visits what CPython's does: a str its characters, a dict its keys, a set its members in CPython's order, in a comprehension,
sum,sorted,min, andmaxalike - Values compare by what they hold:
==,!=, and the orderings on tuples, lists, and strings follow CPython, and so doin,list.index/count, set membership, and dict keys, so a tuple can be a set member or a dict key. Dicts compare by their entries, whatever their order. Sets compare as sets (==, and<=/<as subset tests).iscompares identity: two instances, an instance andNone, or astragainstNone
Classes and objects
- Module-level variables and class definitions. A module-level definition that is not a plain constant (a collection, an instance, a call) is evaluated once at instantiation, in source order, and shared by every function, so mutating it is seen everywhere
- Classes with heap-allocated instances, several live per class, usable as arguments and return values. Class-level variables are read through an instance or the class (a subclass's own when it defines one) and rebound through the class that defines them (
Counter.count += 1) - Object-oriented Python: inheritance from one base or several, with CPython's C3 method resolution order and cooperative
super(),isinstance/issubclassover the class hierarchy,@staticmethod/@classmethod/@property(with setters),@dataclass(generated__init__/__eq__/__repr__), abstract base classes viaabc.ABC, and rich comparisons:==calls__eq__, and</<=/>/>=between instances call__lt__/__le__/__gt__/__ge__(the left operand's own, then the right operand's reflection, as CPython does) - Special methods: binary operators call
__add__,__sub__,__mul__, and the rest (the left operand's, or the right one's reflected__radd__, a subclass's reflection first, as CPython orders them),-x,+x, and~xcall__neg__,__pos__, and__invert__,x in ccalls__contains__,c[k]andc[k] = vcall__getitem__and__setitem__,a += bcalls__iadd__(or__add__),len()calls__len__(a negative answer is CPython'sValueError),if x:calls__bool__(or__len__),abs(),int(),float(), andround(x)call theirs, and calling an instance calls__call__. A method's defaulted parameters can be left out - Virtual dispatch: a method call reaches the override the receiver's run-time class defines, so a base method calling
self.method()gets the subclass's version (the template-method pattern), including through a variable, a parameter, or a list element declared as the base.@propertygetters and__eq__dispatch the same way, andsuper().method()stays non-virtual as in Python - Classes are values:
type(x)andx.__class__give an instance's class, a class name is its class object, compared by identity (type(s) is Square), named by__name__, shown as<class '__main__.Square'>, read for its class variables, and annotatedtype[A]. Calling one held in a variable, andtype()of a builtin value, are refused - Metaclasses:
class M(type)withmetaclass=M, inherited by subclasses. M's__new__and__init__run once as each class is created, specialized for it at compile time, so a registry or a counter works and the attributes they give the class (cls.plugin_id = ...) are its class variables;M.__call__replaces instantiation (a singleton works); M's other methods and class variables are reached through the class (Plugin.describe()). The class namespace argument,__prepare__, and creating a class by calling the metaclass are refused - Decorators: the standard ones (
@staticmethod,@classmethod,@property/@x.setter,@abstractmethod,@dataclass),functools's@singledispatchwith@f.register(dispatch is static, on the first argument's type) and@total_ordering(the missing ordering methods are derived from the one defined),@lru_cache/@cache(accepted, and change nothing observable), and decorators the program writes, on module functions. A user-written decorator is applied at compile time: its wrapper is specialized for each function it decorates, so a wrapper forwarding*argsand**kwargsgets the function's own parameters, state it keeps throughnonlocalis one per decorated function as in CPython, and a factory (@repeat(3)) and a stack of decorators work
Exceptions and context managers
- Exceptions are objects, as in CPython:
raisetransfers control,try/except/finallycatch and clean up, and an exception propagates out of a call into the caller's handler.raise E(args)raises an instance,except E as ebinds it, andstr(e),repr(e),e.args, a user exception's own fields and__str__, andraise X from Y(e.__cause__) work.except (A, B):catches either type, andexcept Basecatches its subclasses, along user classes and CPython's builtin hierarchy (LookupErroroverIndexErrorandKeyError,ArithmeticErroroverZeroDivisionError, and the rest). A bareraise(orraise e) in a handler re-raises. The run-time's own faults carry CPython's messages (list index out of range,invalid literal for int() with base 10: 'x',KeyError('k')) - Faults raise where CPython raises, catchably: division or modulo by zero (
ZeroDivisionError, integer and float), an index outside a sequence (IndexError; a negative index counts from the end, and slicing clamps), a missing dict key (KeyError),list.index(v)andlist.remove(v)of a missing value (ValueError),set.remove(v)of a missing member (KeyError), andlist.pop()from an empty list (IndexError). Item assignment into a tuple or a string is a compile error - Context managers:
with obj as name:over a user class implementing__enter__/__exit__, including nested blocks and inherited protocols.__exit__runs on every way out, receives an exception leaving the block, and suppresses it by answering true, as in CPython
Iteration, generators, and closures
- Comprehensions: list, set, and dict comprehensions with filters, multiple generators, nesting, and
{k: v for k, v in pairs}unpacking. A comprehension over a generator or iterator pulls one value at a time, as CPython's does, so their side effects interleave the same way;list(),tuple(),sum(),sorted(),min(),max(),any(),all(), andset()take one too - Generators with real state preservation:
yieldsuspends and resumes (insidetry/except/finallyandwithtoo),yield fromdelegates, andnext()/send()/close()work,close()raisingGeneratorExitat the suspendedyieldsofinallyblocks and handlers run; generator methods; user classes implementing__iter__/__next__iterate inforloops withStopIterationending the loop, andzip()pulls a generator one value per round in any argument position - Tuple targets in
forloops (for a, b in pairs, star targets included) and the iterator-shaped builtins:enumerate(xs[, start])(start=too),zip(...), anddict.items()/.keys()/.values() - Closures: lambdas compile to real functions dispatched through a
call_indirecttable, capture enclosing variables by reference, and work as first-class values: returned, passed as arguments, and stored in collections. A module function,len, orabsnamed as a value works too (sorted(words, key=len)) - Nested functions: a
definside a function or method keeps its annotations, closes over the enclosing variables by reference (floats and strings included), recurses, calls its siblings, and takes keywords and defaults. Called by name it is a direct, fully typed call; returned or passed on it is a closure value called at its signature where that is known (Callable[[float], float]annotations are understood).nonlocal namerebinds the enclosing variable for every reader
Modules, files, and the standard library
- Imports of your own modules:
import mod,import mod as m, andfrom mod import f [as g]resolve sibling.pyfiles (andpkg/mod.pypackages) and statically link them into the single output module, each compiled once however many import paths reach it - File I/O through WASI:
open(),read([n]),write(s),close(), andwith open(...) as f:(see File I/O) - The standard library, module by module (the modules board tracks each function; anything not listed is refused at compile time, naming the call):
math: every function on the board, on runtime values, with CPython'sValueErrorandOverflowError;math.pi,e,tau,inf,nan. The transcendental functions give musl libm's results, bit for bit (see Numbers)random: CPython's generator, so a seed gives CPython's sequence:seed,random,getrandbits,randrange,randint,uniform,choice,shuffle,sample,gauss,normalvariate,expovariate. Unseeded, it seeds from the host's entropysys:argv,stdin/stdout/stderr,exit(),maxsize,platform("wasi"),version, andpath(empty: imports are linked at compile time)os:environandgetenv()from the host,urandom(),getcwd()("/") andgetpid()(42), which is what CPython on WASI answers, and the constants (os.nameis"posix",os.sepand friends)os.path:join,basename,dirname,split,splitext,normpath,abspath,isabs, asposixpathcomputes them, andexists,isfile,isdirthrough the hostfunctools:reduce,partial,partialmethod,cmp_to_key,@cached_property,@singledispatchmethod, and the decorators aboveitertools: every function, in CPython's order and with its argument checks:count,cycle,repeat,accumulate,batched,chain(andchain.from_iterable),compress,dropwhile,takewhile,filterfalse,starmap,islice,pairwise,tee,zip_longest,groupby(whose groups end when it moves on, as CPython's do),product,permutations,combinations, andcombinations_with_replacementcollections:namedtuple()andtyping.NamedTuple(a module-level class whose instances are their fields as a tuple, with_replace,_asdict,_make, and_fields;namedtuple()'s fields take their types from the calls of the class), andCounter,defaultdict,OrderedDict,deque, andChainMap. Each of those is a class specialized by its element types, writtenCounter[str]ordefaultdict[str, list[int]]in an annotation, and found otherwise from the constructor's arguments or from what the function does with the variable (c[w] += 1keys a Counter byw's type,d[k].append(x)makes adefaultdict(list)hold lists ofx's type). They read as the dict or list they hold wherever CPython reads them as one, a parameter declareddictincluded, and add their own behaviour: Counter's missing counts of 0,most_common,elements,total,update,subtract, and multiset operators; defaultdict's factory; OrderedDict'smove_to_endandpopitem(last=False); deque's ends,maxlen,rotate, and indexing; ChainMap's maps,new_child, andparents; and their reprs and errorsjson:dumpsanddumpof ints, floats (CPython's repr, andNaN/Infinity), bools, None, strings (withensure_ascii's escapes), lists, tuples, dicts (keys of str, int, float, bool, or None), and thecollectionsclasses that are dicts, withindent,separators,sort_keys,ensure_ascii, andallow_nanas constants;loadsandloadinto the type the value is declared to have (data: dict[str, list[int]] = json.loads(text)), raising CPython'sJSONDecodeError(aValueError) with its message and position for a document that is not JSONdatetime:timedelta,date,time,datetime, andtimezone, CPython's own algorithms with its messages and reprs. Construction checks every field;timedeltanormalizes days, seconds, and microseconds from any of its arguments, ints or floats, rounding halves to even; the classes add, subtract, negate, multiply, divide, compare, and print as CPython's;dateanddatetimegivetoordinal,weekday,isoweekday,isocalendar,isoformat,ctime,strftime(the C locale's directives,%f,%z,%:z, and%Z),replace, and f-string specs;fromisoformatreads every ISO 8601 form CPython 3.13 reads;datetime.strptimematches its format as CPython's regular expression does, backtracking included;timestamp,fromtimestamp,utcfromtimestamp,now,today, andutcnowread the host's clock (the local time zone is UTC, as on every WASI host); and an aware value compares, subtracts, and converts (astimezone) by its offset.isinstance(dt, date)is True for a datetime, as in CPythonlogging:getLogger()and the logger hierarchy (a logger made after its descendants becomes their parent, as in CPython),debug/info/warning/error/critical/logon a logger and as the module's functions (which configure the root logger first, as CPython's do),basicConfig()withlevel,format,datefmt,style,stream,filename,filemode,handlers, andforce,setLevel(by number or name),disable(),propagate,disabled,getChild,hasHandlers,getLevelName, andlastResortfor a record no handler takes.Handler,StreamHandler(tosys.stdoutorsys.stderr),FileHandler(modesaandw,delay),NullHandler,Formatterin the%,{, and$styles with CPython's validation errors,Filter, andLogRecord, each subclassable. A call's arguments are always evaluated and its message formatted only when the level is enabled; a record carries the call's line (%(lineno)d) and function (%(funcName)s), its time (%(asctime)s, from the host's clock, local time being UTC), andprocess42, as CPython on WASI reportsre:compile,search,match,fullmatch,findall,finditer,split,subandsubn(with a template or a function),escape, andpurge, the flags (I,M,S,X,A,U) as arguments and inline ((?i),(?i:...)), andPatternandMatchobjects (groupby number or name,groups,groupdict,start,end,span,expand,lastindex,lastgroup,pos,endpos,m[k], and their reprs). Patterns are parsed as CPython 3.13 parses them, so a bad one is reported when compiling withre.error's message and position, and matched as CPython'ssrematches: backtracking in its order, greedy, lazy, and possessive repeats with its zero-width rule, atomic groups, lookahead and fixed-width lookbehind, back references, conditional groups,\b, the Unicode classes (\dis any decimal digit,\wany alphanumeric) or ASCII ones underre.A, and case folding with CPython's special cases (the Kelvin sign, long s). Empty matches infindall,split, andsubfollow CPython 3.7+. A group that did not take part is None, anOptional[str]that compares, concatenates, and converts as CPython's None wouldre.subandre.escapeover constant arguments are folded at compile time where the result is provably CPython's; thereflags, thelogginglevels, anddatetime.MINYEAR/MAXYEAR.json'sJSONEncoderandJSONDecoder, andcollections'UserDict,UserList, andUserString, are refused for now