Skip to content

Commit 13afc03

Browse files
committed
Provide better type hints for a variety of generic types
* Makes better documentation * tuple, dict, list, set, function
1 parent fab1eeb commit 13afc03

File tree

5 files changed

+120
-0
lines changed

5 files changed

+120
-0
lines changed

docs/advanced/misc.rst

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

include/pybind11/cast.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,36 @@ struct handle_type_name<kwargs> {
906906
static constexpr auto name = const_name("**kwargs");
907907
};
908908

909+
template <typename... Types>
910+
struct handle_type_name<Tuple<Types...>> {
911+
static constexpr auto name
912+
= const_name("Tuple[") + concat(make_caster<Types>::name...) + const_name("]");
913+
};
914+
915+
template <typename K, typename V>
916+
struct handle_type_name<Dict<K, V>> {
917+
static constexpr auto name = const_name("Dict[") + make_caster<K>::name + const_name(", ")
918+
+ make_caster<V>::name + const_name("]");
919+
};
920+
921+
template <typename T>
922+
struct handle_type_name<List<T>> {
923+
static constexpr auto name = const_name("List[") + make_caster<T>::name + const_name("]");
924+
};
925+
926+
template <typename T>
927+
struct handle_type_name<Set<T>> {
928+
static constexpr auto name = const_name("Set[") + make_caster<T>::name + const_name("]");
929+
};
930+
931+
template <typename Return, typename... Args>
932+
struct handle_type_name<Callable<Return(Args...)>> {
933+
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
934+
static constexpr auto name = const_name("Callable[[") + concat(make_caster<Args>::name...)
935+
+ const_name("], ") + make_caster<retval_type>::name
936+
+ const_name("]");
937+
};
938+
909939
template <typename type>
910940
struct pyobject_caster {
911941
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
@@ -1937,6 +1937,11 @@ class tuple : public object {
19371937
detail::tuple_iterator end() const { return {*this, PyTuple_GET_SIZE(m_ptr)}; }
19381938
};
19391939

1940+
template <typename... Types>
1941+
class Tuple : public tuple {
1942+
using tuple::tuple;
1943+
};
1944+
19401945
// We need to put this into a separate function because the Intel compiler
19411946
// fails to compile enable_if_t<all_of<is_keyword_or_ds<Args>...>::value> part below
19421947
// (tested with ICC 2021.1 Beta 20200827).
@@ -1984,6 +1989,11 @@ class dict : public object {
19841989
}
19851990
};
19861991

1992+
template <typename K, typename V>
1993+
class Dict : public dict {
1994+
using dict::dict;
1995+
};
1996+
19871997
class sequence : public object {
19881998
public:
19891999
PYBIND11_OBJECT_DEFAULT(sequence, object, PySequence_Check)
@@ -2043,6 +2053,11 @@ class list : public object {
20432053
}
20442054
};
20452055

2056+
template <typename T>
2057+
class List : public list {
2058+
using list::list;
2059+
};
2060+
20462061
class args : public tuple {
20472062
PYBIND11_OBJECT_DEFAULT(args, tuple, PyTuple_Check)
20482063
};
@@ -2080,6 +2095,11 @@ class set : public anyset {
20802095
void clear() /* py-non-const */ { PySet_Clear(m_ptr); }
20812096
};
20822097

2098+
template <typename T>
2099+
class Set : public set {
2100+
using set::set;
2101+
};
2102+
20832103
class frozenset : public anyset {
20842104
public:
20852105
PYBIND11_OBJECT_CVT(frozenset, anyset, PyFrozenSet_Check, PyFrozenSet_New)
@@ -2098,6 +2118,14 @@ class function : public object {
20982118
bool is_cpp_function() const { return (bool) cpp_function(); }
20992119
};
21002120

2121+
template <typename Signature>
2122+
class Callable;
2123+
2124+
template <typename Return, typename... Args>
2125+
class Callable<Return(Args...)> : public function {
2126+
using function::function;
2127+
};
2128+
21012129
class staticmethod : public object {
21022130
public:
21032131
PYBIND11_OBJECT_CVT(staticmethod, object, detail::PyStaticMethod_Check, PyStaticMethod_New)

tests/test_pytypes.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,4 +804,10 @@ TEST_SUBMODULE(pytypes, m) {
804804
a >>= b;
805805
return a;
806806
});
807+
808+
m.def("annotate_tuple_float_str", [](const py::Tuple<py::float_, py::str>&) {});
809+
m.def("annotate_dict_str_int", [](const py::Dict<py::str, int>&) {});
810+
m.def("annotate_list_int", [](const py::List<int>&) {});
811+
m.def("annotate_set_str", [](const py::Set<std::string>&) {});
812+
m.def("annotate_fn", [](const py::Callable<int(py::List<py::str>, py::str)>&) {});
807813
}

tests/test_pytypes.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,3 +863,32 @@ def test_inplace_lshift(a, b):
863863
def test_inplace_rshift(a, b):
864864
expected = a >> b
865865
assert m.inplace_rshift(a, b) == expected
866+
867+
868+
def test_tuple_annotations(doc):
869+
assert (
870+
doc(m.annotate_tuple_float_str)
871+
== "annotate_tuple_float_str(arg0: Tuple[float, str]) -> None"
872+
)
873+
874+
875+
def test_dict_annotations(doc):
876+
assert (
877+
doc(m.annotate_dict_str_int)
878+
== "annotate_dict_str_int(arg0: Dict[str, int]) -> None"
879+
)
880+
881+
882+
def test_list_annotations(doc):
883+
assert doc(m.annotate_list_int) == "annotate_list_int(arg0: List[int]) -> None"
884+
885+
886+
def test_set_annotations(doc):
887+
assert doc(m.annotate_set_str) == "annotate_set_str(arg0: Set[str]) -> None"
888+
889+
890+
def test_fn_annotations(doc):
891+
assert (
892+
doc(m.annotate_fn)
893+
== "annotate_fn(arg0: Callable[[List[str], str], int]) -> None"
894+
)

0 commit comments

Comments
 (0)