prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Lemmatizer exceptions: `lemma_strings` get sorted anyway
  Lemmatization exceptions have been working inconsistently, even when they are added directly in `corpora/en/wordnet/dict/verb.exc`
  
  The minimal test case of `coping` at https://github.com/explosion/spaCy/issues/389 reveals that, at 
  https://github.com/explosion/spaCy/blob/master/spacy/lemmatizer.py#L94, the lemmatizer's list of  potential forms (where the thing in lemmatizer.exceptions is item [0]) is cast to a `set` (and so loses ordering); then in https://github.com/explosion/spaCy/blob/master/spacy/morphology.pyx#L149, that `set` gets sorted. So lemmatizer exceptions only work if they also come first alphabetically!
  
  I've implemented the fix for this, but I need this issue in order to submit the PR!
  
  One question, though, for @honnibal: this can be fixed either as I did it locally -- return the whole list, with exceptions and then anything that comes back from the lemmatizer's `rules` -- or skip the rules altogether if we used an exception. I think it's more useful downstream if we keep all lemma candidates, even if we're not using them in the default pipeline. But it also seems only destructive to do `sorted(set())` on them!
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `spacy/lemmatizer.py`
  Content:
  ```
  1 # coding: utf8
  2 from __future__ import unicode_literals
  3 
  4 from .symbols import POS, NOUN, VERB, ADJ, PUNCT
  5 from .symbols import VerbForm_inf, VerbForm_none, Number_sing, Degree_pos
  6 
  7 
  8 class Lemmatizer(object):
  9     @classmethod
  10     def load(cls, path, index=None, exc=None, rules=None):
  11         return cls(index or {}, exc or {}, rules or {})
  12 
  13     def __init__(self, index, exceptions, rules):
  14         self.index = index
  15         self.exc = exceptions
  16         self.rules = rules
  17 
  18     def __call__(self, string, univ_pos, morphology=None):
  19         if univ_pos == NOUN:
  20             univ_pos = 'noun'
  21         elif univ_pos == VERB:
  22             univ_pos = 'verb'
  23         elif univ_pos == ADJ:
  24             univ_pos = 'adj'
  25         elif univ_pos == PUNCT:
  26             univ_pos = 'punct'
  27         # See Issue #435 for example of where this logic is requied.
  28         if self.is_base_form(univ_pos, morphology):
  29             return set([string.lower()])
  30         lemmas = lemmatize(string, self.index.get(univ_pos, {}),
  31                            self.exc.get(univ_pos, {}),
  32                            self.rules.get(univ_pos, []))
  33         return lemmas
  34 
  35     def is_base_form(self, univ_pos, morphology=None):
  36         """
  37         Check whether we're dealing with an uninflected paradigm, so we can
  38         avoid lemmatization entirely.
  39         """
  40         morphology = {} if morphology is None else morphology
  41         others = [key for key in morphology if key not in (POS, 'number', 'pos', 'verbform')]
  42         true_morph_key = morphology.get('morph', 0)
  43         if univ_pos == 'noun' and morphology.get('Number') == 'sing':
  44             return True
  45         elif univ_pos == 'verb' and morphology.get('VerbForm') == 'inf':
  46             return True
  47         elif univ_pos == 'adj' and morphology.get('Degree') == 'pos':
  48             return True
  49         elif VerbForm_inf in morphology:
  50             return True
  51         elif VerbForm_none in morphology:
  52             return True
  53         elif Number_sing in morphology:
  54             return True
  55         elif Degree_pos in morphology:
  56             return True
  57         else:
  58             return False
  59 
  60     def noun(self, string, morphology=None):
  61         return self(string, 'noun', morphology)
  62 
  63     def verb(self, string, morphology=None):
  64         return self(string, 'verb', morphology)
  65 
  66     def adj(self, string, morphology=None):
  67         return self(string, 'adj', morphology)
  68 
  69     def punct(self, string, morphology=None):
  70         return self(string, 'punct', morphology)
  71 
  72 
  73 def lemmatize(string, index, exceptions, rules):
  74     string = string.lower()
  75     forms = []
  76     # TODO: Is this correct? See discussion in Issue #435.
  77     #if string in index:
  78     #    forms.append(string)
  79     forms.extend(exceptions.get(string, []))
  80     oov_forms = []
  81     for old, new in rules:
  82         if string.endswith(old):
  83             form = string[:len(string) - len(old)] + new
  84             if not form:
  85                 pass
  86             elif form in index or not form.isalpha():
  87                 forms.append(form)
  88             else:
  89                 oov_forms.append(form)
  90     if not forms:
  91         forms.extend(oov_forms)
  92     if not forms:
  93         forms.append(string)
  94     return set(forms)
  95
  ```
  --- 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/spacy/lemmatizer.py b/spacy/lemmatizer.py
  --- a/spacy/lemmatizer.py
  +++ b/spacy/lemmatizer.py
  @@ -78,15 +78,16 @@
       #    forms.append(string)
       forms.extend(exceptions.get(string, []))
       oov_forms = []
  -    for old, new in rules:
  -        if string.endswith(old):
  -            form = string[:len(string) - len(old)] + new
  -            if not form:
  -                pass
  -            elif form in index or not form.isalpha():
  -                forms.append(form)
  -            else:
  -                oov_forms.append(form)
  +    if not forms:
  +        for old, new in rules:
  +            if string.endswith(old):
  +                form = string[:len(string) - len(old)] + new
  +                if not form:
  +                    pass
  +                elif form in index or not form.isalpha():
  +                    forms.append(form)
  +                else:
  +                    oov_forms.append(form)
       if not forms:
           forms.extend(oov_forms)
       if not forms:
  
