Mailing List Archive

1 2  View All
Re: In support of PEP 649 [ In reply to ]
El vie, 16 abr 2021 a las 21:32, Christopher Barker (<pythonchb@gmail.com>)
escribió:

> ...
> dataclasses may be the only part of the stdlib that uses annotations.
>
>
> There's another one, actually: functools.singledispatch, at
https://github.com/python/cpython/blob/3.9/Lib/functools.py#L860. It uses
the annotations on the first argument to an `@register`ed function to
figure out how to dispatch. Currently it uses `typing.get_type_hints` to
get the type. Presumably, this would break with PEP 563 semantics if you
try to create a singledispatch function in a nested namespace.
Re: In support of PEP 649 [ In reply to ]
Related, `inspect.Parameter.annotation` is affected too, but at least this attribute is called `annotation` instead of `type`.

I noticed this today with `multipledispatch` (though [reported](https://github.com/mrocklin/multipledispatch/issues/104) in 2019) and some other internal code, both using `inspect.signature`. `multipledispatch` could reasonably swap to `typing.get_type_hints`, but the other code I was working with also used `Parameter.default` and `Parameter.kind`, so would require both `inspect.signature` and `get_type_hints`. Not the worst and arguably necessary now/pre PEP 563 with explicit string annotations, but the PEP 649 semantics would be a welcome improvement.
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@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/RW7NUODC5YLSEXDNE4DBN6TGFGMGVF4L/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: In support of PEP 649 [ In reply to ]
On Sat, Apr 17, 2021 at 6:12 AM Eric V. Smith <eric@trueblade.com> wrote:

> I wonder if anyone has considered the impact of PEP 563 on dataclasses ?
>
> I have!
>

Thanks Eric.


> In retrospect, that field probably should have been named "annotation".
> Regardless, the intent was always "store what's in
> __annotations__[field_name]", or what the user specified in field(...,
> type=whatever, ...).
>

intent is all well and good, but what was the intent of __annotations__
In the first place. Static typing was certainly one of the intents, but as
it DID store a value, not a string from the beginning, we can only assume
that there was some intent that the value might be used.

As for dataclasses -- why store it at all? It seems that dataclasses use
the fact that a class attribute has an annotation to determine what to add
to the field -- which is a nifty use of teh syntax. But presumably the
intent of storing the annotation in the Field object, and providing a
public attribute for it, was so that it could be used.

I suppose your point is that the type attribute stored the annotation, and
PEP 653 changes what the annotation will be so there's nothing specific at
all about dataclasses here. Which is true. But users of dataclasses may
have been less aware of all the implications as they didn't write that
annotation etrating code themselves. I know I wasn't.

> In any event, all of this mess is one reason I'd like to see PEP 649 get
> accepted:
>
Indeed -- that is the title of this thread, after all :-)

And see others' notes, there seems to be two other places in the stdlib
that will be affected.

-CHB


--
Christopher Barker, PhD (Chris)

Python Language Consulting
- Teaching
- Scientific Software Development
- Desktop GUI and Web Development
- wxPython, numpy, scipy, Cython
Re: In support of PEP 649 [ In reply to ]
On 4/17/2021 6:44 PM, Christopher Barker wrote:
> On Sat, Apr 17, 2021 at 6:12 AM Eric V. Smith <eric@trueblade.com
> <mailto:eric@trueblade.com>> wrote:
>
>> I wonder if anyone has considered the impact of PEP 563 on
>> dataclasses ?
> I have!
>
>
> Thanks Eric.
>
> In retrospect, that field probably should have been named
> "annotation". Regardless, the intent was always "store what's in
> __annotations__[field_name]", or what the user specified in
> field(..., type=whatever, ...).
>
>
> intent is all well and good, but what was the intent of
> __annotations__ In the first place. Static typing was certainly one of
> the intents, but as it DID store a value, not a string from the
> beginning, we can only assume that there was some intent that the
> value might be used.
Yes, but wouldn't that say that PEP 563 could never be implemented then,
since it would break that intent? We already knew it broke all code that
was expecting a type object in __annotations__. I'm just saying my
expectation is 563 will break any code expecting a type object in
__annotations__, or anything that's derived from __annotations__
(specifically in my case, dataclass users looking at Field.type). My
probably unarticulated intent was that Field.type agree with
__annotations__[field_name].
>
> As for dataclasses -- why store it at all? It seems that dataclasses
> use the fact that a class attribute has an annotation to determine
> what to add to the field -- which is a nifty use of teh syntax. But
> presumably the intent of storing the annotation in the Field object,
> and providing a public attribute for it, was so that it could be used.

I didn't want to predict what someone was using dataclasses for.
Especially someone using field(..., metadata=something) might want
access to any of the field() values, including .type. I supposed I could
have said "for defaults, default_factory, repr, etc., look at the Field
object, but for the type look at __annotations__[field_name]", but I
decided it would be easier if everything about a field would be in the
Field object. Not that it makes much difference: I believe that
Field.type always agrees with __annotations__[field_name].

