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/