Skip to content

Commit 9acd090

Browse files
committed
Provide better type hints for a variety of generic types
* Makes better documentation * tuple, dict, list, set, function
1 parent 0694ec6 commit 9acd090

File tree

5 files changed

+133
-0
lines changed

5 files changed

+133
-0
lines changed

docs/advanced/misc.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,3 +370,30 @@ before they are used as a parameter or return type of a function:
370370
pyFoo.def(py::init<const ns::Bar&>());
371371
pyBar.def(py::init<const ns::Foo&>());
372372
}
373+
374+
Setting inner type hints in docstrings
375+
======================================
376+
377+
When you use pybind11 wrappers for ``list``, ``dict``, and other generic python
378+
types, the docstring will just display the generic type. You can convey the
379+
inner types in the docstring by using a special 'typed' version of the generic
380+
type.
381+
382+
.. code-block:: cpp
383+
384+
PYBIND11_MODULE(example, m) {
385+
m.def("pass_list_of_str", [](py::List<py::str> arg) {
386+
// arg can be used just like py::list
387+
));
388+
}
389+
390+
The following special types are available:
391+
392+
* ``py::Tuple<Args...>``
393+
* ``py::Dict<K, V>``
394+
* ``py::List<V>``
395+
* ``py::Set<V>``
396+
* ``py::Callable<Signature>``
397+
398+
.. warning:: Just like in python, these are merely hints. They don't actually
399+
enforce the types of their contents at runtime or compile time.

include/pybind11/cast.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,42 @@ struct handle_type_name<kwargs> {
910910
static constexpr auto name = const_name("**kwargs");
911911
};
912912

913+
template <typename... Types>
914+
struct handle_type_name<Tuple<Types...>> {
915+
static constexpr auto name
916+
= const_name("Tuple[") + concat(make_caster<Types>::name...) + const_name("]");
917+
};
918+
919+
template <>
920+
struct handle_type_name<Tuple<>> {
921+
// PEP 484 specifies this syntax for an empty tuple
922+
static constexpr auto name = const_name("Tuple[()]");
923+
};
924+
925+
template <typename K, typename V>
926+
struct handle_type_name<Dict<K, V>> {
927+
static constexpr auto name = const_name("Dict[") + make_caster<K>::name + const_name(", ")
928+
+ make_caster<V>::name + const_name("]");
929+
};
930+
931+
template <typename T>
932+
struct handle_type_name<List<T>> {
933+
static constexpr auto name = const_name("List[") + make_caster<T>::name + const_name("]");
934+
};
935+
936+
template <typename T>
937+
struct handle_type_name<Set<T>> {
938+
static constexpr auto name = const_name("Set[") + make_caster<T>::name + const_name("]");
939+
};
940+
941+
template <typename Return, typename... Args>
942+
struct handle_type_name<Callable<Return(Args...)>> {
943+
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
944+
static constexpr auto name = const_name("Callable[[") + concat(make_caster<Args>::name...)
945+
+ const_name("], ") + make_caster<retval_type>::name
946+
+ const_name("]");
947+
};
948+
913949
template <typename type>
914950
struct pyobject_caster {
915951
template <typename T = type, enable_if_t<std::is_same<T, handle>::value, int> = 0>

include/pybind11/pytypes.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,6 +1964,11 @@ class tuple : public object {
19641964
detail::tuple_iterator end() const { return {*this, PyTuple_GET_SIZE(m_ptr)}; }
19651965
};
19661966

1967+
template <typename... Types>
1968+
class Tuple : public tuple {
1969+
using tuple::tuple;
1970+
};
1971+
19671972
// We need to put this into a separate function because the Intel compiler
19681973
// fails to compile enable_if_t<all_of<is_keyword_or_ds<Args>...>::value> part below
19691974
// (tested with ICC 2021.1 Beta 20200827).
@@ -2011,6 +2016,11 @@ class dict : public object {
20112016
}
20122017
};
20132018

