**kwargs and *args backtracing¶
stubpy’s signature feature is resolving **kwargs and typed *args
into explicit parameter lists by walking the MRO.
Single-level **kwargs¶
class Shape:
def __init__(self, color: str = "black", opacity: float = 1.0) -> None: ...
class Circle(Shape):
def __init__(self, radius: float, **kwargs) -> None:
super().__init__(**kwargs)
class Shape:
def __init__(self, color: str = 'black', opacity: float = 1.0) -> None: ...
class Circle(Shape):
def __init__(
self,
radius: float,
color: str = 'black',
opacity: float = 1.0,
) -> None: ...
Multi-level (four-deep)¶
class A:
def __init__(self, name: str, legs: int, wild: bool = True) -> None: ...
class B(A):
def __init__(self, owner: str, **kwargs): super().__init__(**kwargs)
class C(B):
def __init__(self, breed: str, **kwargs): super().__init__(**kwargs)
class D(C):
def __init__(self, job: str, **kwargs): super().__init__(**kwargs)
class D(C):
def __init__(
self,
job: str,
breed: str,
owner: str,
name: str,
legs: int,
wild: bool = True,
) -> None: ...
Typed *args preserved¶
*args that carry an explicit annotation survive because they represent
a typed variadic — not a blind passthrough:
class Container:
def __init__(self, *elements: "Element", label: str = "", **kwargs):
super().__init__(**kwargs)
class Container(Element):
def __init__(
self,
*elements: Element,
label: str = '',
id: Optional[str] = None,
opacity: float = 1.0,
) -> None: ...
*args and **kwargs together¶
When a method has both *args and **kwargs, both are preserved.
*args always appears before **kwargs and before any keyword-only
parameters:
from typing import Any
class Case1:
def __init__(self, x: int, *args: str, flag: bool = False, **kwargs: Any) -> None: ...
class Case1:
def __init__(
self,
x: int,
*args: str,
flag: bool = False,
**kwargs: Any,
) -> None: ...
*args in child, **kwargs resolved from parent¶
When a child has typed *args and its **kwargs resolves from the
parent, *args is placed after the resolved concrete params:
class ParentA:
def __init__(self, color: str = "black", size: int = 10) -> None: ...
class ChildA(ParentA):
def __init__(self, *args: str, **kwargs) -> None:
super().__init__(**kwargs)
class ChildA(ParentA):
def __init__(
self,
color: str = 'black',
size: int = 10,
*args: str,
) -> None: ...
*args in child, open **kwargs from parent¶
When the parent’s **kwargs cannot be fully resolved, *args is
placed before the residual **kwargs:
class ParentB:
def __init__(self, label: str, **kwargs) -> None: ...
class ChildB(ParentB):
def __init__(self, *items: int, **kwargs) -> None:
super().__init__(**kwargs)
class ChildB(ParentB):
def __init__(
self,
label: str,
*items: int,
**kwargs,
) -> None: ...
Open **kwargs preserved¶
If the chain cannot be fully resolved (the root ancestor also has
**kwargs), the remaining **kwargs is kept in the stub so the
signature stays correct:
class Widget:
def __init__(self, label: str, **kwargs) -> None: ...
class Widget:
def __init__(self, label: str, **kwargs) -> None: ...
@classmethod with cls(**kwargs)¶
When a classmethod forwards **kwargs directly into cls(...),
stubpy detects this via AST analysis and resolves the kwargs against
cls.__init__ rather than walking the MRO for the same method name:
class Circle(Shape):
def __init__(self, cx: float, cy: float, r: float, **kwargs): ...
@classmethod
def unit(cls, **kwargs) -> "Circle":
return cls(r=1, cx=0, cy=0, **kwargs)
@classmethod
def at_origin(cls, r: float = 50, **kwargs) -> "Circle":
return cls(r=r, cx=0, cy=0, **kwargs)
class Circle(Shape):
@classmethod
def unit(
cls,
fill: types.Color = 'black',
opacity: float = 1.0,
...
) -> Circle: ...
@classmethod
def at_origin(
cls,
r: float = 50,
fill: types.Color = 'black',
opacity: float = 1.0,
...
) -> Circle: ...
unit:r,cx,cyare hardcoded incls(r=1, cx=0, cy=0, ...)→ excluded.at_origin:ris an explicit own parameter → appears in the stub.cxandcyare hardcoded → excluded.