2

I have a Python class with special values, "EMPTY" and "UNIVERSE":

class RealSet:
    """Continuous open, half-open, and closed regions and discreet values of the Reals"""

    # implementation placeholder
    def __init__(self, intervals, *, canonicalize):
        pass

# Outside the class

RealSet.EMPTY = RealSet(tuple(), canonicalize=False)  # type: ignore
RealSet.UNIVERSE = RealSet(((None, None),), canonicalize=False)  # type: ignore

However, linting, code-completion, etc. don't like this because they're not seen as static attributes of the class. Even setting them is reported as a mypy error, hence the # type: ignore.

The below doesn't work because I can't construct a RealSet in the class scope because it doesn't exist yet:

class RealSet:
    """Continuous open, half-open, and closed regions and discreet values of the Reals"""
    ...
    ...

    EMPTY = RealSet(tuple(), canonicalize=False)  # error
    UNIVERSE = RealSet(((None, None),), canonicalize=False)  # error

And this doesn't work because it defines instance attributes, not class attributes:

class RealSet:
    """Continuous open, half-open, and closed regions and discreet values of the Reals"""
    ...
    ...

    EMPTY: "RealSet"
    UNIVERSE: "RealSet"

# Outside the class

RealSet.EMPTY = RealSet(tuple(), canonicalize=False)
RealSet.UNIVERSE = RealSet(((None, None),), canonicalize=False)

This seems to be a corner case in the design of Python classes. How can I make class attributes where the attribute's type is the class in which it resides? Bonus: make them constant.

Brent
  • 3,489
  • 3
  • 22
  • 55

2 Answers2

2

You can use typing.ClassVar to annotate class variables:

class RealSet:
    def __init__(self, intervals, *, canonicalize):
        pass

    EMPTY: ClassVar['RealSet']
    UNIVERSE: ClassVar['RealSet']


RealSet.EMPTY = RealSet(tuple(), canonicalize=False)
RealSet.UNIVERSE = RealSet(((None, None),), canonicalize=False)
a_guest
  • 25,051
  • 7
  • 38
  • 80
2

Starting with Python 3.9 classmethod can decorate other descriptors such as property. This way one can create a "classproperty":

class RealSet:
    def __init__(self, intervals, *, canonicalize):
        pass

    @classmethod
    @property
    def EMPTY(cls):
        return cls(tuple(), canonicalize=False)

    @classmethod
    @property
    def UNIVERSE(cls):
        return cls(((None, None),), canonicalize=False)
a_guest
  • 25,051
  • 7
  • 38
  • 80