This is an actual attempt at specifying the defer expression alternative to PEP-671. Note that I am not entirely sure if this is actually a good idea, and the syntax I am suggesting for sure can’t work as-is (it isn’t backwards compatible at all).
A new kind of expressions would be added, with temporary syntax defer <expr>
. This would be almost equivalent to DeferType(lambda: <expr>)
and would be valid anywhere the left form would also be valid. Preferably, DeferType
would also store a string representation of <expr>
for documentation purposes. [1]
The other half of the proposal is that when such a Defer
instance is encountered in the default arguments of a function while those are copied over into the locals of the function, the callable contained within the Defer
is executed and it’s value is used instead.
dataclasses.dataclass
also special cases defer
and treats it somewhat similar to specifying a default_factory
.
DeferType
instances would otherwise not have any special behavior. The underlying function can be read with .callable
, it can be constructed with types.DeferType
and the defer object can be called directly, i.e. defer_instance()
is the exact same as defer_instance.callable()
.
This means that the following code would work as one might expect:
from dataclasses import dataclass, field
def test_defer(val, a=defer [1], /, b=defer [2], *, c=defer [3]):
a.append(val)
b.append(val)
c.append(val)
return a, b, c
print(test_defer('', [], b=[], c=[]))
print(test_defer('a'))
print(test_defer('b'))
@dataclass
class TestDefer:
values1: list[int] = field(default_factory=lambda: [1])
values2: list[int] = field(default_factory=lambda: [2], init=False)
values3: list[int] = field(default=defer [3])
values4: list[int] = field(default=defer [4], init=False)
values5: list[int] = defer [5]
a = TestDefer()
a.values1.append('a')
a.values2.append('a')
a.values3.append('a')
a.values4.append('a')
a.values5.append('a')
b = TestDefer()
b.values1.append('b')
b.values2.append('b')
b.values3.append('b')
b.values4.append('b')
b.values5.append('b')
print(a, b)
A Proof-of-Concept capable of running the above code correctly is here: GitHub - MegaIng/cpython at add-defer
I am sure I made quite a few mistakes while implementing it, but that isn’t the point.
The point is whether or not this feature could be useful with these semantics, just maybe not this exact syntax or name: defer
is the name used within the context of the other thread, but tbh it doesn’t quite make sense for the general feature.
IMO the benefit of this approach over 671 is that it allows for a more general usage of this feature, especially within dataclasses, and it allows an from what I can tell simpler implementation.
But I do admit that beyond dataclass-likes I don’t quite see many good uses for this feature. The primary point would be that it’s a callable that is treated specially within certain contexts.
Not part of the PoC ↩︎