2019+
template <typename K, typename V>
2020+
class Dict : public dict {
2021+
using dict::dict;
2022+
};
2023+
20142024
class sequence : public object {
20152025
public:
20162026
PYBIND11_OBJECT_DEFAULT(sequence, object, PySequence_Check)
@@ -2070,6 +2080,11 @@ class list : public object {
20702080
}
20712081
};
20722082

2083+
template <typename T>
2084+
class List : public list {
2085+
using list::list;
2086+
};
2087+
20732088
class args : public tuple {
20742089
PYBIND11_OBJECT_DEFAULT(args, tuple, PyTuple_Check)
20752090
};
@@ -2107,6 +2122,11 @@ class set : public anyset {
21072122
void clear() /* py-non-const */ { PySet_Clear(m_ptr); }
21082123
};
21092124

2125+
template <typename T>
2126+
class Set : public set {
2127+
using set::set;
2128+
};
2129+
21102130
class frozenset : public anyset {
21112131
public:
21122132
PYBIND11_OBJECT_CVT(frozenset, anyset, PyFrozenSet_Check, PyFrozenSet_New)
@@ -2125,6 +2145,14 @@ class function : public object {
21252145
bool is_cpp_function() const { return (bool) cpp_function(); }
21262146
};
21272147

2148+
template <typename Signature>
2149+
class Callable;
2150+
2151+
template <typename Return, typename... Args>
2152+
class Callable<Return(Args...)> : public function {
2153+
using function::function;
2154+
};
2155+
21282156
class staticmethod : public object {
21292157
public:
21302158
PYBIND11_OBJECT_CVT(staticmethod, object, detail::PyStaticMethod_Check, PyStaticMethod_New)

tests/test_pytypes.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,4 +809,11 @@ TEST_SUBMODULE(pytypes, m) {
809809
a >>= b;
810810
return a;
811811
});
812+
813+
m.def("annotate_tuple_float_str", [](const py::Tuple<py::float_, py::str> &) {});
814+
m.def("annotate_tuple_empty", [](const py::Tuple<> &) {});
815+
m.def("annotate_dict_str_int", [](const py::Dict<py::str, int> &) {});
816+
m.def("annotate_list_int", [](const py::List<int> &) {});
817+
m.def("annotate_set_str", [](const py::Set<std::string> &) {});
818+
m.def("annotate_fn", [](const py::Callable<int(py::List<py::str>, py::str)> &) {});
812819
}

tests/test_pytypes.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,3 +877,38 @@ def test_inplace_lshift(a, b):
877877
def test_inplace_rshift(a, b):
878878
expected = a >> b
879879
assert m.inplace_rshift(a, b) == expected
880+
881+
882+
def test_tuple_nonempty_annotations(doc):
883+
assert (
884+
doc(m.annotate_tuple_float_str)
885+
== "annotate_tuple_float_str(arg0: Tuple[float, str]) -> None"
886+
)
887+
888+
889+
def test_tuple_empty_annotations(doc):
890+
assert (
891+
doc(m.annotate_tuple_empty) == "annotate_tuple_empty(arg0: Tuple[()]) -> None"
892+
)
893+
894+
895+
def test_dict_annotations(doc):
896+
assert (
897+
doc(m.annotate_dict_str_int)
898+
== "annotate_dict_str_int(arg0: Dict[str, int]) -> None"
899+
)
900+
901+
902+
def test_list_annotations(doc):
903+
assert doc(m.annotate_list_int) == "annotate_list_int(arg0: List[int]) -> None"
904+
905+
906+
def test_set_annotations(doc):
907+
assert doc(m.annotate_set_str) == "annotate_set_str(arg0: Set[str]) -> None"
908+
909+
910+
def test_fn_annotations(doc):
911+
assert (
912+
doc(m.annotate_fn)
913+
== "annotate_fn(arg0: Callable[[List[str], str], int]) -> None"
914+
)

0 commit comments

Comments
 (0)