Problem 612062 · medium · Level 06 Heuristics & Optimization

Event Records That Know Their Own Fields

py-slots · py-metaprogramming · py-inheritance · py-dunder · __init_subclass__

A user-interface recorder stores millions of small events (clicks, key presses, scrolls), so every event class uses __slots__. Writing __init__, __repr__ and __eq__ for each class by hand is repetitive, and a subclass that forgets its own __slots__ quietly gets a __dict__ again. Write a base class Record that does all of this once:

class Event(Record):
    __slots__ = ("time",)

class Click(Event):
    __slots__ = ("x", "y")
  • Record itself has __slots__ = (). Defining a subclass whose class body has no __slots__ raises TypeError (at the moment the class is defined).
  • fields() (a class method) returns a tuple of all field names of the class: those of the class closest to Record first, and within a class in the order written. A __slots__ written as a single string is one field.
  • __init__ accepts the fields as positional arguments (in field order) and keyword arguments. Too many positional arguments, an unknown keyword, a field given twice or a missing field raises TypeError.
  • repr(record) looks like Click(time=3, x=10, y=20): the class name and every field as name=repr(value).
  • Two records are equal when they are of the same class and all their fields are equal.
  • replace(**changes) returns a new record of the same class with some fields changed (an unknown field raises TypeError).

The tests define the event classes on your Record inside setup helpers you can call with Run: event_classes(), basics(), bad_calls(), forgot_slots(), replaced() and event_log(n, seed). outcome(call) returns a result or the exception's name.

Examples

Input:  basics()
Output: (("time", "x", "y"), ("time", "amount"), "Click(time=3, x=10, y=20)", "Key(time=4, key='a', shift=False)", "Scroll(time=9, amount=-2.5)", True, False, False, False, "AttributeError")

Input:  forgot_slots()
Output: ("TypeError", "Hover(time=1)")

Constraints

  • Field values are plain Python values; records are compared only with ==.

Goals

  • Use `__slots__` across a class hierarchy, knowing that every subclass needs its own
  • Collect the fields of a class from the `__slots__` of every class in its method resolution order
  • Write a generic `__init__`, `__repr__` and `__eq__` once, in a base class, for every record type
Starting Python…