This allows someone to say (where some_dataclass_field_object is a Field):

process_field(some_dataclass_field_object)

instead of this, if the .type field weren't present:

process_field(some_dataclass_field_object,
SomeDataclass.__annotations__[some_dataclass_field_object.name])

>
> I suppose your point is that the type attribute stored the annotation,
> and PEP 653 changes what the annotation will be so there's nothing
> specific at all about dataclasses here. Which is true. But users of
> dataclasses may have been less aware of all the implications as they
> didn't write that annotation etrating code themselves. I know I wasn't.
>
> In any event, all of this mess is one reason I'd like to see PEP
> 649 get accepted:
>
> Indeed -- that is the title of this thread, after all :-)

Well, not everyone is not in support of the subject!

Eric

>
> And see others' notes, there seems to be two other places in the
> stdlib that will be affected.
>
> -CHB
>
>
> --
> Christopher Barker, PhD (Chris)
>
> Python Language Consulting
>   - Teaching
>   - Scientific Software Development
>   - Desktop GUI and Web Development
>   - wxPython, numpy, scipy, Cython

--
Eric V. Smith
Re: In support of PEP 649 [ In reply to ]
I'd like to chime in with an example of how PEP 563 breaks code that uses dataclasses.

I've written a library instant_api (https://github.com/alexmojaki/instant_api) that is heavily inspired by FastAPI but uses dataclasses for complex types instead of pydantic. The example at the beginning of the README is short and demonstrates it nicely. Basically it lets you write code on both the client and server sides that work seamlessly with standard dataclasses, type hints, and type checkers without any plugins, instead of untyped dicts parsed from the JSON that is communicated behind the scenes.

`from __future__ import annotations` breaks that README example, even though there are no locally defined types, because as mentioned the dataclass field now contains a string instead of a type.

Going a bit deeper, instant_api is powered by https://github.com/alexmojaki/datafunctions, which is more generic than instant_api so that others can build similar tools. Again, the idea is that you can write code with nice dataclasses and type hints, but call it with basic JSON serializable types like dicts. For example:

```
from dataclasses import dataclass
from datafunctions import datafunction

@dataclass
class Point:
x: int
y: int

@datafunction
def translate(p: Point, dx: int, dy: int) -> Point:
return Point(p.x + dx, p.y + dy)

assert translate({"x": 1, "y": 2}, 3, 4) == {"x": 4, "y": 6}

# This is equivalent to the following without @datafunction
# assert translate(Point(1, 2), 3, 4) == Point(4, 6)
```

In the same way as before, `from __future__ import annotations` breaks this code. The reason is that datafunctions itself is powered by https://github.com/lovasoa/marshmallow_dataclass. Here's an example:

```
from dataclasses import dataclass
from marshmallow_dataclass import class_schema

@dataclass
class Point:
x: int
y: int

schema = class_schema(Point)()
assert schema.load({"x": 1, "y": 2}) == Point(1, 2)
```

Again, in the same way as before, `from __future__ import annotations` breaks this code. Specifically `class_schema(Point)` breaks trying to deal with the string `'int'` instead of a type.

This problem was raised in https://github.com/lovasoa/marshmallow_dataclass/issues/13 two years ago. It's by far the oldest open issue in the repo. It was clear from the beginning that it's a difficult problem to solve. Little progress has been made, there's one PR that's not in good shape, and it seems there's been no activity there for a while. A couple of other issues have been closed as duplicates. One of those issues is about being unable to use recursive types at all.

marshmallow_dataclass has 266 stars. It builds on https://github.com/marshmallow-code/marshmallow, an extremely popular and important data (de)serialization and validation library. Here's a little timeline:

- 2013: marshmallow 0.1.0 first released in 2013
- 2014: marshmallow 1.0.0 released
- 2015: attrs (precursor to dataclasses) first released
- 2016: Python 3.6.0 final released, allowing the variable annotations which make pydantic and dataclasses possible.
- 2017: First version of pydantic released
- 2018: Python 3.7.0 final released, introducing dataclasses

Nowadays pydantic is the natural successor/alternative to marshmallow - Google autocompletes "pydantic vs " with marshmallow as the first option, and vice versa. But marshmallow is clearly well established and entrenched, and thanks to marshmallow_dataclass it was the better fit for my particular use case just last year when I made instant_api.

If someone wants to keep combining dataclasses and marshmallow, but without marshmallow_dataclass (e.g. if PEP 563 goes through before marshmallow_dataclass is ready) then they need to revert to the raw marshmallow API which doesn't use type hints. The previous example becomes much uglier:

