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 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"

Methods are a bit trickier. The status quo pollutes the global 
namespace, and repeats the method name three times:

    def method(self):
        pass

    X.method = method

but we can do better with just a small amount of new syntax:

    def X.method(self):
        pass

This has been proposed before, and rejected for lack of any real need. 
But your "forward class/continue class" suggests that the need has now 
appeared.

So where you would write this:


    forward class Node()

    continue class Node:
        """Node in a linked list."""
        def __init__(self, value, next:Optional[Node]):
            self.value = value
            self.next = next


we could instead do this:

    class Node():
        """Node in a linked list."""

    def Node.__init__(self, value, next:Optional[Node]):
        self.value = value
        self.next = next


The only downside -- although perhaps that's an upside -- is that we 
save an indent level, which may make it marginally harder to recognise 
which function defs are being added into a class, and which are not.

If that really is an issue, then we could keep the "continue class ..." 
syntax solely to give us that block structure.

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.

But we don't need the "forward class". Just create a class!

Any methods, docstrings, attributes etc which aren't part of a forward 
reference cycle can go in the initial class creation, and only those 
which are part of a cycle need to be added afterwards.


Here is your circular class example:


> ```Python
>     class A:
>         value: B
> 
>     class B:
>         value: A
> ```

Isn't this case solved by either forward references:

    class A:
        value: "B"

or by either of PEP 563 or PEP 649? I don't think this is a compelling 
example. But let's continue anyway.

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. We only need minimal new 
syntax to allow methods to be defined outside of their class body:

    def A.method(self):
        ...


with no need to overload `continue` for a second meaning. Or we could 
use the `continue A` syntax, but either way, the `forward A` syntax 
seems to be unneeded.


> But nothing so far has been both satisfying and complete; either it
> is wordy and clumsy to use (manually stringizing annotations), or it
> added restrictions and caused massive code breakage for runtime use of
> annotations (PEP 563), or simply didn't solve every problem (PEP 649).

But doesn't PEP 649 solve problems which this proposal does not? 
Delaying the evaluation of annotations is more general than merely the 
"forward reference" problem.


> 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. How about forward references for 
function defaults?

    def func(arg=func(0)):
        if arg == 0:
            return "something"
        ...


Obviously there are other ways to solve that, but the point is, your 
forward/continue class proposal is not one of them!



> using an elegant and Pythonic new syntax.

That's a matter of opinion. Personally, I think that *declarations* as 
in `forward MyClass` are not especially Pythonic, although at least 
your declaration also creates a real class object. But having two ways 
to create a class (three if we count type()) is not especially Pythonic 
either.


> 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. That's so rudimentary that it 
doesn't deserve the name "interface".

"What's the Node class' API?"

"It's called Node."


> Syntax
> ------
> 
> The `forward class` statement is the same as the `class` statement,
> except it doesn't end with a colon and is not followed by an indented block.

Or we could end it with a colon and follow it by an indented block, and 
then dispose of the unnecessary "forward" keyword :-)



> Using this new syntax, the forward-reference problem illustrated
> in the *Rationale* section above is now easy to solve:

It's already easy to solve. Aside from the two PEPs, we have stringified 
forward references and type comments:

    class A:
        value: "B" = None
        value = None  # type: B

If this is your most compelling example, I don't think this feature is 
needed.



> `forward class X` declares a class, but the class is explicitly
> not yet fully defined.  It won't be fully defined and ready to
> be instantiated until after the corresponding `continue class`
> statement.  We'll refer to a class object in this state as
> a "forward-declared class object".  How does this object behave?
> 
> As per the "consenting adults" rule, the forward-declared class
> object must permit most operations.  You should be able to examine
> the object, compare it to other objects, inspect some attributes
> (`__name__`, `__mro__`, `__class__`), and even set attributes.
> 
> However, the user isn't permitted to instantiate a forward-declared
> class object until after the corresponding `continue class X`.

Why not?

Since you explicitly allow the user to instantiate the class by first 
removing the `__forward__` dunder, this reminds me of the ancient Apple 
Mac file system which had a copy-protect bit that even Apple called "the 
Bozo bit". If the Bozo bit was set, you couldn't copy the file. But the 
user could just unset the Bozo bit and then copy it.



> #### Semantics of `continue class`
> 
> `continue class` may only be run on a class once.
> (As Eric V. Smith pointed out in response to an early version of
> this proposal, allowing multiple "continue" declarations on the
> same class would lead directly to language-condoned monkey-patching.)

We already have language-condoned monkey-patching.

If we can do C.attr = value, *and we can*, that's monkey-patching.



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

Reply via email to