Just off the top of my head, a context manager like this would give access
to the required local scope, if inspecting the execution frames isn't
considered too hacky.

class LocalsProcessor:
    def __enter__(self):
        self.locals_ref = inspect.currentframe().f_back.f_locals
        self.locals_prev = copy.deepcopy(self.locals_ref)
        # deep copy to ensure we also get a copy of the
        # __annotations__

    def __exit__(self, exception_type, exception_value, traceback):
        ...
        # modify self.locals_ref based on the difference between
        # self.locals_ref and self.locals_prev and their
        # respective __annotations__


An issue I can see with this approach is that the context manager can only
work off the difference between locals() before and after its scope, so it
would ignore a duplicate assignment to the same value as before, for a name
that existed before the context manager entered, in a way that might be
unexpected:

class Example:
    a: str
    b: int

    with LocalsProcessor():
        a: bool
        # we can detect that 'a' changed and what it changed to/from
        # because its value in the __annotations__ dict is different

        b: int
        # there is no way to detect that 'b' was redeclared within
        # the scope of the contextmanager because it has the same
        # annotation before and after


Granted, I have no idea who would ever write code like this (!) but I
thought I'd mention that as a problematic edge-case.

Maybe there's a better way to approach this that I can't think of.

Or maybe it's possible that using context managers for this isn't realistic
because of implementation issues that just can't be resolved. I just really
like the semantics of it :)

On Thu, Mar 11, 2021 at 11:08 PM Paul Bryan <pbr...@anode.ca> wrote:

