On Tue, Apr 26, 2022 at 4:04 AM <dw-git@d-woods.co.uk> wrote:
Larry Hastings wrote:
[...]
Now comes the one thing that we might call a "trick". The trick: when we allocate the ForwardClass instance C, we make it as big as a class object can ever get. (Mark Shannon assures me this is simply "heap type", and he knows far more about CPython internals than I ever will.)
This proposal will indeed surpass almost all concerns I raised earlier, if not all. It is perfectly legal to have a custom metaclass not passing "**kwargs" to type.__new__, and I think most classes don't do it: these in general do not expect a "**kwargs" in general and will simply not run the first time the code is called, and could be updated at that point. Besides, there is no problem in keeping this compatible for "pre" and "pos' this PEP. I had thought of that possibility before typing my other answers, and the major problem of "inplace modifying' is that one can't know for sure the final size of the class due to "__slots__". Over allocating a couple hundred bytes could make for reasonable slots, but it could simply raise a runtime exception if the final call would require more slots than this maximum size - so, I don't think this is a blocking concern. As for
What could go wrong? My biggest question so far: is there such a thing as a metaclass written in C, besides type itself? Are there metaclasses with a __new__ that *doesn't* call super().__new__ or three-argument type? If there are are metaclasses that allocate their own class objects out of raw bytes, they'd likely sidestep this entire process. I suspect this is rare, if indeed it has ever been done. Anyway, that'd break this mechanism, so exotic metaclasses like these wouldn't work with "forward-declared classes". But at least they needn't fail silently. We just need to add a guard after the call to metaclass.__new__: if we passed in "__forward__=C" into metaclass.__new__, and metaclass.__new__ didn't return C, we raise an exception.
That. There are some metaclasses. I did not check, but I do suspect even some of the builtin-types like "list" and "tuple" do bypass "type.__new__" (they fail when used with multiple inheritance along with ABCs, in that they do not check for abstractmethods). This is fixable. Third party "raw' metaclasses that re-do the object structure could simply runtime err until, _and if ever desired_, rewritten to support this feature as you put it. Even for metaclasses in pure Python, sometimes they won't resolve to an instance of the class itself (think of the behavior seen in pathlib.Path which acts as a factory to a subclass)- I see no problem in these just not supporting this feature as well. ====================== With this in mind, the major concerns are those put by Carl Meyer on the "Part 1" thread, namely, that this might not be usable for static checking at all - https://mail.python.org/archives/list/python-dev@python.org/message/NMCS77YF... And, of course, my suggestion that the problem this tries to resolve is already resolved by the use of typing.Protocol, as far as type-checking is concerned. Adding a way for a Protocol to be able to find its registered implementations and instantiate one of them when needed would solve this for "real forward referencing" as well. All in all, I still think this complicates things too much for little gain - people needing real forward references always could find a way out, since Python 2 times. And, while in my earlier e-mails I wrote that I mentioned PEP 563 could also resolve this, I really was thinking about PEP 649 - although, I think, actually, any of the 2 could solve the problem annotation wise. If needed for "real code" instead of annotations, extending Protocol so that it can find implementations, could work as well.