roboflow/supervision · error · AttributeError

unreadable attribute

Error message

unreadable attribute

What it means

Raised by classproperty.__get__ in supervision.utils.internal when a classproperty is accessed but its fget getter is None. This mirrors Python's built-in property behavior for properties defined without a getter. In practice it signals a misconstructed classproperty, typically from subclassing or dynamically creating one without a getter function.

Source

Thrown at src/supervision/utils/internal.py:166

        Args:
            The function that is called when the property is accessed.
        """
        self.fget = fget

    def __get__(self, owner_self: Any, owner_cls: type | None = None) -> T:
        """
        Override the __get__ method to return the result of the function call.

        Args:
            owner_self: The instance through which the attribute was accessed, or None.
                Irrelevant for class properties.
            owner_cls: The class through which the attribute was accessed.

        Returns:
            The result of calling the function stored in 'fget' with 'owner_cls'.
        """
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(owner_cls)


def get_instance_variables(instance: Any, include_properties: bool = False) -> set[str]:
    """
    Get the public variables of a class instance.

    Args:
        instance: The instance of a class
        include_properties: Whether to include properties in the result

    Usage:
        ```pycon
        >>> from supervision.utils.internal import get_instance_variables
        >>> import numpy as np
        >>> from supervision import Detections
        >>> detections = Detections(xyxy=np.array([[1, 2, 3, 4]]))
        >>> variables = get_instance_variables(detections)

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Define the classproperty with a getter: @classproperty def name(cls): ...
  2. If you were trying to assign to a classproperty, replace the whole object with a new classproperty(getter) instead of mutating.
  3. Audit custom code that constructs classproperty(...) directly and ensure the callable is passed.

Example fix

// before
prop = classproperty(None)
class A: x = prop
A.x  # AttributeError

// after
class A:
    @classproperty
    def x(cls):
        return 42
A.x  # 42
Defensive patterns

Strategy: type-guard

Validate before calling

if isinstance(obj, classproperty) and obj.fget is None:
    raise AttributeError('classproperty defined without getter')

Type guard

def has_getter(prop: classproperty) -> bool:
    return callable(getattr(prop, 'fget', None))

Try / catch

try:
    value = MyClass.some_prop
except AttributeError as e:
    if 'unreadable attribute' in str(e):
        logger.error('MyClass.some_prop is a classproperty without fget')
    raise

Prevention

When it happens

Trigger: Accessing SomeClass.some_classproperty where the classproperty was created with fget=None — e.g. manually assigning classproperty(None), or a decorator pipeline that dropped the getter. Reading the attribute on the class or any instance triggers it.

Common situations: Advanced metaprogramming that wraps or clones classproperty objects; copy-pasting the classproperty implementation and omitting the getter; trying to set a classproperty via attribute assignment (setter is not supported, leaving a broken object).


AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15). Data as JSON: /api/errors/d40f35cb329e7026. Report an issue: GitHub.