How are interfaces initialized in Python? (looking for docs)

Suppose we have a Python class that inherits from GObject.Object and from some interface.

class MyClass(GObject.Object, Gio.ListModel):
    def __init__(self):
        GObject.Object.__init__(self) # GObject initialized, what about ListModel?

    def do_get_item(self, index):
        whatever()

As far as I could see, every interface defines some methods and also methods prefixed by do_. Those latter ones are the symbols to be overriden in the subclass.

I guess the unprefixed symbols get instantiated with some stubs that call Python functions back (the ones with do_).

My question is, how are the slots for those stubs initialized? Does the GObject.Object.__init__ look for interfaces and creates slots for them? Or does something need to be called explicitly?

I tried to read the code, but everything seems to be auto-generated and there’s no actual code implementing most functions.

1 Like

Hi,

There are some docs here: Subclassing - PyGObject

Yes

That’s the idea, yes.
The last overridden virtual method (the do_ in Python) is the one that will be called.
Also, sometimes those unprefixed APIs do a few more stuff than just calling the virtual.

There is always a default implementation available, provided by the class itself.
Sometimes they’re just empty, sometimes they are actual implementations.

For example, it’s how the virtuals of Gio.Application are defined:

static void
g_application_class_init (GApplicationClass *class)
{
  ...
  class->startup = g_application_real_startup;
  class->shutdown = g_application_real_shutdown;
  class->activate = g_application_real_activate;
  class->open = g_application_real_open;
  ...
}

Then, Gtk.Application inherits from Gio.Application and overrides some of these virtuals:

static void
gtk_application_class_init (GtkApplicationClass *class)
{
  GApplicationClass *application_class = G_APPLICATION_CLASS (class);
  ...
  application_class->startup = gtk_application_startup;
  application_class->shutdown = gtk_application_shutdown;
  ...
}

There is no reliable way from the docs to know if a virtual does something or not. Must look into the code.

For example, the startup virtual of Gtk.Application will do a lot of important stuff like calling gtk_init(), so it’s recommended to chain it up if you inherit:

class MyApplication(Gtk.Application):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def do_startup(self):
        # Chain up
        Gtk.Application.do_startup(self)
        # Do your custom stuff here
        whatever()

Some other default virtuals do nothing, and the chain-up is then not necessary.
You can also omit the chain-up to be sure to completely replace the original behavior.

If any of the do_ functions is not implemented, the class segfaults.

Ah, I misread your question it’s about interfaces not classes. :slight_smile:

That depends too.
From what I see in the code, Gio.ListModel is purely abstract, and expects an implementation to be provided for all these virtuals.

Others interfaces like Gtk.Scrollable.get_border can deal with missing virtuals implementations and return a fallback value if missing.

OK, but how does it work on C-level? Are there any docs?

Unprefixed functions are normal C functions I presume, and the ones prefixed with do_ are Python callbacks that are called from unprefixed ones. But if the do_ function is not implemented the program segfaults instead of raising an exception.

I guess the callbacks are null pointer by default and Gtk doesn’t check it but tries to call them.

On C-level, you can refer to the GObject docs:

When using Python, the do_myvirtual method will be mapped to the interface->myvirtual C structure member.

That depends on the unprefixed function implementation.

For example, gtk_scrollable_get_border() checks if a virtual is set (i.e. not NULL) before trying to call it.
On the other side, g_list_model_get_item() blindly tries to call the virtual, which leads to a segfault if it wasn’t set.

OK, that’s a blatant bug. Where do I report?

Not a bug: a ListModel implementation without a way to get an item at the given index would be absolutely pointless.

You’re supposed to always implement all virtual functions in an interface.

1 Like

Segfault is not a proper way to report a bug to a developer. The library should raise an exception with a right error message to indicate what’s wrong. In Python it should be: NotImplementedError("Virtual method do_whatever is missing from class UserClass implementing interface SomeInterface.").

It could be fixed easily by adding default implementations of all do_ functions raising the correct exception. This could even be automated by analyzing class hierarchy.

A Python developer, maybe. But we are talking about a C library, and it’s perfectly valid to segfault and break horribly during development.

Feel free to open a merge request to that effect against pygobject.

Sadly, it’s not so easy. That would break some usecases like gtk_scrollable_get_border(), where the border thing is an optional feature so the interface gracefully manages the absence of virtual. Raising a NotImplemented exception in that case breaks the whole thing…

For abstract virtuals, ideally the unprefixed C functions should do something like:

g_return_if_fail (iface->myvirtual != NULL);

but that’s not always a good idea, especially for ListModel which need to have a minimal overhead because typically called very frequently.

As ebassi said, it’s the interface’s user job to make sure all required virtuals are implemented. Raising exceptions just replace errors by other errors, which is not helping.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.