chiark / gitweb /
documentation/python: revisit None in function return type annotations.
authorVladimír Vondruš <mosra@centrum.cz>
Fri, 23 Aug 2019 13:45:20 +0000 (15:45 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Sun, 25 Aug 2019 10:38:27 +0000 (12:38 +0200)
Until now it was ignored in pybind return annotations as it seemed to be
superfluous. OTOH, while the pybind function is fully annotated, it's
often not clear which function comes from pybind and which from pure
python code, meaning it's not clear if the return type is None or the
return type annotation is just missing.

Additionally, the pure python annotations were showing None as NoneType,
which is too verbose. According to PEP484 type(None) and None are
equivalent, so patching the output to show None instead of NoneType,
making it also consistent with pybind.

documentation/python.py
documentation/test_python/inspect_annotations/inspect_annotations.html
documentation/test_python/inspect_annotations/inspect_annotations.py
documentation/test_python/pybind_signatures/pybind_signatures.MyClass.html
documentation/test_python/pybind_signatures/pybind_signatures.cpp
documentation/test_python/pybind_signatures/pybind_signatures.html
documentation/test_python/pybind_type_links/pybind_type_links.Foo.html
documentation/test_python/pybind_type_links/pybind_type_links.html
documentation/test_python/test_pybind.py

index 4732b80817657d30aedbfd2608ce11d89e5da007..c1008f77fc03c8a891b29e1045df3be6952f2824 100755 (executable)
@@ -832,7 +832,7 @@ def get_type_hints_or_nothing(state: State, path: List[str], object) -> Dict:
         return {}
 
 def extract_annotation(state: State, referrer_path: List[str], annotation) -> str:
-    # TODO: why this is not None directly?
+    # Empty annotation, as opposed to a None annotation, handled below
     if annotation is inspect.Signature.empty: return None
 
     # If dereferencing with typing.get_type_hints() failed, we might end up
@@ -901,6 +901,12 @@ def extract_annotation(state: State, referrer_path: List[str], annotation) -> st
         logging.warning("invalid annotation %s in %s, ignoring", annotation, '.'.join(referrer_path))
         return None
 
+    # According to https://www.python.org/dev/peps/pep-0484/#using-none,
+    # None and type(None) are equivalent. Calling extract_type() on None would
+    # give us NoneType, which is unnecessarily long.
+    elif annotation is type(None):
+        return 'None'
+
     # Otherwise it's a plain type. Turn it into a link.
     return make_name_link(state, referrer_path, map_name_prefix(state, extract_type(annotation)))
 
@@ -1027,10 +1033,7 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
             out.has_complex_params = False
             out.summary, out.content = extract_docs(state, state.function_docs, entry.path, summary)
             out.has_details = bool(out.content)
-
-            # Don't show None return type for functions w/o a return
-            out.type = None if type == 'None' else type
-            if out.type: out.type = make_name_link(state, entry.path, out.type)
+            out.type = type
 
             # There's no other way to check staticmethods than to check for
             # self being the name of first parameter :( No support for
index bcdff17e951aa0af6fa4ff0f95ac0b4a9864e3f1..87d15ffa6727dfb6b1ab671514680ba37b1b3122 100644 (file)
               <span class="m-doc-wrap-bumper">def <a href="#positional_keyword" class="m-doc-self">positional_keyword</a>(</span><span class="m-doc-wrap">positional_kw,<span class="m-text m-dim"> *,</span> kw_only)</span>
             </dt>
             <dd>Function with explicitly delimited keyword args</dd>
+            <dt id="returns_none">
+              <span class="m-doc-wrap-bumper">def <a href="#returns_none" class="m-doc-self">returns_none</a>(</span><span class="m-doc-wrap">a: typing.Callable[[], None]) -&gt; None</span>
+            </dt>
+            <dd>In order to disambiguate between a missing return annotation and an
+annotated none, the None return annotation is kept, converted from NoneType
+to None</dd>
+            <dt id="returns_none_type">
+              <span class="m-doc-wrap-bumper">def <a href="#returns_none_type" class="m-doc-self">returns_none_type</a>(</span><span class="m-doc-wrap">a: typing.Callable[[], None]) -&gt; None</span>
+            </dt>
+            <dd>And it should behave the same when using None or type(None)</dd>
           </dl>
         </section>
         <section id="data">
index 65594737295d1fa2d4731314db1696aa20d4aa6c..349d982472a7dcbecff579db4ce7c694255d552d 100644 (file)
@@ -83,6 +83,14 @@ def annotated_positional_keyword(bar = False, *, foo: str, **kwargs):
     """Function with explicitly delimited keyword args and type annotations"""
     pass
 
+def returns_none(a: Callable[[], None]) -> None:
+    """In order to disambiguate between a missing return annotation and an
+    annotated none, the None return annotation is kept, converted from NoneType
+    to None"""
+
+def returns_none_type(a: Callable[[], type(None)]) -> type(None):
+    """And it should behave the same when using None or type(None)"""
+
 UNANNOTATED_VAR = 3.45
 
 ANNOTATED_VAR: Tuple[bool, str] = (False, 'No.')
index e62b1f2e08158c7f53e9d57632e0b0ae773c5f4e..258e917afb5773ed75d4b606cfd2b3e9d8224cfe 100644 (file)
@@ -72,7 +72,7 @@
           <h2><a href="#dunder-methods">Special methods</a></h2>
           <dl class="m-doc">
             <dt id="__init__-6eef6">
-              <span class="m-doc-wrap-bumper">def <a href="#__init__-6eef6" class="m-doc-self">__init__</a>(</span><span class="m-doc-wrap">self<span class="m-text m-dim">, /</span>)</span>
+              <span class="m-doc-wrap-bumper">def <a href="#__init__-6eef6" class="m-doc-self">__init__</a>(</span><span class="m-doc-wrap">self<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>Constructor</dd>
           </dl>
index 62e2aaa8c846465abaa638efa5d60ca6d31fcfd4..62a9f655f7c4bf7217922a80597c3778a3b8e135 100644 (file)
@@ -24,6 +24,7 @@ bool overloaded(float) { return {}; }
 
 // Doesn't work with just a plain function pointer, MEH
 void takesAFunction(std::function<int(float, std::vector<float>&)>) {}
+void takesAFunctionReturningVoid(std::function<void()>) {}
 
 struct MyClass {
     static MyClass staticFunction(int, float) { return {}; }
@@ -61,6 +62,7 @@ PYBIND11_MODULE(pybind_signatures, m) {
         .def("overloaded", static_cast<bool(*)(float)>(&overloaded), "Overloaded for floats")
         .def("duck", &duck, "A function taking args/kwargs directly")
         .def("takes_a_function", &takesAFunction, "A function taking a Callable")
+        .def("takes_a_function_returning_none", &takesAFunctionReturningVoid, "A function taking a Callable that returns None")
 
         .def("tenOverloads", &tenOverloads<float, float>, "Ten overloads of a function")
         .def("tenOverloads", &tenOverloads<int, float>, "Ten overloads of a function")
index c557155d6082aad273c9fd53bd4c4e92ac639c17..d7136b39c322afb45538035826689cdf4de02477 100644 (file)
@@ -52,7 +52,7 @@
             </dt>
             <dd>Function that failed to get parsed</dd>
             <dt id="duck-9024d">
-              <span class="m-doc-wrap-bumper">def <a href="#duck-9024d" class="m-doc-self">duck</a>(</span><span class="m-doc-wrap">*args, **kwargs)</span>
+              <span class="m-doc-wrap-bumper">def <a href="#duck-9024d" class="m-doc-self">duck</a>(</span><span class="m-doc-wrap">*args, **kwargs) -&gt; None</span>
             </dt>
             <dd>A function taking args/kwargs directly</dd>
             <dt id="overloaded-46f8a">
             </dt>
             <dd>Scale an integer, kwargs</dd>
             <dt id="takes_a_function-b0069">
-              <span class="m-doc-wrap-bumper">def <a href="#takes_a_function-b0069" class="m-doc-self">takes_a_function</a>(</span><span class="m-doc-wrap">arg0: Callable[[float, List[float]], int]<span class="m-text m-dim">, /</span>)</span>
+              <span class="m-doc-wrap-bumper">def <a href="#takes_a_function-b0069" class="m-doc-self">takes_a_function</a>(</span><span class="m-doc-wrap">arg0: Callable[[float, List[float]], int]<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>A function taking a Callable</dd>
+            <dt id="takes_a_function_returning_none-08451">
+              <span class="m-doc-wrap-bumper">def <a href="#takes_a_function_returning_none-08451" class="m-doc-self">takes_a_function_returning_none</a>(</span><span class="m-doc-wrap">arg0: Callable[[], None]<span class="m-text m-dim">, /</span>) -&gt; None</span>
+            </dt>
+            <dd>A function taking a Callable that returns None</dd>
             <dt id="taking_a_list_returning_a_tuple-54d79">
               <span class="m-doc-wrap-bumper">def <a href="#taking_a_list_returning_a_tuple-54d79" class="m-doc-self">taking_a_list_returning_a_tuple</a>(</span><span class="m-doc-wrap">arg0: List[float]<span class="m-text m-dim">, /</span>) -&gt; Tuple[int, int, int]</span>
             </dt>
             <dd>Takes a list, returns a tuple</dd>
             <dt id="tenOverloads-fe11a">
               <span class="m-doc-wrap-bumper">def <a href="#tenOverloads-fe11a" class="m-doc-self">tenOverloads</a>(</span><span class="m-doc-wrap">arg0: float,
-              arg1: float<span class="m-text m-dim">, /</span>)</span>
+              arg1: float<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>Ten overloads of a function</dd>
             <dt id="tenOverloads-8f19c">
               <span class="m-doc-wrap-bumper">def <a href="#tenOverloads-8f19c" class="m-doc-self">tenOverloads</a>(</span><span class="m-doc-wrap">arg0: int,
-              arg1: float<span class="m-text m-dim">, /</span>)</span>
+              arg1: float<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>Ten overloads of a function</dd>
             <dt id="tenOverloads-bd997">
               <span class="m-doc-wrap-bumper">def <a href="#tenOverloads-bd997" class="m-doc-self">tenOverloads</a>(</span><span class="m-doc-wrap">arg0: bool,
-              arg1: float<span class="m-text m-dim">, /</span>)</span>
+              arg1: float<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>Ten overloads of a function</dd>
             <dt id="tenOverloads-8710e">
               <span class="m-doc-wrap-bumper">def <a href="#tenOverloads-8710e" class="m-doc-self">tenOverloads</a>(</span><span class="m-doc-wrap">arg0: float,
-              arg1: int<span class="m-text m-dim">, /</span>)</span>
+              arg1: int<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>Ten overloads of a function</dd>
             <dt id="tenOverloads-e9329">
               <span class="m-doc-wrap-bumper">def <a href="#tenOverloads-e9329" class="m-doc-self">tenOverloads</a>(</span><span class="m-doc-wrap">arg0: int,
-              arg1: int<span class="m-text m-dim">, /</span>)</span>
+              arg1: int<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>Ten overloads of a function</dd>
             <dt id="tenOverloads-3d438">
               <span class="m-doc-wrap-bumper">def <a href="#tenOverloads-3d438" class="m-doc-self">tenOverloads</a>(</span><span class="m-doc-wrap">arg0: bool,
-              arg1: int<span class="m-text m-dim">, /</span>)</span>
+              arg1: int<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>Ten overloads of a function</dd>
             <dt id="tenOverloads-841cb">
               <span class="m-doc-wrap-bumper">def <a href="#tenOverloads-841cb" class="m-doc-self">tenOverloads</a>(</span><span class="m-doc-wrap">arg0: float,
-              arg1: bool<span class="m-text m-dim">, /</span>)</span>
+              arg1: bool<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>Ten overloads of a function</dd>
             <dt id="tenOverloads-a6f98">
               <span class="m-doc-wrap-bumper">def <a href="#tenOverloads-a6f98" class="m-doc-self">tenOverloads</a>(</span><span class="m-doc-wrap">arg0: int,
-              arg1: bool<span class="m-text m-dim">, /</span>)</span>
+              arg1: bool<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>Ten overloads of a function</dd>
             <dt id="tenOverloads-2d308">
               <span class="m-doc-wrap-bumper">def <a href="#tenOverloads-2d308" class="m-doc-self">tenOverloads</a>(</span><span class="m-doc-wrap">arg0: bool,
-              arg1: bool<span class="m-text m-dim">, /</span>)</span>
+              arg1: bool<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>Ten overloads of a function</dd>
             <dt id="tenOverloads-6e57b">
               <span class="m-doc-wrap-bumper">def <a href="#tenOverloads-6e57b" class="m-doc-self">tenOverloads</a>(</span><span class="m-doc-wrap">arg0: str,
-              arg1: str<span class="m-text m-dim">, /</span>)</span>
+              arg1: str<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>Ten overloads of a function</dd>
             <dt id="void_function-46f8a">
-              <span class="m-doc-wrap-bumper">def <a href="#void_function-46f8a" class="m-doc-self">void_function</a>(</span><span class="m-doc-wrap">arg0: int<span class="m-text m-dim">, /</span>)</span>
+              <span class="m-doc-wrap-bumper">def <a href="#void_function-46f8a" class="m-doc-self">void_function</a>(</span><span class="m-doc-wrap">arg0: int<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>Returns nothing</dd>
           </dl>
index 636ed1eb01a3e3f71f1023d2ff4a029d4f8e59ac..a8064058821b766cd660f64e45117337f88f61b7 100644 (file)
@@ -41,7 +41,7 @@
           <dl class="m-doc">
             <dt id="__init__-8f54a">
               <span class="m-doc-wrap-bumper">def <a href="#__init__-8f54a" class="m-doc-self">__init__</a>(</span><span class="m-doc-wrap">self,
-              arg0: <a href="pybind_type_links.html#Enum" class="m-doc">Enum</a><span class="m-text m-dim">, /</span>)</span>
+              arg0: <a href="pybind_type_links.html#Enum" class="m-doc">Enum</a><span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>Constructor</dd>
           </dl>
index c3dd014b615a8702ea3b34973182eaafd579e369..49291baaf1af177131a81cde9a452a1b42ae65f7 100644 (file)
           <h2><a href="#functions">Functions</a></h2>
           <dl class="m-doc">
             <dt id="type_enum-3b87d">
-              <span class="m-doc-wrap-bumper">def <a href="#type_enum-3b87d" class="m-doc-self">type_enum</a>(</span><span class="m-doc-wrap">value: <a href="pybind_type_links.html#Enum" class="m-doc">Enum</a> = <a href="pybind_type_links.html#Enum-SECOND" class="m-doc">Enum.SECOND</a>)</span>
+              <span class="m-doc-wrap-bumper">def <a href="#type_enum-3b87d" class="m-doc-self">type_enum</a>(</span><span class="m-doc-wrap">value: <a href="pybind_type_links.html#Enum" class="m-doc">Enum</a> = <a href="pybind_type_links.html#Enum-SECOND" class="m-doc">Enum.SECOND</a>) -&gt; None</span>
             </dt>
             <dd>A function taking an enum</dd>
             <dt id="type_nested-9cd35">
-              <span class="m-doc-wrap-bumper">def <a href="#type_nested-9cd35" class="m-doc-self">type_nested</a>(</span><span class="m-doc-wrap">arg0: Tuple[<a href="pybind_type_links.Foo.html" class="m-doc">Foo</a>, List[<a href="pybind_type_links.html#Enum" class="m-doc">Enum</a>]]<span class="m-text m-dim">, /</span>)</span>
+              <span class="m-doc-wrap-bumper">def <a href="#type_nested-9cd35" class="m-doc-self">type_nested</a>(</span><span class="m-doc-wrap">arg0: Tuple[<a href="pybind_type_links.Foo.html" class="m-doc">Foo</a>, List[<a href="pybind_type_links.html#Enum" class="m-doc">Enum</a>]]<span class="m-text m-dim">, /</span>) -&gt; None</span>
             </dt>
             <dd>A function with nested type annotation</dd>
             <dt id="type_return-da39a">
index 21b5e44e92dc96d558f6ad0a1f1a88b026b00b79..451df5755e936be0c377c327df9bf200afa72b09 100644 (file)
@@ -71,6 +71,13 @@ class Signature(unittest.TestCase):
                 ('self', 'module.Thing', 'module.Thing', None),
             ], None))
 
+    def test_none_return(self):
+        self.assertEqual(parse_pybind_signature(self.state, [],
+            '__init__(self: module.Thing) -> None'),
+            ('__init__', '', [
+                ('self', 'module.Thing', 'module.Thing', None),
+            ], 'None'))
+
     def test_no_arg_types(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'thingy(self, the_other_thing)'),
@@ -79,6 +86,14 @@ class Signature(unittest.TestCase):
                 ('the_other_thing', None, None, None),
             ], None))
 
+    def test_none_arg_types(self):
+        self.assertEqual(parse_pybind_signature(self.state, [],
+            'thingy(self, the_other_thing: Callable[[], None])'),
+            ('thingy', '', [
+                ('self', None, None, None),
+                ('the_other_thing', 'Callable[[], None]', 'Callable[[], None]', None),
+            ], None))
+
     def test_square_brackets(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: Tuple[int, str], no_really: str) -> List[str]'),