mirror of
https://github.com/pybind/pybind11.git
synced 2026-04-29 03:01:32 +00:00
Add py::module_local() attribute for module-local type bindings
This commit adds a `py::module_local` attribute that lets you confine a
registered type to the module (more technically, the shared object) in
which it is defined, by registering it with:
py::class_<C>(m, "C", py::module_local())
This will allow the same C++ class `C` to be registered in different
modules with independent sets of class definitions. On the Python side,
two such types will be completely distinct; on the C++ side, the C++
type resolves to a different Python type in each module.
This applies `py::module_local` automatically to `stl_bind.h` bindings
when the container value type looks like something global: i.e. when it
is a converting type (for example, when binding a `std::vector<int>`),
or when it is a registered type itself bound with `py::module_local`.
This should help resolve potential future conflicts (e.g. if two
completely unrelated modules both try to bind a `std::vector<int>`.
Users can override the automatic selection by adding a
`py::module_local()` or `py::module_local(false)`.
Note that this does mildly break backwards compatibility: bound stl
containers of basic types like `std::vector<int>` cannot be bound in one
module and returned in a different module. (This can be re-enabled with
`py::module_local(false)` as described above, but with the potential for
eventual load conflicts).
This commit is contained in:
@@ -635,3 +635,139 @@ inheritance, which can lead to undefined behavior. In such cases, add the tag
|
||||
|
||||
The tag is redundant and does not need to be specified when multiple base types
|
||||
are listed.
|
||||
|
||||
.. _module_local:
|
||||
|
||||
Module-local class bindings
|
||||
===========================
|
||||
|
||||
When creating a binding for a class, pybind by default makes that binding
|
||||
"global" across modules. What this means is that a type defined in one module
|
||||
can be passed to functions of other modules that expect the same C++ type. For
|
||||
example, this allows the following:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// In the module1.cpp binding code for module1:
|
||||
py::class_<Pet>(m, "Pet")
|
||||
.def(py::init<std::string>());
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// In the module2.cpp binding code for module2:
|
||||
m.def("pet_name", [](Pet &p) { return p.name(); });
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from module1 import Pet
|
||||
>>> from module2 import pet_name
|
||||
>>> mypet = Pet("Kitty")
|
||||
>>> pet_name(mypet)
|
||||
'Kitty'
|
||||
|
||||
When writing binding code for a library, this is usually desirable: this
|
||||
allows, for example, splitting up a complex library into multiple Python
|
||||
modules.
|
||||
|
||||
In some cases, however, this can cause conflicts. For example, suppose two
|
||||
unrelated modules make use of an external C++ library and each provide custom
|
||||
bindings for one of that library's classes. This will result in an error when
|
||||
a Python program attempts to import both modules (directly or indirectly)
|
||||
because of conflicting definitions on the external type:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// dogs.cpp
|
||||
|
||||
// Binding for external library class:
|
||||
py::class<pets::Pet>(m, "Pet")
|
||||
.def("name", &pets::Pet::name);
|
||||
|
||||
// Binding for local extension class:
|
||||
py::class<Dog, pets::Pet>(m, "Dog")
|
||||
.def(py::init<std::string>());
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// cats.cpp, in a completely separate project from the above dogs.cpp.
|
||||
|
||||
// Binding for external library class:
|
||||
py::class<pets::Pet>(m, "Pet")
|
||||
.def("get_name", &pets::Pet::name);
|
||||
|
||||
// Binding for local extending class:
|
||||
py::class<Cat, pets::Pet>(m, "Cat")
|
||||
.def(py::init<std::string>());
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> import cats
|
||||
>>> import dogs
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
ImportError: generic_type: type "Pet" is already registered!
|
||||
|
||||
To get around this, you can tell pybind11 to keep the external class binding
|
||||
localized to the module by passing the ``py::module_local()`` attribute into
|
||||
the ``py::class_`` constructor:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// Pet binding in dogs.cpp:
|
||||
py::class<pets::Pet>(m, "Pet", py::module_local())
|
||||
.def("name", &pets::Pet::name);
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// Pet binding in cats.cpp:
|
||||
py::class<pets::Pet>(m, "Pet", py::module_local())
|
||||
.def("get_name", &pets::Pet::name);
|
||||
|
||||
This makes the Python-side ``dogs.Pet`` and ``cats.Pet`` into distinct classes
|
||||
that can only be accepted as ``Pet`` arguments within those classes. This
|
||||
avoids the conflict and allows both modules to be loaded.
|
||||
|
||||
One limitation of this approach is that because ``py::module_local`` types are
|
||||
distinct on the Python side, it is not possible to pass such a module-local
|
||||
type as a C++ ``Pet``-taking function outside that module. For example, if the
|
||||
above ``cats`` and ``dogs`` module are each extended with a function:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
m.def("petname", [](pets::Pet &p) { return p.name(); });
|
||||
|
||||
you will only be able to call the function with the local module's class:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> import cats, dogs # No error because of the added py::module_local()
|
||||
>>> mycat, mydog = cats.Cat("Fluffy"), dogs.Dog("Rover")
|
||||
>>> (cats.petname(mycat), dogs.petname(mydog))
|
||||
('Fluffy', 'Rover')
|
||||
>>> cats.petname(mydog)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: petname(): incompatible function arguments. The following argument types are supported:
|
||||
1. (arg0: cats.Pet) -> str
|
||||
|
||||
Invoked with: <dogs.Dog object at 0x123>
|
||||
|
||||
.. note::
|
||||
|
||||
STL bindings (as provided via the optional :file:`pybind11/stl_bind.h`
|
||||
header) apply ``py::module_local`` by default when the bound type might
|
||||
conflict with other modules; see :ref:`stl_bind` for details.
|
||||
|
||||
.. note::
|
||||
|
||||
The localization of the bound types is actually tied to the shared object
|
||||
or binary generated by the compiler/linker. For typical modules created
|
||||
with ``PYBIND11_MODULE()``, this distinction is not significant. It is
|
||||
possible, however, when :ref:`embedding` to embed multiple modules in the
|
||||
same binary (see :ref:`embedding_modules`). In such a case, the
|
||||
localization will apply across all embedded modules within the same binary.
|
||||
|
||||
.. seealso::
|
||||
|
||||
The file :file:`tests/test_local_bindings.cpp` contains additional examples
|
||||
that demonstrate how ``py::module_local()`` works.
|
||||
|
||||
Reference in New Issue
Block a user