ruff/crates/ty_python_semantic/resources/mdtest/call/getattr_static.md
2025-05-03 19:49:15 +02:00

4.3 KiB

inspect.getattr_static

Basic usage

inspect.getattr_static is a function that returns attributes of an object without invoking the descriptor protocol (for caveats, see the official documentation).

Consider the following example:

import inspect

class Descriptor:
    def __get__(self, instance, owner) -> str:
        return "a"

class C:
    normal: int = 1
    descriptor: Descriptor = Descriptor()

If we access attributes on an instance of C as usual, the descriptor protocol is invoked, and we get a type of str for the descriptor attribute:

c = C()

reveal_type(c.normal)  # revealed: int
reveal_type(c.descriptor)  # revealed: str

However, if we use inspect.getattr_static, we can see the underlying Descriptor type:

reveal_type(inspect.getattr_static(c, "normal"))  # revealed: int
reveal_type(inspect.getattr_static(c, "descriptor"))  # revealed: Descriptor

For non-existent attributes, a default value can be provided:

reveal_type(inspect.getattr_static(C, "normal", "default-arg"))  # revealed: int
reveal_type(inspect.getattr_static(C, "non_existent", "default-arg"))  # revealed: Literal["default-arg"]

When a non-existent attribute is accessed without a default value, the runtime raises an AttributeError. We could emit a diagnostic for this case, but that is currently not supported:

# TODO: we could emit a diagnostic here
reveal_type(inspect.getattr_static(C, "non_existent"))  # revealed: Never

We can access attributes on objects of all kinds:

import sys

reveal_type(inspect.getattr_static(sys, "dont_write_bytecode"))  # revealed: bool
# revealed: def getattr_static(obj: object, attr: str, default: Any | None = ellipsis) -> Any
reveal_type(inspect.getattr_static(inspect, "getattr_static"))

reveal_type(inspect.getattr_static(1, "real"))  # revealed: property

(Implicit) instance attributes can also be accessed through inspect.getattr_static:

class D:
    def __init__(self) -> None:
        self.instance_attr: int = 1

reveal_type(inspect.getattr_static(D(), "instance_attr"))  # revealed: int

And attributes on metaclasses can be accessed when probing the class:

class Meta(type):
    attr: int = 1

class E(metaclass=Meta): ...

reveal_type(inspect.getattr_static(E, "attr"))  # revealed: int

Metaclass attributes can not be added when probing an instance of the class:

reveal_type(inspect.getattr_static(E(), "attr", "non_existent"))  # revealed: Literal["non_existent"]

Error cases

We can only infer precise types if the attribute is a literal string. In all other cases, we fall back to Any:

import inspect

class C:
    x: int = 1

def _(attr_name: str):
    reveal_type(inspect.getattr_static(C(), attr_name))  # revealed: Any
    reveal_type(inspect.getattr_static(C(), attr_name, 1))  # revealed: Any

But we still detect errors in the number or type of arguments:

# error: [missing-argument] "No arguments provided for required parameters `obj`, `attr` of function `getattr_static`"
inspect.getattr_static()

# error: [missing-argument] "No argument provided for required parameter `attr`"
inspect.getattr_static(C())

# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `str`, found `Literal[1]`"
inspect.getattr_static(C(), 1)

# error: [too-many-positional-arguments] "Too many positional arguments to function `getattr_static`: expected 3, got 4"
inspect.getattr_static(C(), "x", "default-arg", "one too many")

Possibly unbound attributes

import inspect

def _(flag: bool):
    class C:
        if flag:
            x: int = 1

    reveal_type(inspect.getattr_static(C, "x", "default"))  # revealed: int | Literal["default"]

Gradual types

import inspect
from typing import Any

def _(a: Any, tuple_of_any: tuple[Any]):
    reveal_type(inspect.getattr_static(a, "x", "default"))  # revealed: Any | Literal["default"]

    # TODO: Ideally, this would just be `def index(self, value: Any, start: SupportsIndex = Literal[0], stop: SupportsIndex = int, /) -> int`
    # revealed: (def index(self, value: Any, start: SupportsIndex = Literal[0], stop: SupportsIndex = int, /) -> int) | Literal["default"]
    reveal_type(inspect.getattr_static(tuple_of_any, "index", "default"))