Skip to content

polyfills

basedpython backfills modern python syntax and stdlib features to older supported versions by rewriting them at transpile time. minimum supported runtime is python 3.10

scope of this page: rewrites that apply to plain python source (forms a user could type into a .py file). basedpython-specific surface syntax has its own feature page

polyfills fall into a few categories:

  • syntax rewrites — new grammar that basedpython desugars into equivalent 3.10 code
  • import redirectstyping names that are not yet in 3.10/3.11/3.12 stdlib, transparently redirected to typing_extensions
  • expression rewrites — simple attribute or call expressions with a direct 3.10 equivalent
  • stdlib shims — functions or classes not yet in the stdlib, injected as pure-python implementations

Python 3.14

bracketless except (PEP 758)

except clauses without parentheses are rewritten to use them:

# python source
except TimeoutError, ConnectionRefusedError:
    ...
# generated Python
except (TimeoutError, ConnectionRefusedError):
    ...

template strings / t-strings (PEP 750)

requires grammar support — planned for a future release.

t'...' literals produce string.templatelib.Template objects. basedpython will rewrite them to explicit constructor calls

operator.is_none / operator.is_not_none

rewritten to lambda equivalents or inline expressions:

# python source
filter(operator.is_none, items)
filter(operator.is_not_none, items)
# generated Python
filter(lambda x: x is None, items)
filter(lambda x: x is not None, items)

heapq max-heap functions

heapq.heapify_max, heapq.heappush_max, heapq.heappop_max, heapq.heapreplace_max, and heapq.heappushpop_max are injected as pure-Python shims when used

datetime.date.strptime / datetime.time.strptime

rewritten to the existing datetime.datetime.strptime with appropriate extraction:

# python source
datetime.date.strptime("2024-01-15", "%Y-%m-%d")
datetime.time.strptime("14:30:00", "%H:%M:%S")
# generated Python
datetime.datetime.strptime("2024-01-15", "%Y-%m-%d").date()
datetime.datetime.strptime("14:30:00", "%H:%M:%S").time()

Python 3.13

generic type parameter defaults (PEP 696)

TypeVar with a default= argument requires Python 3.13+. basedpython imports TypeVar from typing_extensions instead (which supports default=). this applies when using PEP 695 generic syntax with a default (see the generics polyfill below)

typing.TypeIs (PEP 742)

redirected to typing_extensions.TypeIs:

# python source
from typing import TypeIs
# generated Python
from typing_extensions import TypeIs

typing.ReadOnly (PEP 705)

redirected to typing_extensions.ReadOnly

warnings.deprecated (PEP 702)

redirected to typing_extensions.deprecated

copy.replace()

injected as a pure-Python shim that calls obj.__replace__(**changes):

# python source
from copy import replace
new = replace(obj, x=1)
# generated Python
def _replace(obj, **changes):
    return obj.__replace__(**changes)
new = _replace(obj, x=1)

base64.z85encode / base64.z85decode

injected as pure-Python shims (the Z85 alphabet and algorithm are fully specifiable in Python)


Python 3.12

generic classes and functions (PEP 695)

the [T] type parameter syntax and type alias statement are desugared to TypeVar, Generic, and TypeAlias. See the detailed examples in the section below

typing.override (PEP 698)

redirected to typing_extensions.override on 3.10–3.11

typing.TypedDict with Unpack / **kwargs (PEP 692)

redirected to typing_extensions.Unpack on 3.10–3.11

itertools.batched()

injected as a pure-Python shim on 3.10–3.11:

def _batched(iterable, n, *, strict=False):
    it = iter(iterable)
    while batch := tuple(itertools.islice(it, n)):
        if strict and len(batch) < n:
            raise ValueError("batched(): incomplete batch")
        yield batch

math.sumprod(x, y)

injected as sum(a * b for a, b in zip(x, y))

pathlib.Path.walk()

injected as a wrapper around os.walk()

random.binomialvariate(n, p)

injected as a pure-Python shim


Python 3.11

typing.Self (PEP 673)

redirected to typing_extensions.Self

typing.Never / typing.assert_never

redirected to typing_extensions.Never / typing_extensions.assert_never