```
from dataclasses import dataclass
from marshmallow import Schema, fields, post_load

@dataclass
class Point:
x: int
y: int

class PointSchema(Schema):
x = fields.Int()
y = fields.Int()

@post_load
def make_point(self, data, **kwargs):
return Point(**data)

schema = PointSchema()
assert schema.load({"x": 1, "y": 2}) == Point(1, 2)
```

This post turned out longer than I initially planned! In summary, my point is that type hints and dataclasses as they work right now make it possible to write some really nice code - nice for humans to both write and read, nice for type checkers and other static analysis, and providing very nice features using annotations at runtime. And despite clear demand and benefits and ample time, people haven't managed to make this code continue working with stringified type annotations. Clearly doing so is not easy. So there's a good case for the dataclasses module to resolve these annotations to actual types, especially if PEP 563 goes through but even if it doesn't.
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@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/GPPWPKQ2RHABB64REWHE6HDHRNRFUXXM/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: In support of PEP 649 [ In reply to ]
As far as I know, both Pydantic and marshmallow start using annotation
for runtime type after PEP 563 is accepted. Am I right?
When PEP 563 is accepted, there are some tools using runtime type in
annotation, but they are not adopted widely yet.

But we didn't have any good way to emit DeprecationWarning for use
cases that PEP 563 breaks.
Maybe, we should have changed the default behavior soon in Python 3.8.
With long preparation period without DeperecationWarning, use cases
broken by PEP 563 beccome much.

I still love PEP 563. Python is dynamic language so runtime type and
static type can not be 100% consistent.
Annotation is handy tool. But there is only one annotation per object.
When we start using annotation for multiple purposes, it becomes ugly
monster soon.

Using annotation syntax only for static typing like TypeScript is the ideal.
TypeScript is far more succeeded than Python about Gradual Typing.

But, current status is went to where I hate already. Annotation is
used for multiple purposes already.
I'm sad and disappointed about it, but that's that.

I'm OK to keep PEP 563 opt-in. And I think we should do it. (off
course, I'm not a SC member. I follow the SC decision).

And if we keep PEP 563 opt-in, no need to compare PEP 563 with PEP 649.
PEP 649 should be compared with "stock semantics + opt-in PEP 563 semantics".

Of course, one semantics is better than two semanticses.
But we need to have three semanticses until we can remove stock + PEP
563 semantices.
We should think about PEP 649 very carefully, spending more time.

Regards,
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@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/BYSKN5TXEVIOHN6WVMSFRIVMF66OEPOT/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: In support of PEP 649 [ In reply to ]
Hi Samuel,

I have a naive question about pydantic. IIRC there's something in your
tracker about not being able to handle recursive definitions correctly with
PEP 563. But I must be misremembering, because PEP 563 actually makes that
easier, not harder. Without PEP 563, how does it handle classes like this?

@<pydantic decorator>
class C:
def method(self) -> C: ...

(My apologies for not doing more research, but I figured this would be
quicker for you to answer than for me to research, given that I've never
used pydantic.)

--
--Guido van Rossum (python.org/~guido)
*Pronouns: he/him **(why is my pronoun here?)*
<http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
Re: In support of PEP 649 [ In reply to ]
There are a number of issues around recursive types, in general PEP 563
doesn't make a big difference either way in this case.

I think you mean something like

from pydantic import BaseModel
from typing import Optional

class Foo(BaseModel):
x: int
foo: Optional['Foo']

Foo.update_forward_refs()
print(Foo(x=1, foo={'x': 1}))

The only difference with "from __future__ import annotations" is you can
unquote Foo, e.g. Optional[Foo]. In all cases you still need
"Foo.update_forward_refs()" since Foo is not defined while creating Foo.

You could also use ForwardRef: "FooType =
ForwardRef('Foo')...Optional[FooType]", but that's slightly more verbose
and doesn't add anything.

(*Sidenote:* as I'm writing this, I realise you could know what Foo is
while creating Foo (same name -> reference to itself) and perhaps
automatically call update_forward_refs() at the end of
ModelMetaclass.__new__(), but you still need the
public update_forward_refs() for more complex cases; not least bodging
around PEP 563 scopes, see here
<https://github.com/samuelcolvin/pydantic/issues/2678#issuecomment-821437650>
.)

Samuel
Re: In support of PEP 649 [ In reply to ]
>
> As far as I know, both Pydantic and marshmallow start using annotation
> for runtime type after PEP 563 is accepted. Am I right?


Not quite, pydantic was released in June 2017 (see HN post:
https://news.ycombinator.com/item?id=14477222) and always used annotations,
PEP 563 was created in September 2017, a few months later.

I can't speak about Marshmallow, back then I don't think it used
annotations at all, but maybe it does now.

When PEP 563 is accepted, there are some tools using runtime type in
> annotation, but they are not adopted widely yet.


Yes, that's completely fair.

Samuel

1 2  View All