Source code for scenic.core.lazy_eval


"""Support for lazy evaluation of expressions and specifiers."""

import itertools

[docs]class LazilyEvaluable: """Values which may require evaluation in the context of an object being constructed. If a LazilyEvaluable specifies any properties it depends on, then it cannot be evaluated to a normal value except during the construction of an object which already has values for those properties. """ def __init__(self, requiredProps): self._dependencies = () # TODO improve? self._requiredProperties = set(requiredProps)
[docs] def evaluateIn(self, context): """Evaluate this value in the context of an object being constructed. The object must define all of the properties on which this value depends. """ assert all(hasattr(context, prop) for prop in self._requiredProperties) value = self.evaluateInner(context) assert not needsLazyEvaluation(value) # value should not require further evaluation return value
[docs] def evaluateInner(self, context): """Actually evaluate in the given context, which provides all required properties.""" return self
[docs]class DelayedArgument(LazilyEvaluable): """Specifier arguments requiring other properties to be evaluated first. The value of a DelayedArgument is given by a function mapping the context (object under construction) to a value. """ def __init__(self, requiredProps, value): self.value = value super().__init__(requiredProps) def evaluateInner(self, context): return self.value(context) def __getattr__(self, name): return DelayedArgument(self._requiredProperties, lambda context: getattr(self.evaluateIn(context), name)) def __call__(self, *args, **kwargs): dargs = [toDelayedArgument(arg) for arg in args] kwdargs = { name: toDelayedArgument(arg) for name, arg in kwargs.items() } subprops = (darg._requiredProperties for darg in itertools.chain(dargs, kwdargs.values())) props = self._requiredProperties.union(*subprops) def value(context): subvalues = (darg.evaluateIn(context) for darg in dargs) kwsvs = { name: darg.evaluateIn(context) for name, darg in kwdargs.items() } return self.evaluateIn(context)(*subvalues, **kwsvs) return DelayedArgument(props, value)
# Operators which can be applied to DelayedArguments allowedOperators = [ '__neg__', '__pos__', '__abs__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__add__', '__radd__', '__sub__', '__rsub__', '__mul__', '__rmul__', '__truediv__', '__rtruediv__', '__floordiv__', '__rfloordiv__', '__mod__', '__rmod__', '__divmod__', '__rdivmod__', '__pow__', '__rpow__', '__round__', '__len__', '__getitem__' ] def makeDelayedOperatorHandler(op): def handler(self, *args): dargs = [toDelayedArgument(arg) for arg in args] props = self._requiredProperties.union(*(darg._requiredProperties for darg in dargs)) def value(context): subvalues = (darg.evaluateIn(context) for darg in dargs) return getattr(self.evaluateIn(context), op)(*subvalues) return DelayedArgument(props, value) return handler for op in allowedOperators: setattr(DelayedArgument, op, makeDelayedOperatorHandler(op))
[docs]def makeDelayedFunctionCall(func, args, kwargs): """Utility function for creating a lazily-evaluated function call.""" dargs = [toDelayedArgument(arg) for arg in args] kwdargs = { name: toDelayedArgument(arg) for name, arg in kwargs.items() } props = set().union(*(darg._requiredProperties for darg in itertools.chain(dargs, kwdargs.values()))) def value(context): subvalues = (darg.evaluateIn(context) for darg in dargs) kwsubvals = { name: darg.evaluateIn(context) for name, darg in kwdargs.items() } return func(*subvalues, **kwsubvals) return DelayedArgument(props, value)
[docs]def valueInContext(value, context): """Evaluate something in the context of an object being constructed.""" try: return value.evaluateIn(context) except AttributeError: return value
def toDelayedArgument(thing): if isinstance(thing, DelayedArgument): return thing return DelayedArgument(set(), lambda context: thing) def requiredProperties(thing): if hasattr(thing, '_requiredProperties'): return thing._requiredProperties return set() def needsLazyEvaluation(thing): return isinstance(thing, DelayedArgument) or requiredProperties(thing)