TL;DR (literally, I will go back and read it now, but after reading the first paragraphs: _a proxy_ object yes, then dividing class creation in 2 blocks would not break things)
/me goes back to text. On Fri, Apr 22, 2022 at 10:20 PM Larry Hastings <la...@hastings.org> wrote: > > Here's one alternate idea for how to implement the "forward class" syntax. > > The entire point of the "forward class" statement is that it creates > the real actual class object. But what if it wasn't actually the > "real" class object? What if it was only a proxy for the real object? > > In this scenario, the syntax of "forward object" remains the same. > You define the class's bases and metaclass. But all "forward class" > does is create a simple, lightweight class proxy object. This object > has a few built-in dunder values, __name__ etc. It also allows you > to set attributes, so let's assume (for now) it calls > metaclass.__prepare__ and uses the returned "dict-like object" as > the class proxy object __dict__. > > "continue class" internally performs all the rest of the > class-creation machinery. (Everything except __prepare__, as we > already called that.) The first step is metaclass.__new__, which > returns the real class object. "continue class" takes that > object and calls a method on the class proxy object that says > "here's your real class object". From that moment on, the proxy > becomes a pass-through for the "real" class object, and nobody > ever sees a reference to the "real" class object ever again. > Every interaction with the class proxy object is passed through > to the underlying class object. __getattribute__ calls on the > proxy look up the attribute in the underlying class object. If > the object returned is a bound method object, it rebinds that > callable with the class proxy instead, so that the "self" passed > in to methods is the proxy object. Both base_cls.__init_subclass__ > and cls.__init__ see the proxy object during class creation. As far > as Python user code is concerned, the class proxy *is* the class, > in every way, important or not. > > The upside: this moves all class object creation code into "continue > class" call. We don't have to replace __new__ with two new calls. > > The downside: a dinky overhead to every interaction with a "forward > class" class object and with instances of a "forward class" class > object. > > > A huge concern: how does this interact with metaclasses implemented > in C? If you make a method call on a proxy class object, and that > calls a C function from the metaclass, we'd presumably have to pass > in the "real class object", not the proxy class object. Which means > references to the real class object could leak out somewhere, and > now we have a real-class-object vs proxy-class-object identity crisis. > Is this a real concern? > > > A possible concern: what if metaclass.__new__ keeps a reference to > the object it created? Now we have two objects with an identity > crisis. I don't know if people ever do that. Fingers crossed that > they don't. Or maybe we add a new dunder method: > > @special_cased_staticmethod > metaclass.__bind_proxy__(metaclass, proxy, cls) > > This tells the metaclass "bind cls to this proxy object", so > metaclasses that care can update their database or whatever. > The default implementation uses the appropriate mechanism, > whatever it is. > > One additional probably-bad idea: in the case where it's just a > normal "class" statement, and we're not binding it to a proxy, > should we call this? > > metaclass.__bind_proxy__(metaclass, None, cls) > > The idea there being "if you register the class objects you create, > do the registration in __bind_proxy__, it's always called, and you'll > always know the canonical object in there". I'm guessing probably not, > in which case we tell metaclasses that track the class objects we > create "go ahead and track the object you return from __new__, but > be prepared to update your tracking info in case we call __bind_proxy__ > on you". > > > A small but awfully complicated wrinkle here: what do we do if the > metaclass implements __del__? Obviously, we have to call __del__ > with the "real" class object, so it can be destroyed properly. > But __del__ might resurrect that object, which means someone took a > reference to it. > > > > One final note. Given that, in this scenario, all real class creation > happens in "continue class", we could move the bases and metaclass > declaration down to the "continue class" statement. The resulting > syntax would look like: > > forward class X > > ... > > continue class X(base1, base2, metaclass=AmazingMeta, > rocket="booster") > > Is that better? worse? doesn't matter? I don't have an intuition about > it right now--I can see advantages to both sides, and no obvious > deciding factor. Certainly this syntax prevents us from calling > __prepare__ so early, so we'd have to use a real dict in the "forward > class" proxy object until we reached continue, then copy the values from > that dict into the "dict-like object", etc. > > _______________________________________________ > Python-Dev mailing list -- python-dev@python.org > To unsubscribe send an email to python-dev-le...@python.org > https://mail.python.org/mailman3/lists/python-dev.python.org/ > Message archived at > https://mail.python.org/archives/list/python-dev@python.org/message/OJRA7F7EMRJCIXQPRRKZZ7YMFD2ZKQV2/ > Code of Conduct: http://python.org/psf/codeofconduct/ >
_______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-le...@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/DWPAEYPLYDRV2LQV7YOQY4JVG7QJCGLC/ Code of Conduct: http://python.org/psf/codeofconduct/