typing.LiteralString (PEP 675)

redirected to typing_extensions.LiteralString

typing.Required / typing.NotRequired (PEP 655)

redirected to typing_extensions.Required / typing_extensions.NotRequired

typing.TypeVarTuple / typing.Unpack (PEP 646)

Redirected to typing_extensions.TypeVarTuple / typing_extensions.Unpack

typing.dataclass_transform (PEP 681)

Redirected to typing_extensions.dataclass_transform

typing.reveal_type / typing.assert_type

Redirected to typing_extensions.reveal_type / typing_extensions.assert_type

datetime.UTC

Rewritten to datetime.timezone.utc:

# python source
from datetime import UTC
# generated Python
from datetime import timezone as UTC

Or inline:

# python source
datetime.UTC
# generated Python
datetime.timezone.utc

sys.exception()

Rewritten to sys.exc_info()[1]:

# python source
err = sys.exception()
# generated Python
err = sys.exc_info()[1]

math.exp2(x)

Rewritten to 2 ** x.

math.cbrt(x)

Injected as a shim: x ** (1 / 3) for positive values, with sign handling for negative values.

enum.StrEnum

Injected as a pure-Python shim:

class StrEnum(str, enum.Enum):
    pass

BaseException.add_note()

injected as a monkey-patch on 3.10 when used:

# python source
e.add_note("context")
# generated Python
if not hasattr(e, "__notes__"):
    e.__notes__ = []
e.__notes__.append("context")

tomllib

rewritten to fall back to tomli (the third-party backport):

# python source
import tomllib
# generated Python
try:
    import tomllib
except ModuleNotFoundError:
    import tomli as tomllib

generic classes and functions (PEP 695)

Python 3.12 introduced compact generic syntax. basedpython rewrites it using typing.TypeVar and typing.Generic

basedpython Python output
class A[T]: ... class A(Generic[T]): ...
class A[T=int]: ... class A(Generic[T]): ... (with default=int)
class A[T: int]: ... class A(Generic[T]): ... (with bound=int)
def f[T](x: T) -> T: ... def f(x: T) -> T: ...
type Point = tuple[float, float] Point: TypeAlias = tuple[float, float]

each type parameter becomes a module-level TypeVar with a mangled name (_T, _K, etc)

# python source
class A[T=int]: ...
# generated Python
from typing import TypeVar, Generic

_T = TypeVar("_T", default=int)  # from typing_extensions

class A(Generic[_T]): ...

multiple parameters:

# python source
class Map[K, V]: ...
# generated Python
from typing import TypeVar, Generic

_K = TypeVar("_K")
_V = TypeVar("_V")

class Map(Generic[_K, _V]): ...

existing base classes are preserved:

# python source
class SortedMap[K, V](dict): ...
# generated Python
class SortedMap(dict, Generic[_K, _V]): ...

generic functions:

# python source
def identity[T](x: T) -> T:
    return x
# generated Python
from typing import TypeVar

_T = TypeVar("_T")

def identity(x: T) -> T:
    return x

a bound (T: Foo) maps to TypeVar("_T", bound=Foo). constraints (T: (Foo, Bar)) map to TypeVar("_T", Foo, Bar).

type aliases:

# python source
type Point = tuple[float, float]
type Grid[T] = list[list[T]]
# generated Python
from typing_extensions import TypeAliasType

Point = TypeAliasType("Point", tuple[float, float])
Grid = TypeAliasType("Grid", list[list[T]])

import redirect summary

when basedpython detects one of these names imported from typing on a runtime older than the version that added it, it silently redirects to typing_extensions

Name Added in Redirect source
Self 3.11 typing_extensions
Never, assert_never 3.11 typing_extensions
LiteralString 3.11 typing_extensions
Required, NotRequired 3.11 typing_extensions
TypeVarTuple, Unpack 3.11 typing_extensions
dataclass_transform 3.11 typing_extensions
reveal_type, assert_type 3.11 typing_extensions
override 3.12 typing_extensions
TypeVar(default=...) 3.13 typing_extensions
TypeIs 3.13 typing_extensions
ReadOnly 3.13 typing_extensions
deprecated (warnings) 3.13 typing_extensions