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

[BUG]: Can't monkeypatch __init__ with Python 3.12 and nanobind 2.2.0 #750

Closed
matthiasdiener opened this issue Oct 4, 2024 · 3 comments
Closed

Comments

@matthiasdiener
Copy link
Contributor

matthiasdiener commented Oct 4, 2024

Problem description

It seems that starting from nanobind 2.2.0, monkeypatching a C++ class' __init__ method with a Python function is silently ignored. This happens only in Python 3.12, not (e.g.) in 3.11.

I have bisected this to #701.

Reproducible example code

t.py:

from my_ext import MyType

def my_init(self):
    print("python __init__")

MyType.__init__ = my_init

# Calls C++'s __init__ in Python 3.12 and nanobind 2.2, not the monkeypatched one above:
MyType()

my_ext.cpp:

#include <nanobind/nanobind.h>
#include <iostream>

struct MyType {
    MyType() {}};

NB_MODULE(my_ext, m) {
    nanobind::class_<MyType>(m, "MyType")
        .def("__init__",
                [](MyType *t)
                {
                    std::cout << "C++ __init__ " << std::endl;
                    new (t) MyType();
                });
}

Output on Python 3.12 with nanobind 2.2.0:

$ python t.py
C++ __init__

Output on Python 3.11 or (Python 3.12 with nanobind 2.1.0):

$ python t.py
python __init__

cc inducer/pyopencl#789 @inducer

@wjakob
Copy link
Owner

wjakob commented Oct 5, 2024

This wasn't supported behavior before, so I don't consider this a regression. Basically what happens is that nanobind now uses a faster code path to simultaneously execute __new__ and __init__ without having to go through Python dictionary lookups to find both methods. The downside is that if you interfere with the official object construction method, things will break.

However, there has always been an official mechanism to customize object construction by specifying the Py_tp_init slot (see here for details), and this turns off the optimization.

What you should be able to do is to specify a dummy Py_tp_init slot that just fails and then override it with extra C++ and/or Python bindings.

wjakob added a commit that referenced this issue Oct 8, 2024
wjakob added a commit that referenced this issue Oct 8, 2024
@wjakob
Copy link
Owner

wjakob commented Oct 8, 2024

In commit bbbd022, I added a test that demonstrates how to do this. (No functionality changes in nanobind, this is just to show you and make sure that it keeps on working).

@wjakob wjakob closed this as completed Oct 8, 2024
@inducer
Copy link

inducer commented Oct 8, 2024

Thank you for the demonstration! Even so, we'll simply move the relevant constructors to C++. inducer/pyopencl#791

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants