def conduct(self): ''' Run control & candidate functions and return the control's return value. ``control()`` must be called first. :raise LaboratoryException: when no control case has been set :return: Control function's return value ''' if self._control is None: raise exceptions.LaboratoryException( 'Your experiment must contain a control case' ) # execute control and exit if experiment is not enabled if not self.enabled(): control = self._run_tested_func(raise_on_exception=True, **self._control) return control.value # otherwise, let's wrap an executor around all of our functions and randomise the ordering def get_func_executor(obs_def, is_control): """A lightweight wrapper around a tested function in order to retrieve state""" return lambda *a, **kw: (self._run_tested_func(raise_on_exception=is_control, **obs_def), is_control) control = self._run_tested_func(raise_on_exception=True, **self._control) funcs = [get_func_executor(cand, is_control=False,) for cand in self._candidates] thr = threading.Thread(target=self.run_candidates, args=(control, funcs), kwargs={}) thr.start() return control.value
def control(self, control_func, args=None, kwargs=None, name='Control', context=None): ''' Set the experiment's control function. Must be set before ``conduct()`` is called. :param callable control_func: your control function :param iterable args: positional arguments to pass to your function :param dict kwargs: keyword arguments to pass to your function :param string name: a name for your observation :param dict context: observation-specific context :raises LaboratoryException: If attempting to set a second control case ''' if self._control is not None: raise exceptions.LaboratoryException( 'You have already established a control case') self._control = { 'func': control_func, 'args': args or [], 'kwargs': kwargs or {}, 'name': name, 'context': context or {}, }
def conduct(self, randomize=True): ''' Run control & candidate functions and return the control's return value. ``control()`` must be called first. :param bool randomize: controls whether we shuffle the order of execution between control and candidate :raise LaboratoryException: when no control case has been set :return: Control function's return value ''' if self._control is None: raise exceptions.LaboratoryException( 'Your experiment must contain a control case') # execute control and exit if experiment is not enabled if not self.enabled(): control = self._run_tested_func(raise_on_exception=True, **self._control) return control.value # otherwise, let's wrap an executor around all of our functions and randomise the ordering def get_func_executor(obs_def, is_control): """A lightweight wrapper around a tested function in order to retrieve state""" return lambda *a, **kw: (self._run_tested_func( raise_on_exception=is_control, **obs_def), is_control) funcs = [ get_func_executor(self._control, is_control=True), ] + [ get_func_executor( cand, is_control=False, ) for cand in self._candidates ] if randomize: random.shuffle(funcs) control = None candidates = [] # go through the randomised list and execute the functions for func in funcs: observation, is_control = func() if is_control: control = observation else: candidates.append(observation) result = Result(self, control, candidates) try: self.publish(result) except Exception: msg = 'Exception occured when publishing %s experiment data' logger.exception(msg % self.name) return control.value
def run(self): if self._control is None: raise exceptions.LaboratoryException( 'Your experiment must record a control case') result = Result(self, self._control, self._observations) try: self.publish(result) except Exception as e: msg = 'Exception occured when publishing %s experiment data' logger.exception(msg % self.name) return self._control.value