verification_info: {"golden_diff": "diff --git a/spacy/lemmatizer.py b/spacy/lemmatizer.py\n--- a/spacy/lemmatizer.py\n+++ b/spacy/lemmatizer.py\n@@ -78,15 +78,16 @@\n     #    forms.append(string)\n     forms.extend(exceptions.get(string, []))\n     oov_forms = []\n-    for old, new in rules:\n-        if string.endswith(old):\n-            form = string[:len(string) - len(old)] + new\n-            if not form:\n-                pass\n-            elif form in index or not form.isalpha():\n-                forms.append(form)\n-            else:\n-                oov_forms.append(form)\n+    if not forms:\n+        for old, new in rules:\n+            if string.endswith(old):\n+                form = string[:len(string) - len(old)] + new\n+                if not form:\n+                    pass\n+                elif form in index or not form.isalpha():\n+                    forms.append(form)\n+                else:\n+                    oov_forms.append(form)\n     if not forms:\n         forms.extend(oov_forms)\n     if not forms:\n", "issue": "Lemmatizer exceptions: `lemma_strings` get sorted anyway\nLemmatization exceptions have been working inconsistently, even when they are added directly in `corpora/en/wordnet/dict/verb.exc`\r\n\r\nThe minimal test case of `coping` at https://github.com/explosion/spaCy/issues/389 reveals that, at \r\nhttps://github.com/explosion/spaCy/blob/master/spacy/lemmatizer.py#L94, the lemmatizer's list of  potential forms (where the thing in lemmatizer.exceptions is item [0]) is cast to a `set` (and so loses ordering); then in https://github.com/explosion/spaCy/blob/master/spacy/morphology.pyx#L149, that `set` gets sorted. So lemmatizer exceptions only work if they also come first alphabetically!\r\n\r\nI've implemented the fix for this, but I need this issue in order to submit the PR!\r\n\r\nOne question, though, for @honnibal: this can be fixed either as I did it locally -- return the whole list, with exceptions and then anything that comes back from the lemmatizer's `rules` -- or skip the rules altogether if we used an exception. I think it's more useful downstream if we keep all lemma candidates, even if we're not using them in the default pipeline. But it also seems only destructive to do `sorted(set())` on them!\n", "before_files": [{"content": "# coding: utf8\nfrom __future__ import unicode_literals\n\nfrom .symbols import POS, NOUN, VERB, ADJ, PUNCT\nfrom .symbols import VerbForm_inf, VerbForm_none, Number_sing, Degree_pos\n\n\nclass Lemmatizer(object):\n    @classmethod\n    def load(cls, path, index=None, exc=None, rules=None):\n        return cls(index or {}, exc or {}, rules or {})\n\n    def __init__(self, index, exceptions, rules):\n        self.index = index\n        self.exc = exceptions\n        self.rules = rules\n\n    def __call__(self, string, univ_pos, morphology=None):\n        if univ_pos == NOUN:\n            univ_pos = 'noun'\n        elif univ_pos == VERB:\n            univ_pos = 'verb'\n        elif univ_pos == ADJ:\n            univ_pos = 'adj'\n        elif univ_pos == PUNCT:\n            univ_pos = 'punct'\n        # See Issue #435 for example of where this logic is requied.\n        if self.is_base_form(univ_pos, morphology):\n            return set([string.lower()])\n        lemmas = lemmatize(string, self.index.get(univ_pos, {}),\n                           self.exc.get(univ_pos, {}),\n                           self.rules.get(univ_pos, []))\n        return lemmas\n\n    def is_base_form(self, univ_pos, morphology=None):\n        \"\"\"\n        Check whether we're dealing with an uninflected paradigm, so we can\n        avoid lemmatization entirely.\n        \"\"\"\n        morphology = {} if morphology is None else morphology\n        others = [key for key in morphology if key not in (POS, 'number', 'pos', 'verbform')]\n        true_morph_key = morphology.get('morph', 0)\n        if univ_pos == 'noun' and morphology.get('Number') == 'sing':\n            return True\n        elif univ_pos == 'verb' and morphology.get('VerbForm') == 'inf':\n            return True\n        elif univ_pos == 'adj' and morphology.get('Degree') == 'pos':\n            return True\n        elif VerbForm_inf in morphology:\n            return True\n        elif VerbForm_none in morphology:\n            return True\n        elif Number_sing in morphology:\n            return True\n        elif Degree_pos in morphology:\n            return True\n        else:\n            return False\n\n    def noun(self, string, morphology=None):\n        return self(string, 'noun', morphology)\n\n    def verb(self, string, morphology=None):\n        return self(string, 'verb', morphology)\n\n    def adj(self, string, morphology=None):\n        return self(string, 'adj', morphology)\n\n    def punct(self, string, morphology=None):\n        return self(string, 'punct', morphology)\n\n\ndef lemmatize(string, index, exceptions, rules):\n    string = string.lower()\n    forms = []\n    # TODO: Is this correct? See discussion in Issue #435.\n    #if string in index:\n    #    forms.append(string)\n    forms.extend(exceptions.get(string, []))\n    oov_forms = []\n    for old, new in rules:\n        if string.endswith(old):\n            form = string[:len(string) - len(old)] + new\n            if not form:\n                pass\n            elif form in index or not form.isalpha():\n                forms.append(form)\n            else:\n                oov_forms.append(form)\n    if not forms:\n        forms.extend(oov_forms)\n    if not forms:\n        forms.append(string)\n    return set(forms)\n", "path": "spacy/lemmatizer.py"}], "after_files": [{"content": "# coding: utf8\nfrom __future__ import unicode_literals\n\nfrom .symbols import POS, NOUN, VERB, ADJ, PUNCT\nfrom .symbols import VerbForm_inf, VerbForm_none, Number_sing, Degree_pos\n\n\nclass Lemmatizer(object):\n    @classmethod\n    def load(cls, path, index=None, exc=None, rules=None):\n        return cls(index or {}, exc or {}, rules or {})\n\n    def __init__(self, index, exceptions, rules):\n        self.index = index\n        self.exc = exceptions\n        self.rules = rules\n\n    def __call__(self, string, univ_pos, morphology=None):\n        if univ_pos == NOUN:\n            univ_pos = 'noun'\n        elif univ_pos == VERB:\n            univ_pos = 'verb'\n        elif univ_pos == ADJ:\n            univ_pos = 'adj'\n        elif univ_pos == PUNCT:\n            univ_pos = 'punct'\n        # See Issue #435 for example of where this logic is requied.\n        if self.is_base_form(univ_pos, morphology):\n            return set([string.lower()])\n        lemmas = lemmatize(string, self.index.get(univ_pos, {}),\n                           self.exc.get(univ_pos, {}),\n                           self.rules.get(univ_pos, []))\n        return lemmas\n\n    def is_base_form(self, univ_pos, morphology=None):\n        \"\"\"\n        Check whether we're dealing with an uninflected paradigm, so we can\n        avoid lemmatization entirely.\n        \"\"\"\n        morphology = {} if morphology is None else morphology\n        others = [key for key in morphology if key not in (POS, 'number', 'pos', 'verbform')]\n        true_morph_key = morphology.get('morph', 0)\n        if univ_pos == 'noun' and morphology.get('Number') == 'sing':\n            return True\n        elif univ_pos == 'verb' and morphology.get('VerbForm') == 'inf':\n            return True\n        elif univ_pos == 'adj' and morphology.get('Degree') == 'pos':\n            return True\n        elif VerbForm_inf in morphology:\n            return True\n        elif VerbForm_none in morphology:\n            return True\n        elif Number_sing in morphology:\n            return True\n        elif Degree_pos in morphology:\n            return True\n        else:\n            return False\n\n    def noun(self, string, morphology=None):\n        return self(string, 'noun', morphology)\n\n    def verb(self, string, morphology=None):\n        return self(string, 'verb', morphology)\n\n    def adj(self, string, morphology=None):\n        return self(string, 'adj', morphology)\n\n    def punct(self, string, morphology=None):\n        return self(string, 'punct', morphology)\n\n\ndef lemmatize(string, index, exceptions, rules):\n    string = string.lower()\n    forms = []\n    # TODO: Is this correct? See discussion in Issue #435.\n    #if string in index:\n    #    forms.append(string)\n    forms.extend(exceptions.get(string, []))\n    oov_forms = []\n    if not forms:\n        for old, new in rules:\n            if string.endswith(old):\n                form = string[:len(string) - len(old)] + new\n                if not form:\n                    pass\n                elif form in index or not form.isalpha():\n                    forms.append(form)\n                else:\n                    oov_forms.append(form)\n    if not forms:\n        forms.extend(oov_forms)\n    if not forms:\n        forms.append(string)\n    return set(forms)\n", "path": "spacy/lemmatizer.py"}]}