prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Fitter preprocessors
  <!--
  This is an issue template. Please fill in the relevant details in the
  sections below.
  -->
  
  ##### Orange version
  <!-- From menu _Help→About→Version_ or code `Orange.version.full_version` -->
  3.4.0
  
  ##### Expected behavior
  Learners use preprocessors.
  
  
  ##### Actual behavior
  Learners extending the Fitter base class do not use preprocessors.
  
  
  ##### Steps to reproduce the behavior
  Use a learner on e.g. hearth_disease data set
  
  
  ##### Additional info (worksheets, data, screenshots, ...)
  
  
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `Orange/modelling/base.py`
  Content:
  ```
  1 from Orange.base import Learner, Model
  2 
  3 
  4 class FitterMeta(type):
  5     """Ensure that each subclass of the `Fitter` class overrides the `__fits__`
  6     attribute with a valid value."""
  7     def __new__(mcs, name, bases, attrs):
  8         # Check that a fitter implementation defines a valid `__fits__`
  9         if any(cls.__name__ == 'Fitter' for cls in bases):
  10             fits = attrs.get('__fits__')
  11             assert isinstance(fits, dict), '__fits__ must be dict instance'
  12             assert fits.get('classification') and fits.get('regression'), \
  13                 ('`__fits__` property does not define classification '
  14                  'or regression learner. Use a simple learner if you don\'t '
  15                  'need the functionality provided by Fitter.')
  16         return super().__new__(mcs, name, bases, attrs)
  17 
  18 
  19 class Fitter(Learner, metaclass=FitterMeta):
  20     """Handle multiple types of target variable with one learner.
  21 
  22     Subclasses of this class serve as a sort of dispatcher. When subclassing,
  23     we provide a `dict` which contain actual learner classes that handle
  24     appropriate data types. The fitter can then be used on any data and will
  25     delegate the work to the appropriate learner.
  26 
  27     If the learners that handle each data type require different parameters,
  28     you should pass in all the possible parameters to the fitter. The fitter
  29     will then determine which parameters have to be passed to individual
  30     learners.
  31 
  32     """
  33     __fits__ = None
  34     __returns__ = Model
  35 
  36     # Constants to indicate what kind of problem we're dealing with
  37     CLASSIFICATION, REGRESSION = 'classification', 'regression'
  38 
  39     def __init__(self, preprocessors=None, **kwargs):
  40         super().__init__(preprocessors=preprocessors)
  41         self.kwargs = kwargs
  42         # Make sure to pass preprocessor params to individual learners
  43         self.kwargs['preprocessors'] = preprocessors
  44         self.__learners = {self.CLASSIFICATION: None, self.REGRESSION: None}
  45 
  46     def _fit_model(self, data):
  47         if data.domain.has_discrete_class:
  48             learner = self.get_learner(self.CLASSIFICATION)
  49         else:
  50             learner = self.get_learner(self.REGRESSION)
  51 
  52         if type(self).fit is Learner.fit:
  53             return learner.fit_storage(data)
  54         else:
  55             X, Y, W = data.X, data.Y, data.W if data.has_weights() else None
  56             return learner.fit(X, Y, W)
  57 
  58     def get_learner(self, problem_type):
  59         """Get the learner for a given problem type.
  60 
  61         Returns
  62         -------
  63         Learner
  64             The appropriate learner for the given problem type.
  65 
  66         """
  67         # Prevent trying to access the learner when problem type is None
  68         if problem_type not in self.__fits__:
  69             raise TypeError("No learner to handle '{}'".format(problem_type))
  70         if self.__learners[problem_type] is None:
  71             learner = self.__fits__[problem_type](**self.__kwargs(problem_type))
  72             learner.use_default_preprocessors = self.use_default_preprocessors
  73             self.__learners[problem_type] = learner
  74         return self.__learners[problem_type]
  75 
  76     def __kwargs(self, problem_type):
  77         learner_kwargs = set(
  78             self.__fits__[problem_type].__init__.__code__.co_varnames[1:])
  79         changed_kwargs = self._change_kwargs(self.kwargs, problem_type)
  80         return {k: v for k, v in changed_kwargs.items() if k in learner_kwargs}
  81 
  82     def _change_kwargs(self, kwargs, problem_type):
  83         """Handle the kwargs to be passed to the learner before they are used.
  84 
  85         In some cases we need to manipulate the kwargs that will be passed to
  86         the learner, e.g. SGD takes a `loss` parameter in both the regression
  87         and classification learners, but the learner widget cannot
  88         differentiate between these two, so it passes classification and
  89         regression loss parameters individually. The appropriate one must be
  90         renamed into `loss` before passed to the actual learner instance. This
  91         is done here.
  92 
  93         """
  94         return kwargs
  95 
  96     @property
  97     def supports_weights(self):
  98         """The fitter supports weights if both the classification and
  99         regression learners support weights."""
  100         return (
  101             hasattr(self.get_learner(self.CLASSIFICATION), 'supports_weights')
  102             and self.get_learner(self.CLASSIFICATION).supports_weights) and (
  103             hasattr(self.get_learner(self.REGRESSION), 'supports_weights')
  104             and self.get_learner(self.REGRESSION).supports_weights)
  105 
  106     @property
  107     def params(self):
  108         raise TypeError(
  109             'A fitter does not have its own params. If you need to access '
  110             'learner params, please use the `get_params` method.')
  111 
  112     def get_params(self, problem_type):
  113         """Access the specific learner params of a given learner."""
  114         return self.get_learner(problem_type).params
  115
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/Orange/modelling/base.py b/Orange/modelling/base.py
  --- a/Orange/modelling/base.py
  +++ b/Orange/modelling/base.py
  @@ -55,6 +55,12 @@
               X, Y, W = data.X, data.Y, data.W if data.has_weights() else None
               return learner.fit(X, Y, W)
   
  +    def preprocess(self, data):
  +        if data.domain.has_discrete_class:
  +            return self.get_learner(self.CLASSIFICATION).preprocess(data)
  +        else:
  +            return self.get_learner(self.REGRESSION).preprocess(data)
  +
       def get_learner(self, problem_type):
           """Get the learner for a given problem type.
  
verification_info: {"golden_diff": "diff --git a/Orange/modelling/base.py b/Orange/modelling/base.py\n--- a/Orange/modelling/base.py\n+++ b/Orange/modelling/base.py\n@@ -55,6 +55,12 @@\n             X, Y, W = data.X, data.Y, data.W if data.has_weights() else None\n             return learner.fit(X, Y, W)\n \n+    def preprocess(self, data):\n+        if data.domain.has_discrete_class:\n+            return self.get_learner(self.CLASSIFICATION).preprocess(data)\n+        else:\n+            return self.get_learner(self.REGRESSION).preprocess(data)\n+\n     def get_learner(self, problem_type):\n         \"\"\"Get the learner for a given problem type.\n", "issue": "Fitter preprocessors\n<!--\r\nThis is an issue template. Please fill in the relevant details in the\r\nsections below.\r\n-->\r\n\r\n##### Orange version\r\n<!-- From menu _Help\u2192About\u2192Version_ or code `Orange.version.full_version` -->\r\n3.4.0\r\n\r\n##### Expected behavior\r\nLearners use preprocessors.\r\n\r\n\r\n##### Actual behavior\r\nLearners extending the Fitter base class do not use preprocessors.\r\n\r\n\r\n##### Steps to reproduce the behavior\r\nUse a learner on e.g. hearth_disease data set\r\n\r\n\r\n##### Additional info (worksheets, data, screenshots, ...)\r\n\r\n\r\n\n", "before_files": [{"content": "from Orange.base import Learner, Model\n\n\nclass FitterMeta(type):\n    \"\"\"Ensure that each subclass of the `Fitter` class overrides the `__fits__`\n    attribute with a valid value.\"\"\"\n    def __new__(mcs, name, bases, attrs):\n        # Check that a fitter implementation defines a valid `__fits__`\n        if any(cls.__name__ == 'Fitter' for cls in bases):\n            fits = attrs.get('__fits__')\n            assert isinstance(fits, dict), '__fits__ must be dict instance'\n            assert fits.get('classification') and fits.get('regression'), \\\n                ('`__fits__` property does not define classification '\n                 'or regression learner. Use a simple learner if you don\\'t '\n                 'need the functionality provided by Fitter.')\n        return super().__new__(mcs, name, bases, attrs)\n\n\nclass Fitter(Learner, metaclass=FitterMeta):\n    \"\"\"Handle multiple types of target variable with one learner.\n\n    Subclasses of this class serve as a sort of dispatcher. When subclassing,\n    we provide a `dict` which contain actual learner classes that handle\n    appropriate data types. The fitter can then be used on any data and will\n    delegate the work to the appropriate learner.\n\n    If the learners that handle each data type require different parameters,\n    you should pass in all the possible parameters to the fitter. The fitter\n    will then determine which parameters have to be passed to individual\n    learners.\n\n    \"\"\"\n    __fits__ = None\n    __returns__ = Model\n\n    # Constants to indicate what kind of problem we're dealing with\n    CLASSIFICATION, REGRESSION = 'classification', 'regression'\n\n    def __init__(self, preprocessors=None, **kwargs):\n        super().__init__(preprocessors=preprocessors)\n        self.kwargs = kwargs\n        # Make sure to pass preprocessor params to individual learners\n        self.kwargs['preprocessors'] = preprocessors\n        self.__learners = {self.CLASSIFICATION: None, self.REGRESSION: None}\n\n    def _fit_model(self, data):\n        if data.domain.has_discrete_class:\n            learner = self.get_learner(self.CLASSIFICATION)\n        else:\n            learner = self.get_learner(self.REGRESSION)\n\n        if type(self).fit is Learner.fit:\n            return learner.fit_storage(data)\n        else:\n            X, Y, W = data.X, data.Y, data.W if data.has_weights() else None\n            return learner.fit(X, Y, W)\n\n    def get_learner(self, problem_type):\n        \"\"\"Get the learner for a given problem type.\n\n        Returns\n        -------\n        Learner\n            The appropriate learner for the given problem type.\n\n        \"\"\"\n        # Prevent trying to access the learner when problem type is None\n        if problem_type not in self.__fits__:\n            raise TypeError(\"No learner to handle '{}'\".format(problem_type))\n        if self.__learners[problem_type] is None:\n            learner = self.__fits__[problem_type](**self.__kwargs(problem_type))\n            learner.use_default_preprocessors = self.use_default_preprocessors\n            self.__learners[problem_type] = learner\n        return self.__learners[problem_type]\n\n    def __kwargs(self, problem_type):\n        learner_kwargs = set(\n            self.__fits__[problem_type].__init__.__code__.co_varnames[1:])\n        changed_kwargs = self._change_kwargs(self.kwargs, problem_type)\n        return {k: v for k, v in changed_kwargs.items() if k in learner_kwargs}\n\n    def _change_kwargs(self, kwargs, problem_type):\n        \"\"\"Handle the kwargs to be passed to the learner before they are used.\n\n        In some cases we need to manipulate the kwargs that will be passed to\n        the learner, e.g. SGD takes a `loss` parameter in both the regression\n        and classification learners, but the learner widget cannot\n        differentiate between these two, so it passes classification and\n        regression loss parameters individually. The appropriate one must be\n        renamed into `loss` before passed to the actual learner instance. This\n        is done here.\n\n        \"\"\"\n        return kwargs\n\n    @property\n    def supports_weights(self):\n        \"\"\"The fitter supports weights if both the classification and\n        regression learners support weights.\"\"\"\n        return (\n            hasattr(self.get_learner(self.CLASSIFICATION), 'supports_weights')\n            and self.get_learner(self.CLASSIFICATION).supports_weights) and (\n            hasattr(self.get_learner(self.REGRESSION), 'supports_weights')\n            and self.get_learner(self.REGRESSION).supports_weights)\n\n    @property\n    def params(self):\n        raise TypeError(\n            'A fitter does not have its own params. If you need to access '\n            'learner params, please use the `get_params` method.')\n\n    def get_params(self, problem_type):\n        \"\"\"Access the specific learner params of a given learner.\"\"\"\n        return self.get_learner(problem_type).params\n", "path": "Orange/modelling/base.py"}], "after_files": [{"content": "from Orange.base import Learner, Model\n\n\nclass FitterMeta(type):\n    \"\"\"Ensure that each subclass of the `Fitter` class overrides the `__fits__`\n    attribute with a valid value.\"\"\"\n    def __new__(mcs, name, bases, attrs):\n        # Check that a fitter implementation defines a valid `__fits__`\n        if any(cls.__name__ == 'Fitter' for cls in bases):\n            fits = attrs.get('__fits__')\n            assert isinstance(fits, dict), '__fits__ must be dict instance'\n            assert fits.get('classification') and fits.get('regression'), \\\n                ('`__fits__` property does not define classification '\n                 'or regression learner. Use a simple learner if you don\\'t '\n                 'need the functionality provided by Fitter.')\n        return super().__new__(mcs, name, bases, attrs)\n\n\nclass Fitter(Learner, metaclass=FitterMeta):\n    \"\"\"Handle multiple types of target variable with one learner.\n\n    Subclasses of this class serve as a sort of dispatcher. When subclassing,\n    we provide a `dict` which contain actual learner classes that handle\n    appropriate data types. The fitter can then be used on any data and will\n    delegate the work to the appropriate learner.\n\n    If the learners that handle each data type require different parameters,\n    you should pass in all the possible parameters to the fitter. The fitter\n    will then determine which parameters have to be passed to individual\n    learners.\n\n    \"\"\"\n    __fits__ = None\n    __returns__ = Model\n\n    # Constants to indicate what kind of problem we're dealing with\n    CLASSIFICATION, REGRESSION = 'classification', 'regression'\n\n    def __init__(self, preprocessors=None, **kwargs):\n        super().__init__(preprocessors=preprocessors)\n        self.kwargs = kwargs\n        # Make sure to pass preprocessor params to individual learners\n        self.kwargs['preprocessors'] = preprocessors\n        self.__learners = {self.CLASSIFICATION: None, self.REGRESSION: None}\n\n    def _fit_model(self, data):\n        if data.domain.has_discrete_class:\n            learner = self.get_learner(self.CLASSIFICATION)\n        else:\n            learner = self.get_learner(self.REGRESSION)\n\n        if type(self).fit is Learner.fit:\n            return learner.fit_storage(data)\n        else:\n            X, Y, W = data.X, data.Y, data.W if data.has_weights() else None\n            return learner.fit(X, Y, W)\n\n    def preprocess(self, data):\n        if data.domain.has_discrete_class:\n            return self.get_learner(self.CLASSIFICATION).preprocess(data)\n        else:\n            return self.get_learner(self.REGRESSION).preprocess(data)\n\n    def get_learner(self, problem_type):\n        \"\"\"Get the learner for a given problem type.\n\n        Returns\n        -------\n        Learner\n            The appropriate learner for the given problem type.\n\n        \"\"\"\n        # Prevent trying to access the learner when problem type is None\n        if problem_type not in self.__fits__:\n            raise TypeError(\"No learner to handle '{}'\".format(problem_type))\n        if self.__learners[problem_type] is None:\n            learner = self.__fits__[problem_type](**self.__kwargs(problem_type))\n            learner.use_default_preprocessors = self.use_default_preprocessors\n            self.__learners[problem_type] = learner\n        return self.__learners[problem_type]\n\n    def __kwargs(self, problem_type):\n        learner_kwargs = set(\n            self.__fits__[problem_type].__init__.__code__.co_varnames[1:])\n        changed_kwargs = self._change_kwargs(self.kwargs, problem_type)\n        return {k: v for k, v in changed_kwargs.items() if k in learner_kwargs}\n\n    def _change_kwargs(self, kwargs, problem_type):\n        \"\"\"Handle the kwargs to be passed to the learner before they are used.\n\n        In some cases we need to manipulate the kwargs that will be passed to\n        the learner, e.g. SGD takes a `loss` parameter in both the regression\n        and classification learners, but the learner widget cannot\n        differentiate between these two, so it passes classification and\n        regression loss parameters individually. The appropriate one must be\n        renamed into `loss` before passed to the actual learner instance. This\n        is done here.\n\n        \"\"\"\n        return kwargs\n\n    @property\n    def supports_weights(self):\n        \"\"\"The fitter supports weights if both the classification and\n        regression learners support weights.\"\"\"\n        return (\n            hasattr(self.get_learner(self.CLASSIFICATION), 'supports_weights')\n            and self.get_learner(self.CLASSIFICATION).supports_weights) and (\n            hasattr(self.get_learner(self.REGRESSION), 'supports_weights')\n            and self.get_learner(self.REGRESSION).supports_weights)\n\n    @property\n    def params(self):\n        raise TypeError(\n            'A fitter does not have its own params. If you need to access '\n            'learner params, please use the `get_params` method.')\n\n    def get_params(self, problem_type):\n        \"\"\"Access the specific learner params of a given learner.\"\"\"\n        return self.get_learner(problem_type).params\n", "path": "Orange/modelling/base.py"}]}