mirror of
https://github.com/pybind/pybind11.git
synced 2025-01-19 01:15:52 +00:00
Merge branch 'master' into sh_merge_master
This commit is contained in:
commit
3c85ffcf5a
@ -1649,6 +1649,31 @@ object object_or_cast(T &&o) {
|
|||||||
return pybind11::cast(std::forward<T>(o));
|
return pybind11::cast(std::forward<T>(o));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Declared in pytypes.h:
|
||||||
|
// Implemented here so that make_caster<T> can be used.
|
||||||
|
template <typename D>
|
||||||
|
template <typename T>
|
||||||
|
str_attr_accessor object_api<D>::attr_with_type_hint(const char *key) const {
|
||||||
|
#if !defined(__cpp_inline_variables)
|
||||||
|
static_assert(always_false<T>::value,
|
||||||
|
"C++17 feature __cpp_inline_variables not available: "
|
||||||
|
"https://en.cppreference.com/w/cpp/language/static#Static_data_members");
|
||||||
|
#endif
|
||||||
|
object ann = annotations();
|
||||||
|
if (ann.contains(key)) {
|
||||||
|
throw std::runtime_error("__annotations__[\"" + std::string(key) + "\"] was set already.");
|
||||||
|
}
|
||||||
|
ann[key] = make_caster<T>::name.text;
|
||||||
|
return {derived(), key};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename D>
|
||||||
|
template <typename T>
|
||||||
|
obj_attr_accessor object_api<D>::attr_with_type_hint(handle key) const {
|
||||||
|
(void) attr_with_type_hint<T>(key.cast<std::string>().c_str());
|
||||||
|
return {derived(), reinterpret_borrow<object>(key)};
|
||||||
|
}
|
||||||
|
|
||||||
// Placeholder type for the unneeded (and dead code) static variable in the
|
// Placeholder type for the unneeded (and dead code) static variable in the
|
||||||
// PYBIND11_OVERRIDE_OVERRIDE macro
|
// PYBIND11_OVERRIDE_OVERRIDE macro
|
||||||
struct override_unused {};
|
struct override_unused {};
|
||||||
|
@ -632,6 +632,14 @@ struct instance {
|
|||||||
static_assert(std::is_standard_layout<instance>::value,
|
static_assert(std::is_standard_layout<instance>::value,
|
||||||
"Internal error: `pybind11::detail::instance` is not standard layout!");
|
"Internal error: `pybind11::detail::instance` is not standard layout!");
|
||||||
|
|
||||||
|
// Some older compilers (e.g. gcc 9.4.0) require
|
||||||
|
// static_assert(always_false<T>::value, "...");
|
||||||
|
// instead of
|
||||||
|
// static_assert(false, "...");
|
||||||
|
// to trigger the static_assert() in a template only if it is actually instantiated.
|
||||||
|
template <typename>
|
||||||
|
struct always_false : std::false_type {};
|
||||||
|
|
||||||
/// from __cpp_future__ import (convenient aliases from C++14/17)
|
/// from __cpp_future__ import (convenient aliases from C++14/17)
|
||||||
#if defined(PYBIND11_CPP14)
|
#if defined(PYBIND11_CPP14)
|
||||||
using std::conditional_t;
|
using std::conditional_t;
|
||||||
|
@ -113,6 +113,17 @@ public:
|
|||||||
/// See above (the only difference is that the key is provided as a string literal)
|
/// See above (the only difference is that the key is provided as a string literal)
|
||||||
str_attr_accessor attr(const char *key) const;
|
str_attr_accessor attr(const char *key) const;
|
||||||
|
|
||||||
|
/** \rst
|
||||||
|
Similar to the above attr functions with the difference that the templated Type
|
||||||
|
is used to set the `__annotations__` dict value to the corresponding key. Worth noting
|
||||||
|
that attr_with_type_hint is implemented in cast.h.
|
||||||
|
\endrst */
|
||||||
|
template <typename T>
|
||||||
|
obj_attr_accessor attr_with_type_hint(handle key) const;
|
||||||
|
/// See above (the only difference is that the key is provided as a string literal)
|
||||||
|
template <typename T>
|
||||||
|
str_attr_accessor attr_with_type_hint(const char *key) const;
|
||||||
|
|
||||||
/** \rst
|
/** \rst
|
||||||
Matches * unpacking in Python, e.g. to unpack arguments out of a ``tuple``
|
Matches * unpacking in Python, e.g. to unpack arguments out of a ``tuple``
|
||||||
or ``list`` for a function call. Applying another * to the result yields
|
or ``list`` for a function call. Applying another * to the result yields
|
||||||
@ -182,6 +193,9 @@ public:
|
|||||||
/// Get or set the object's docstring, i.e. ``obj.__doc__``.
|
/// Get or set the object's docstring, i.e. ``obj.__doc__``.
|
||||||
str_attr_accessor doc() const;
|
str_attr_accessor doc() const;
|
||||||
|
|
||||||
|
/// Get or set the object's annotations, i.e. ``obj.__annotations__``.
|
||||||
|
object annotations() const;
|
||||||
|
|
||||||
/// Return the object's current reference count
|
/// Return the object's current reference count
|
||||||
ssize_t ref_count() const {
|
ssize_t ref_count() const {
|
||||||
#ifdef PYPY_VERSION
|
#ifdef PYPY_VERSION
|
||||||
@ -2558,6 +2572,19 @@ str_attr_accessor object_api<D>::doc() const {
|
|||||||
return attr("__doc__");
|
return attr("__doc__");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename D>
|
||||||
|
object object_api<D>::annotations() const {
|
||||||
|
#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9
|
||||||
|
// https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
|
||||||
|
if (!hasattr(derived(), "__annotations__")) {
|
||||||
|
setattr(derived(), "__annotations__", dict());
|
||||||
|
}
|
||||||
|
return attr("__annotations__");
|
||||||
|
#else
|
||||||
|
return getattr(derived(), "__annotations__", dict());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
template <typename D>
|
template <typename D>
|
||||||
handle object_api<D>::get_type() const {
|
handle object_api<D>::get_type() const {
|
||||||
return type::handle_of(derived());
|
return type::handle_of(derived());
|
||||||
|
@ -82,6 +82,18 @@ class Optional : public object {
|
|||||||
using object::object;
|
using object::object;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Final : public object {
|
||||||
|
PYBIND11_OBJECT_DEFAULT(Final, object, PyObject_Type)
|
||||||
|
using object::object;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class ClassVar : public object {
|
||||||
|
PYBIND11_OBJECT_DEFAULT(ClassVar, object, PyObject_Type)
|
||||||
|
using object::object;
|
||||||
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class TypeGuard : public bool_ {
|
class TypeGuard : public bool_ {
|
||||||
using bool_::bool_;
|
using bool_::bool_;
|
||||||
@ -251,6 +263,16 @@ struct handle_type_name<typing::Optional<T>> {
|
|||||||
= const_name("Optional[") + as_return_type<make_caster<T>>::name + const_name("]");
|
= const_name("Optional[") + as_return_type<make_caster<T>>::name + const_name("]");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct handle_type_name<typing::Final<T>> {
|
||||||
|
static constexpr auto name = const_name("Final[") + make_caster<T>::name + const_name("]");
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct handle_type_name<typing::ClassVar<T>> {
|
||||||
|
static constexpr auto name = const_name("ClassVar[") + make_caster<T>::name + const_name("]");
|
||||||
|
};
|
||||||
|
|
||||||
// TypeGuard and TypeIs use as_return_type to use the return type if available, which is usually
|
// TypeGuard and TypeIs use as_return_type to use the return type if available, which is usually
|
||||||
// the narrower type.
|
// the narrower type.
|
||||||
|
|
||||||
|
@ -1037,6 +1037,38 @@ TEST_SUBMODULE(pytypes, m) {
|
|||||||
#else
|
#else
|
||||||
m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false;
|
m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(__cpp_inline_variables)
|
||||||
|
// Exercises const char* overload:
|
||||||
|
m.attr_with_type_hint<py::typing::List<int>>("list_int") = py::list();
|
||||||
|
// Exercises py::handle overload:
|
||||||
|
m.attr_with_type_hint<py::typing::Set<py::str>>(py::str("set_str")) = py::set();
|
||||||
|
|
||||||
|
struct Empty {};
|
||||||
|
py::class_<Empty>(m, "EmptyAnnotationClass");
|
||||||
|
|
||||||
|
struct Static {};
|
||||||
|
auto static_class = py::class_<Static>(m, "Static");
|
||||||
|
static_class.def(py::init());
|
||||||
|
static_class.attr_with_type_hint<py::typing::ClassVar<float>>("x") = 1.0;
|
||||||
|
static_class.attr_with_type_hint<py::typing::ClassVar<py::typing::Dict<py::str, int>>>(
|
||||||
|
"dict_str_int")
|
||||||
|
= py::dict();
|
||||||
|
|
||||||
|
struct Instance {};
|
||||||
|
auto instance = py::class_<Instance>(m, "Instance", py::dynamic_attr());
|
||||||
|
instance.def(py::init());
|
||||||
|
instance.attr_with_type_hint<float>("y");
|
||||||
|
|
||||||
|
m.def("attr_with_type_hint_float_x",
|
||||||
|
[](py::handle obj) { obj.attr_with_type_hint<float>("x"); });
|
||||||
|
|
||||||
|
m.attr_with_type_hint<py::typing::Final<int>>("CONST_INT") = 3;
|
||||||
|
|
||||||
|
m.attr("defined___cpp_inline_variables") = true;
|
||||||
|
#else
|
||||||
|
m.attr("defined___cpp_inline_variables") = false;
|
||||||
|
#endif
|
||||||
m.def("half_of_number", [](const RealNumber &x) { return RealNumber{x.value / 2}; });
|
m.def("half_of_number", [](const RealNumber &x) { return RealNumber{x.value / 2}; });
|
||||||
// std::vector<T>
|
// std::vector<T>
|
||||||
m.def("half_of_number_vector", [](const std::vector<RealNumber> &x) {
|
m.def("half_of_number_vector", [](const std::vector<RealNumber> &x) {
|
||||||
|
@ -1103,6 +1103,96 @@ def test_dict_ranges(tested_dict, expected):
|
|||||||
assert m.transform_dict_plus_one(tested_dict) == expected
|
assert m.transform_dict_plus_one(tested_dict) == expected
|
||||||
|
|
||||||
|
|
||||||
|
# https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
|
||||||
|
def get_annotations_helper(o):
|
||||||
|
if isinstance(o, type):
|
||||||
|
return o.__dict__.get("__annotations__", None)
|
||||||
|
return getattr(o, "__annotations__", None)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
not m.defined___cpp_inline_variables,
|
||||||
|
reason="C++17 feature __cpp_inline_variables not available.",
|
||||||
|
)
|
||||||
|
def test_module_attribute_types() -> None:
|
||||||
|
module_annotations = get_annotations_helper(m)
|
||||||
|
|
||||||
|
assert module_annotations["list_int"] == "list[int]"
|
||||||
|
assert module_annotations["set_str"] == "set[str]"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
not m.defined___cpp_inline_variables,
|
||||||
|
reason="C++17 feature __cpp_inline_variables not available.",
|
||||||
|
)
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
sys.version_info < (3, 10),
|
||||||
|
reason="get_annotations function does not exist until Python3.10",
|
||||||
|
)
|
||||||
|
def test_get_annotations_compliance() -> None:
|
||||||
|
from inspect import get_annotations
|
||||||
|
|
||||||
|
module_annotations = get_annotations(m)
|
||||||
|
|
||||||
|
assert module_annotations["list_int"] == "list[int]"
|
||||||
|
assert module_annotations["set_str"] == "set[str]"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
not m.defined___cpp_inline_variables,
|
||||||
|
reason="C++17 feature __cpp_inline_variables not available.",
|
||||||
|
)
|
||||||
|
def test_class_attribute_types() -> None:
|
||||||
|
empty_annotations = get_annotations_helper(m.EmptyAnnotationClass)
|
||||||
|
static_annotations = get_annotations_helper(m.Static)
|
||||||
|
instance_annotations = get_annotations_helper(m.Instance)
|
||||||
|
|
||||||
|
assert empty_annotations is None
|
||||||
|
assert static_annotations["x"] == "ClassVar[float]"
|
||||||
|
assert static_annotations["dict_str_int"] == "ClassVar[dict[str, int]]"
|
||||||
|
|
||||||
|
assert m.Static.x == 1.0
|
||||||
|
|
||||||
|
m.Static.x = 3.0
|
||||||
|
static = m.Static()
|
||||||
|
assert static.x == 3.0
|
||||||
|
|
||||||
|
static.dict_str_int["hi"] = 3
|
||||||
|
assert m.Static().dict_str_int == {"hi": 3}
|
||||||
|
|
||||||
|
assert instance_annotations["y"] == "float"
|
||||||
|
instance1 = m.Instance()
|
||||||
|
instance1.y = 4.0
|
||||||
|
|
||||||
|
instance2 = m.Instance()
|
||||||
|
instance2.y = 5.0
|
||||||
|
|
||||||
|
assert instance1.y != instance2.y
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
not m.defined___cpp_inline_variables,
|
||||||
|
reason="C++17 feature __cpp_inline_variables not available.",
|
||||||
|
)
|
||||||
|
def test_redeclaration_attr_with_type_hint() -> None:
|
||||||
|
obj = m.Instance()
|
||||||
|
m.attr_with_type_hint_float_x(obj)
|
||||||
|
assert get_annotations_helper(obj)["x"] == "float"
|
||||||
|
with pytest.raises(
|
||||||
|
RuntimeError, match=r'^__annotations__\["x"\] was set already\.$'
|
||||||
|
):
|
||||||
|
m.attr_with_type_hint_float_x(obj)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
not m.defined___cpp_inline_variables,
|
||||||
|
reason="C++17 feature __cpp_inline_variables not available.",
|
||||||
|
)
|
||||||
|
def test_final_annotation() -> None:
|
||||||
|
module_annotations = get_annotations_helper(m)
|
||||||
|
assert module_annotations["CONST_INT"] == "Final[int]"
|
||||||
|
|
||||||
|
|
||||||
def test_arg_return_type_hints(doc):
|
def test_arg_return_type_hints(doc):
|
||||||
assert doc(m.half_of_number) == "half_of_number(arg0: Union[float, int]) -> float"
|
assert doc(m.half_of_number) == "half_of_number(arg0: Union[float, int]) -> float"
|
||||||
assert m.half_of_number(2.0) == 1.0
|
assert m.half_of_number(2.0) == 1.0
|
||||||
|
Loading…
Reference in New Issue
Block a user