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/