Search code examples
pythonpython-3.8python-class

Making A Class Constant Who's Type Is The Class In Which It Resides


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.


Solution

  • 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)