-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Differences between BaseModel and @dataclass not expected based on documentation #710
Comments
happy to accept a PR to improve the documentation. |
hi @samuelcolvin, first just want to say thanks for such a nice tool! So the reason I believe the mutable fields are not allowed on regular data classes is due to them being shared (probably unexpectedly for most people) similar to how if you do this: class Y(object):
def __init__(self,mutable=[]):
self._mutable = mutable
y1 = Y() # y1._mutable = []
y2 = Y() # y2._mutable = []
y1._mutable.append(1) # y1._mutable = [1], but surprise! y2._mutable = [1] The Pydantic BaseModel does not seem to suffer from this: class X(pydantic.BaseModel):
list_: List[int] = []
x1 = X() # x1.list_ = []
x2 = X() # x2.list_ = []
x1.list_.append(1) # x1.list_ = [1], x2.list_ = [] Have I understood that correctly? (Sorry if it's in the docs, I looked but couldn't find it specifically mentioned) |
Is not a pydantic error, check https://docs.python.org/3/library/dataclasses.html#dataclasses.field |
@leiserfg Yes, I tried to make that point very clear in the original issue with the text
The point of this issue is that the documentation makes it seem like you can get the same behavior inheriting from |
I had another issue with dataclasses, they don't support extra fields even when you have |
We should make it clear that |
I stumbled upon this issue when trying to understand the functional differences between
My question is: WHY would I not want to use From the comments, there are a few key differences:
Are their any other differences between the two? Should I put a PR in to update the docs to include a comparison? |
Thanks for the question, it's probably not worth putting in a PR at this time as we're in the middle of a rebuild for V2. The main reason to use |
Another different I found when experimenting with the two:
|
I cannot reproduce what you are saying. Both situations mentions all the missing fields: (python 3.10.4 and pydantic 1.10.2) import dataclasses
import pydantic
@dataclasses.dataclass
class ChairBuiltin:
width: int
height: int
@pydantic.dataclasses.dataclass
class ChairPydantic:
width: int
height: int
class ChairPydanticBaseModel(pydantic.BaseModel):
width: int
height: int
ChairBuiltin()
# TypeError: ChairBuiltin.__init__() missing 2 required positional arguments: 'width' and 'height'
ChairPydantic()
# TypeError: ChairPydantic.__init__() missing 2 required positional arguments: 'width' and 'height'
ChairPydanticBaseModel()
# ValidationError: 2 validation errors for ChairPydanticBaseModel
#width
# field required (type=value_error.missing)
#height
# field required (type=value_error.missing) |
Another difference I've discovered: using |
… in `python/pydantic_core/_pydantic_core.pyi` (pydantic#710)
after test, I found that the I test it in python3.11 debian class ThirdPartType(NamedTuple):
a: int
print('TypedDict', timeit('t.a', setup="t=ThirdPartType(a=114)\n", globals=globals()))
class ThirdPartType:
a: int
class ThirdPartType(TypedDict):
a: int
print('TypedDict', timeit('t["a"]', setup="t=ThirdPartType(a=114)\n", globals=globals()))
class ThirdPartType:
a: int
print('vanila',timeit('t.a', setup="t=ThirdPartType()\nt.a=114", globals=globals()))
class ThirdPartType(BaseModel):
a: int
print('BaseModel', timeit('t.a', setup="t=ThirdPartType(a=114)", globals=globals()))
@dataclass
class ThirdPartType:
a: int
print('pydantic.dataclass', timeit('t.a', setup="t=ThirdPartType(a=114)", globals=globals()))
from dataclasses import dataclass
@dataclass
class ThirdPartType:
a: int
print('dataclasses.dataclass',timeit('t.a', setup="t=ThirdPartType(a=114)", globals=globals())) the result is:
for the access speed, vanila == dataclasses.dataclass > pydantic.dataclass > TypedDict > NamedTuple > BaseModel |
Another difference between from pydantic import BaseModel
from pydantic.dataclasses import dataclass
class Works(BaseModel):
one: int = 0
two: int
@dataclass
class Broken:
one: int = 0
two: int # Mypy: Attributes without a default cannot follow attributes with one
# TypeError: non-default argument 'two' follows default argument This is especially relevant with inheritance: from pydantic.dataclasses import dataclass
@dataclass
class Base:
field: int = 0
@dataclass
class Test(Base):
test: int # Mypy: Attributes without a default cannot follow attributes with one
# TypeError: non-default argument 'test' follows default argument from pydantic import BaseModel
class Base(BaseModel):
field: int = 0
class Test(Base):
test: int
t = Test(test=2)
# no errors |
Documentation Update Request
For bugs/questions:
I liked the idea of using a
dataclass
instead of subclassing fromBaseModel
, so I tried changing the very first example from the docs to usedataclass
instead ofBaseModel
and it fails.Result:
I realize that this error is coming from the Std. Lib.
dataclasses
module, notpydantic
. However, based on the language in the dataclasses section of the documentation I had expected what anything I could do withBaseModel
I could do withdataclass
as well.Can I suggest that there be a note or warning to the user that there are certain restrictions associated with using a
dataclass
that are not present when usingBaseModel
(such as not being able to use mutable defaults, as well as #484 and #639)?The text was updated successfully, but these errors were encountered: