We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
string representation of various Maker classes may cause a TypeError
This was tested against Gammapy 0.19 and the development version.

In some cases, printing (or using the string representation in another way) of an instance of a `gammapy.makers.Maker` subclass may cause a TypeError.

An example directly from the tutorials (introduction, low-level analysis):
```
from astropy import units
from regions import CircleSkyRegion
from gammapy.makers import FoVBackgroundMaker
from astropy.coordinates import SkyCoord
from gammapy.maps import WcsGeom, MapAxis

energy_axis = MapAxis.from_energy_bounds(1.0, 10.0, 4, unit="TeV")
geom = WcsGeom.create(
    skydir=(83.633, 22.014),
    binsz=0.02,
    width=(2, 2),
    frame="icrs",
    proj="CAR",
    axes=[],
)
circle = CircleSkyRegion(
    center=SkyCoord("83.63 deg", "22.14 deg"), radius=0.2 * units.deg
)
exclusion_mask = ~geom.region_mask(regions=[circle])
maker_fov = FoVBackgroundMaker(method="fit", exclusion_mask=exclusion_mask)
str(maker_fov)
```

will cause a 
```
TypeError: Cannot parse "not available" as a Quantity. It does not start with a number.
```
(full traceback at the bottom).

The reason is in the `__str__` implementation of the `gammapy.makers.Maker` abstract class:
```
    def __str__(self):
        # <snip>
        for name in names:
            value = getattr(self, name, "not available")

            if value == "not available":
                continue
            else:
                s += f"\t{name:{max_len}s}: {value}\n"

        return s.expandtabs(tabsize=2)
```

When an attribute is not found, it is set to the string "not available". Otherwise, the attribute's value is retrieved.
The resulting value is then compared to the string "not available" to determine whether it is an existing attribute. But some classes can't compare to string. In this particular case, comparing an instance of `WcsNDMap` fails this comparison, resulting (indirectly) in the `TypeError`.

Perhaps the most Pythonic solution is to have `WcsNDMap` handle comparisons with any arbirtrary type, and return `False`. This is what Python does: `1 == "abc"` is valid and returns `False`; similar for e.g. `1 = ["abc", 5.5]`.

Perhaps easier, and in my opinion semantically better, is to use
```
value = getattr(self, name, None)
if value is None:
    continue
s += f"\t{name:{max_len}s}: {value}\n"
```
since `None` signifies the optional type in Python.

Though even better, in my opinion, is to simply use
```
for name in names:
    try:
        value = getattr(self, name)
    except AttributeError:
        continue
    s += f"\t{name:{max_len}s}: {value}\n" 
```
After all, this is what exceptions are for. (People sometimes mention speed reasons if the lookup fails a lot of times, but I don't think that's relevant here for a `__str__` implementation.)

I would even simply use `self.name`, but that'll fail because `Maker` is an abstract class, so more dynamic retrieval of attributes is required. I assume this is why it's implemented in its current way.


-----

Full traceback:
```
Traceback (most recent call last):
  File "/home/user/.miniconda3/envs/gammapy-dev/lib/python3.7/site-packages/astropy/units/quantity.py", line 333, in __new__
    value = float(v.group())
AttributeError: 'NoneType' object has no attribute 'group'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "gammapy_maker_str.py", line 22, in <module>
    str(maker_fov)
  File "/home/user/.miniconda3/envs/gammapy-dev/lib/python3.7/site-packages/gammapy/makers/core.py", line 31, in __str__
    if value == "not available":
  File "/home/user/.miniconda3/envs/gammapy-dev/lib/python3.7/site-packages/gammapy/maps/core.py", line 1612, in __eq__
    return self._arithmetics(np.equal, other, copy=True)
  File "/home/user/.miniconda3/envs/gammapy-dev/lib/python3.7/site-packages/gammapy/maps/core.py", line 1552, in _arithmetics
    q = u.Quantity(other, copy=False)
  File "/home/user/.miniconda3/envs/gammapy-dev/lib/python3.7/site-packages/astropy/units/quantity.py", line 338, in __new__
    .format(value, cls.__name__))
TypeError: Cannot parse "not available" as a Quantity. It does not start with a number.
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gammapy/makers/core.py`
Content:
```
1 # Licensed under a 3-clause BSD style license - see LICENSE.rst
2 import abc
3 import numpy as np
4 
5 __all__ = ["Maker"]
6 
7 
8 class Maker(abc.ABC):
9     """Abstract maker base class."""
10 
11     @property
12     @abc.abstractmethod
13     def tag(self):
14         pass
15 
16     @abc.abstractmethod
17     def run(self):
18         pass
19 
20     def __str__(self):
21         s = f"{self.__class__.__name__}\n"
22         s += "-" * (len(s) - 1) + "\n\n"
23 
24         names = self.__init__.__code__.co_varnames
25 
26         max_len = np.max([len(_) for _ in names]) + 1
27 
28         for name in names:
29             value = getattr(self, name, "not available")
30 
31             if value == "not available":
32                 continue
33             else:
34                 s += f"\t{name:{max_len}s}: {value}\n"
35 
36         return s.expandtabs(tabsize=2)
37
```
--- 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()

