On 4/22/22 20:58, Steven D'Aprano wrote:
On Fri, Apr 22, 2022 at 06:13:57PM -0700, Larry Hastings wrote:

This PEP proposes an additional syntax for declaring a class which splits
this work across two statements:
* The first statement is `forward class`, which declares the class and binds
   the class object.
* The second statement is `continue class`, which defines the contents
   of the class in the "class body".

To be clear: `forward class` creates the official, actual class object.
Code that wants to take a reference to the class object may take references
to the `forward class` declared class, and interact with it as normal.
However, a class created by `forward class` can't be *instantiated*
until after the matching `continue class` statement finishes.
Since the "forward class" is a real class,

It's a "forward-declared class object".  It's the real class object, but it hasn't been fully initialized yet, and won't be until the "continue class" statement.


it doesn't need any new
syntax to create it. Just use plain old regular class syntax.

     class X(object):
         """Doc string"""
         attribute = 42

And now we have our X class, ready to use in annotations.

To add to it, or "continue class" in your terms, we can already do this:

     X.value = "Hello World"

But if X has a metaclass that defines __new__ , "value" won't be defined yet, so metaclass.__new__ won't be able to react to and possibly modify it.  Similarly for metaclass.__init__ and BaseClass.__init_subclass__.

So, while your suggested technique doesn't "break" class creation per se, it prevents the user from benefiting from metaclasses and base classes using these advanced techniques.


Counter proposal:

`continue class expression:` evaluates expression to an existing class
object (or raises an exception) and introduces a block. The block is
executed inside that class' namespace, as the `class` keyword does,
except the class already exists.

If "continue class" is run on an already-created class, this breaks the functionality of __prepare__, which creates the namespace used during class body execution and is thrown away afterwards.  The "dict-like object" returned by __prepare__ will have been thrown away by the time "continue class" is executed.

Also, again, this means that the contents added to the class in the "continue class" block won't be visible to metaclass.__new__, metaclass.__init__, and BaseClass.__init_subclass__.

Also, we would want some way of preventing the user from running "continue class" multiple times on the same class--else we accidentally condone monkey-patching in Python, which we don't want to do.


Isn't this case solved by either forward references:

     class A:
         value: "B"

or by either of PEP 563 or PEP 649?

It is, but:

a) manual stringizing was rejected by the community as too tiresome and too error-prone (the syntax of the string isn't checked until you run your static type analysis tool).  Also, if you need the actual Python value at runtime, you need to eval() it, which causes a lot of headaches.

b) PEP 649 doesn't solve this only-slightly-more-advanced case:

   @dataclass
   class A:
      value: B

   @dataclass
   class B:
      value: A

as the dataclass decorator examines the contents of the class, including its annotations.

c) PEP 563 has the same "what if you need the actual Python value at runtime" problem as manual stringization, which I believe is why the SC has delayed its becoming default behavior.

Perhaps my example for b) would be a better example for the PEP.


That could become:

     class A:
         pass

     class B:
         value: A  # This is fine, A exists.

     A.value: B  # And here B exists, so this is fine too.

No new syntax is needed. This is already legal.

It's legal, but it doesn't set the annotation of "value" on A. Perhaps this is just a bug and could be fixed.  (TBH I'm not sure what the intended semantics of this statement are, or where that annotation ends up currently.  I couldn't find it in A.__annotations__ or the module's __annotations__.  Is it just forgotten?)

I assert this approach will be undesirable to Python programmers.  This makes for two very-different feeling approaches to defining the members of a class.  One of the goals of my PEP was to preserve the existing "feel" of Python as much as possible.

Also, as previously mentioned, your technique prevents "A.value", and all other attributes and methods set using this technique, from being visible to metaclass.__new__, metaclass.__init__, and BaseClass.__init_subclass__.


This proposed  `forward class` / `continue class` syntax should permit
solving *every* forward-reference and circular-reference problem faced
in Python,
I think that's overselling the concept.

Okay, perhaps I should have said "the forward-reference and circular-reference problems of class definitions" or something like that.  I'll adjust the text for the second draft.


using an elegant and Pythonic new syntax.
That's a matter of opinion.

Yes.  Are PEPs not permitted to express opinions?


As a side benefit, `forward class` and `continue class` syntax enables
rudimentary separation of "interface" from "implementation", at least for
classes.
I don't think so. The `forward class` syntax doesn't define any part of
the interface except the class' name.

It also defines the base classes and metaclass, as well as some other simple metadata ("__file__"), all which may be of interest to external consumers of the object.

If you don't like the PEP calling this 'the rudimentary separation of 'interface' from 'implementation'", what is your counter-proposal?


However, the user isn't permitted to instantiate a forward-declared
class object until after the corresponding `continue class X`.
Why not?

Because the class object is not fully initialized yet; it's a "forward-declared class object".  I thought my PEP was pretty clear on that point.

Feel free to make your counter-proposal, but it's incoherent to debate the statements of my proposal as if they're statements about your counter-proposal.


Since you explicitly allow the user to instantiate the class by first
removing the `__forward__` dunder,

"allowing" and "condoning" are two different things.  Python allows a lot of things that are not condoned.


//arry/
_______________________________________________
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/JRXWLDSKTRX5ARYZYVJXUAZGWFOUU2U3/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to