Skip to content
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

Enable specifyingpy::metaclass(PyType_Type) without a C/C++ cast. #5015

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ interactive Python session demonstrating this example is shown below:
Static member functions can be bound in the same way using
:func:`class_::def_static`.

.. note::

By default pybind11 uses a custom metaclass which is known to be
incompatible with
`abc.ABCMeta <https://docs.python.org/3/library/abc.html#abc.ABCMeta>`_
and can also lead to other surprising side effects. In such cases,
using ``py::metaclass(PyType_Type)`` is often a good solution
(e.g. ``py::class_<Pet>(m, "Pet", py::metaclass(PyType_Type))``).
Please see
`#5015 <https://github.com/pybind/pybind11/pull/5015>`_
for more background.

.. note::

Binding C++ types in unnamed namespaces (also known as anonymous namespaces)
Expand Down
7 changes: 7 additions & 0 deletions include/pybind11/attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ struct dynamic_attr {};
struct buffer_protocol {};

/// Annotation which requests that a special metaclass is created for a type
/// NOTE: pybind11's default metaclass is not compatible with abc.ABCMeta
struct metaclass {
handle value;

Expand All @@ -90,6 +91,12 @@ struct metaclass {

/// Override pybind11's default metaclass
explicit metaclass(handle value) : value(value) {}

/// Example usage: py::metaclass(PyType_Type)
/// PyType_Type is recommended if compatibility with abc.ABCMeta is required.
/// The only potential downside is that static properties behave differently
/// (see pybind/pybind11#679 for background).
explicit metaclass(PyTypeObject &type_obj) : value(reinterpret_cast<PyObject *>(&type_obj)) {}
};

/// Specifies a custom callback with signature `void (PyHeapTypeObject*)` that
Expand Down
2 changes: 1 addition & 1 deletion tests/test_methods_and_attributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ TEST_SUBMODULE(methods_and_attributes, m) {

// test_metaclass_override
struct MetaclassOverride {};
py::class_<MetaclassOverride>(m, "MetaclassOverride", py::metaclass((PyObject *) &PyType_Type))
py::class_<MetaclassOverride>(m, "MetaclassOverride", py::metaclass(PyType_Type))
.def_property_readonly_static("readonly", [](const py::object &) { return 1; });

// test_overload_ordering
Expand Down
43 changes: 43 additions & 0 deletions tests/test_methods_and_attributes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import abc
import sys

import pytest
Expand Down Expand Up @@ -227,6 +228,48 @@ def test_metaclass_override():
assert isinstance(m.MetaclassOverride.__dict__["readonly"], int)


def test_abc_meta_incompatibility(): # Mostly to clearly expose the behavior.
with pytest.raises(TypeError, match="metaclass conflict"):

class ExampleMandAABC(m.ExampleMandA, metaclass=abc.ABCMeta):
pass


def test_abc_abc_1st_incompatibility(): # Mostly to clearly expose the behavior.
with pytest.raises(TypeError, match="metaclass conflict"):

class ABCExampleMandA(abc.ABC, m.ExampleMandA):
pass


def test_abc_abc_2nd_incompatibility(): # Mostly to clearly expose the behavior.
with pytest.raises(TypeError, match="metaclass conflict"):

class ExampleMandAABC(m.ExampleMandA, abc.ABC):
pass


def test_abc_meta_compatibility():
class MetaclassOverrideABC(m.MetaclassOverride, metaclass=abc.ABCMeta):
pass

assert type(MetaclassOverrideABC).__name__ == "ABCMeta"


def test_abc_abc_1st_compatibility():
class ABCMetaclassOverride(abc.ABC, m.MetaclassOverride):
pass

assert type(ABCMetaclassOverride).__name__ == "ABCMeta"


def test_abc_abc_2nd_compatibility():
class MetaclassOverrideABC(m.MetaclassOverride, abc.ABC):
pass

assert type(MetaclassOverrideABC).__name__ == "ABCMeta"


def test_no_mixed_overloads():
from pybind11_tests import detailed_error_messages_enabled

Expand Down
Loading