Skip to content
Advertisement

__slots__ type annotations for Python / PyCharm

How can I provide type annotations for attributes which are defined via __slots__? E.g. for this class:

class DiffMatch:
  __slots__ = ["ref_seq_idx", "ref_txt", "hyp_txt", "start_time", "end_time"]

  def __repr__(self):
    return "%s(%s)" % (self.__class__.__name__, ", ".join(["%s=%r" % (a, getattr(self, a)) for a in self.__slots__]))

(Python 2 compatible in a comment if possible (otherwise Python 3 only is fine), and also such that PyCharm can handle it (actually that is most important for me now).)

Advertisement

Answer

__slots__ only tells the type() object to make space for potential attributes. The names in the list are not themselves attributes. All you have is a bunch of descriptors. There are no values, so no types.

So you need to make actual attributes, and those have all the regular options for type annotations.

For Python 3.6 and up, use variable type annotations:

class DiffMatch:
    __slots__ = ["ref_seq_idx", "ref_txt", "hyp_txt", "start_time", "end_time"]

    ref_seq_idx: int
    # ...

or for versions before 3.6 (including Python 2), you have to annotate the types on a method that can set the attributes. There is no real instance attribute support otherwise. You can add a dummy method for this:

class DiffMatch:
    __slots__ = ["ref_seq_idx", "ref_txt", "hyp_txt", "start_time", "end_time"]

    def __type_hints__(self, ref_seq_idx, ...):
        """Dummy method to annotate the instance attribute types
        # type: (int, ...) -> None
        """
        self.ref_seq_idx = ref_seq_idx
        # ...

where all the types for each of the arguments are listed in the docstring. If you have a __init__ method for your class that also touches all the attributes, then a dummy method is not needed.

Note that you can’t set class default values for these, which means that you can’t use ref_seq_idx = None # type: int (pre-Python 3.6) or ref_seq_idx: int = None (Python 3.6 and up); __slots__ names are converted to descriptor objects on the class, so the names are already set.

Last but not least, I would take a serious look at the attrs library to build these types for you. Support for this library has recently been added to PyCharm 2018.2, so type information is automatically picked up:

@attr.s(slots=True)
class DiffMatch:
    ref_seq_idx = attr.ib(init=False)  # type: int
    # ...

and you’ll get your __repr__ method generated for free. The init=False annotation tells attrs not to include that name in the __init__, at which point instances won’t have that attribute set at all on instantiation.

Advertisement