python / cpython

The Python programming language
https://www.python.org
Other
63.19k stars 30.26k forks source link

ClassVar breaks dataclass fields #125994

Closed michael-123123 closed 2 hours ago

michael-123123 commented 2 hours ago

Bug report

Bug description:

Hi,

I searched issues and couldn't find anything similar to this. I'm not 100% sure it's a bug - possibly this is desired behavior?

To reproduce:

from dataclasses import Field, field, dataclass
from typing import ClassVar

class A:
    sentinel: ClassVar[Field] = field()

@dataclass
class B:
    sentinel: ClassVar[Field] = field()

print("Class A")
print(A.sentinel)
print(A().sentinel)
print()

print("Class B")
try:
    print(B.sentinel)
except AttributeError as ex:
    print(ex)

try:
    print(B().sentinel)
except AttributeError as ex:
    print(ex)

print()

Output:

Class A
Field(name=None,type=None,default=<dataclasses._MISSING_TYPE object at 0x78ec801c1010>,default_factory=<dataclasses._MISSING_TYPE object at 0x78ec801c1010>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=<dataclasses._MISSING_TYPE object at 0x78ec801c1010>,_field_type=None)
Field(name=None,type=None,default=<dataclasses._MISSING_TYPE object at 0x78ec801c1010>,default_factory=<dataclasses._MISSING_TYPE object at 0x78ec801c1010>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=<dataclasses._MISSING_TYPE object at 0x78ec801c1010>,_field_type=None)

Class B
type object 'B' has no attribute 'sentinel'
'B' object has no attribute 'sentinel'

Upon inspection of B.__dataclass_fields__ the sentinel field does appear in B. Which is somewhat confusing behavior.

CPython versions tested on:

3.13

Operating systems tested on:

Linux

ericvsmith commented 2 hours ago

It's not clear what you're trying to accomplish.

__dataclass_fields__ is private. Use dataclasses.fields() and you'll see it's not a normal field. You've not initialized the class field to anything.

This is the normal way to initialize a ClassVar.

>>> @dataclass
... class B:
...     sentinel: ClassVar = 3
michael-123123 commented 2 hours ago

Hi @ericvsmith. Thanks for the quick reply.

I realize that. Let me clarify that I wanted to have a class variable that is of type Field.

The point I was making with the bug report is that if you want a ClassVar of type Field -- then it is neither a class var nor an attribute of the instance.

This may be some kind of undefined/undocumented behavior. I would expect this to just be a regular class variable of type Field (like in class A in my example) Alternatively - I would expect some error message that I have an uninstantiated field - similar to non class var fields that are not initialized.

michael-123123 commented 2 hours ago

Actually I am closing this bug -- this behavior is documented. The workaround (in case anyone is interested) is:

@dataclass
class B:
    x: ClassVar[Field] = field(default=field())