def Node(*child_names): """Create a new Node class. You will typically use this when declaring a new class. For example: class Coordinate(Node("x", "y")): pass Arguments: *child_names: Names of the children of this node. Returns: A subclass of (named)tuple. """ precondition_pairs = [preconditions.parse_arg(x) for x in child_names] namedtuple_type = collections.namedtuple("_", (p[0] for p in precondition_pairs)) assert "__init__" not in namedtuple_type.__dict__ # see below class NamedTupleNode(namedtuple_type): """A Node class based on namedtuple.""" _CHECKER = preconditions.CallChecker(precondition_pairs) def __init__(self, *args, **kwargs): if _CHECK_PRECONDITIONS: self._CHECKER.check(*args, **kwargs) # We do *not* call super() here, for performance reasons. Neither # namedtuple (our base class) nor tuple (namedtuple's base class) do # anything in __init__, so it's safe to leave it out. def Validate(self): """Re-run precondition checks on the node's data.""" self._CHECKER.check(*self) def __eq__(self, other): """Compare two nodes for equality. This will return True if the two underlying tuples are the same *and* the two node types match. Arguments: other: The Node to compare this one with. Returns: True or False. """ # This comparison blows up if "other" is an old-style class (not an # instance). That's fine, because trying to compare a tuple to a class is # almost certainly a programming error, and blowing up is better than # silently returning False. if self is other: return True elif self.__class__ is other.__class__: return tuple.__eq__(self, other) else: return False # or NotImplemented def __ne__(self, other): """Compare two nodes for inequality. See __eq__.""" return not self == other def __lt__(self, other): """Smaller than other node? Define so we can to deterministic ordering.""" if self is other: return False elif self.__class__ is other.__class__: return tuple.__lt__(self, other) else: return self.__class__.__name__ < other.__class__.__name__ def __gt__(self, other): """Larger than other node? Define so we can to deterministic ordering.""" if self is other: return False elif self.__class__ is other.__class__: return tuple.__gt__(self, other) else: return self.__class__.__name__ > other.__class__.__name__ def __le__(self, other): return self == other or self < other def __ge__(self, other): return self == other or self > other def __repr__(self): """Returns this tuple converted to a string. We output this as <classname>(values...). This differs from raw tuple output in that we use the class name, not the name of the tuple this class extends. Also, Nodes with only one child will be output as Name(value), not Name(value,) to match the constructor syntax. Returns: Representation of this tuple as a string, including the class name. """ if len(self) == 1: return "%s(%r)" % (self.__class__.__name__, self[0]) else: return "%s%r" % (self.__class__.__name__, tuple(self)) # Expose namedtuple._replace as "Replace", so avoid lint warnings # and have consistent method names. Replace = namedtuple_type._replace # pylint: disable=no-member,invalid-name def Visit(self, visitor, *args, **kwargs): """Visitor interface for transforming a tree of nodes to a new tree. You can pass a visitor, and callback functions on that visitor will be called for all nodes in the tree. Note that nodes are also allowed to be stored in lists and as the values of dictionaries, as long as these lists/dictionaries are stored in the named fields of the Node class. It's possible to overload the Visit function on Nodes, to do your own processing. Arguments: visitor: An instance of a visitor for this tree. For every node type you want to transform, this visitor implements a "Visit<Classname>" function named after the class of the node this function should target. Note that <Classname> is the *actual* class of the node, so if you subclass a Node class, visitors for the superclasses will *not* be triggered anymore. Also, visitor callbacks are only triggered for subclasses of Node. *args: Passed to the visitor callback. **kwargs: Passed to the visitor callback. Returns: Transformed version of this node. """ # This function is overwritten below, so that we have the same im_func # even though we generate classes here. pass # COV_NF_LINE Visit = _VisitNode # pylint: disable=invalid-name return NamedTupleNode
def testParseArg(self): self.assertEquals(("x", None), preconditions.parse_arg("x")) name, cond = preconditions.parse_arg("foo: str") self.assertEquals("foo", name) self.assertClassName("str", cond)