[Python-ideas] Implicit submodule imports

Andrew Barnert abarnert at yahoo.com
Fri Sep 26 16:59:39 CEST 2014


On Sep 25, 2014, at 18:02, Nathaniel Smith <njs at pobox.com> wrote:

> On Thu, Sep 25, 2014 at 11:31 PM, Greg Ewing
> <greg.ewing at canterbury.ac.nz> wrote:
>> Nathaniel Smith wrote:
>>> 
>>> They are really really hard
>>> to do cleanly, and you risk all kinds of breakage in edge-cases (e.g.
>>> try reload()'ing a module that's been replaced by an object).
>> 
>> One small thing that might help is to allow the
>> __class__ of a module to be reassigned to a
>> subclass of the module type. That would allow
>> a module to be given custom behaviours, while
>> remaining a real module object so that reload()
>> etc. continue to work.
> 
> Heh, I was actually just pondering whether it would be opening too big
> a can of worms to suggest this myself. This is the best design I
> managed to come up with last time I looked at it, though in existing
> versions of python it requires ctypes hackitude to accomplish the
> __class__ reassignment. (The advantages of this approach are that (1)
> you get to use the full class machinery to define your "metamodule",
> (2) any existing references to the module get transformed in-place, so
> you don't have to worry about ending up with a mixture of old and new
> instances existing in the same program, (3) by subclassing and
> avoiding copying you automatically support all the subtleties and
> internal fields of actual module objects in a forward- and
> backward-compatible way.)

When I tried this a year or two ago, I did I with an import hook that allows you to specify metaclass=absolute.qualified.spam in any comment that comes before any non-comment lines, so you actually construct the module object as a subclass instance rather than re-classing it.

In theory that seems a lot cleaner. In practice it's a weird way to specify your type; it only works if the import-hooking module and the module that defines your type have already been imported and otherwise silently does the wrong thing; and my implementation was pretty hideous.

Is there a cleaner version of that we could do if we were modifying the normal import machinery instead of hooking it, and if it didn't have to work pre-3.4, and if it were part of the language instead of a hack?

IIRC (too hard to check from my phone on the train), a module is built by calling exec with a new global dict and then calling the module constructor with that dict, so it's just a matter of something like:

    cls = g.get('__metamodule__', module)
    if not issubclass(cls, module):
        raise TypeError('metamodule
{} is not a module type'.format(cls))
    mod = cls(name, doc, g)
    # etc.

Then you could import the module subclass and assign it to __metamodule__ from inside, rather than needing to pre-import stuff, and you'd get perfectly understandable errors, and so on.

It seems less hacky and more flexible than re-classing the module after construction, for the same reason metaclasses and, for that matter, normal class constructors are better than reclassing after the fact.

Of course I could be misremembering how modules are constructed, in which case... Never mind.

> 
> This would work today, and would solve all these problems, except for
> the following code in Objects/typeobject.c:object_set_class:
> 
>    if (!(newto->tp_flags & Py_TPFLAGS_HEAPTYPE) ||
>        !(oldto->tp_flags & Py_TPFLAGS_HEAPTYPE))
>    {
>        PyErr_Format(PyExc_TypeError,
>                     "__class__ assignment: only for heap types");
>        return -1;
>    }
>    if (compatible_for_assignment(oldto, newto, "__class__")) {
>        Py_INCREF(newto);
>        Py_TYPE(self) = newto;
>        Py_DECREF(oldto);
>        return 0;
>    }
> 
> The builtin "module" type is not a HEAPTYPE, so if we try to do
> mymodule.__class__ = mysubclass, then the !(oldto->tp_flags &
> Py_TPFLAGS_HEAPTYPE) check gets triggered and the assignment fails.
> 
> This code has been around forever, but I don't know why. AFAIK we
> could replace the above with
> 
>    if (compatible_for_assignment(oldto, newto, "__class__")) {
>        if (newto->tp_flags & Py_TPFLAGS_HEAPTYPE) {
>            Py_INCREF(newto);
>        }
>        Py_TYPE(self) = newto;
>        if (oldto->tp_flags & Py_TPFLAGS_HEAPTYPE) {
>           Py_DECREF(oldto);
>        }
>        return 0;
>    }
> 
> and everything would just work, but I could well be missing something?
> Is there some dragon lurking inside Python's memory management or is
> this just an ancient overabundance of caution?
> 
> -n
> 
> -- 
> Nathaniel J. Smith
> Postdoctoral researcher - Informatics - University of Edinburgh
> http://vorpus.org
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/


More information about the Python-ideas mailing list