```

diff --git a/gammapy/makers/core.py b/gammapy/makers/core.py
--- a/gammapy/makers/core.py
+++ b/gammapy/makers/core.py
@@ -26,9 +26,9 @@
         max_len = np.max([len(_) for _ in names]) + 1
 
         for name in names:
-            value = getattr(self, name, "not available")
+            value = getattr(self, name, None)
 
-            if value == "not available":
+            if value is None:
                 continue
             else:
                 s += f"\t{name:{max_len}s}: {value}\n"

{"golden_diff": "diff --git a/gammapy/makers/core.py b/gammapy/makers/core.py\n--- a/gammapy/makers/core.py\n+++ b/gammapy/makers/core.py\n@@ -26,9 +26,9 @@\n         max_len = np.max([len(_) for _ in names]) + 1\n \n         for name in names:\n-            value = getattr(self, name, \"not available\")\n+            value = getattr(self, name, None)\n \n-            if value == \"not available\":\n+            if value is None:\n                 continue\n             else:\n                 s += f\"\\t{name:{max_len}s}: {value}\\n\"\n", "issue": "string representation of various Maker classes may cause a TypeError\nThis was tested against Gammapy 0.19 and the development version.\r\n\r\nIn some cases, printing (or using the string representation in another way) of an instance of a `gammapy.makers.Maker` subclass may cause a TypeError.\r\n\r\nAn example directly from the tutorials (introduction, low-level analysis):\r\n```\r\nfrom astropy import units\r\nfrom regions import CircleSkyRegion\r\nfrom gammapy.makers import FoVBackgroundMaker\r\nfrom astropy.coordinates import SkyCoord\r\nfrom gammapy.maps import WcsGeom, MapAxis\r\n\r\nenergy_axis = MapAxis.from_energy_bounds(1.0, 10.0, 4, unit=\"TeV\")\r\ngeom = WcsGeom.create(\r\n    skydir=(83.633, 22.014),\r\n    binsz=0.02,\r\n    width=(2, 2),\r\n    frame=\"icrs\",\r\n    proj=\"CAR\",\r\n    axes=[],\r\n)\r\ncircle = CircleSkyRegion(\r\n    center=SkyCoord(\"83.63 deg\", \"22.14 deg\"), radius=0.2 * units.deg\r\n)\r\nexclusion_mask = ~geom.region_mask(regions=[circle])\r\nmaker_fov = FoVBackgroundMaker(method=\"fit\", exclusion_mask=exclusion_mask)\r\nstr(maker_fov)\r\n```\r\n\r\nwill cause a \r\n```\r\nTypeError: Cannot parse \"not available\" as a Quantity. It does not start with a number.\r\n```\r\n(full traceback at the bottom).\r\n\r\nThe reason is in the `__str__` implementation of the `gammapy.makers.Maker` abstract class:\r\n```\r\n    def __str__(self):\r\n        # <snip>\r\n        for name in names:\r\n            value = getattr(self, name, \"not available\")\r\n\r\n            if value == \"not available\":\r\n                continue\r\n            else:\r\n                s += f\"\\t{name:{max_len}s}: {value}\\n\"\r\n\r\n        return s.expandtabs(tabsize=2)\r\n```\r\n\r\nWhen an attribute is not found, it is set to the string \"not available\". Otherwise, the attribute's value is retrieved.\r\nThe resulting value is then compared to the string \"not available\" to determine whether it is an existing attribute. But some classes can't compare to string. In this particular case, comparing an instance of `WcsNDMap` fails this comparison, resulting (indirectly) in the `TypeError`.\r\n\r\nPerhaps the most Pythonic solution is to have `WcsNDMap` handle comparisons with any arbirtrary type, and return `False`. This is what Python does: `1 == \"abc\"` is valid and returns `False`; similar for e.g. `1 = [\"abc\", 5.5]`.\r\n\r\nPerhaps easier, and in my opinion semantically better, is to use\r\n```\r\nvalue = getattr(self, name, None)\r\nif value is None:\r\n    continue\r\ns += f\"\\t{name:{max_len}s}: {value}\\n\"\r\n```\r\nsince `None` signifies the optional type in Python.\r\n\r\nThough even better, in my opinion, is to simply use\r\n```\r\nfor name in names:\r\n    try:\r\n        value = getattr(self, name)\r\n    except AttributeError:\r\n        continue\r\n    s += f\"\\t{name:{max_len}s}: {value}\\n\" \r\n```\r\nAfter all, this is what exceptions are for. (People sometimes mention speed reasons if the lookup fails a lot of times, but I don't think that's relevant here for a `__str__` implementation.)\r\n\r\nI would even simply use `self.name`, but that'll fail because `Maker` is an abstract class, so more dynamic retrieval of attributes is required. I assume this is why it's implemented in its current way.\r\n\r\n\r\n-----\r\n\r\nFull traceback:\r\n```\r\nTraceback (most recent call last):\r\n  File \"/home/user/.miniconda3/envs/gammapy-dev/lib/python3.7/site-packages/astropy/units/quantity.py\", line 333, in __new__\r\n    value = float(v.group())\r\nAttributeError: 'NoneType' object has no attribute 'group'\r\n\r\nDuring handling of the above exception, another exception occurred:\r\n\r\nTraceback (most recent call last):\r\n  File \"gammapy_maker_str.py\", line 22, in <module>\r\n    str(maker_fov)\r\n  File \"/home/user/.miniconda3/envs/gammapy-dev/lib/python3.7/site-packages/gammapy/makers/core.py\", line 31, in __str__\r\n    if value == \"not available\":\r\n  File \"/home/user/.miniconda3/envs/gammapy-dev/lib/python3.7/site-packages/gammapy/maps/core.py\", line 1612, in __eq__\r\n    return self._arithmetics(np.equal, other, copy=True)\r\n  File \"/home/user/.miniconda3/envs/gammapy-dev/lib/python3.7/site-packages/gammapy/maps/core.py\", line 1552, in _arithmetics\r\n    q = u.Quantity(other, copy=False)\r\n  File \"/home/user/.miniconda3/envs/gammapy-dev/lib/python3.7/site-packages/astropy/units/quantity.py\", line 338, in __new__\r\n    .format(value, cls.__name__))\r\nTypeError: Cannot parse \"not available\" as a Quantity. It does not start with a number.\r\n```\r\n\n", "before_files": [{"content": "# Licensed under a 3-clause BSD style license - see LICENSE.rst\nimport abc\nimport numpy as np\n\n__all__ = [\"Maker\"]\n\n\nclass Maker(abc.ABC):\n    \"\"\"Abstract maker base class.\"\"\"\n\n    @property\n    @abc.abstractmethod\n    def tag(self):\n        pass\n\n    @abc.abstractmethod\n    def run(self):\n        pass\n\n    def __str__(self):\n        s = f\"{self.__class__.__name__}\\n\"\n        s += \"-\" * (len(s) - 1) + \"\\n\\n\"\n\n        names = self.__init__.__code__.co_varnames\n\n        max_len = np.max([len(_) for _ in names]) + 1\n\n        for name in names:\n            value = getattr(self, name, \"not available\")\n\n            if value == \"not available\":\n                continue\n            else:\n                s += f\"\\t{name:{max_len}s}: {value}\\n\"\n\n        return s.expandtabs(tabsize=2)\n", "path": "gammapy/makers/core.py"}], "after_files": [{"content": "# Licensed under a 3-clause BSD style license - see LICENSE.rst\nimport abc\nimport numpy as np\n\n__all__ = [\"Maker\"]\n\n\nclass Maker(abc.ABC):\n    \"\"\"Abstract maker base class.\"\"\"\n\n    @property\n    @abc.abstractmethod\n    def tag(self):\n        pass\n\n    @abc.abstractmethod\n    def run(self):\n        pass\n\n    def __str__(self):\n        s = f\"{self.__class__.__name__}\\n\"\n        s += \"-\" * (len(s) - 1) + \"\\n\\n\"\n\n        names = self.__init__.__code__.co_varnames\n\n        max_len = np.max([len(_) for _ in names]) + 1\n\n        for name in names:\n            value = getattr(self, name, None)\n\n            if value is None:\n                continue\n            else:\n                s += f\"\\t{name:{max_len}s}: {value}\\n\"\n\n        return s.expandtabs(tabsize=2)\n", "path": "gammapy/makers/core.py"}]}