crates/ty_python_semantic/resources/mdtest/call/functools_partial.md
functools.partialfrom functools import partial
def f(a: int, b: str) -> bool:
return True
p = partial(f, 1)
reveal_type(p) # revealed: partial[(b: str) -> bool]
Keyword-bound parameters are kept with a default, but they become keyword-only in the resulting
callable. partial allows overriding them at call time, but only by keyword.
from functools import partial
def f(a: int, b: str) -> bool:
return True
p = partial(f, b="hello")
reveal_type(p) # revealed: partial[(a: int, *, b: str = "hello") -> bool]
Binding a positional-or-keyword parameter by keyword makes it defaulted and keyword-only in the reduced signature, and later positional-or-keyword parameters become keyword-only too:
from functools import partial
def f(a: int, b: str) -> bool:
return True
p = partial(f, a=1)
reveal_type(p) # revealed: partial[(*, a: int = 1, b: str) -> bool]
reveal_type(p(b="hello")) # revealed: bool
# error: [missing-argument]
# error: [too-many-positional-arguments]
p("hello")
from functools import partial
def f(a: int, b: str, c: float) -> bool:
return True
p = partial(f, 1, c=3.14)
reveal_type(p) # revealed: partial[(b: str, *, c: int | float = ...) -> bool]
from functools import partial
def f(a: int, b: str) -> bool:
return True
p = partial(f, 1, "hello")
reveal_type(p) # revealed: partial[() -> bool]
from functools import partial
def f(a: int, b: str) -> bool:
return True
p = partial(f)
reveal_type(p) # revealed: partial[(a: int, b: str) -> bool]
from functools import partial
def f(a: int, b: str, /) -> bool:
return True
p = partial(f, 1)
reveal_type(p) # revealed: partial[(b: str, /) -> bool]
from functools import partial
def f(a: int, *, b: str) -> bool:
return True
p = partial(f, 1)
reveal_type(p) # revealed: partial[(*, b: str) -> bool]
from functools import partial
def f(a: int, *, b: str) -> bool:
return True
p = partial(f, b="hello")
reveal_type(p) # revealed: partial[(a: int, *, b: str = "hello") -> bool]
from functools import partial
def f(a: int, *args: str) -> bool:
return True
p = partial(f, 1)
reveal_type(p) # revealed: partial[(*args: str) -> bool]
from functools import partial
def f(a: int, *args: str) -> bool:
return True
p = partial(f, 1, "hello")
reveal_type(p) # revealed: partial[(*args: str) -> bool]
from functools import partial
def f(a: int, **kwargs: str) -> bool:
return True
p = partial(f, 1)
reveal_type(p) # revealed: partial[(**kwargs: str) -> bool]
from functools import partial
def f(a: int, b: str = "default") -> bool:
return True
p = partial(f, 1)
reveal_type(p) # revealed: partial[(b: str = "default") -> bool]
from functools import partial
p = partial(lambda x, y: x + y, 1)
reveal_type(p) # revealed: partial[(y: Any) -> Any]
from functools import partial
class Greeter:
def greet(self, name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}"
g = Greeter()
p = partial(g.greet, "world")
reveal_type(p) # revealed: partial[(greeting: str = "Hello") -> str]
reveal_type(p()) # revealed: str
from functools import partial
def f(a: int, b: str, c: float) -> bool:
return True
p = partial(f, 1)
reveal_type(p("hello", 3.14)) # revealed: bool
reveal_type(p(b="hello", c=3.14)) # revealed: bool
from functools import partial
def f(a: int, b: str) -> bool:
return True
p = partial(f, "not_an_int") # error: [invalid-argument-type]
reveal_type(p) # revealed: partial[(b: str) -> bool]
from functools import partial
def f(a: int, b: str) -> bool:
return True
p = partial(f, b=42) # error: [invalid-argument-type]
reveal_type(p) # revealed: partial[(a: int, *, b: str = 42) -> bool]
from functools import partial
def f(a: int, b: str) -> bool:
return True
p = partial(f, c=1) # error: [unknown-argument]
from functools import partial
def f(a: int, b: str) -> bool:
return True
p = partial(f, 1, a=2) # error: [parameter-already-assigned]
from functools import partial
def f(a: int, **kwargs: str) -> bool:
return True
# error: [invalid-argument-type]
# error: [parameter-already-assigned]
p = partial(f, 1, a="hello")
reveal_type(p) # revealed: partial[(**kwargs: str) -> bool]
from functools import partial
def f(a: int, b: int) -> int:
return a + b
p = partial(f, 1, 2, 3) # error: [too-many-positional-arguments]
reveal_type(p) # revealed: partial[() -> int]
p()
p(1) # error: [too-many-positional-arguments]
partial(42) is an error caught by the constructor call; we fall back to the default partial[T]
type.
from functools import partial
p = partial(42) # error: [invalid-argument-type]
reveal_type(p) # revealed: partial[Unknown]
Positional-only parameters cannot be bound by keyword in partial(). The parameter should be
preserved in the resulting callable, while still reporting a construction-time error:
from functools import partial
def f(x: int, /, y: str) -> bool:
return True
# `x` is positional-only, so `x=1` does not bind it.
p = partial(f, x=1) # error: [positional-only-parameter-as-kwarg]
reveal_type(p) # revealed: partial[(x: int, /, y: str) -> bool]
Type variables are inferred from the bound arguments:
from functools import partial
from typing import TypeVar
T = TypeVar("T")
def identity(x: T) -> T:
return x
p = partial(identity, 1)
reveal_type(p) # revealed: partial[() -> Literal[1]]
from functools import partial
from typing import TypeVar
T = TypeVar("T")
def pair(a: T, b: T) -> tuple[T, T]:
return (a, b)
p = partial(pair, 1)
reveal_type(p) # revealed: partial[(b: int) -> tuple[int, int]]
reveal_type(p(2)) # revealed: tuple[int, int]
reveal_type(p(2)[1]) # revealed: int
from functools import partial
from typing import cast
from typing_extensions import TypeVar
T = TypeVar("T")
U = TypeVar("U", default=T)
def with_default(x: T) -> tuple[T, U]:
return (x, cast(U, x))
reveal_type(with_default(1)) # revealed: tuple[Literal[1], Literal[1]]
p = partial(with_default, 1)
reveal_type(p) # revealed: partial[() -> tuple[Literal[1], Literal[1]]]
reveal_type(p()) # revealed: tuple[Literal[1], Literal[1]]
from functools import partial
from typing import Generic, TypeVar
T = TypeVar("T")
class Box(Generic[T]):
def __init__(self, value: T) -> None:
self.value = value
list_factory = partial(list, [1])
# TODO: should reveal `partial[() -> list[int]]` once constructor partials are modeled.
reveal_type(list_factory) # revealed: partial[Unknown]
# TODO: should reveal `list[int]` once constructor partials are modeled.
reveal_type(list_factory()) # revealed: Unknown
box_factory = partial(Box, "hi")
# TODO: should reveal `partial[() -> Box[str]]` once constructor partials are modeled.
reveal_type(box_factory) # revealed: partial[Unknown]
# TODO: should reveal `Box[str]` once constructor partials are modeled.
reveal_type(box_factory()) # revealed: Unknown
from functools import partial
from typing import Callable, Generic, TypeVar
T = TypeVar("T")
def identity(x: T) -> T:
return x
class Box(Generic[T]):
def __init__(self, value: T) -> None:
self.callback = partial(identity, value)
box = Box[int](1)
reveal_type(box.callback) # revealed: partial[() -> int]
target: Callable[[], int] = box.callback
Self[environment]
python-version = "3.11"
from functools import partial
from typing import Self
class Builder:
def clone_with(self, value: int) -> Self:
return self
class Fancy(Builder):
pass
fancy = Fancy()
p = partial(fancy.clone_with, 1)
reveal_type(p) # revealed: partial[() -> Fancy]
reveal_type(p()) # revealed: Fancy
from functools import partial
from typing import overload
@overload
def f(a: int) -> int: ...
@overload
def f(a: str) -> str: ...
def f(a: int | str) -> int | str:
return a
p = partial(f, 1)
reveal_type(p) # revealed: partial[() -> int]
from functools import partial
from typing import Callable
def zero_arg(x: int) -> int:
return x
def one_arg(x: int, y: str) -> int:
return x + len(y)
def test_union_partial(flag: bool) -> None:
f = zero_arg if flag else one_arg
p = partial(f, 1)
reveal_type(p) # revealed: partial[() -> int] | partial[(y: str) -> int]
bad: Callable[[bytes, bytes], int] = p # error: [invalid-assignment]
from functools import partial
from typing import overload
@overload
def g(path: bytes, start: bytes | None = b".") -> bytes: ...
@overload
def g(path: str, start: str | None = ".") -> str: ...
def g(path: bytes | str, start: bytes | str | None = None) -> bytes | str:
return path
p = partial(g, start=".")
paths: list[str] = ["x"]
reveal_type(p) # revealed: partial[(path: str, *, start: str | None = ".") -> str]
reveal_type(list(map(p, paths))) # revealed: list[str]
partialfrom functools import partial
from typing import Any, Callable, TypeVar
from typing_extensions import ParamSpec
P = ParamSpec("P")
R = TypeVar("R")
def invoke(func: Callable[P, R], *args: P.args, **kwargs: P.kwargs) -> R:
return func(*args, **kwargs)
def pre(cfg: Any) -> Any:
return cfg
bound = partial(invoke, pre)
reveal_type(bound) # revealed: partial[(cfg: Any) -> Any]
reveal_type(bound({})) # revealed: Any
from functools import partial
from typing import Callable, TypeVar
from typing_extensions import ParamSpec
P = ParamSpec("P")
R = TypeVar("R")
def invoke(flag: int, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs) -> R:
return func(*args, **kwargs)
def pre(*, cfg: str) -> int:
return 1
bound = partial(invoke, flag=1, func=pre)
reveal_type(bound(cfg="x")) # revealed: int
partial(reduce, mul) should keep the narrowed return type from the bound reducer:
from functools import partial, reduce
def mul(x: int, y: int, /) -> int:
return x * y
prod = partial(reduce, mul)
shape: list[int] = [1, 2, 3]
# revealed: partial[Overload[(iterable: Iterable[int], initial: int, /) -> int, (iterable: Iterable[int], /) -> int]]
reveal_type(prod)
reveal_type(prod(shape)) # revealed: int
partial(zip, strict=True) should accept the keyword-only argument and preserve the element types
of the resulting iterator:
[environment]
python-version = "3.12"
from functools import partial
import builtins
zips = partial(builtins.zip, strict=True)
xs = [1]
ys = ["a"]
pairs = list(zips(xs, ys))
# TODO: should reveal `list[tuple[int, str]]` once keyword-only constructor bindings are preserved.
reveal_type(pairs) # revealed: list[Unknown]
partial(...) should accept keyword arguments whose literal container types are inferred without
context at the call site:
from functools import partial
from typing import Literal, Sequence
Distribution = Literal["sdist", "wheel", "editable"]
def build(distributions: Sequence[Distribution]) -> None:
pass
p = partial(build, distributions=["wheel"]) # error: [invalid-argument-type]
# TODO: should accept this keyword literal without a construction-time error.
reveal_type(p) # revealed: partial[(*, distributions: Sequence[Literal["sdist", "wheel", "editable"]] = ...) -> None]
reveal_type(p()) # revealed: None
partial(...) should still re-run argument refinement even when the initial constructor binding
already succeeds, so empty literals keep the parameter's contextual element type:
from functools import partial
from typing import Literal, Sequence
Distribution = Literal["sdist", "wheel", "editable"]
def build(distributions: Sequence[Distribution]) -> None:
pass
p = partial(build, distributions=[])
reveal_type(p) # revealed: partial[(*, distributions: Sequence[Literal["sdist", "wheel", "editable"]] = ...) -> None]
reveal_type(p()) # revealed: None
from functools import partial
from typing import overload
@overload
def g(a: int, b: str) -> int: ...
@overload
def g(a: str, b: str) -> str: ...
def g(a: int | str, b: str) -> int | str:
return a
p = partial(g, 1)
reveal_type(p) # revealed: partial[(b: str) -> int]
from functools import partial
def f(a: int, b: str) -> bool:
return True
args: tuple[int] = (1,)
p = partial(f, *args)
reveal_type(p) # revealed: partial[(b: str) -> bool]
from functools import partial
def f(a: int, b: str, c: float) -> bool:
return True
args: tuple[int, str] = (1, "hello")
p = partial(f, *args)
reveal_type(p) # revealed: partial[(c: int | float) -> bool]
from functools import partial
def f(a: int, b: str, c: float) -> bool:
return True
args: tuple[str] = ("hello",)
p = partial(f, 1, *args)
reveal_type(p) # revealed: partial[(c: int | float) -> bool]
We intentionally fall back here instead of enumerating possible tuple lengths; this matches the existing treatment of variable-length tuple splats in ordinary calls.
from functools import partial
def f(a: int, b: str) -> bool:
return True
def get_args() -> tuple[int, ...]:
return (1,)
p = partial(f, *get_args())
reveal_type(p) # revealed: partial[bool]
from functools import partial
from typing import TypedDict
class MyKwargs(TypedDict):
b: str
def f(a: int, b: str) -> bool:
return True
kwargs: MyKwargs = {"b": "hello"}
p = partial(f, **kwargs)
reveal_type(p) # revealed: partial[(a: int, *, b: str = ...) -> bool]
from functools import partial
from typing import TypedDict
class KwargsA(TypedDict):
b: str
class KwargsB(TypedDict):
b: str
def f(*, b: str) -> bool:
return True
def make(kwargs: KwargsA | KwargsB) -> None:
p = partial(f, **kwargs)
reveal_type(p) # revealed: partial[(*, b: str = ...) -> bool]
from functools import partial
from typing import TypedDict
class MyKwargs(TypedDict):
c: float
def f(a: int, b: str, c: float) -> bool:
return True
kwargs: MyKwargs = {"c": 3.14}
p = partial(f, b="hello", **kwargs)
reveal_type(p) # revealed: partial[(a: int, *, b: str = "hello", c: int | float = ...) -> bool]
We intentionally fall back for a plain dict, even if a literal key is visible at the call site,
because we don't track the key set precisely the way we do for direct keyword arguments or
TypedDict. In particular, we don't know whether a required parameter like b will be supplied by
the mapping, so we can't safely choose between a reduced signature where b remains required and
one where b becomes a defaulted keyword-only parameter.
from functools import partial
def f(a: int, b: str) -> bool:
return True
kwargs = {"a": 1}
p = partial(f, **kwargs)
reveal_type(p) # revealed: partial[bool]
Optional TypedDict keys have the same ambiguity: a key like b may or may not be present at
runtime, so we can't safely decide whether the reduced signature should keep b required or mark it
as already bound with a default.
from functools import partial
from typing import TypedDict
class MaybeKwargs(TypedDict, total=False):
b: str
def f(a: int, *, b: str) -> None:
pass
def make(kwargs: MaybeKwargs) -> None:
p = partial(f, **kwargs)
reveal_type(p) # revealed: partial[None]
from functools import partial
def f(a: int, b: str, c: float) -> bool:
return True
p1 = partial(f, 1)
reveal_type(p1) # revealed: partial[(b: str, c: int | float) -> bool]
p2 = partial(p1, "hello")
reveal_type(p2) # revealed: partial[(c: int | float) -> bool]
from functools import partial
class MyClass:
def __init__(self, x: int, y: str) -> None:
pass
p = partial(MyClass, 1)
# TODO: should reveal `partial[(y: str) -> MyClass]` once constructor partials are modeled.
reveal_type(p) # revealed: partial[Unknown]
__new__ and __init__from functools import partial
class MyClass:
def __new__(cls, x: int) -> "MyClass":
return super().__new__(cls)
def __init__(self, x: int) -> None: ...
p = partial(MyClass, 1)
reveal_type(p) # revealed: partial[Unknown]
p()
# TODO: should reveal `partial[() -> MyClass]` once constructor signatures are merged.
# TODO: should error: [too-many-positional-arguments] once constructor signatures are merged.
p("extra")
__new__ positional paramsfrom functools import partial
class MyClass:
def __new__(cls, x: int) -> "MyClass":
return super().__new__(cls)
def __init__(self) -> None: ...
p = partial(MyClass, 1)
# TODO: should reveal `partial[(x: Never) -> MyClass]` once constructor signatures are merged.
reveal_type(p) # revealed: partial[Unknown]
# TODO: should error: [missing-argument] once constructor signatures are merged.
p()
# TODO: should error: [invalid-argument-type] once constructor signatures are merged.
p(1)
__new__ keyword paramsfrom functools import partial
class MyClass:
def __new__(cls, x: int) -> "MyClass":
return super().__new__(cls)
def __init__(self) -> None: ...
p = partial(MyClass, x=1)
# TODO: should reveal `partial[(*, x: Never) -> MyClass]` once constructor signatures are merged.
reveal_type(p) # revealed: partial[Unknown]
# TODO: should error: [missing-argument] once constructor signatures are merged.
p()
# TODO: should error: [invalid-argument-type] once constructor signatures are merged.
p(x=1)
from functools import partial
class MyClass:
def __new__(cls, x: int) -> "MyClass":
return super().__new__(cls)
def __init__(self, x: int, y: str) -> None: ...
p = partial(MyClass, 1)
# TODO: should reveal `partial[(y: Never) -> MyClass]` once constructor signatures are merged.
reveal_type(p) # revealed: partial[Unknown]
# TODO: should error: [missing-argument] once constructor signatures are merged.
p()
# TODO: should error: [invalid-argument-type] once constructor signatures are merged.
p("extra")
__init__ paramsfrom functools import partial
class MyClass:
def __new__(cls) -> "MyClass":
return super().__new__(cls)
def __init__(self, x: int) -> None: ...
p = partial(MyClass)
# TODO: should reveal `partial[(x: Never) -> MyClass]` once constructor signatures are merged.
reveal_type(p) # revealed: partial[Unknown]
# TODO: should error: [missing-argument] once constructor signatures are merged.
p()
# TODO: should error: [invalid-argument-type] once constructor signatures are merged.
p(1)
from functools import partial
class MyClass:
def __new__(cls, x: int) -> "MyClass":
return super().__new__(cls)
def __init__(self, *, y: str) -> None: ...
p = partial(MyClass, 1)
# TODO: should reveal `partial[(x: Never, *, y: Never) -> MyClass]` once constructor signatures are merged.
reveal_type(p) # revealed: partial[Unknown]
# TODO: should error: [missing-argument] once constructor signatures are merged.
p()
# TODO: should error: [missing-argument] and [invalid-argument-type] once constructor signatures are merged.
p(y="extra")
from functools import partial
class MyClass:
def __new__(cls, x: int) -> "MyClass":
return super().__new__(cls)
def __init__(self, x: object) -> None: ...
p = partial(MyClass)
# TODO: should reveal `partial[(x: int) -> MyClass]` once constructor signatures are merged.
reveal_type(p) # revealed: partial[Unknown]
# TODO: should reveal `MyClass` once constructor signatures are merged.
reveal_type(p(1)) # revealed: Unknown
# TODO: should error: [invalid-argument-type] once constructor signatures are merged.
p("s")
from functools import partial
class MyClass:
def __new__(cls, x: int, *, y: str) -> "MyClass":
return super().__new__(cls)
def __init__(self, *, y: str, x: int) -> None: ...
p = partial(MyClass)
# TODO: should reveal `partial[(*, x: int, y: str) -> MyClass]` once constructor signatures are merged.
reveal_type(p) # revealed: partial[Unknown]
# TODO: should reveal `MyClass` once constructor signatures are merged.
reveal_type(p(x=1, y="s")) # revealed: Unknown
# TODO: should error: [missing-argument] once constructor signatures are merged.
p(y="s")
from functools import partial
class MyClass:
def __new__(cls, x: int, y: str) -> "MyClass":
return super().__new__(cls)
def __init__(self, y: str, x: int) -> None: ...
p = partial(MyClass)
# TODO: should reveal `partial[(*, x: int, y: str) -> MyClass]` once constructor signatures are merged.
reveal_type(p) # revealed: partial[Unknown]
# TODO: should reveal `MyClass` once constructor signatures are merged.
reveal_type(p(x=1, y="s")) # revealed: Unknown
# TODO: should error: [missing-argument] and [too-many-positional-arguments] once constructor signatures are merged.
p(1, "s")
__new__ and __init__from functools import partial
class MyClass:
def __new__(cls, x: int | str) -> "MyClass":
return super().__new__(cls)
def __init__(self, x: int) -> None: ...
p = partial(MyClass)
# TODO: should reveal `partial[(x: int) -> MyClass]` once constructor signatures are merged.
reveal_type(p) # revealed: partial[Unknown]
p(1)
# TODO: should error: [invalid-argument-type] once constructor signatures are merged.
p("s")
from functools import partial
from typing import TypeVar
T = TypeVar("T")
class MyClass:
def __new__(cls, x: T, y: T) -> "MyClass":
return super().__new__(cls)
def __init__(self, x: int, y: str) -> None: ...
p = partial(MyClass)
# TODO: should error twice with [invalid-argument-type] once constructor signatures are merged.
p(1, "s")
__new__ overloadsfrom __future__ import annotations
from functools import partial
from typing import overload
class MyClass:
@overload
def __new__(cls, x: int) -> "MyClass": ...
@overload
def __new__(cls, x: str) -> str: ...
def __new__(cls, x: int | str) -> "MyClass" | str:
if isinstance(x, str):
return x
return super().__new__(cls)
def __init__(self, x: int) -> None: ...
p = partial(MyClass)
# TODO: should preserve the non-instance `__new__` overload in the reduced partial signature.
reveal_type(p) # revealed: partial[Unknown]
# TODO: should reveal `MyClass` once constructor signatures are merged.
reveal_type(p(1)) # revealed: Unknown
# TODO: should reveal `str` once constructor signatures are merged.
reveal_type(p("s")) # revealed: Unknown
Binding a parameter that has a default value removes it from the signature.
from functools import partial
def f(a: int, b: str = "default", c: float = 0.0) -> bool:
return True
p = partial(f, 1, "hello")
reveal_type(p) # revealed: partial[(c: int | float = ...) -> bool]
from functools import partial
def f(a: int, b: str, c: float, d: bool) -> int:
return 0
p = partial(f, b="hello", d=True)
reveal_type(p) # revealed: partial[(a: int, *, b: str = "hello", c: int | float, d: bool = True) -> int]
from functools import partial
def f(a: int, /, b: str, *, c: float) -> bool:
return True
# Bind the positional-only param
p1 = partial(f, 1)
reveal_type(p1) # revealed: partial[(b: str, *, c: int | float) -> bool]
# Bind a keyword-only param by keyword
p2 = partial(f, c=3.14)
reveal_type(p2) # revealed: partial[(a: int, /, b: str, *, c: int | float = ...) -> bool]
# Bind both positional-only and keyword-only
p3 = partial(f, 1, c=3.14)
reveal_type(p3) # revealed: partial[(b: str, *, c: int | float = ...) -> bool]
from functools import partial
def f(a: int, b: str, c: float) -> bool:
return True
args: tuple[int] = (1,)
p = partial(f, *args, c=3.14)
reveal_type(p) # revealed: partial[(b: str, *, c: int | float = ...) -> bool]
from functools import partial
def f(a: int, b: str) -> bool:
return True
args: tuple[()] = ()
p = partial(f, *args)
reveal_type(p) # revealed: partial[(a: int, b: str) -> bool]
TODO: preserve uninferred type variables in the resulting partial signature.
from functools import partial
from typing import TypeVar
T = TypeVar("T")
U = TypeVar("U")
def combine(a: T, b: U) -> tuple[T, U]:
return (a, b)
p = partial(combine, 1)
reveal_type(p) # revealed: partial[(b: Unknown) -> tuple[Literal[1], Unknown]]
__call__)from functools import partial
class Adder:
def __call__(self, a: int, b: int) -> int:
return a + b
adder = Adder()
p = partial(adder, 1)
reveal_type(p) # revealed: partial[(b: int) -> int]
from functools import partial
class MyClass:
@staticmethod
def f(a: int, b: str) -> bool:
return True
p = partial(MyClass.f, 1)
reveal_type(p) # revealed: partial[(b: str) -> bool]
When the bound argument matches a later overload but not the first, no error should be emitted:
from functools import partial
from typing import overload
@overload
def f(a: int) -> int: ...
@overload
def f(a: str) -> str: ...
def f(a: int | str) -> int | str:
return a
# "hello" matches the second overload (str -> str), so no error.
p = partial(f, "hello")
reveal_type(p) # revealed: partial[() -> str]
partial allows keyword arguments to be overridden when calling the result:
from functools import partial
def f(a: int, b: str, c: float) -> bool:
return True
p = partial(f, b="hello")
reveal_type(p) # revealed: partial[(a: int, *, b: str = "hello", c: int | float) -> bool]
# Override b at call time
reveal_type(p(1, b="world", c=3.14)) # revealed: bool
TODO: preserve the override branch when a keyword-bound generic is rebound at call time.
from functools import partial
from typing import TypeVar
T = TypeVar("T")
def pair(a: T, b: T) -> tuple[T, T]:
return (a, b)
p = partial(pair, b=1)
reveal_type(p) # revealed: partial[(a: int, *, b: int = 1) -> tuple[int, int]]
p("x") # error: [invalid-argument-type]
# error: [invalid-argument-type]
# error: [invalid-argument-type]
p("x", b="y")
A partial result is assignable to a Callable with the matching signature:
from functools import partial
from typing import Callable
def f(a: int, b: str) -> bool:
return True
p = partial(f, 1)
reveal_type(p) # revealed: partial[(b: str) -> bool]
def takes_callable(fn: Callable[[str], bool]) -> None:
pass
takes_callable(p) # OK -- partial[(b: str) -> bool] is callable with (str) -> bool
def takes_wrong_callable(fn: Callable[[int], bool]) -> None:
pass
takes_wrong_callable(p) # error: [invalid-argument-type]
def returns_partial() -> partial[bool]:
return p # OK -- partial[(b: str) -> bool] is assignable to partial[bool]
from functools import partial
from typing import Any, Protocol
class Conv(Protocol):
def __call__(self, __x: Any, *, _target_: str = "set", CBuildsFn: type[Any]) -> Any: ...
def build(
_args_: tuple[Any, ...],
_target_: str,
CBuildsFn: type[Any],
) -> None:
pass
p = partial(build, _target_="set")
reveal_type(p) # revealed: partial[(_args_: tuple[Any, ...], *, _target_: str = "set", CBuildsFn: type[Any]) -> None]
conversion: dict[type, Conv] = {}
conversion[set] = p
from functools import partial
from typing import TYPE_CHECKING
def f(a: int, b: str | None, c: dict[str, int]) -> dict[str, int]:
return c
if TYPE_CHECKING:
def g(a: int, b: str | None) -> dict[str, int]: ...
g = partial(f, c={})
from functools import partial
from typing import overload
@overload
def f(a: int) -> int: ...
@overload
def f(a: str) -> str: ...
def f(a: int | str) -> int | str:
return a
def make_partial(x: int | str):
p = partial(f, x)
reveal_type(p) # revealed: partial[Overload[() -> int, () -> str]]
return p
p = make_partial(1)
from functools import partial
from typing import overload
@overload
def f(a: int) -> int: ...
@overload
def f(a: str) -> str: ...
def f(a):
return a
p = partial(f, 1.0) # error: [invalid-argument-type]
reveal_type(p) # revealed: partial[Unknown]
from functools import partial
from typing import Callable
from typing_extensions import Self
class C:
@classmethod
def make(cls, *, x: int = 0) -> Self:
raise RuntimeError
factory: Callable[[], C] = partial(C.make, x=0)
Two partial(...) values from distinct nested local functions should still be assignable when their
remaining callable signatures match:
from functools import partial
def outer(x, y):
def left(a, b):
return a, x
def right(a, b):
return a, y
branches = [partial(left, 1)]
branches.append(partial(right, 2))
Binding a predicate parameter via keyword should still produce a unary predicate acceptable to
filter(...):
from functools import partial
def has_same_ip_version(addr_or_net: str, is_ipv6: bool) -> bool:
return is_ipv6
values = ["127.0.0.1", "::1"]
predicate = partial(has_same_ip_version, is_ipv6=False)
reveal_type(predicate) # revealed: partial[(addr_or_net: str, *, is_ipv6: bool = False) -> bool]
reveal_type(list(filter(predicate, values))) # revealed: list[str]
from functools import partial
from typing import Callable, Literal, Optional, cast, overload
@overload
def task(__fn: Callable[[], int]) -> int: ...
@overload
def task(
__fn: Literal[None] = None,
*,
retries: int = 0,
) -> Callable[[Callable[[], int]], int]: ...
@overload
def task(
*,
retries: int = 0,
) -> Callable[[Callable[[], int]], int]: ...
def task(
__fn: Optional[Callable[[], int]] = None,
*,
retries: Optional[int] = None,
):
if __fn:
return 1
return cast(
Callable[[Callable[[], int]], int],
partial(task, retries=retries), # error: [invalid-argument-type]
)
Binding the first explicit parameter of a bound classmethod callback should preserve assignability
for ReferenceType[Self] arguments:
from functools import partial
from typing import Any, Generic, TypeVar
from weakref import ReferenceType, ref
T = TypeVar("T")
class CallbackHost(Generic[T]):
@classmethod
def callback(cls, wself: ReferenceType["CallbackHost[Any]"], x: int) -> None: ...
def __init__(self) -> None:
p = partial(self.callback, ref(self)) # error: [invalid-argument-type]
# TODO: should accept `ReferenceType[Self]` here and preserve the reduced signature.
reveal_type(p) # revealed: partial[(x: int) -> None]
A partial result is assignable to a Protocol with a matching __call__ signature. Required
keyword-only parameters can be bound away, and extra keyword-only parameters with defaults in the
resulting partial are allowed, since they don't need to be provided by the caller:
from functools import partial
from typing import Protocol
class Request: ...
class Response: ...
class Context: ...
class Handler(Protocol):
def __call__(
self,
request: Request,
*,
header: str | None = None,
) -> Response: ...
def handle(
request: Request,
*,
header: str | None = None,
verbose: bool = False,
context: Context,
) -> Response:
return Response()
handler: Handler = partial(handle, context=Context())
__call__ directly__call__ on a partial result should reflect the refined callable signature, not the broad
(*args: Any, **kwargs: Any) -> T from the partial class stub.
from functools import partial
def f(a: int, b: str) -> bool:
return True
p = partial(f, 1)
reveal_type(p.__call__) # revealed: (b: str) -> bool
reveal_type(p.__call__("hello")) # revealed: bool
Standard partial attributes like .func, .args, and .keywords should be accessible:
from functools import partial
from typing import Callable
def f(a: int, b: str) -> bool:
return True
p = partial(f, 1)
reveal_type(p.func) # revealed: def f(a: int, b: str) -> bool
reveal_type(p.func(2, "hello")) # revealed: bool
reveal_type(p.args) # revealed: tuple[Any, ...]
reveal_type(p.keywords) # revealed: dict[str, Any]
partial.func keeps the original callable typefrom functools import partial
from typing import TypeVar
T = TypeVar("T")
U = TypeVar("U")
def combine(a: T, b: U) -> tuple[T, U]:
return (a, b)
p = partial(combine, 1)
reveal_type(p.func(2, "x")) # revealed: tuple[Literal[2], Literal["x"]]
Attribute assignment should go through the standard nominal instance path:
from functools import partial
def f(a: int, b: str) -> bool:
return True
p = partial(f, 1)
p.func = f # error: [invalid-assignment]
We intentionally reject ad-hoc attributes on functools.partial results. This matches pyright and
mypy, even though these assignments work at runtime.
from functools import partial
def f() -> None:
pass
p = partial(f)
# error: [unresolved-attribute]
p.__name__ = "renamed"