> The syntax of what you're proposing seems fairly intuitive (they might not
> even need to be callable).  I'm struggling to envision how the context
> manager would acquire scope to mark fields up in the (not yet defined)
> dataclass, and to capture the attributes that are being defined within.
>
>
> On Thu, 2021-03-11 at 22:53 +0000, Matt del Valle wrote:
>
> Disclaimer: I posted this earlier today but I think due to some first-post 
> moderation related issues (that I've hopefully now gotten sorted out!) it may 
> not have gone through. I'm posting this again just in case. If it's gone 
> through and you've already seen it then I'm super sorry, please just ignore 
> this.
>
> If something like what you're suggesting were to be implemented I would much 
> rather it be done with context managers than position-dependent special 
> values, because otherwise you once again end up in a situation where it's 
> impossible to easily subclass a dataclass (which was one of the primary 
> reasons this conversation even got started in the first place). So, for 
> example:
>
> import dataclasses
>
>
> @dataclasses.dataclass
> class SomeClass:
>     c: bool = False
>     # a normal field with a default value does not
>     # prevent subsequent positional fields from
>     # having no default value (such as 'a' below)
>     # however, all further normal fields now must
>     # specify a default value (such as 'd' below)
>
>     with dataclasses.positional():
>         a: int
>         b: float = 3.14
>         # once a positional field with a default value shows up
>         # all further positional fields and ALL normal fields
>         # (even retroactively!) must also specify defaults
>         # (for example, field 'c' above is
>         # now forced to specify a default value)
>
>     with dataclasses.keyword():
>         e: list
>         f: set = dataclasses.field(default_factory=set)
>         # once a keyword field with a default value shows up
>         # all further keyword fields must also specify defaults
>
>     d: dict = dataclasses.field(default_factory=dict)
>     # This ordering is clearly insane, but the essential
>     # point is that it works even with weird ordering
>     # which is necessary for it to work when subclassing
>     # where the order will almost always be wonky
>     #
>     # A sane version of the above would be:
>
>
> @dataclasses.dataclass
> class SomeClass:
>     with dataclasses.positional():
>         a: int
>         b: float = 3.14
>
>     c: bool = False
>     d: dict = dataclasses.field(default_factory=dict)
>
>     with dataclasses.keyword():
>         e: list
>         f: set = dataclasses.field(default_factory=set)
>
>     # either of the above will generate an __init__ like:
>     def __init__(self, a: int, b: float = 3.14,
>                  /, c: bool = False, d: dict = None,
>                  *, e: list, f: set = None):
>         self.a = a
>         self.b = b
>         self.c = c
>         self.d = dict() if d is None else d
>         self.e = e
>         self.f = set() if f is None else f
>     # parameters are arranged in order as
>     # positional -> normal -> keyword
>     # within the order they were defined in each
>     # individual category, but not necessarily
>     # whatever order they were defined in overall
>     #
>     # This is subclass-friendly!
>     #
>     # it should hopefully be obvious that we could
>     # have cut this class in half at literally any
>     # point (as long as the the parent class has
>     # the earlier arguments within each category)
>     # and put the rest into a child class and
>     # it would still have worked and generated the
>     # same __init__ signature
>     #
>     # For example:
>
>
> @dataclasses.dataclass
> class Parent:
>     with dataclasses.positional():
>         a: int
>
>     c: bool = False
>
>     with dataclasses.keyword():
>         e: list
>
>
> @dataclasses.dataclass
> class Child(Parent):
>     with dataclasses.positional():
>         b: float = 3.14
>
>     d: dict = dataclasses.field(default_factory=dict)
>
>     with dataclasses.keyword():
>         f: set = dataclasses.field(default_factory=set)
>     # Child now has the same __init__ signature as
>     # SomeClass above
>
>
> (In case the above code doesn't render properly on your screen, I've
> uploaded it to GitHub at:
> https://github.com/matthewgdv/dataclass_arg_contextmanager/blob/main/example.py
> )
>
> Honestly, the more I think about it, the more I love the idea of something
> like this (even if it's not *exactly* the same as my suggestion). Right
> now dataclasses do not support the full range of __init__ signatures you
> could generate with a normal class, and they are extremely hostile to
> subclassing. That is a failing that often forces people to fall back to
> normal classes in otherwise ideal dataclass use-case situations.
>
> On Thu, Mar 11, 2021 at 10:15 PM Paul Bryan <pbr...@anode.ca> wrote:
>
> If you're proposing something like this, then I think it would be
> compatible:
>
> class Hmm:
>
>  #
>
>  this: int
>
>  that: float
>
>  #
>
>  pos: PosOnly
>
>  #
>
>  these: str
>
>  those: str
>
>  #
>
>  key: KWOnly
>
>  #
>
>  some: list
>
>
>
> On Thu, 2021-03-11 at 14:06 -0800, Ethan Furman wrote:
>
> On 3/11/21 10:50 AM, Paul Bryan wrote:
>
> On Thu, 2021-03-11 at 10:45 -0800, Ethan Furman wrote:
>
> On 3/10/21 9:47 PM, Eric V. Smith wrote:
>
> I'm not sure of the best way to achieve this. Using flags to field()
> doesn't sound awesome, but could be made to work. Or maybe special
> field names or types? I'm not crazy about that, but using special
> types would let you do something like:
>
> @dataclasses.dataclass
> class Point:
>      x: int = 0
>      _: dataclasses.KEYWORD_ONLY
>      y: int
>      z: int
>      t: int = 0
>
>
> Maybe something like this?
>
>      class Hmm:
>          #
>          this: int
>          that: float
>          #
>          pos: '/'
>          #
>          these: str
>          those: str
>          #
>          key: '*'
>          #
>          some: list
>
>      >>> Hmm.__dict__['__annotations__']
>      {
>          'this': <class 'int'>,
>          'that': <class 'float'>,
>          'pos': '/',
>          'these': <class 'str'>,
>          'those': <class 'str'>,
>          'key': '*',
>          'some': <class 'list'>,
>          }
>
> The name of 'pos' and 'key' can be convention, since the actual name
> is irrelevant.  They do have to be unique, though.  ;-)
>
>
> It's current convention (and is used by typing module and static type
> checkers) that string annotations evaluate to valid Python types.
>
>
> So make '/' and '*' be imports from dataclasses:
>
>      from dataclasses import dataclass, PosOnly, KWOnly
>
> --
> ~Ethan~
> _______________________________________________
> Python-ideas mailing list -- python-ideas@python.org
> To unsubscribe send an email to python-ideas-le...@python.org
> https://mail.python.org/mailman3/lists/python-ideas.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-ideas@python.org/message/6L4W5OB23FBWZ7EZYDNCYSGT2CUAKYSX/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
>
> _______________________________________________
> Python-ideas mailing list -- python-ideas@python.org
> To unsubscribe send an email to python-ideas-le...@python.org
> https://mail.python.org/mailman3/lists/python-ideas.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-ideas@python.org/message/VPSE34Z35XOXGFJMGTMLWDAMF7JKJYOJ/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
> _______________________________________________
> Python-ideas mailing list -- python-ideas@python.org
> To unsubscribe send an email to python-ideas-le...@python.org
> https://mail.python.org/mailman3/lists/python-ideas.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-ideas@python.org/message/WBL4X46QG2HY5ZQWYVX4MXG5LK7QXBWB/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
>
>
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/2LCC7M6XSCQMU2ZKJ63DRI2KLLB7TXAX/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to