-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(types): provide better type hints for a variety of generic types #4259
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of wrappers, is there anyway we can abuse the existing C++ classes? Like having an optional template arg that has a default value of "Any" or "object". Then if that special template arg is specified we revert to the old caster?
I think that would work.... though, would it break users that are using multiple modules with different versions of pybind11? |
Or if we want to go with legacy Python names, just copy the capitalized nature of the collections |
a3ed9d2
to
8633a05
Compare
I like that better, and it matches the annotation as well. I also changed |
8633a05
to
13afc03
Compare
If you are trying to support stub generation, you should probably add some unit tests for that stub generation (but in a separate PR). |
Thinking about this a bit more, certain casters already STRICTLY define and check/these types when relevant and these annotations won't be runtime checks (nor should they for performance reasons), but i worry that this may be confusing to a user who would be surprised when a particular cast breaks. @virtuald Also, some more information could specified with typing.Annotated, do we have know how we would support that in the future? We probably should also move some of our casters like the Array Caster to that see #3916 for example. |
Then let's put a note in there that it's just for documentation purposes? It's python, we're never guaranteed that anything gets validated at runtime.
I'm less interested in mypy validation and more interested in documentation and better IDE support. Historically IDEs haven't tried to do introspection on compiled packages, so things like autocomplete and documentation wouldn't work. In that spirit, if there are things we can add that make pybind11-stubgen's life easier, then that makes a lot of sense to me... but other things seem less useful. With a @lalaland I did add tests for stub generation? pybind11-stubgen generates stubs from the docstrings that pybind11 generates, and the test I added ensures they're in the right form. Unless you're going to integrate pybind11-stubgen into pybind11, there's nothing more to do here. |
90a0e08
to
7089306
Compare
@virtuald What about the edge case where a user wants to specify |
Added that. I note that the capitalized form of the builtin types (eg, |
What about untyped List, Set, etc... |
You have to use the lowercase version for those. |
I was contemplating a way to add an arbitrary annotation to types (like |
@virtuald You can make a string a template parameter in C++ now https://ctrpeach.io/posts/cpp20-string-literal-template-parameters/ Simply add some #include guards to only enable this for new enough compilers |
Ha, neat: template <typename T, detail::descr Name>
struct Annotate : T {
using T::T;
};
template <typename T, descr Name>
struct handle_type_name<Annotate<T, Name>> {
static constexpr auto name = Name;
};
...
m.def("annotate_int", [](const py::Annotate<py::int_, py::detail::const_name("Annotate[int, (1,2)]")> &) {}); That feels like a separate PR though, there's lots of room for bikeshedding and depending on how one feels about const_name could be more invasive. |
Then we should have a static_assert to enforce that at least one template is specified. Otherwise, people are going to accidentally create List[] and other invalid stubs. |
@Skylion007 Only tuple has variable templates, everything else has a required template arg, no need for static assert: template <typename T>
class List : public list {
using list::list;
}; I don't think python type hints allow per-element hints for lists, dicts, sets. |
@virtuald I like this idea. |
@virtuald , Ah typo, I meant handling the template args for Callable. |
I think having at least the basic python types in pybind11 makes sense, a third party library would be harder to discover. I'm.. +0.5 on putting all of this in |
@virtuald Why can't annotations be part of pybind11::arg? a.def("foo_function", [](py::list name){}, py::arg("name", "List[str]") ; py::annotate is going to be extremely confusing. |
@lalaland that's a neat approach, I like that we could specify arbitrarily complex type hints. It would have to also include a way to change the hint for return values and properties as well. Better than py::annotate. In theory that approach could replace this current PR. However, I do like having the ability to do |
Yep. Totally agree. I think the goal should be that pybind11 should automatically generate the correct types from the C++ argument types, with manual annotations in py::arg only as a fallback solution for when the automatic approach fails. |
@virtuald Could this be extended to solve this typing issue too? #3986 (comment) |
@virtuald, Actually, this should just be automatic for newer versions of Python for return types, right? Something to consider if we decide to refactor casters. |
de87faf
to
9acd090
Compare
I moved the typing related classes to I've been using this for awhile with no ill effects as far as I can tell. I don't believe there are any further changes to be made to this specific PR -- other suggestions mentioned above are better suited for their own PRs. |
I'll run this through global testing, to see if there are any surprises. (That will take 6+ hours.) |
Global testing passed (the internal test id is OCL:549398140:BASE:549657321:1689871259083:7fa91c99) I'll review asap. |
There is no additional enforcement of types at runtime. | ||
*/ | ||
|
||
template <typename... Types> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this feature will be clearer as such by having the types below in a typing
sub-namespace.
I'm not sure about (e.g.) py::typing::Tuple
vs py::typing::tuple
. Pure Python typing.tuple
does not exist (I tried with Python 3.11), therefore I'm guessing py::typing::Tuple
might be less surprising.
}; | ||
|
||
template <typename T> | ||
class List : public list { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be template <typename... Types>
like or Tuple
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update: I just ran this by a typing expert on my team, from that I learned:
List[float, str]
isn't a valid type annotation, since a list (unlike atuple
) can only have one element type. A list that contains both floats and strs can be typed aslist[float | str]
(orList[Union[float, str]]
for Python 3.9- compatibility).
Is that too tricky to implement for this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, list[float | str]
is fine / best, that's valid for older versions of Python as long as you only use it for typing and not at runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the equivalent for tuple
is tuple[float | str, ...]
. tuple[float, str]
is a size 2 tuple with a float as the first item, and a str as the second.
include/pybind11/typing.h
Outdated
template <typename... Types> | ||
struct handle_type_name<Tuple<Types...>> { | ||
static constexpr auto name | ||
= const_name("Tuple[") + concat(make_caster<Types>::name...) + const_name("]"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another thing I learned from the typing expert team mate:
Would it be better to have lower-case names (tuple, dict, list, set) in the output?
It depends on what Python versions you need to support. In Python 3.10 and higher, it is indeed preferred to use the lowercase builtin names.
That makes me think #ifdef
s of some sort would be best (to produce Tuple
for <= 3.9 and tuple
otherwise), although that adds a bit of clutter until we can drop 3.9 support.
Tying that back to my other comment, py::typing::tuple
vs py::typing::Tuple
:
Pure Python typing.Tuple
will likely be deleted at some point in the future, although IIUC there is no timeline yet.
We cannot reuse py::tuple
like pure Python reuses tuple
for typing purposes.
Should we have py::typing::tuple
or py::typing::Tuple
? I don't know. It's on the awkward side either way.
I think I'd settle for py::typing::tuple
(for any Python version), to not look old fashioned in the long run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For Python 3.7-3.9, you still can use lower case as long as you only use them for typing. In this case, it can only be used for typing, not runtime, so please use the lowercase, un-namespaced names, no ifdefs. I've mostly removed typing.* stuff from libraries that are 3.7+.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
Note that we have already firmly decided to remove Python 3.6 support asap (as soon as I find a moment).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Yes, rather assuming that in this discussion :) - besides, typing in 3.6 is pretty hard since everything has dropped it a while back)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add a class Union<T...> : object
to implement union markers in a more general way.
If there was a py::typing
namespace it would make more sense to implement such a thing, since py::Union
is a bit weird.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add a
class Union<T...> : object
to implement union markers in a more general way.If there was a
py::typing
namespace it would make more sense to implement such a thing, sincepy::Union
is a bit weird.
Sounds great to me. I felt strongly already that py::typing
is the way to go, now even more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I gave it a shot, but I think adding would be out of scope for this current PR because of two messy issues that came up.
If you add a Union
type then you get compilation errors when loading the argument because one would need to define a PYBIND11_OBJECT_COMMON
for it... which, you could add a check function to check if the incoming PyObject is one of the arg types, but the common types also have implicit conversions which wouldn't be caught here.
Additionally this would require defining a concat_pipe
version of concat
which is mostly copy/paste, but it's another thing to maintain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great to me now.
The only small doubt I still have, do we want py::typing::Tuple
or py::typing::tuple
?
I slightly favor py::typing::Tuple
as we have right now in this PR. It might look old fashioned from a pure Python viewpoint, but in the context of C++ it's a nice way to not have surprise name collisions (between py::tuple
and py::typing::tuple
).
I prefer |
Cool, thanks. @EthanSteinberg, @Skylion007, @henryiii, @sizmailov does anyone have a strong reason to prefer I'll wait a couple days for responses. If there are no objections, I'll merge this PR. |
(I just fixed my previous comment: should have been |
I would support upper case Tuple to avoid name collisions. |
Perfect, thanks, that makes 3 votes for upper case |
Oh, wait, @Skylion007, merging is blocked because you requested changes. Could you please look again? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also prefer Tuple
include/pybind11/typing.h
Outdated
pybind11/typing.h: Convenience wrapper classes for basic Python types | ||
with more explicit annotations. | ||
|
||
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I forgot to comment on this before, could you please either put your name & current year, or maybe:
// Copyright (c) 2023 The pybind Community.
(That's what I usually use.)
… information was copy-pasted from the git log output.
* Provide better type hints for a variety of generic types * Makes better documentation * tuple, dict, list, set, function * Move to py::typing * style: pre-commit fixes * Update copyright line with correct year and actual author. The author information was copy-pasted from the git log output. --------- Co-authored-by: Ralf W. Grosse-Kunstleve <rwgk@google.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
…#4772) * Copy clang 17 compatibility fixes from PR #4762 to a separate PR. * static py::exception<> -> static py::handle * Add `py::set_error()` but also try the suggestion of @malfet (pytorch/pytorch#106401 (review)). * clang 17 compatibility fixes (#4767) * Copy clang 17 compatibility fixes from PR #4762 to a separate PR. * Add gcc:13 C++20 * Add silkeh/clang:16-bullseye C++20 * chore(deps): update pre-commit hooks (#4770) updates: - [github.com/psf/black: 23.3.0 → 23.7.0](psf/black@23.3.0...23.7.0) - [github.com/astral-sh/ruff-pre-commit: v0.0.276 → v0.0.281](astral-sh/ruff-pre-commit@v0.0.276...v0.0.281) - [github.com/asottile/blacken-docs: 1.14.0 → 1.15.0](adamchainz/blacken-docs@1.14.0...1.15.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * docs: Remove upper bound on pybind11 in example pyproject.toml for setuptools (#4774) * docs: Remove upper bound on pybind11 in example pyproject.toml for setuptools * Update docs/compiling.rst --------- Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com> * Provide better type hints for a variety of generic types (#4259) * Provide better type hints for a variety of generic types * Makes better documentation * tuple, dict, list, set, function * Move to py::typing * style: pre-commit fixes * Update copyright line with correct year and actual author. The author information was copy-pasted from the git log output. --------- Co-authored-by: Ralf W. Grosse-Kunstleve <rwgk@google.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * Use `py::set_error()` everywhere possible (only one special case, in common.h). Overload `py::set_error(py::handle, py::handle)`. Change back to `static py::handle exc = ... .release();` Deprecate `py::exception<>::operator()` * Add `PYBIND11_WARNING_DISABLE` for INTEL and MSVC (and sort alphabetically). * `PYBIND11_WARNING_DISABLE_INTEL(10441)` does not work. For ICC only, falling back to the recommended `py::set_error()` to keep the testing simple. It is troublesome to add `--diag-disable=10441` specifically for test_exceptions.cpp, even that is non-ideal because it covers the entire file, not just the one line we need it for, and the value of exercising the trivial deprecated `operator()` on this one extra platform is practically zero. * Fix silly oversight. * NVHPC 23.5.0 generates deprecation warnings. They are currently not treated as errors, but falling back to using `py::set_error()` to not have to deal with that distraction. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Keto D. Zhang <keto.zhang@gmail.com> Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com> Co-authored-by: Dustin Spicuzza <dustin@virtualroadside.com>
Description
@sizmailov I'm pretty sure this will allow users to make better
.pyi
files too. :)Note: I'm open to bikeshedding on the names, but adding an underscore seemed like a reasonable approach.
Suggested changelog entry: