differences from python¶
.by is not a superset of .py. almost all python means the same thing in
basedpython, and this page is the rest of it: every construct that reads
differently, so that renaming a .py file to .by is a decision rather than a
formality
each one is a deliberate fix to something python cannot change without breaking the world
you don't have to port by hand
by transpile --reverse
rewrites python source into basedpython idioms, including the constructs
below
runtime behaviour¶
the same source, running, does something else
is is an instance check¶
is means isinstance and === means identity:
| you write | python does |
|---|---|
x is y |
isinstance(x, y) |
x is not y |
not isinstance(x, y) |
x === y |
x is y |
x !== y |
x is not y |
the compiler doesn't always do isinstance, for example x is None will become x is None in
python, this is because "type of x is None" and "value of x is None" have identical meanings
a mutable default is re-evaluated per call¶
python returns an ever-growing list; basedpython returns [1] every time. only
non-scalar defaults are affected — numbers, bools, strings, None and ...
stay as plain python defaults. see
default argument re-evaluation
a loop target is a fresh binding per iteration¶
python prints 3 3 3, because the loop has one cell shared by every iteration.
basedpython prints 1 2 3. comprehension targets bind the same way. see
unique loop bindings
imports are lazy by default¶
every import and from ... import in a .by file is marked lazy, so the
module's body does not execute until something first touches it:
an import with a side effect — registering a plugin, patching something at
module scope — no longer happens just because the importing module was loaded.
from __future__ import ..., from x import *, and an unaliased import a.b
stay eager. see lazy imports
what an annotation means¶
the same annotation denotes a different type
a string is a string type, not a forward reference¶
python reads that as a deferred reference to the name Foo; basedpython reads
it as the string-literal type. there is no manual forward-reference syntax
because none is needed — the transpiler
quotes a self-reference for you when the
runtime requires it
float means float¶
python's typing spec special-cases float to mean int | float, and complex
to mean int | float | complex. basedpython does not:
[T: (int, str)] is a tuple bound, not constraints¶
python reads a tuple after a type parameter's : as a list of discrete
constraints, which leaves no way to say "bounded by the type tuple[int, str]".
basedpython reads the : literally — it is a bound like any other:
the constraint set is reframed as a "type mapping", and gets its own keyword, in:
so [T: (int, str)] ported from python means something else, and wants
rewriting to [T in (int, str)] — which is what
by transpile --reverse
does for you. see type mappings
class A[**Kwargs] is keyword type arguments, not a parameter specification¶
python's typing denotes that **P is a parameter specification, but basedpython
generalises the concept as an upper bound of a standard type parameter:
def f[P: (*: *, **: *)](fn: (**P) -> None): ...
f[(int, str, foo: bool)]
class HasKeywords[**Kwargs]
HasKeywords[foo=int, bar=str]
see generics
a parameter specification is forwarded with stars¶
python names a parameter specification's two halves as attributes of the type variable,
*args: P.args and **kwargs: P.kwargs. basedpython unpacks them the way it unpacks every
other pack, and the attribute spelling is an error:
def deco[P: (*: *, **: *), R](fn: (**P) -> R) -> (**P) -> R:
def inner(*args: *P, **kwargs: **P) -> R:
return fn(*args, **kwargs)
return inner
see forwarding
type checking¶
the code runs the same; the checker's verdict differs
an unsolved type variable is Never¶
where a type variable is never solved, python's checkers infer Unknown and
stop checking. basedpython infers Never in covariant and bivariant positions,
which keeps checking. see
precise unsolved type variables
inference is sound where python's is gradual¶
basedpython infers a precise type in places the spec allows a gradual one, so
code that leaned on Any flowing through silently now reports. see
sound types
the stdlib is typed differently¶
typeshed improvements lists the whole set. the ones most likely to report on existing code:
- an optional
recapture group isstr | None, notAny, som.group(1).upper()is an error - a
functools.cached function keeps its parameter list, so a wrong-arity call to it is an error dict/setkeys are bounded byHashable- a membership test checks that the operands
overlap, so
"a" in [1, 2]is an error rather than a guaranteedFalse