Thanks Carl. What you're saying makes sense, and I agree that we should make this change. Eric Traut suggested to me that in addition to your suggestion, we should change transform_descriptor_types to mean that *all* fields are treated as class variables, rather than just having special treatment for descriptor fields. We'd rename transform_descriptor_types to something else if we made this change of course. Maybe class_variable_fields or class_variables.
Maybe this should be something you specify in a field descriptor rather than globally for the entire dataclass?
That's a reasonable suggestion. However, we're not aware of any libraries requiring per-field control of this behavior. And if we only support this behavior via field descriptors (not class-wide), I believe it will make life more awkward for libraries -- they wouldn't want users to need to set this parameter explicitly, so it would need to be inferred via overloads. Thoughts? -Erik -----Original Message----- From: Carl Meyer <carl@oddbird.net> Sent: Monday, February 28, 2022 1:09 PM To: Erik De Bonte <Erik.DeBonte@microsoft.com> Cc: typing-sig@python.org; Eric Traut <erictr@microsoft.com>; Jelle Zijlstra <jelle.zijlstra@gmail.com>; mike_mp@zzzcomputing.com Subject: Re: [Typing-sig] PEP 681: Descriptor fields with dataclass_transform [You don't often get email from carl@oddbird.net. Learn why this is important at http://aka.ms/LearnAboutSenderIdentification.] Hi Erik, On Tue, Feb 22, 2022 at 12:35 PM Erik De Bonte via Typing-sig <typing-sig@python.org> wrote:
We're considering an addition to PEP 681 (dataclass_transform) [1] to better support classes with fields that are descriptors. Setting a new dataclass_transform parameter named "transform_descriptor_types" to True would indicate that __init__ parameters corresponding to descriptor fields have the type of the descriptor's setter value parameter rather than the descriptor type.
Here's an example:
@dataclass_transform(transform_descriptor_types=True) def decorator() -> Callable[[Type[T]], Type[T]]: ...
class Descriptor(Generic[_T]): def __get__(self, instance: object, owner: Any) -> Any: ... def __set__(self, instance: object, value: T | None): ...
@decorator class InventoryItem: quantity_on_hand: Descriptor[int]
In this case, type checkers would understand that the quantity_on_hand parameter of InventoryItem's __init__ method is of type "int | None" rather than "Descriptor[int]".
You only mention the impact on the `__init__` method. Is that the only effect of `transform_descriptor_types`? Or should a typechecker also understand that `instance_of_inventory_item.quantity_on_hand` is of type `Any` (due to the annotation on the `__get__` method of the descriptor type), not of type `Descriptor[int]`? Does that understanding depend on the presence of `transform_descriptor_types=True`? If I write a dataclass without `transform_descriptor_types=True` and give an annotation of `x: SomeTypeImplementingDescriptorProtocol`, can I assign and get instances of that type to the `x` attribute without the typechecker trying to invoke the descriptor protocol? In general, the annotation `x: Descriptor[int]` should only be understood as invoking the descriptor protocol if it is describing the type of the class attribute `x`, not instance attribute `x`. For a non-dataclass-transform type, clearly that annotation on a class attribute should mean the descriptor protocol is invoked, and typecheckers do this: https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fmypy-play.... But dataclass-transform generally means the annotation should be understood as annotating the instance attribute type, not a class attribute. E.g. `x: Final[int] = 3` on a dataclass (at least in the current typechecker support for stdlib dataclasses) does not mean "final int class attribute with value 3," it means "instance attribute of type int with default value 3 that cannot be modified after instantiation." It seems the proposal is for `transform_descriptor_types=True` to modify this such that annotations of descriptor types (only) are understood as class attribute type annotations. I think this is probably pragmatic and useful, but I hope that it does not only impact `__init__`, but also means the descriptor protocol is invoked for attribute gets/sets as well. And I hope the latter does not happen without `transform_descriptor_types=True`, so we can still have descriptor-protocol-implementing types stored as normal instance attributes. (If one wants both behaviors in a single dataclass, I guess one is just out of luck. Maybe this should be something you specify in a field descriptor rather than globally for the entire dataclass?) Carl