Skip to content

consider descriptor pattern for GraphicFeature. #389

@tlambert03

Description

@tlambert03

While playing around with some examples, I saw this type error reported by mypy:

Screenshot 2023-12-04 at 12 01 06 PM

I dug into a bit and learned about the Graphic/GraphicFeature relationship. Apologies if you already know this and specifically decided against it... but the general pattern of an abstract _set method on GraphicFeature objects that retain a pointer to some parent object looks very much like it could be replaced/improved with python descriptors.

For example, the current pattern (highly simplified) is:

class GraphicFeature:
    def __init__(self, parent):
        self._parent = weakref.proxy(parent)

    def _set(self, value):
        raise NotImplementedError


class SomeFeature(GraphicFeature):
    def _set(self, value):
        print(f"setting {self.name!r} on {self._parent} to {value!r}")


class Graphic:
    def __setattr__(self, key, value):
        if hasattr(self, key):
            attr = getattr(self, key)
            if isinstance(attr, GraphicFeature):
                attr._set(value)
                return

        super().__setattr__(key, value)


class SomeGraphic(Graphic):
    def __init__(self) -> None:
        self.feature = SomeFeature()

with the descriptor pattern, that all reduces to

class SomeFeature:
    def __get__(self, instance, owner):
        ...

    def __set__(self, obj, value):
        print(f"setting feature on {obj} to {value!r}")


class SomeGraphic:
    feature = SomeFeature()

and when you set the feature attribute on an instance of SomeGraphic, then SomeFeature.__set__ will be called with the instance and value:

In [18]: g = SomeGraphic()

In [19]: g.feature = 1
setting feature on <__main__.SomeGraphic object at 0x106c33750> to 1

this also works very nicely for typing:

Screenshot 2023-12-04 at 12 11 15 PM

note that it neither complains when setting .feature = [1,2,3], and it also recognizes that accessing .feature will return a str (i.e. it's been coerced from list to some internal value), based on the type hints of __set__ and __get__

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions