Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

is_dataclass() returns True for non-dataclass subclass of dataclass #119260

Closed
JelleZijlstra opened this issue May 20, 2024 · 8 comments
Closed
Labels
docs Documentation in the Doc dir easy stdlib Python modules in the Lib dir tests Tests in the Lib/test dir

Comments

@JelleZijlstra
Copy link
Member

JelleZijlstra commented May 20, 2024

Bug report

Bug description:

If a dataclass has a subclass that is not itself a dataclass, is_dataclass() returns True on the subclass and its instances:

from dataclasses import dataclass, is_dataclass

@dataclass
class X:
    y: int

class Z(X):
    pass

print(is_dataclass(Z))  # True
print(is_dataclass(Z()))  # True

Documentation of is_dataclass() for reference: https://docs.python.org/3.13/library/dataclasses.html#dataclasses.is_dataclass

Intuitively this seems wrong: Z is not itself a dataclass. In pyanalyze I wrote a replacement for is_dataclass() because I needed to check whether the exact class was a dataclass:

def is_dataclass_type(cls: type) -> bool:
    try:
        return "__dataclass_fields__" in cls.__dict__
    except Exception:
        return False

Changing the CPython behavior might be impossible for compatibility reasons, but in that case, we should document and test this edge case.

cc @ericvsmith @carljm for dataclasses.

CPython versions tested on:

CPython main branch

Operating systems tested on:

macOS

Linked PRs

@JelleZijlstra JelleZijlstra added the type-bug An unexpected behavior, bug, or error label May 20, 2024
@Eclips4 Eclips4 added the stdlib Python modules in the Lib dir label May 20, 2024
@blhsing
Copy link
Contributor

blhsing commented May 21, 2024

I think the current behavior makes sense. A subclass of a dataclass still possesses all the characteristics of the dataclass even if the subclass is not decorated with dataclass. Its instances can still be passed to astuple, asdict and replace. It's just that without the dataclass decorator it cannot declare additional fields.

@carljm
Copy link
Member

carljm commented May 21, 2024

I agree with @blhsing -- I think the current behavior is probably better.

This behavior should be explicitly tested and documented, though; currently it isn't.

@carljm carljm added tests Tests in the Lib/test dir docs Documentation in the Doc dir easy and removed type-bug An unexpected behavior, bug, or error labels May 21, 2024
@ericvsmith
Copy link
Member

Agreed with @carljm, especially the part about adding tests and docs. I'm surprised there's no test for this.

@adiaholic
Copy link
Contributor

adiaholic commented May 23, 2024

I think the current behavior makes sense. A subclass of a dataclass still possesses all the characteristics of the dataclass even if the subclass is not decorated with dataclass. Its instances can still be passed to astuple, asdict and replace. It's just that without the dataclass decorator it cannot declare additional fields.

I was able to declare additional fields in subclasses.

from dataclasses import dataclass, is_dataclass
import unittest

@dataclass
class X:
    y: int

class Z(X):
    z: int = 0  # Adding a default field to demonstrate inheritance and dataclass behavior

class TestDataclassInheritance(unittest.TestCase):
    def test_dataclass_inheritance(self):
        # Check if X is a dataclass
        self.assertTrue(is_dataclass(X), "X should be a dataclass")
        # Check if Z is a dataclass, it should inherit the dataclass behavior from X
        self.assertTrue(is_dataclass(Z), "Z should be a dataclass because it inherits from X")
        # Create an instance of Z and check if the default values are set correctly
        z_instance = Z(y=5)
        self.assertEqual(z_instance.y, 5, "The value of y should be set to 5")
        self.assertEqual(z_instance.z, 0, "The default value of z should be 0")

if __name__ == '__main__':
    unittest.main()

@JelleZijlstra
Copy link
Member Author

Your subclass also has the @dataclass decorator. This issue is about subclasses without the decorator.

@adiaholic
Copy link
Contributor

adiaholic commented May 23, 2024

Your subclass also has the @dataclass decorator. This issue is about subclasses without the decorator.

My bad, corrected the example in the comment above 😃.

Also I have a PR on the way !

@carljm
Copy link
Member

carljm commented May 28, 2024

I was able to declare additional fields in subclasses.

No, you were able to add a normal Python class attribute to a subclass, which is sufficient to fool your test because of Python's normal behavior where instance attribute access falls back to the class. The class attribute you added in the subclass is not a dataclass field:

>>> Z.__dataclass_fields__.keys()
dict_keys(['y'])
>>> Z(y=1, z=2)
Traceback (most recent call last):
  File "<python-input-6>", line 1, in <module>
    Z(y=1, z=2)
    ~^^^^^^^^^^
TypeError: X.__init__() got an unexpected keyword argument 'z'

carljm added a commit that referenced this issue May 29, 2024
miss-islington pushed a commit to miss-islington/cpython that referenced this issue May 30, 2024
…mentation and Tests (pythonGH-119480)

(cherry picked from commit bf4ff3a)

Co-authored-by: Aditya Borikar <[email protected]>
Co-authored-by: Carl Meyer <[email protected]>
miss-islington pushed a commit to miss-islington/cpython that referenced this issue May 30, 2024
…mentation and Tests (pythonGH-119480)

(cherry picked from commit bf4ff3a)

Co-authored-by: Aditya Borikar <[email protected]>
Co-authored-by: Carl Meyer <[email protected]>
carljm added a commit that referenced this issue May 30, 2024
…umentation and Tests (GH-119480) (#119760)

gh-119260: Clarify is_dataclass Behavior for Subclasses in Documentation and Tests (GH-119480)
(cherry picked from commit bf4ff3a)

Co-authored-by: Aditya Borikar <[email protected]>
Co-authored-by: Carl Meyer <[email protected]>
carljm added a commit that referenced this issue May 30, 2024
…umentation and Tests (GH-119480) (#119761)

gh-119260: Clarify is_dataclass Behavior for Subclasses in Documentation and Tests (GH-119480)
(cherry picked from commit bf4ff3a)

Co-authored-by: Aditya Borikar <[email protected]>
Co-authored-by: Carl Meyer <[email protected]>
@carljm
Copy link
Member

carljm commented May 30, 2024

Thanks @adiaholic for the PR!

@carljm carljm closed this as completed May 30, 2024
noahbkim pushed a commit to hudson-trading/cpython that referenced this issue Jul 11, 2024
estyxx pushed a commit to estyxx/cpython that referenced this issue Jul 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Documentation in the Doc dir easy stdlib Python modules in the Lib dir tests Tests in the Lib/test dir
Projects
None yet
Development

No branches or pull requests

6 participants