On 8/11/21 2:48 AM, Jukka Lehtosalo wrote:
On Wed, Aug 11, 2021 at 10:32 AM Thomas Grainger <tagrain@gmail.com> wrote:
Larry Hastings wrote:
> On 8/11/21 12:02 AM, Thomas Grainger wrote:
> > I think as long as there's a test case for something like
> > @dataclass
> > class Node:
> >      global_node: ClassVar[Node | None]
> >      left: InitVar[Node | None]
> >      right: InitVar[None | None]
> >
> > the bug https://bugs.python.org/issue33453 and the current implementation https://github.com/python/cpython/blob/bfc2d5a5c4550ab3a2fadeb9459b4bd948ff6... shows this is a tricky problem
> > The most straightforward workaround for this is to skip the decorator
> syntax.  With PEP 649 active, this code should work:
> class Node:
>          global_node: ClassVar[Node | None]
>          left: InitVar[Node | None]
>          right: InitVar[None | None]
>     Node = dataclass(Node)
> //arry/

the decorator version simply has to work

I also think that it would be unfortunate if the decorator version wouldn't work. This is a pretty basic use case.


So, here's an idea, credit goes to Eric V. Smith.  What if we tweak how decorators work, juuuust sliiiightly, so that they work like the workaround code above?

Specifically: currently, decorators are called just after the function or class object is created, before it's bound to a variable.  But we could change it so that we first bind the variable to the initial value, then call the decorator, then rebind.  That is, this code:

@dekor8
class C:
    ...

would become equivalent to this code:

class C:
    ...
C = dekorate(C)

This seems like it would solve the class self-reference problem--the "Node" example above--when PEP 649 is active.

This approach shouldn't break reasonable existing code.  That said, this change would be observable from Python, and pathological code could notice and break.  For example:

def ensure_Foo_is_a_class(o):
    assert isinstance(Foo, type)
    return o

class Foo:
    ...

@ensure_Foo_is_a_class
def Foo():
    ...

This terrible code currently would not raise an assertion.  But if we made the proposed change to the implementation of decorators, it would.  I doubt anybody does this sort of nonsense, I just wanted to fully flesh out the topic.


If this approach seems interesting, here's one wrinkle to iron out.  When an object has multiple decorators, would we want to re-bind after each decorator call?  That is, would

@dekor1
@dekor2
@dekor3
class C:
    ...

turn into approach A:

class C:
    ...
C = dekor1(dekor2(dekor3(C)))

or approach B:

class C:
    ...
C = dekor3(C)
C = dekor2(C)
C = dekor1(C)

I definitely think "approach B" makes more sense.


/arry