Language
Limitations
What the compiler refuses, or traps on, rather than answering differently from CPython.
On this page
Each of these is refused at compile time, or traps, rather than answering differently from CPython.
Types
- A place with no type holds a single word read back as an int: an unannotated parameter or return value, and the elements of a bare
list,set, ordictparameter or return. Only ints, bools, None, and functions pass through one; a string, float, collection, or instance is refused with a hint to annotate, since it would lose its length, width, or type on the way. A bare annotation on a variable or field is fine: its elements take the type of the value assigned.len()of an untyped value is refused for the same reason - None is the word 0, which an untyped value can also be, so
x is Noneon an untyped value is refused. AnOptional[int],Optional[bool], orOptional[float]is boxed, so None is told apart from 0:is None,==, truthiness,str(), anddict.get(key)on number values give CPython's answer, and arithmetic or an ordering on None raises itsTypeError. An optional number passed or returned where a plain one is declared (past anis Nonetest, typically) is its number, and None there traps, since CPython would pass the None on and the place cannot hold it. Arithmetic between two optional numbers is refused dict.get(key)without a default is refused where the values are ints, floats, or strings, since a missing key answers None, which those values have no distinct word for; pass a default- Reading an attribute through a value whose type is not known, most often an unannotated parameter, is a compile error with a hint to annotate it. The rich comparison methods are the exception: an unannotated
otherin__eq__/__lt__/... is typed as the class. A field the class does not have is refused too - A variable, field, or collection that holds instances of more than one class is typed as their nearest common base for the whole function, so a method call on it dispatches on each instance's own class. Types are not tracked per program point, so reading a field that only one of the subclasses has through it is refused, and so is a method call on instances with no common base. Storing an instance where an annotation names an unrelated class (a
Rectpassed to aCircleparameter, or returned from-> Circle) is refused rather than dispatched as the annotated class - A lambda's parameters, and a nested function's unannotated ones, are typed from the calls in their scope when those agree, and a
sorted()key's from the elements it is given. A parameter nothing types (a lambda only passed on to an unannotated parameter, or called with an int in one place and a str in another) holds a word like any untyped place, so indexing it, measuring it, or calling a method on it is refused; annotate a nameddefinstead - A
@singledispatchcall needs a first argument whose type the compiler knows (a literal, an annotated parameter, an instance); dispatching on an untyped value is refused rather than sent to the base implementation
Collections
- A collection holds one element type, so a literal mixing types no one type holds (
[1, "a"],{"n": "bob", "age": 30},[1, True]) is refused; use a class or a tuple for a record. A tuple is typed per position, so(1, 2.5)and(1, "a")are fine, but walking one whose members differ (a loop,sorted,sum) is refused - A collection reads every slot at one width, so a value written into one is converted to its element type: an int into a
List[float]widens. Where it cannot be converted the write is refused rather than stored wrong: a float into a collection of ints (it would truncate), a float into a collection with no element type, a value whose type is unknown into a collection of floats, andlist.extend()between lists of different widths.list.extend()takes a list, or a tuple whose members share a type; any other iterable (a range, a set, a string) is refused - Comparing two collections needs both to have the same, known element types:
(1, 2) == (1.0, 2)(True in CPython) and a comparison with an untyped list are refused rather than read at the wrong width. Comparing two empty literals ({} == {},[] == []), whose element types nothing gives, comparing collections whose elements are sets, and a list, dict, or set as a set member or dict key (unhashable in CPython) are refused; two sets compare directly (==, and<=/<as subset tests). A class that defines__eq__cannot be a set member or dict key, since calling a user__hash__is not supported; a named tuple can, as it hashes as its tuple - A method called with the wrong number of arguments is a compile error:
xs.append(3, 4)andxs.clear(9)are refused. The optional forms Python accepts compile, sopop(),pop(i),sort(), andsort(reverse=True)all work for k, v in obj.items()(and.keys()/.values()) over a user class defining that method is refused, since it is read as the dict form
Functions and classes
- Closures capture the variable, not a snapshot of it: a captured variable reassigned after the closure is made changes what the closure sees, and closures created in a loop share the loop variable. A captured variable keeps one type (a float or a string included), so rebinding it to a value of another type is refused
- A nested
defhas to sit at the top level of the enclosing function's body (not inside anif, loop,try, orwith), outside a generator, with constant defaults, and it cannot be called before itsdefruns. A function value that passes through an untyped place (an unannotated parameter) is called with int arguments for an int result, so one taking or returning a float, string, collection, or instance is refused there; annotate the parameter withCallable[[...], R] - Every implementation of an overridden method is reached through one indirect call, which names a single WebAssembly signature, so an override that changes the parameter or return types is a compile error naming both methods
- Decorators: a wrapper that uses
*argsother than to pass it on, a user decorator on a method or a class, and any decorator name the program does not define and the compiler does not implement are refused. So are calls to any other name the program does not define and the compiler does not implement (divmod()andhash()of a builtin value, for example) - There is no
NotImplemented, so the special method the operator's rules pick answers: an__add__cannot defer to the right operand's__radd__. A reflected method is called only when the left operand has no method of its own, and needs one of the two operands to be a constant or a variable, since it runs the right operand first. A method's default must be a constant to be left out of a call (CPython evaluates it once, when the method is defined) reversed()gives the list it walks, so only where it is walked (a loop, a comprehension,list(),sorted(),str.join()), and only over a variable or a field, which it reads twice; anywhere else, where CPython gives an iterator object, it is refused- A
withstatement needs its context manager's class to be known at compile time (an instantiation, a call with an annotated class return type, or a variable of known class type).__exit__'sexc_typereceives the exception object as well asexc, andtbNone; either may only be compared with None or tested for truth, and any other use is refused - The default
reprof an instance whose class defines neither__repr__nor__str__is refused, since CPython's includes an address - The
if __name__ == "__main__":block runs after every module definition is made, so one that reaches a definition placed after it (aNameErrorin CPython) is refused; move the block to the end of the file. A name the block binds for the first time is its own, not a module global another function can read, and rebinding a module function or class there is refused
Exceptions and formatting
- Types are static, so the name a handler binds has the class the handler names (the common base of several): a field only a subclass has is refused through it.
e.argsreads back when every argument is a str, and traps otherwise - A traceback is not kept: a
__main__block's uncaught exception writes only the last line of CPython's report (ValueError: boom), andUnicodeDecodeError's position counts from the start of the line where CPython counts from its read buffer - A format spec CPython rejects (
format(1.5, "d"),"{:,s}".format("a")) is a compile error carrying CPython'sValueErrormessage when the spec is a literal, and traps when it is built at run time.str.formatand%need a literal format string; a*width or precision in%, and a precision on%d/%x/%o, are refused
Generators
- A generator is not finalized when it is no longer reachable (there is no collector), so a generator abandoned mid-iteration does not run its
finallyblocks untilclose()is called - A generator method that another class of its hierarchy also defines is refused, since which generator a call makes would depend on the instance, and so is iterating a generator whose class is not known where it is iterated (one returned through an unannotated function, say)
- A comprehension can iterate a generator or iterator only in its first
forclause: an inner clause is evaluated twice (once to size the result, once to fill it), so an iterator there is refused rather than drained twice.len()of a generator is refused, as CPython raisesTypeError
The standard library
random.seed()of a str or bytes is refused: CPython seeds those from their SHA-512 digest. Seed with an int or a floatmath's transcendental functions round as musl's libm does, which CPython on macOS or glibc can differ from in the last bit (see Numbers)os.getcwd()is"/"andos.getpid()is 42, as on CPython for WASI, which has neither;sys.pathis empty, since every import is linked at compile timedatetime'sdatetimeis not a subclass ofdatehere, so a datetime passed where adateis declared is refused (isinstance(dt, date)still answers True). The onlytzinfoistimezone(a fixed offset, all the module defines), the local time zone is UTC, anddate.strftimeand the others render in the C locale.strptimetakes a constant format, without%G,%V,%C, the%O/%Eforms, or flags and widths (%-d).replace()takes the fields by name. The classes are not hashable here, so they cannot be set members or dict keys.sorted()orders them, butlist.sort(),min(), andmax()of instances are refused (min(days, key=lambda d: d.toordinal())works)repatterns are compiled with the program, so a pattern is written as a literal, a module constant, or a pattern compiled from one; one built at run time is refused, as are bytes patterns,\N{name}escapes, the LOCALE flag, and a pattern CPython prints a FutureWarning for ([[,--in a set).finditeranswers a list (it iterates the same matches, all found at once),Pattern.groupindexis not supported, and a pattern'sfindallandsplitare only on a pattern whose group count is known (not one held where a plainre.Patternis declared).re.subandre.splittakecount,maxsplit, andflagsby name, as CPython 3.13 prints a DeprecationWarning for them by position. A group reference inside a lookbehind is refusedloggingformats are compiled, so a format string is written as a literal or a module constant, and one naming a field CPython could not fill (%(pathname)s,%(filename)s,%(module)s,%(thread)d, or a name a record does not have) is refused rather than reported as a logging error at every record. A message formatted with arguments is a literal.exception(),exc_info=True,stack_info=True,extra=,warn(),addLevelName,logging.config, andlogging.handlersare refused, as is aStreamHandlerwriting anywhere butsys.stdoutorsys.stderr, and a callable used as a filterjson.loads()parses into the type its value is declared to have, so it needs one (an annotated variable, parameter, or return); a JSON object becomes adict[str, T], an array a list or tuple of the declared element types. A document the type cannot hold (a string where an int is declared) raisesTypeError, where CPython would hand back the value it found, and an integer read where a float is declared becomes that float (1.0, where CPython keeps the int 1).json.dumps()takes its options as constants, anddefault=,cls=,skipkeys=,JSONEncoder, andJSONDecoderare refuseditertools.tee()andzip_longest()take sequences, andgroupby()a sequence, not a generator;itertools' functions take a function of the module or a lambda that reads nothing of the scope it is written innamedtuple()is supported as a module-level assignment (a class made inside a function would be a new class on every call), withdefaults=but notrename=ormodule=; a field given values of two types in different calls has no one type and holds ints only. A named tuple's fields cannot be assigned, as in CPython;_asdict()needs every field to have one type- A
Counter,defaultdict,OrderedDict,deque, orChainMapneeds its element types to be known where it is made: an annotation (c: Counter[str] = Counter()), the constructor's argument, or what the function does with the variable afterwards. An empty one used for nothing that types it holds ints. A bare annotation (c: Counter) is refused with a hint to write the types - A
defaultdictwhose factory is a function or a lambda has no repr (CPython's shows the function's address), and a lambda factory cannot read the scope it is written in. Adeque'spopleft()andappendleft()move its other elements, so they cost its length, where CPython's take constant time collections' classes are not subclasses ofdicthere, though aCounter,defaultdict, orOrderedDictpasses where adictis declared (as the dict it holds) and answersisinstance(c, dict). A|merge of one is refused. Two specializations are two classes, sotype(Counter("a")) is type(Counter([1]))is False where CPython answers True.UserDict,UserList, andUserStringare refused
Memory, modules, and files
- Memory is never reclaimed: the bump allocator has no
free, there is no garbage collection or reference counting, every object lives until the module is torn down, and__del__is not invoked - Imported user modules share one flat namespace in the output module, so two modules defining the same function name is a compile error naming both files
f.read()without a size reads up to 64 KiB per call;open()modes must be string literals